/* eslint max-len: ["error", { "code": 150 }]*/ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); describe('workerTimeControl add/delete timeEntry()', () => { const HHRRId = 37; const teamBossId = 13; const employeeId = 1; const salesPersonId = 1106; const salesBossId = 19; const hankPymId = 1107; const jessicaJonesId = 1110; const monday = 1; const tuesday = 2; const thursday = 4; const friday = 5; const saturday = 6; const sunday = 7; const activeCtx = { accessToken: {userId: 50}, }; const ctx = {req: activeCtx}; beforeAll(() => { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx }); }); describe('as Role errors', () => { it('should fail to add a time entry if the target user is not a subordinate', async() => { activeCtx.accessToken.userId = employeeId; const workerId = 2; let error; try { ctx.args = {timed: new Date(), direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId); } catch (e) { error = e; } expect(error).toBeDefined(); expect(error.statusCode).toBe(400); expect(error.message).toBe(`You don't have enough privileges`); }); it('should fail to add if the current and the target user are the same and is not team boss', async() => { activeCtx.accessToken.userId = employeeId; const workerId = employeeId; let error; try { ctx.args = {timed: new Date(), direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId); } catch (e) { error = e; } expect(error).toBeDefined(); expect(error.statusCode).toBe(400); expect(error.message).toBe(`You don't have enough privileges`); }); it('should add if the current user is team boss and the target user is himself', async() => { activeCtx.accessToken.userId = teamBossId; const workerId = teamBossId; const tx = await models.WorkerTimeControl.beginTransaction({}); try { const options = {transaction: tx}; const todayAtOne = new Date(); todayAtOne.setHours(1, 0, 0, 0); ctx.args = {timed: todayAtOne, direction: 'in'}; const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); expect(createdTimeEntry.id).toBeDefined(); await tx.rollback(); } catch (e) { await tx.rollback(); throw e; } }); it('should try but fail to delete his own time entry', async() => { activeCtx.accessToken.userId = salesBossId; const workerId = salesBossId; let error; const tx = await models.WorkerTimeControl.beginTransaction({}); try { const options = {transaction: tx}; const todayAtOne = new Date(); todayAtOne.setHours(1, 0, 0, 0); ctx.args = {timed: todayAtOne, direction: 'in'}; const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); activeCtx.accessToken.userId = salesPersonId; await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); await tx.rollback(); } catch (e) { error = e; await tx.rollback(); } expect(error).toBeDefined(); expect(error.statusCode).toBe(400); expect(error.message).toBe(`You don't have enough privileges`); }); it('should delete the created time entry for the team boss as himself', async() => { activeCtx.accessToken.userId = teamBossId; const workerId = teamBossId; const tx = await models.WorkerTimeControl.beginTransaction({}); try { const options = {transaction: tx}; const todayAtOne = new Date(); todayAtOne.setHours(1, 0, 0, 0); ctx.args = {timed: todayAtOne, direction: 'in'}; const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); expect(createdTimeEntry.id).toBeDefined(); await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); expect(deletedTimeEntry).toBeNull(); await tx.rollback(); } catch (e) { await tx.rollback(); throw e; } }); it('should delete the created time entry for the team boss as HHRR', async() => { activeCtx.accessToken.userId = HHRRId; const workerId = teamBossId; const tx = await models.WorkerTimeControl.beginTransaction({}); try { const options = {transaction: tx}; const todayAtOne = new Date(); todayAtOne.setHours(1, 0, 0, 0); ctx.args = {timed: todayAtOne, direction: 'in'}; const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); expect(createdTimeEntry.id).toBeDefined(); await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); expect(deletedTimeEntry).toBeNull(); await tx.rollback(); } catch (e) { await tx.rollback(); throw e; } }); it('should edit the created time entry for the team boss as HHRR', async() => { activeCtx.accessToken.userId = HHRRId; const workerId = teamBossId; const tx = await models.WorkerTimeControl.beginTransaction({}); try { const options = {transaction: tx}; const todayAtOne = new Date(); todayAtOne.setHours(1, 0, 0, 0); ctx.args = {timed: todayAtOne, direction: 'in'}; const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); expect(createdTimeEntry.id).toBeDefined(); ctx.args = {direction: 'out'}; const updatedTimeEntry = await models.WorkerTimeControl.updateTimeEntry(ctx, createdTimeEntry.id, options); expect(updatedTimeEntry.direction).toEqual('out'); await tx.rollback(); } catch (e) { await tx.rollback(); throw e; } }); }); describe('WorkerTimeControl_clockIn calls', () => { it('should fail to add a time entry if the target user has an absence that day', async() => { pending('https://redmine.verdnatura.es/issues/4707'); activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; const date = new Date(); date.setDate(date.getDate() - 16); date.setHours(8, 0, 0); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; try { ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error.message).toBe(`No está permitido trabajar`); }); it('should fail to add a time entry for a worker without an existing contract', async() => { activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; const date = new Date(); date.setFullYear(date.getFullYear() - 2); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); try { const options = {transaction: tx}; ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error.message).toBe(`No hay un contrato en vigor`); }); describe('direction errors', () => { it('should throw an error when trying "in" direction twice', async() => { pending('https://redmine.verdnatura.es/issues/4707'); activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; let date = new Date(); date.setDate(date.getDate() - 21); date = weekDay(date, monday); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; date.setHours(8, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); try { date.setHours(10, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error.message).toBe(`Dirección incorrecta`); }); it('should throw an error when trying "in" direction after insert "in" and "middle"', async() => { pending('https://redmine.verdnatura.es/issues/4707'); activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; let date = new Date(); date.setDate(date.getDate() - 21); date = weekDay(date, monday); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; date.setHours(8, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); date.setHours(9, 0, 0); ctx.args = {timed: date, direction: 'middle'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); try { date.setHours(10, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error.message).toBe(`Dirección incorrecta`); }); it('Should throw an error when trying "out" before closing a "middle" couple', async() => { pending('https://redmine.verdnatura.es/issues/4707'); activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; let date = new Date(); date.setDate(date.getDate() - 21); date = weekDay(date, monday); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; date.setHours(8, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); date.setHours(9, 0, 0); ctx.args = {timed: date, direction: 'middle'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); try { date.setHours(10, 0, 0); ctx.args = {timed: date, direction: 'out'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error.message).toBe(`Dirección incorrecta`); }); it('should throw an error when trying "middle" after "out"', async() => { pending('https://redmine.verdnatura.es/issues/4707'); activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; let date = new Date(); date.setDate(date.getDate() - 21); date = weekDay(date, monday); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; date.setHours(8, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); date.setHours(9, 0, 0); ctx.args = {timed: date, direction: 'out'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); try { date.setHours(10, 0, 0); ctx.args = {timed: date, direction: 'middle'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error.message).toBe(`Dirección incorrecta`); }); it('should throw an error when trying "out" direction twice', async() => { pending('https://redmine.verdnatura.es/issues/4707'); activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; let date = new Date(); date.setDate(date.getDate() - 21); date = weekDay(date, monday); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; date.setHours(8, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); date.setHours(9, 0, 0); ctx.args = {timed: date, direction: 'out'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); try { date.setHours(10, 0, 0); ctx.args = {timed: date, direction: 'out'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error.message).toBe(`Dirección incorrecta`); }); }); describe('12h rest', () => { it('should throw an error when the 12h rest is not fulfilled yet', async() => { pending('https://redmine.verdnatura.es/issues/4707'); activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; let date = new Date(); date.setDate(date.getDate() - 21); date = weekDay(date, monday); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; date.setHours(8, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); date.setHours(16, 0, 0); ctx.args = {timed: date, direction: 'out'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); try { date = weekDay(date, tuesday); date.setHours(4, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error.message).toBe(`Descanso diario 12h.`); }); it('should not fail as the 12h rest is fulfilled', async() => { pending('https://redmine.verdnatura.es/issues/4707'); activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; let date = new Date(); date.setDate(date.getDate() - 21); date = weekDay(date, monday); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; date.setHours(8, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); date.setHours(16, 0, 0); ctx.args = {timed: date, direction: 'out'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); try { date = weekDay(date, tuesday); date.setHours(4, 1, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error).not.toBeDefined; }); }); describe('for 3500kg drivers with enforced 9h rest', () => { it('should throw an error when the 9h enforced rest is not fulfilled', async() => { pending('https://redmine.verdnatura.es/issues/4707'); activeCtx.accessToken.userId = salesBossId; const workerId = jessicaJonesId; let date = new Date(); date.setDate(date.getDate() - 21); date = weekDay(date, monday); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; date.setHours(8, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); date.setHours(16, 0, 0); ctx.args = {timed: date, direction: 'out'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); try { date = weekDay(date, tuesday); date.setHours(1, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error.message).toBe(`Descanso diario 9h.`); }); it('should not fail when the 9h enforced rest is fulfilled', async() => { pending('https://redmine.verdnatura.es/issues/4707'); activeCtx.accessToken.userId = salesBossId; const workerId = jessicaJonesId; let date = new Date(); date.setDate(date.getDate() - 21); date = weekDay(date, monday); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; date.setHours(8, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); date.setHours(16, 0, 0); ctx.args = {timed: date, direction: 'out'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); try { date = weekDay(date, tuesday); date.setHours(1, 1, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error).not.toBeDefined; }); }); describe('for 36h weekly rest', () => { it('should throw an error when the 36h weekly rest is not fulfilled', async() => { pending('https://redmine.verdnatura.es/issues/4707'); activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; let date = new Date(); date.setMonth(date.getMonth() - 2); date.setDate(1); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; await populateWeek(date, monday, sunday, ctx, workerId, options); date = nextWeek(date); await populateWeek(date, monday, thursday, ctx, workerId, options); date = weekDay(date, friday); date.setHours(7, 59, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); try { date.setHours(8, 1, 0); ctx.args = {timed: date, direction: 'out'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error.message).toBe(`Descanso semanal 36h. / 72h.`); }); it('should throw an error when the 36h weekly rest is not fulfilled again', async() => { pending('https://redmine.verdnatura.es/issues/4707'); activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; let date = new Date(); date.setMonth(date.getMonth() - 2); date.setDate(1); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; await populateWeek(date, monday, sunday, ctx, workerId, options); date = nextWeek(date); await populateWeek(date, monday, thursday, ctx, workerId, options); try { date = weekDay(date, saturday); date.setHours(3, 59, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error.message).toBe(`Descanso semanal 36h. / 72h.`); }); }); describe('for 72h weekly rest', () => { it('should throw when the 72h weekly rest is not fulfilled yet', async() => { pending('https://redmine.verdnatura.es/issues/4707'); activeCtx.accessToken.userId = salesBossId; const workerId = hankPymId; let date = new Date(); date.setMonth(date.getMonth() - 2); date.setDate(1); let error; const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; await populateWeek(date, monday, sunday, ctx, workerId, options); date = nextWeek(date); await populateWeek(date, monday, thursday, ctx, workerId, options); date = nextWeek(date); await populateWeek(date, monday, friday, ctx, workerId, options); date = nextWeek(date); await populateWeek(date, monday, saturday, ctx, workerId, options); date = nextWeek(date); await populateWeek(date, monday, saturday, ctx, workerId, options); date = lastWeek(date); try { date = weekDay(date, sunday); date.setHours(8, 0, 0); ctx.args = {timed: date, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); await tx.rollback(); } catch (e) { await tx.rollback(); error = e; } expect(error.message).toBe(`Descanso semanal 36h. / 72h.`); }); }); }); }); function weekDay(date, dayToSet) { const currentDay = date.getDay(); const distance = dayToSet - currentDay; date.setDate(date.getDate() + distance); return date; } function nextWeek(date) { const sunday = 7; const currentDay = date.getDay(); let newDate = date; if (currentDay != 0) newDate = weekDay(date, sunday); newDate.setDate(newDate.getDate() + 1); return newDate; } function lastWeek(date) { const monday = 1; newDate = weekDay(date, monday); newDate.setDate(newDate.getDate() - 1); return newDate; } async function populateWeek(date, dayStart, dayEnd, ctx, workerId, options) { const dateStart = new Date(weekDay(date, dayStart)); const dateEnd = new Date(dateStart); dateEnd.setDate(dateStart.getDate() + dayEnd); for (let i = dayStart; i <= dayEnd; i++) { dateStart.setHours(8, 0, 0); ctx.args = {timed: dateStart, direction: 'in'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); dateStart.setHours(16, 0, 0); ctx.args = {timed: dateStart, direction: 'out'}; await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); dateStart.setDate(dateStart.getDate() + 1); } }