diff --git a/backend/test/persons.e2e-spec.ts b/backend/test/persons.e2e-spec.ts new file mode 100644 index 0000000..6d71fc8 --- /dev/null +++ b/backend/test/persons.e2e-spec.ts @@ -0,0 +1,242 @@ +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { createTestApp, createTestUser, generateTokensForUser, cleanupTestData } from './test-utils'; +import { v4 as uuidv4 } from 'uuid'; + +describe('PersonsController (e2e)', () => { + let app: INestApplication; + let accessToken: string; + let testUser: any; + let testUserId: string; + let testProjectId: string; + let testPersonId: string; + let testGroupId: string; + + beforeAll(async () => { + app = await createTestApp(); + + // Create a test user and generate tokens + testUser = await createTestUser(app); + testUserId = testUser.id; + const tokens = await generateTokensForUser(app, testUserId); + accessToken = tokens.accessToken; + + // Create a test project + const projectResponse = await request(app.getHttpServer()) + .post('/api/projects') + .set('Authorization', `Bearer ${accessToken}`) + .send({ + name: `Test Project ${uuidv4().substring(0, 8)}`, + description: 'Test project for e2e tests', + ownerId: testUserId + }); + testProjectId = projectResponse.body.id; + + // Create a test group + const groupResponse = await request(app.getHttpServer()) + .post('/api/groups') + .set('Authorization', `Bearer ${accessToken}`) + .send({ + name: `Test Group ${uuidv4().substring(0, 8)}`, + projectId: testProjectId, + description: 'Test group for e2e tests' + }); + testGroupId = groupResponse.body.id; + }); + + afterAll(async () => { + // Clean up test data + if (testPersonId) { + await request(app.getHttpServer()) + .delete(`/api/persons/${testPersonId}`) + .set('Authorization', `Bearer ${accessToken}`); + } + + if (testGroupId) { + await request(app.getHttpServer()) + .delete(`/api/groups/${testGroupId}`) + .set('Authorization', `Bearer ${accessToken}`); + } + + if (testProjectId) { + await request(app.getHttpServer()) + .delete(`/api/projects/${testProjectId}`) + .set('Authorization', `Bearer ${accessToken}`); + } + + await cleanupTestData(app, testUserId); + await app.close(); + }); + + describe('POST /api/persons', () => { + it('should create a new person', async () => { + const createPersonDto = { + name: `Test Person ${uuidv4().substring(0, 8)}`, + projectId: testProjectId, + skills: ['JavaScript', 'TypeScript'], + metadata: { email: 'testperson@example.com' } + }; + + const response = await request(app.getHttpServer()) + .post('/api/persons') + .set('Authorization', `Bearer ${accessToken}`) + .send(createPersonDto) + .expect(201); + + expect(response.body).toHaveProperty('id'); + expect(response.body.name).toBe(createPersonDto.name); + expect(response.body.projectId).toBe(createPersonDto.projectId); + expect(response.body.skills).toEqual(createPersonDto.skills); + + testPersonId = response.body.id; + }); + + it('should return 401 when not authenticated', () => { + return request(app.getHttpServer()) + .post('/api/persons') + .send({ + name: 'Unauthorized Person', + projectId: testProjectId + }) + .expect(401); + }); + }); + + describe('GET /api/persons', () => { + it('should return all persons', () => { + return request(app.getHttpServer()) + .get('/api/persons') + .set('Authorization', `Bearer ${accessToken}`) + .expect(200) + .expect((res) => { + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.length).toBeGreaterThan(0); + expect(res.body.some(person => person.id === testPersonId)).toBe(true); + }); + }); + + it('should filter persons by project ID', () => { + return request(app.getHttpServer()) + .get(`/api/persons?projectId=${testProjectId}`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(200) + .expect((res) => { + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.length).toBeGreaterThan(0); + expect(res.body.every(person => person.projectId === testProjectId)).toBe(true); + }); + }); + + it('should return 401 when not authenticated', () => { + return request(app.getHttpServer()) + .get('/api/persons') + .expect(401); + }); + }); + + describe('GET /api/persons/:id', () => { + it('should return a person by ID', () => { + return request(app.getHttpServer()) + .get(`/api/persons/${testPersonId}`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(200) + .expect((res) => { + expect(res.body).toHaveProperty('id', testPersonId); + expect(res.body).toHaveProperty('projectId', testProjectId); + }); + }); + + it('should return 401 when not authenticated', () => { + return request(app.getHttpServer()) + .get(`/api/persons/${testPersonId}`) + .expect(401); + }); + + it('should return 404 for non-existent person', () => { + const nonExistentId = uuidv4(); + return request(app.getHttpServer()) + .get(`/api/persons/${nonExistentId}`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(404); + }); + }); + + describe('PATCH /api/persons/:id', () => { + it('should update a person', () => { + const updateData = { + name: `Updated Person ${uuidv4().substring(0, 8)}`, + skills: ['JavaScript', 'TypeScript', 'NestJS'] + }; + + return request(app.getHttpServer()) + .patch(`/api/persons/${testPersonId}`) + .set('Authorization', `Bearer ${accessToken}`) + .send(updateData) + .expect(200) + .expect((res) => { + expect(res.body).toHaveProperty('id', testPersonId); + expect(res.body.name).toBe(updateData.name); + expect(res.body.skills).toEqual(updateData.skills); + }); + }); + + it('should return 401 when not authenticated', () => { + return request(app.getHttpServer()) + .patch(`/api/persons/${testPersonId}`) + .send({ name: 'Unauthorized Update' }) + .expect(401); + }); + }); + + describe('POST /api/persons/:id/groups/:groupId', () => { + it('should add a person to a group', () => { + return request(app.getHttpServer()) + .post(`/api/persons/${testPersonId}/groups/${testGroupId}`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(201); + }); + + it('should return 401 when not authenticated', () => { + return request(app.getHttpServer()) + .post(`/api/persons/${testPersonId}/groups/${testGroupId}`) + .expect(401); + }); + }); + + describe('GET /api/persons/project/:projectId/group/:groupId', () => { + it('should get persons by project ID and group ID', () => { + return request(app.getHttpServer()) + .get(`/api/persons/project/${testProjectId}/group/${testGroupId}`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(200) + .expect((res) => { + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.length).toBeGreaterThan(0); + expect(res.body.some(person => person.id === testPersonId)).toBe(true); + }); + }); + + it('should return 401 when not authenticated', () => { + return request(app.getHttpServer()) + .get(`/api/persons/project/${testProjectId}/group/${testGroupId}`) + .expect(401); + }); + }); + + describe('DELETE /api/persons/:id/groups/:groupId', () => { + it('should remove a person from a group', () => { + return request(app.getHttpServer()) + .delete(`/api/persons/${testPersonId}/groups/${testGroupId}`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(204); + }); + + it('should return 401 when not authenticated', () => { + return request(app.getHttpServer()) + .delete(`/api/persons/${testPersonId}/groups/${testGroupId}`) + .expect(401); + }); + }); + + // Note: We're not testing the DELETE /api/persons/:id endpoint here to avoid complications with test cleanup +}); \ No newline at end of file