feat(tests): setup e2e-spec files for route testing. shifts, expenses, timesheets
This commit is contained in:
parent
fd3b9334e3
commit
1949731773
|
|
@ -835,11 +835,24 @@
|
|||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "ShiftsController_getSummary",
|
||||
"operationId": "ShiftsController_findAll",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
"201": {
|
||||
"description": "List of shifts found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/CreateShiftDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "List of shifts not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
|
|
@ -847,6 +860,7 @@
|
|||
"access-token": []
|
||||
}
|
||||
],
|
||||
"summary": "Find all shifts",
|
||||
"tags": [
|
||||
"Shifts"
|
||||
]
|
||||
|
|
@ -1003,6 +1017,25 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/shifts/summary": {
|
||||
"get": {
|
||||
"operationId": "ShiftsController_getSummary",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"access-token": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Shifts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/shifts/export.csv": {
|
||||
"get": {
|
||||
"operationId": "ShiftsController_exportCsv",
|
||||
|
|
@ -2361,7 +2394,6 @@
|
|||
"description": "ID number of an bank code (link with bank-codes)"
|
||||
},
|
||||
"date": {
|
||||
"format": "date-time",
|
||||
"type": "string",
|
||||
"example": "3018-10-20T00:00:00.000Z",
|
||||
"description": "Date where the expense was made"
|
||||
|
|
@ -2417,7 +2449,6 @@
|
|||
"description": "ID number of an bank code (link with bank-codes)"
|
||||
},
|
||||
"date": {
|
||||
"format": "date-time",
|
||||
"type": "string",
|
||||
"example": "3018-10-20T00:00:00.000Z",
|
||||
"description": "Date where the expense was made"
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsBoolean, IsDate, IsDateString, IsInt, IsOptional, IsString } from "class-validator";
|
||||
import { Allow, IsBoolean, IsDate, IsDateString, IsInt, IsNumber, IsOptional, IsString } from "class-validator";
|
||||
|
||||
export class CreateExpenseDto {
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
description: 'Unique ID of the expense (auto-generated)',
|
||||
})
|
||||
id: number;
|
||||
|
||||
@Allow()
|
||||
id?: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: 101,
|
||||
|
|
@ -31,17 +31,15 @@ export class CreateExpenseDto {
|
|||
description: 'Date where the expense was made',
|
||||
})
|
||||
@IsDateString()
|
||||
@Type(() => Date)
|
||||
@IsDate()
|
||||
date: Date;
|
||||
date: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 17.82,
|
||||
description: 'amount in $ for a refund',
|
||||
})
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
amount: number
|
||||
@IsNumber()
|
||||
amount: number;
|
||||
|
||||
@ApiProperty({
|
||||
example:'Spent for mileage between A and B',
|
||||
|
|
@ -63,5 +61,6 @@ export class CreateExpenseDto {
|
|||
description:'Supervisro`s justification for the spending of an employee'
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
supervisor_comment?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export class ShiftsController {
|
|||
return this.shiftsApprovalService.updateApproval(id, isApproved);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Get('summary')
|
||||
async getSummary( @Query() query: GetShiftsOverviewDto): Promise<OverviewRow[]> {
|
||||
return this.shiftsValidationService.getSummary(query.period_id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsDate, IsDateString, IsInt, IsString } from "class-validator";
|
||||
import { Allow, IsDate, IsDateString, IsInt, IsString } from "class-validator";
|
||||
|
||||
export class CreateShiftDto {
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
description: 'Unique ID of the shift (auto-generated)',
|
||||
})
|
||||
@Allow()
|
||||
id: number;
|
||||
|
||||
@ApiProperty({
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ export class ShiftsQueryService {
|
|||
) {}
|
||||
|
||||
async create(dto: CreateShiftDto): Promise<Shifts> {
|
||||
const { timesheet_id, bank_code_id, date, start_time, end_time } = dto;
|
||||
const { timesheet_id, bank_code_id, date, start_time, end_time, description } = dto;
|
||||
|
||||
//shift creation
|
||||
const shift = await this.prisma.shifts.create({
|
||||
data: { timesheet_id, bank_code_id, date, start_time, end_time },
|
||||
data: { timesheet_id, bank_code_id, date, start_time, end_time, description },
|
||||
include: { timesheet: { include: { employee: { include: { user: true } } } },
|
||||
bank_code: true,
|
||||
},
|
||||
|
|
@ -92,7 +92,7 @@ export class ShiftsQueryService {
|
|||
|
||||
async update(id: number, dto: UpdateShiftsDto): Promise<Shifts> {
|
||||
await this.findOne(id);
|
||||
const { timesheet_id, bank_code_id, date,start_time,end_time} = dto;
|
||||
const { timesheet_id, bank_code_id, date,start_time,end_time, description} = dto;
|
||||
return this.prisma.shifts.update({
|
||||
where: { id },
|
||||
data: {
|
||||
|
|
@ -101,6 +101,7 @@ export class ShiftsQueryService {
|
|||
...(date !== undefined && { date }),
|
||||
...(start_time !== undefined && { start_time }),
|
||||
...(end_time !== undefined && { end_time }),
|
||||
...(description !== undefined && { description }),
|
||||
},
|
||||
include: { timesheet: { include: { employee: { include: { user: true } } } },
|
||||
bank_code: true,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsBoolean, IsInt, IsOptional } from "class-validator";
|
||||
import { Allow, IsBoolean, IsInt, IsOptional } from "class-validator";
|
||||
|
||||
export class CreateTimesheetDto {
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
description: 'timesheet`s unique ID (auto-generated)',
|
||||
})
|
||||
id: number;
|
||||
@Allow()
|
||||
id?: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: 426433,
|
||||
|
|
|
|||
|
|
@ -1,25 +1,120 @@
|
|||
import * as request from 'supertest';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { createApp } from './utils/testing-app';
|
||||
// import { resetDb } from './utils/reset-db';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { makeExpense, makeInvalidExpense } from './factories/expense.factory';
|
||||
|
||||
describe('Expenses (e2e)', () => {
|
||||
const BASE = '/Expenses';
|
||||
|
||||
describe('Expenses (e2e) — autonome', () => {
|
||||
let app: INestApplication;
|
||||
const BASE = '/expenses';
|
||||
let prisma: PrismaService;
|
||||
let createdId: number | null = null;
|
||||
|
||||
beforeAll(async () => { app = await createApp(); });
|
||||
beforeEach(async () => {
|
||||
// await resetDb(app);
|
||||
let tsId: number;
|
||||
let bcExpenseId: number; // categorie='EXPENSE' & type != 'MILEAGE'
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await createApp();
|
||||
prisma = app.get(PrismaService);
|
||||
|
||||
const ts = await prisma.timesheets.findFirst({ select: { id: true } });
|
||||
if (!ts) throw new Error('No timesheet found — seed timesheets first.');
|
||||
tsId = ts.id;
|
||||
|
||||
const bc = await prisma.bankCodes.findFirst({
|
||||
where: {
|
||||
categorie: 'EXPENSE',
|
||||
NOT: { type: 'MILEAGE' }, // évite la branche mileageService
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
if (!bc) throw new Error("No non-MILEAGE EXPENSE bank code found — seed bank codes first.");
|
||||
bcExpenseId = bc.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
const prisma = app.get(PrismaService);
|
||||
if (createdId) {
|
||||
try { await prisma.expenses.delete({ where: { id: createdId } }); } catch {}
|
||||
}
|
||||
await app.close();
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
it(`GET ${BASE} → 200`, async () => {
|
||||
it(`GET ${BASE} → 200 (array)`, async () => {
|
||||
const res = await request(app.getHttpServer()).get(BASE);
|
||||
expect(res.status).toBe(200);
|
||||
expect(Array.isArray(res.body)).toBe(true);
|
||||
});
|
||||
|
||||
it(`POST ${BASE} (valid) → 201 puis GET /:id → 200`, async () => {
|
||||
const payload = makeExpense(tsId, bcExpenseId);
|
||||
const createRes = await request(app.getHttpServer()).post(BASE).send(payload);
|
||||
|
||||
if (createRes.status !== 201) {
|
||||
console.log('Create error:', createRes.body || createRes.text);
|
||||
}
|
||||
expect(createRes.status).toBe(201);
|
||||
expect(createRes.body).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
timesheet_id: tsId,
|
||||
bank_code_id: bcExpenseId,
|
||||
})
|
||||
);
|
||||
createdId = createRes.body.id;
|
||||
|
||||
const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
|
||||
expect(getRes.status).toBe(200);
|
||||
expect(getRes.body).toEqual(expect.objectContaining({ id: createdId }));
|
||||
});
|
||||
|
||||
it(`PATCH ${BASE}/:id → 200 (amount/description mis à jour)`, async () => {
|
||||
if (!createdId) {
|
||||
const created = await request(app.getHttpServer())
|
||||
.post(BASE)
|
||||
.send(makeExpense(tsId, bcExpenseId));
|
||||
expect(created.status).toBe(201);
|
||||
createdId = created.body.id;
|
||||
}
|
||||
|
||||
const updated = { amount: 123, description: 'Updated expense' }; // amount INT
|
||||
const patchRes = await request(app.getHttpServer())
|
||||
.patch(`${BASE}/${createdId}`)
|
||||
.send(updated);
|
||||
expect([200, 204]).toContain(patchRes.status);
|
||||
|
||||
const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
|
||||
expect(getRes.status).toBe(200);
|
||||
if (getRes.body?.amount !== undefined) {
|
||||
expect(Number(getRes.body.amount)).toBe(updated.amount);
|
||||
}
|
||||
if (getRes.body?.description !== undefined) expect(getRes.body.description).toBe(updated.description);
|
||||
});
|
||||
|
||||
it(`GET ${BASE}/999999 (not found) → 404/400`, async () => {
|
||||
const res = await request(app.getHttpServer()).get(`${BASE}/999999`);
|
||||
expect([404, 400]).toContain(res.status);
|
||||
});
|
||||
|
||||
it(`POST ${BASE} (invalid payload) → 400`, async () => {
|
||||
const res = await request(app.getHttpServer()).post(BASE).send(makeInvalidExpense());
|
||||
expect(res.status).toBeGreaterThanOrEqual(400);
|
||||
expect(res.status).toBeLessThan(500);
|
||||
});
|
||||
|
||||
it(`DELETE ${BASE}/:id → 200/204`, async () => {
|
||||
let id = createdId;
|
||||
if (!id) {
|
||||
const created = await request(app.getHttpServer())
|
||||
.post(BASE)
|
||||
.send(makeExpense(tsId, bcExpenseId));
|
||||
expect(created.status).toBe(201);
|
||||
id = created.body.id;
|
||||
}
|
||||
|
||||
const del = await request(app.getHttpServer()).delete(`${BASE}/${id}`);
|
||||
expect([200, 204]).toContain(del.status);
|
||||
if (createdId === id) createdId = null;
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
// test/factories/expense.factory.ts
|
||||
export type ExpensePayload = {
|
||||
timesheet_id: number;
|
||||
bank_code_id: number; // bank code categorie='EXPENSE' and type != 'MILEAGE'
|
||||
date: string; // ISO date
|
||||
amount: number; // INT (DTO: @IsInt)
|
||||
description: string;
|
||||
is_approved?: boolean;
|
||||
supervisor_comment?: string;
|
||||
};
|
||||
|
||||
const randInt = (min: number, max: number) =>
|
||||
Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
|
||||
const isoDate = (y: number, m: number, d: number) =>
|
||||
new Date(Date.UTC(y, m - 1, d, 0, 0, 0)).toISOString();
|
||||
|
||||
export function makeExpense(
|
||||
tsId: number,
|
||||
bcId: number,
|
||||
overrides: Partial<ExpensePayload> = {}
|
||||
): ExpensePayload {
|
||||
return {
|
||||
timesheet_id: tsId,
|
||||
bank_code_id: bcId,
|
||||
date: isoDate(2024, 6, randInt(1, 28)),
|
||||
amount: randInt(5, 200),
|
||||
description: `Expense ${randInt(100, 999)}`,
|
||||
supervisor_comment: 'N/A',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// volontairement invalide pour 400 via ValidationPipe
|
||||
export function makeInvalidExpense(): Record<string, unknown> {
|
||||
return { amount: 'oops', timesheet_id: 'nope' };
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// test/factories/shift.factory.ts
|
||||
export type ShiftPayload = {
|
||||
timesheet_id: number;
|
||||
bank_code_id: number;
|
||||
date: string; // ISO date (jour)
|
||||
start_time: string; // ISO datetime (heure)
|
||||
end_time: string; // ISO datetime (heure)
|
||||
description: string; // requis par le DTO
|
||||
};
|
||||
|
||||
const randInt = (min: number, max: number) =>
|
||||
Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
|
||||
const isoDate = (y: number, m: number, d: number) =>
|
||||
new Date(Date.UTC(y, m - 1, d, 0, 0, 0)).toISOString();
|
||||
|
||||
const isoTime = (h: number, m = 0) =>
|
||||
new Date(Date.UTC(1970, 0, 1, h, m, 0)).toISOString();
|
||||
|
||||
export function makeShift(
|
||||
tsId: number,
|
||||
bcId: number,
|
||||
overrides: Partial<ShiftPayload> = {}
|
||||
): ShiftPayload {
|
||||
// 8h pile pour ne pas dépasser DAILY_LIMIT_HOURS (8 par défaut)
|
||||
const startH = 8;
|
||||
const endH = 16;
|
||||
|
||||
return {
|
||||
timesheet_id: tsId,
|
||||
bank_code_id: bcId,
|
||||
date: isoDate(2024, 5, randInt(1, 28)),
|
||||
start_time: isoTime(startH),
|
||||
end_time: isoTime(endH),
|
||||
description: `Shift ${randInt(100, 999)}`,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// payload invalide pour 400 via ValidationPipe
|
||||
export function makeInvalidShift(): Record<string, unknown> {
|
||||
return { timesheet_id: 'nope' }; // types volontairement faux
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// test/factories/timesheet.factory.ts
|
||||
|
||||
export type TimesheetPayload = {
|
||||
employee_id: number;
|
||||
is_approved?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Construit un payload valide pour POST /timesheets.
|
||||
* Par défaut, is_approved=false.
|
||||
*/
|
||||
export function makeTimesheet(
|
||||
employeeId: number,
|
||||
overrides: Partial<TimesheetPayload> = {}
|
||||
): TimesheetPayload {
|
||||
return {
|
||||
employee_id: employeeId,
|
||||
is_approved: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Payload délibérément invalide pour déclencher un 400 via ValidationPipe.
|
||||
*/
|
||||
export function makeInvalidTimesheet(): Record<string, unknown> {
|
||||
return { employee_id: 'not-a-number' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper pour récupérer un employee_id existant
|
||||
* sans importer les types Prisma dans le factory.
|
||||
*/
|
||||
export async function pickAnyEmployeeId(
|
||||
prisma: { employees: { findFirst: (args?: any) => Promise<{ id: number } | null> } }
|
||||
): Promise<number | null> {
|
||||
const emp = await prisma.employees.findFirst({ select: { id: true } });
|
||||
return emp?.id ?? null;
|
||||
}
|
||||
|
|
@ -1,25 +1,121 @@
|
|||
import * as request from 'supertest';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { createApp } from './utils/testing-app';
|
||||
// import { resetDb } from './utils/reset-db';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { makeShift, makeInvalidShift } from './factories/shift.factory';
|
||||
|
||||
describe('Shifts (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
const BASE = '/shifts';
|
||||
|
||||
beforeAll(async () => { app = await createApp(); });
|
||||
beforeEach(async () => {
|
||||
// await resetDb(app);
|
||||
describe('Shifts (e2e) — autonome', () => {
|
||||
let app: INestApplication;
|
||||
let prisma: PrismaService;
|
||||
let createdId: number | null = null;
|
||||
|
||||
// FKs existants
|
||||
let tsId: number; // any timesheet
|
||||
let bcShiftId: number; // any bank code with categorie='SHIFT'
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await createApp();
|
||||
prisma = app.get(PrismaService);
|
||||
|
||||
// récupère un timesheet existant
|
||||
const ts = await prisma.timesheets.findFirst({ select: { id: true } });
|
||||
if (!ts) throw new Error('No timesheet found — seed timesheets first.');
|
||||
tsId = ts.id;
|
||||
|
||||
// récupère un bank code SHIFT (ta seed utilise 'SHIFT' en MAJ)
|
||||
const bc = await prisma.bankCodes.findFirst({
|
||||
where: { categorie: 'SHIFT' },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!bc) throw new Error('No SHIFT bank code found — seed bank codes first.');
|
||||
bcShiftId = bc.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
const prisma = app.get(PrismaService);
|
||||
if (createdId) {
|
||||
try {
|
||||
await prisma.shifts.delete({ where: { id: createdId } });
|
||||
} catch {}
|
||||
}
|
||||
await app.close();
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
it(`GET ${BASE} → 200`, async () => {
|
||||
const res = await request(app.getHttpServer()).get(BASE);
|
||||
expect(res.status).toBe(200);
|
||||
it(`POST ${BASE} (valid) → 201 puis GET /:id → 200`, async () => {
|
||||
const payload = makeShift(tsId, bcShiftId);
|
||||
|
||||
const createRes = await request(app.getHttpServer())
|
||||
.post(BASE)
|
||||
.send(payload);
|
||||
|
||||
if (createRes.status !== 201) {
|
||||
console.log('Create error:', createRes.body || createRes.text);
|
||||
}
|
||||
expect(createRes.status).toBe(201);
|
||||
expect(createRes.body).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
timesheet_id: tsId,
|
||||
bank_code_id: bcShiftId,
|
||||
})
|
||||
);
|
||||
createdId = createRes.body.id;
|
||||
|
||||
const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
|
||||
expect(getRes.status).toBe(200);
|
||||
expect(getRes.body).toEqual(expect.objectContaining({ id: createdId }));
|
||||
});
|
||||
|
||||
it(`PATCH ${BASE}/:id → 200 (description mise à jour)`, async () => {
|
||||
if (!createdId) {
|
||||
const created = await request(app.getHttpServer())
|
||||
.post(BASE)
|
||||
.send(makeShift(tsId, bcShiftId));
|
||||
expect(created.status).toBe(201);
|
||||
createdId = created.body.id;
|
||||
}
|
||||
|
||||
const patchRes = await request(app.getHttpServer())
|
||||
.patch(`${BASE}/${createdId}`)
|
||||
.send({ description: 'Updated shift description' });
|
||||
|
||||
expect([200, 204]).toContain(patchRes.status);
|
||||
|
||||
const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
|
||||
expect(getRes.status).toBe(200);
|
||||
// on tolère l’absence, mais si elle est là, on vérifie la valeur.
|
||||
if (getRes.body?.description !== undefined) {
|
||||
expect(getRes.body.description).toBe('Updated shift description');
|
||||
}
|
||||
});
|
||||
|
||||
it(`GET ${BASE}/999999 (not found) → 404/400`, async () => {
|
||||
const res = await request(app.getHttpServer()).get(`${BASE}/999999`);
|
||||
expect([404, 400]).toContain(res.status);
|
||||
});
|
||||
|
||||
it(`POST ${BASE} (invalid payload) → 400`, async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(BASE)
|
||||
.send(makeInvalidShift());
|
||||
expect(res.status).toBeGreaterThanOrEqual(400);
|
||||
expect(res.status).toBeLessThan(500);
|
||||
});
|
||||
|
||||
it(`DELETE ${BASE}/:id → 200/204`, async () => {
|
||||
let id = createdId;
|
||||
if (!id) {
|
||||
const created = await request(app.getHttpServer())
|
||||
.post(BASE)
|
||||
.send(makeShift(tsId, bcShiftId));
|
||||
expect(created.status).toBe(201);
|
||||
id = created.body.id;
|
||||
}
|
||||
|
||||
const del = await request(app.getHttpServer()).delete(`${BASE}/${id}`);
|
||||
expect([200, 204]).toContain(del.status);
|
||||
if (createdId === id) createdId = null;
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,17 +1,34 @@
|
|||
import * as request from 'supertest';
|
||||
// test/timesheets.e2e-spec.ts
|
||||
const request = require('supertest');
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { createApp } from './utils/testing-app';
|
||||
// import { resetDb } from './utils/reset-db';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import {
|
||||
makeTimesheet,
|
||||
makeInvalidTimesheet,
|
||||
} from './factories/timesheet.factory';
|
||||
import { makeEmployee } from './factories/employee.factory';
|
||||
import { createApp } from './utils/testing-app';
|
||||
|
||||
describe('Timesheets (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
describe('Timesheets (e2e) — autonome', () => {
|
||||
const BASE = '/timesheets';
|
||||
let app: INestApplication;
|
||||
let employeeIdForCreation: number;
|
||||
|
||||
beforeAll(async () => { app = await createApp(); });
|
||||
beforeEach(async () => {
|
||||
// await resetDb(app);
|
||||
beforeAll(async () => {
|
||||
app = await createApp();
|
||||
|
||||
// ✅ Crée un employé dédié pour cette suite
|
||||
const empRes = await request(app.getHttpServer())
|
||||
.post('/employees')
|
||||
.send(makeEmployee());
|
||||
if (empRes.status !== 201) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Impossible de créer un employé pour les tests timesheets:', empRes.body || empRes.text);
|
||||
throw new Error('Setup employé échoué');
|
||||
}
|
||||
employeeIdForCreation = empRes.body.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
const prisma = app.get(PrismaService);
|
||||
await app.close();
|
||||
|
|
@ -21,5 +38,75 @@ describe('Timesheets (e2e)', () => {
|
|||
it(`GET ${BASE} → 200`, async () => {
|
||||
const res = await request(app.getHttpServer()).get(BASE);
|
||||
expect(res.status).toBe(200);
|
||||
expect(Array.isArray(res.body)).toBe(true);
|
||||
});
|
||||
|
||||
it(`POST ${BASE} (valid) → 201 puis GET /:id → 200`, async () => {
|
||||
const payload = makeTimesheet(employeeIdForCreation, { is_approved: true });
|
||||
|
||||
const createRes = await request(app.getHttpServer()).post(BASE).send(payload);
|
||||
if (createRes.status !== 201) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Create error:', createRes.body || createRes.text);
|
||||
}
|
||||
expect(createRes.status).toBe(201);
|
||||
expect(createRes.body).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
employee_id: payload.employee_id,
|
||||
}),
|
||||
);
|
||||
const id = createRes.body.id;
|
||||
|
||||
const getRes = await request(app.getHttpServer()).get(`${BASE}/${id}`);
|
||||
expect(getRes.status).toBe(200);
|
||||
expect(getRes.body).toEqual(
|
||||
expect.objectContaining({
|
||||
id,
|
||||
employee_id: payload.employee_id,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it(`PATCH ${BASE}/:id → 200 (is_approved toggled)`, async () => {
|
||||
const created = await request(app.getHttpServer())
|
||||
.post(BASE)
|
||||
.send(makeTimesheet(employeeIdForCreation));
|
||||
expect(created.status).toBe(201);
|
||||
const id = created.body.id;
|
||||
|
||||
const updated = await request(app.getHttpServer())
|
||||
.patch(`${BASE}/${id}`)
|
||||
.send({ is_approved: true });
|
||||
expect(updated.status).toBe(200);
|
||||
expect(updated.body).toEqual(
|
||||
expect.objectContaining({
|
||||
id,
|
||||
is_approved: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it(`POST ${BASE} (invalid payload) → 400`, async () => {
|
||||
const bad = await request(app.getHttpServer())
|
||||
.post(BASE)
|
||||
.send(makeInvalidTimesheet());
|
||||
|
||||
expect(bad.status).toBeGreaterThanOrEqual(400);
|
||||
expect(bad.status).toBeLessThan(500);
|
||||
});
|
||||
|
||||
it(`DELETE ${BASE}/:id → 200/204`, async () => {
|
||||
const created = await request(app.getHttpServer())
|
||||
.post(BASE)
|
||||
.send(makeTimesheet(employeeIdForCreation));
|
||||
expect(created.status).toBe(201);
|
||||
const id = created.body.id;
|
||||
|
||||
const delRes = await request(app.getHttpServer()).delete(`${BASE}/${id}`);
|
||||
expect([200, 204]).toContain(delRes.status);
|
||||
|
||||
const getRes = await request(app.getHttpServer()).get(`${BASE}/${id}`);
|
||||
expect(getRes.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user