import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { eq } from 'drizzle-orm'; import request from 'supertest'; import { App } from 'supertest/types'; import { AppModule } from '../src/app.module'; import { configureApp } from '../src/common/configure-app'; import { DRIZZLE, type DrizzleDB } from '../src/database/database.module'; import { privilegeDetails, privilegeKeys, privileges, users, } from '../src/database/schema'; import { PRIVILEGE_ACTIONS } from '../src/modules/privileges/privilege-action'; import { registerAndActivate } from './helpers/activate-user'; describe('Employees (e2e)', () => { let app: INestApplication; let db: DrizzleDB; const password = 'password123'; const adminUsername = `emp_admin_${Date.now()}`; const otherUsername = `emp_other_${Date.now()}`; let adminAccessToken: string; let adminUserId: string; let otherAccessToken: string; const payload = { code: `EMP_${Date.now().toString().slice(-6)}`, name: 'Ada Lovelace', phone: '+6281234567890', position: 'sales', }; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); configureApp(app, { NODE_ENV: 'test', SWAGGER_ENABLED: 'false', }); await app.init(); db = app.get(DRIZZLE); const admin = await registerAndActivate(app, db, adminUsername, password); adminAccessToken = admin.accessToken; adminUserId = admin.userId; const other = await registerAndActivate(app, db, otherUsername, password); otherAccessToken = other.accessToken; const now = Date.now(); const [priv] = await db .insert(privileges) .values({ name: 'Employee Admin', code: `EMP_ADMIN_${now}`, status: 'active', createdAt: now, updatedAt: now, createdBy: adminUserId, updatedBy: adminUserId, }) .returning(); const keys = await db.select().from(privilegeKeys); const detailRows = keys.flatMap((key) => PRIVILEGE_ACTIONS.map((action) => ({ privilegeId: priv.id, privilegeKeyId: key.id, action, value: true, })), ); await db.insert(privilegeDetails).values(detailRows); await db .update(users) .set({ privilegeId: priv.id, updatedAt: Date.now() }) .where(eq(users.id, adminUserId)); }); afterAll(async () => { await app.close(); }); it('forbids employees list without permission', async () => { await request(app.getHttpServer()) .get('/employees') .set('Authorization', `Bearer ${otherAccessToken}`) .expect(403); }); it('rejects unauthenticated access', async () => { await request(app.getHttpServer()).get('/employees').expect(401); }); it('CRUD employees with name/code/phone/position rules, status, search, and bulk', async () => { const created = await request(app.getHttpServer()) .post('/employees') .set('Authorization', `Bearer ${adminAccessToken}`) .send(payload) .expect(201); expect(created.body).toMatchObject({ code: payload.code, name: 'Ada Lovelace', phone: '+6281234567890', position: 'sales', status: 'draft', createdBy: adminUserId, }); const id = (created.body as { id: string }).id; await request(app.getHttpServer()) .post('/employees') .set('Authorization', `Bearer ${adminAccessToken}`) .send({ ...payload, code: 'BAD 01', name: 'Jean Luc' }) .expect(400); await request(app.getHttpServer()) .post('/employees') .set('Authorization', `Bearer ${adminAccessToken}`) .send({ ...payload, code: 'PHN_01', phone: '081234567890' }) .expect(400); await request(app.getHttpServer()) .post('/employees') .set('Authorization', `Bearer ${adminAccessToken}`) .send({ ...payload, code: 'POS_01', position: 'pilot' }) .expect(400); await request(app.getHttpServer()) .post('/employees') .set('Authorization', `Bearer ${adminAccessToken}`) .send({ ...payload, code: payload.code, name: 'Jean Luc' }) .expect(409); await request(app.getHttpServer()) .get(`/employees/${id}`) .set('Authorization', `Bearer ${adminAccessToken}`) .expect(200); await request(app.getHttpServer()) .patch(`/employees/${id}`) .set('Authorization', `Bearer ${adminAccessToken}`) .send({ name: 'Jean Luc', position: 'driver' }) .expect(200); await request(app.getHttpServer()) .patch(`/employees/${id}`) .set('Authorization', `Bearer ${adminAccessToken}`) .send({ status: 'active' }) .expect(400); await request(app.getHttpServer()) .patch(`/employees/${id}/status`) .set('Authorization', `Bearer ${adminAccessToken}`) .send({ status: 'active' }) .expect(200); const list = await request(app.getHttpServer()) .get('/employees?search=Jean') .set('Authorization', `Bearer ${adminAccessToken}`) .expect(200); expect( (list.body as { data: unknown[] }).data.length, ).toBeGreaterThanOrEqual(1); expect((list.body as { meta?: unknown }).meta).toBeDefined(); const extra = await request(app.getHttpServer()) .post('/employees') .set('Authorization', `Bearer ${adminAccessToken}`) .send({ ...payload, code: `OTH_${Date.now().toString().slice(-6)}`, name: 'Grace Hopper', position: 'crew', }) .expect(201); await request(app.getHttpServer()) .post('/employees/bulk-status') .set('Authorization', `Bearer ${adminAccessToken}`) .send({ ids: [(extra.body as { id: string }).id], status: 'archived' }) .expect(200); await request(app.getHttpServer()) .post('/employees/bulk-delete') .set('Authorization', `Bearer ${adminAccessToken}`) .send({ ids: [(extra.body as { id: string }).id] }) .expect(200); await request(app.getHttpServer()) .delete(`/employees/${id}`) .set('Authorization', `Bearer ${adminAccessToken}`) .expect(204); }); it('lists employees filtered by an array of positions', async () => { const suffix = Date.now().toString().slice(-6); const auth = { Authorization: `Bearer ${adminAccessToken}` }; const base = { name: 'Ada Lovelace', phone: '+6281234567890', }; const sales = await request(app.getHttpServer()) .post('/employees') .set(auth) .send({ ...base, code: `PS_${suffix}`, position: 'sales' }) .expect(201); const driver = await request(app.getHttpServer()) .post('/employees') .set(auth) .send({ ...base, code: `PD_${suffix}`, position: 'driver' }) .expect(201); const crew = await request(app.getHttpServer()) .post('/employees') .set(auth) .send({ ...base, code: `PC_${suffix}`, position: 'crew' }) .expect(201); const salesId = (sales.body as { id: string }).id; const driverId = (driver.body as { id: string }).id; const crewId = (crew.body as { id: string }).id; const single = await request(app.getHttpServer()) .get(`/employees?position=sales&code=${suffix}`) .set(auth) .expect(200); const singleIds = (single.body as { data: { id: string }[] }).data.map( (row) => row.id, ); expect(singleIds).toEqual([salesId]); const multi = await request(app.getHttpServer()) .get(`/employees?position=sales&position=driver&code=${suffix}`) .set(auth) .expect(200); const multiIds = (multi.body as { data: { id: string }[] }).data.map( (row) => row.id, ); expect(multiIds).toEqual(expect.arrayContaining([salesId, driverId])); expect(multiIds).toHaveLength(2); expect(multiIds).not.toContain(crewId); await request(app.getHttpServer()) .get('/employees?position=pilot') .set(auth) .expect(400); }); it('imports employees from CSV', async () => { const suffix = Date.now().toString().slice(-6); const csv = 'code,name,phone,position,status\n' + `IMP_${suffix},Imported Employee,+6281234567890,crew,draft\n`; const res = await request(app.getHttpServer()) .post('/employees/import') .set('Authorization', `Bearer ${adminAccessToken}`) .attach('file', Buffer.from(csv, 'utf8'), 'employees.csv') .expect(201); expect(res.body).toMatchObject({ imported: 1 }); }); it('creates and updates a linked user from the employee payload', async () => { const suffix = Date.now().toString().slice(-6); const created = await request(app.getHttpServer()) .post('/employees') .set('Authorization', `Bearer ${adminAccessToken}`) .send({ code: `EU_${suffix}`, name: 'Ada Lovelace', phone: '+6281234567890', position: 'sales', user: { username: `emp_usr_${suffix}`, password: 'password123', }, }) .expect(201); expect(created.body.user).toMatchObject({ username: `emp_usr_${suffix}`, }); const employeeId = (created.body as { id: string }).id; const linkedUserId = (created.body.user as { id: string }).id; const user = await request(app.getHttpServer()) .get(`/users/${linkedUserId}`) .set('Authorization', `Bearer ${adminAccessToken}`) .expect(200); expect(user.body.employee).toMatchObject({ id: employeeId, code: `EU_${suffix}`, }); const renamed = await request(app.getHttpServer()) .patch(`/employees/${employeeId}`) .set('Authorization', `Bearer ${adminAccessToken}`) .send({ user: { username: `emp_ren_${suffix}` } }) .expect(200); expect(renamed.body.user).toMatchObject({ id: linkedUserId, username: `emp_ren_${suffix}`, }); const existingUser = await request(app.getHttpServer()) .post('/users') .set('Authorization', `Bearer ${adminAccessToken}`) .send({ username: `emp_ex_${suffix}`, password: 'password123', }) .expect(201); const existingUserId = (existingUser.body as { id: string }).id; const assigned = await request(app.getHttpServer()) .patch(`/employees/${employeeId}`) .set('Authorization', `Bearer ${adminAccessToken}`) .send({ user: { id: existingUserId, username: `emp_as_${suffix}` } }) .expect(200); expect(assigned.body.user).toMatchObject({ id: existingUserId, username: `emp_as_${suffix}`, }); }); });