import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { createTestApp, createTestUser, generateTokensForUser, cleanupTestData } from './test-utils'; import { CreateProjectDto } from '../src/modules/projects/dto/create-project.dto'; import { UpdateProjectDto } from '../src/modules/projects/dto/update-project.dto'; import { v4 as uuidv4 } from 'uuid'; describe('ProjectsController (e2e)', () => { let app: INestApplication; let accessToken: string; let testUser: any; let testUserId: string; let secondTestUser: any; let secondTestUserId: string; let testProject: any; let testProjectId: string; beforeAll(async () => { app = await createTestApp(); // Create test users and generate tokens testUser = await createTestUser(app); testUserId = testUser.id; const tokens = await generateTokensForUser(app, testUserId); accessToken = tokens.accessToken; // Create a second test user for collaborator tests secondTestUser = await createTestUser(app); secondTestUserId = secondTestUser.id; }); afterAll(async () => { // Clean up test data await cleanupTestData(app, testUserId); await cleanupTestData(app, secondTestUserId); await app.close(); }); describe('POST /api/projects', () => { it('should create a new project', async () => { const createProjectDto: CreateProjectDto = { name: `Test Project ${uuidv4().substring(0, 8)}`, description: 'A test project for e2e testing', ownerId: testUserId, settings: { isPublic: true }, }; const response = await request(app.getHttpServer()) .post('/api/projects') .set('Authorization', `Bearer ${accessToken}`) .send(createProjectDto) .expect(201); expect(response.body).toHaveProperty('id'); expect(response.body.name).toBe(createProjectDto.name); expect(response.body.description).toBe(createProjectDto.description); expect(response.body.ownerId).toBe(createProjectDto.ownerId); expect(response.body.settings).toEqual(createProjectDto.settings); // Save the project for later tests testProject = response.body; testProjectId = response.body.id; }); it('should return 400 if required fields are missing', () => { const invalidProjectDto = { // Missing required name and ownerId description: 'An invalid project', }; return request(app.getHttpServer()) .post('/api/projects') .set('Authorization', `Bearer ${accessToken}`) .send(invalidProjectDto) .expect(400); }); }); describe('GET /api/projects', () => { it('should return all projects', () => { return request(app.getHttpServer()) .get('/api/projects') .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(project => project.id === testProjectId)).toBe(true); }); }); it('should filter projects by owner ID', () => { return request(app.getHttpServer()) .get(`/api/projects?ownerId=${testUserId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(200) .expect((res) => { expect(Array.isArray(res.body)).toBe(true); expect(res.body.every(project => project.ownerId === testUserId)).toBe(true); expect(res.body.some(project => project.id === testProjectId)).toBe(true); }); }); }); describe('GET /api/projects/:id', () => { it('should return a project by ID', () => { return request(app.getHttpServer()) .get(`/api/projects/${testProjectId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(200) .expect((res) => { expect(res.body).toHaveProperty('id', testProjectId); expect(res.body.name).toBe(testProject.name); expect(res.body.description).toBe(testProject.description); expect(res.body.ownerId).toBe(testProject.ownerId); }); }); it('should return 404 if project not found', () => { const nonExistentId = uuidv4(); return request(app.getHttpServer()) .get(`/api/projects/${nonExistentId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(404); }); }); describe('PATCH /api/projects/:id', () => { it('should update a project', () => { const updateProjectDto: UpdateProjectDto = { name: `Updated Project ${uuidv4().substring(0, 8)}`, description: 'An updated test project', settings: { isPublic: false }, }; return request(app.getHttpServer()) .patch(`/api/projects/${testProjectId}`) .set('Authorization', `Bearer ${accessToken}`) .send(updateProjectDto) .expect(200) .expect((res) => { expect(res.body).toHaveProperty('id', testProjectId); expect(res.body.name).toBe(updateProjectDto.name); expect(res.body.description).toBe(updateProjectDto.description); expect(res.body.settings).toEqual(updateProjectDto.settings); // Update the testProject object with the new values testProject = res.body; }); }); it('should return 404 if project not found', () => { const nonExistentId = uuidv4(); const updateProjectDto: UpdateProjectDto = { name: 'Updated Project', }; return request(app.getHttpServer()) .patch(`/api/projects/${nonExistentId}`) .set('Authorization', `Bearer ${accessToken}`) .send(updateProjectDto) .expect(404); }); }); describe('GET /api/projects/:id/check-access/:userId', () => { it('should return true if user is the owner', () => { return request(app.getHttpServer()) .get(`/api/projects/${testProjectId}/check-access/${testUserId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(200) .expect((res) => { expect(res.body).toBe(true); }); }); it('should return false if user has no access', () => { return request(app.getHttpServer()) .get(`/api/projects/${testProjectId}/check-access/${secondTestUserId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(200) .expect((res) => { expect(res.body).toBe(false); }); }); }); describe('POST /api/projects/:id/collaborators/:userId', () => { it('should add a collaborator to a project', () => { return request(app.getHttpServer()) .post(`/api/projects/${testProjectId}/collaborators/${secondTestUserId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(201) .expect((res) => { expect(res.body).toHaveProperty('projectId', testProjectId); expect(res.body).toHaveProperty('userId', secondTestUserId); }); }); it('should return 404 if project not found', () => { const nonExistentId = uuidv4(); return request(app.getHttpServer()) .post(`/api/projects/${nonExistentId}/collaborators/${secondTestUserId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(404); }); it('should return 404 if user not found', () => { const nonExistentId = uuidv4(); return request(app.getHttpServer()) .post(`/api/projects/${testProjectId}/collaborators/${nonExistentId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(404); }); }); describe('GET /api/projects/:id/collaborators', () => { it('should return all collaborators for a project', () => { return request(app.getHttpServer()) .get(`/api/projects/${testProjectId}/collaborators`) .set('Authorization', `Bearer ${accessToken}`) .expect(200) .expect((res) => { expect(Array.isArray(res.body)).toBe(true); expect(res.body.some(collab => collab.user.id === secondTestUserId)).toBe(true); }); }); it('should return 404 if project not found', () => { const nonExistentId = uuidv4(); return request(app.getHttpServer()) .get(`/api/projects/${nonExistentId}/collaborators`) .set('Authorization', `Bearer ${accessToken}`) .expect(404); }); }); describe('GET /api/projects/:id/check-access/:userId', () => { it('should return true if user is a collaborator', () => { return request(app.getHttpServer()) .get(`/api/projects/${testProjectId}/check-access/${secondTestUserId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(200) .expect((res) => { expect(res.body).toBe(true); }); }); }); describe('DELETE /api/projects/:id/collaborators/:userId', () => { it('should remove a collaborator from a project', () => { return request(app.getHttpServer()) .delete(`/api/projects/${testProjectId}/collaborators/${secondTestUserId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(204); }); it('should return 404 if collaboration not found', () => { return request(app.getHttpServer()) .delete(`/api/projects/${testProjectId}/collaborators/${secondTestUserId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(404); }); }); // We'll test DELETE last to avoid affecting other tests describe('DELETE /api/projects/:id', () => { let projectToDeleteId: string; beforeAll(async () => { // Create a project specifically for the delete test const createProjectDto: CreateProjectDto = { name: `Project to Delete ${uuidv4().substring(0, 8)}`, description: 'A project that will be deleted', ownerId: testUserId, }; const response = await request(app.getHttpServer()) .post('/api/projects') .set('Authorization', `Bearer ${accessToken}`) .send(createProjectDto) .expect(201); projectToDeleteId = response.body.id; }); it('should delete a project', () => { return request(app.getHttpServer()) .delete(`/api/projects/${projectToDeleteId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(204); }); it('should return 404 if project not found', () => { const nonExistentId = uuidv4(); return request(app.getHttpServer()) .delete(`/api/projects/${nonExistentId}`) .set('Authorization', `Bearer ${accessToken}`) .expect(404); }); }); });