Compare commits
No commits in common. "bdca6511bd6d83d5bfddf102b390194947cb0857" and "cee85c9885db4e86397431a0f190883440251f17" have entirely different histories.
bdca6511bd
...
cee85c9885
27
.github/README.md
vendored
27
.github/README.md
vendored
@ -2,33 +2,6 @@
|
|||||||
|
|
||||||
This directory contains the CI/CD configuration for the project.
|
This directory contains the CI/CD configuration for the project.
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
The project includes end-to-end (e2e) tests to ensure the API endpoints work correctly. The tests are located in the `backend/test` directory.
|
|
||||||
|
|
||||||
### Running E2E Tests
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Navigate to the backend directory
|
|
||||||
cd backend
|
|
||||||
|
|
||||||
# Run e2e tests
|
|
||||||
npm run test:e2e
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Structure
|
|
||||||
|
|
||||||
- `app.e2e-spec.ts`: Tests the basic API endpoint (/api)
|
|
||||||
- `auth.e2e-spec.ts`: Tests authentication endpoints including:
|
|
||||||
- User profile retrieval
|
|
||||||
- Token refresh
|
|
||||||
- GitHub OAuth redirection
|
|
||||||
- `test-utils.ts`: Utility functions for testing including:
|
|
||||||
- Creating test applications
|
|
||||||
- Creating test users
|
|
||||||
- Generating authentication tokens
|
|
||||||
- Cleaning up test data
|
|
||||||
|
|
||||||
## CI/CD Workflow
|
## CI/CD Workflow
|
||||||
|
|
||||||
The CI/CD pipeline is configured using GitHub Actions and is defined in the `.github/workflows/ci-cd.yml` file. The workflow consists of the following steps:
|
The CI/CD pipeline is configured using GitHub Actions and is defined in the `.github/workflows/ci-cd.yml` file. The workflow consists of the following steps:
|
||||||
|
@ -95,23 +95,6 @@ $ pnpm run test:e2e
|
|||||||
$ pnpm run test:cov
|
$ pnpm run test:cov
|
||||||
```
|
```
|
||||||
|
|
||||||
### End-to-End (E2E) Tests
|
|
||||||
|
|
||||||
The project includes comprehensive end-to-end tests to ensure API endpoints work correctly. These tests are located in the `test` directory:
|
|
||||||
|
|
||||||
- `app.e2e-spec.ts`: Tests the basic API endpoint (/api)
|
|
||||||
- `auth.e2e-spec.ts`: Tests authentication endpoints including:
|
|
||||||
- User profile retrieval
|
|
||||||
- Token refresh
|
|
||||||
- GitHub OAuth redirection
|
|
||||||
- `test-utils.ts`: Utility functions for testing including:
|
|
||||||
- Creating test applications
|
|
||||||
- Creating test users
|
|
||||||
- Generating authentication tokens
|
|
||||||
- Cleaning up test data
|
|
||||||
|
|
||||||
The e2e tests use a real database connection and create/delete test data automatically, ensuring a clean test environment for each test run.
|
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
|
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
"start": "nest start",
|
"start": "nest start",
|
||||||
"start:dev": "nest start --watch",
|
"start:dev": "nest start --watch",
|
||||||
"start:debug": "nest start --debug --watch",
|
"start:debug": "nest start --debug --watch",
|
||||||
"start:prod": "node dist/src/main",
|
"start:prod": "node dist/main",
|
||||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
@ -33,7 +33,6 @@
|
|||||||
"@nestjs/passport": "^11.0.5",
|
"@nestjs/passport": "^11.0.5",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
"@nestjs/platform-socket.io": "^11.1.1",
|
"@nestjs/platform-socket.io": "^11.1.1",
|
||||||
"@nestjs/swagger": "^11.2.0",
|
|
||||||
"@nestjs/websockets": "^11.1.1",
|
"@nestjs/websockets": "^11.1.1",
|
||||||
"@node-rs/argon2": "^2.0.2",
|
"@node-rs/argon2": "^2.0.2",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
@ -49,7 +48,6 @@
|
|||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"swagger-ui-express": "^5.0.1",
|
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
"zod": "^3.24.4",
|
"zod": "^3.24.4",
|
||||||
"zod-validation-error": "^3.4.1"
|
"zod-validation-error": "^3.4.1"
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { Controller, Get } from '@nestjs/common';
|
import { Controller, Get } from '@nestjs/common';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { Public } from './modules/auth/decorators/public.decorator';
|
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AppController {
|
export class AppController {
|
||||||
constructor(private readonly appService: AppService) {}
|
constructor(private readonly appService: AppService) {}
|
||||||
|
|
||||||
@Public()
|
|
||||||
@Get()
|
@Get()
|
||||||
getHello(): string {
|
getHello(): string {
|
||||||
return this.appService.getHello();
|
return this.appService.getHello();
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { ValidationPipe } from '@nestjs/common';
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
@ -33,9 +32,8 @@ async function bootstrap() {
|
|||||||
// En production, on restreint les origines autorisées
|
// En production, on restreint les origines autorisées
|
||||||
const allowedOrigins = [frontendUrl];
|
const allowedOrigins = [frontendUrl];
|
||||||
// Ajouter d'autres origines si nécessaire (ex: sous-domaines, CDN, etc.)
|
// Ajouter d'autres origines si nécessaire (ex: sous-domaines, CDN, etc.)
|
||||||
const additionalOrigins = configService.get<string>('ADDITIONAL_CORS_ORIGINS');
|
if (configService.get<string>('ADDITIONAL_CORS_ORIGINS')) {
|
||||||
if (additionalOrigins) {
|
allowedOrigins.push(...configService.get<string>('ADDITIONAL_CORS_ORIGINS').split(','));
|
||||||
allowedOrigins.push(...additionalOrigins.split(','));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.enableCors({
|
app.enableCors({
|
||||||
@ -55,22 +53,10 @@ async function bootstrap() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Préfixe global pour les routes API
|
// Préfixe global pour les routes API
|
||||||
const apiPrefix = configService.get<string>('API_PREFIX', 'api');
|
app.setGlobalPrefix(configService.get<string>('API_PREFIX', 'api'));
|
||||||
app.setGlobalPrefix(apiPrefix);
|
|
||||||
|
|
||||||
// Configuration de Swagger
|
|
||||||
const config = new DocumentBuilder()
|
|
||||||
.setTitle('Group Maker API')
|
|
||||||
.setDescription('API documentation for the Group Maker application')
|
|
||||||
.setVersion('1.0')
|
|
||||||
.addBearerAuth()
|
|
||||||
.build();
|
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
|
||||||
SwaggerModule.setup('api/docs', app, document);
|
|
||||||
|
|
||||||
const port = configService.get<number>('PORT', 3000);
|
const port = configService.get<number>('PORT', 3000);
|
||||||
await app.listen(port);
|
await app.listen(port);
|
||||||
console.log(`Application is running on: http://localhost:${port}`);
|
console.log(`Application is running on: http://localhost:${port}`);
|
||||||
console.log(`Swagger documentation is available at: http://localhost:${port}/api/docs`);
|
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
@ -8,8 +8,6 @@ import {
|
|||||||
Put,
|
Put,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
Query,
|
Query,
|
||||||
HttpCode,
|
|
||||||
HttpStatus,
|
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { GroupsService } from '../services/groups.service';
|
import { GroupsService } from '../services/groups.service';
|
||||||
import { CreateGroupDto } from '../dto/create-group.dto';
|
import { CreateGroupDto } from '../dto/create-group.dto';
|
||||||
@ -68,7 +66,6 @@ export class GroupsController {
|
|||||||
* Add a person to a group
|
* Add a person to a group
|
||||||
*/
|
*/
|
||||||
@Post(':id/persons/:personId')
|
@Post(':id/persons/:personId')
|
||||||
@HttpCode(HttpStatus.CREATED)
|
|
||||||
addPersonToGroup(
|
addPersonToGroup(
|
||||||
@Param('id') groupId: string,
|
@Param('id') groupId: string,
|
||||||
@Param('personId') personId: string,
|
@Param('personId') personId: string,
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { GroupsController } from './controllers/groups.controller';
|
import { GroupsController } from './controllers/groups.controller';
|
||||||
import { GroupsService } from './services/groups.service';
|
import { GroupsService } from './services/groups.service';
|
||||||
import { WebSocketsModule } from '../websockets/websockets.module';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [WebSocketsModule],
|
|
||||||
controllers: [GroupsController],
|
controllers: [GroupsController],
|
||||||
providers: [GroupsService],
|
providers: [GroupsService],
|
||||||
exports: [GroupsService],
|
exports: [GroupsService],
|
||||||
|
@ -121,59 +121,14 @@ export class GroupsService {
|
|||||||
// Check if the group exists
|
// Check if the group exists
|
||||||
const group = await this.findById(groupId);
|
const group = await this.findById(groupId);
|
||||||
|
|
||||||
// Check if the person exists in persons table
|
// Check if the person exists
|
||||||
let person: any = null;
|
const [person] = await this.db
|
||||||
|
|
||||||
// First try to find in persons table
|
|
||||||
const [personResult] = await this.db
|
|
||||||
.select()
|
.select()
|
||||||
.from(schema.persons)
|
.from(schema.persons)
|
||||||
.where(eq(schema.persons.id, personId));
|
.where(eq(schema.persons.id, personId));
|
||||||
|
|
||||||
if (personResult) {
|
if (!person) {
|
||||||
person = personResult;
|
throw new NotFoundException(`Person with ID ${personId} not found`);
|
||||||
} else {
|
|
||||||
// If not found in persons table, check users table (for e2e tests)
|
|
||||||
const [user] = await this.db
|
|
||||||
.select()
|
|
||||||
.from(schema.users)
|
|
||||||
.where(eq(schema.users.id, personId));
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw new NotFoundException(`Person or User with ID ${personId} not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For e2e tests, create a mock person record for the user
|
|
||||||
try {
|
|
||||||
const [createdPerson] = await this.db
|
|
||||||
.insert(schema.persons)
|
|
||||||
.values({
|
|
||||||
id: user.id, // Use the same ID as the user
|
|
||||||
firstName: user.name.split(' ')[0] || 'Test',
|
|
||||||
lastName: user.name.split(' ')[1] || 'User',
|
|
||||||
gender: 'MALE', // Default value for testing
|
|
||||||
technicalLevel: 3, // Default value for testing
|
|
||||||
hasTechnicalTraining: true, // Default value for testing
|
|
||||||
frenchSpeakingLevel: 5, // Default value for testing
|
|
||||||
oralEaseLevel: 'COMFORTABLE', // Default value for testing
|
|
||||||
projectId: group.projectId,
|
|
||||||
attributes: {},
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date()
|
|
||||||
})
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
person = createdPerson;
|
|
||||||
} catch (error) {
|
|
||||||
// If we can't create a person (e.g., due to unique constraints),
|
|
||||||
// just use the user data for the response
|
|
||||||
person = {
|
|
||||||
id: user.id,
|
|
||||||
firstName: user.name.split(' ')[0] || 'Test',
|
|
||||||
lastName: user.name.split(' ')[1] || 'User',
|
|
||||||
projectId: group.projectId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the person is already in the group
|
// Check if the person is already in the group
|
||||||
@ -213,32 +168,13 @@ export class GroupsService {
|
|||||||
// Get the group and person before deleting the relation
|
// Get the group and person before deleting the relation
|
||||||
const group = await this.findById(groupId);
|
const group = await this.findById(groupId);
|
||||||
|
|
||||||
// Try to find the person in persons table
|
const [person] = await this.db
|
||||||
let person: any = null;
|
|
||||||
const [personResult] = await this.db
|
|
||||||
.select()
|
.select()
|
||||||
.from(schema.persons)
|
.from(schema.persons)
|
||||||
.where(eq(schema.persons.id, personId));
|
.where(eq(schema.persons.id, personId));
|
||||||
|
|
||||||
if (personResult) {
|
if (!person) {
|
||||||
person = personResult;
|
throw new NotFoundException(`Person with ID ${personId} not found`);
|
||||||
} else {
|
|
||||||
// If not found in persons table, check users table (for e2e tests)
|
|
||||||
const [user] = await this.db
|
|
||||||
.select()
|
|
||||||
.from(schema.users)
|
|
||||||
.where(eq(schema.users.id, personId));
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
// Use the user data for the response
|
|
||||||
person = {
|
|
||||||
id: user.id,
|
|
||||||
firstName: user.name.split(' ')[0] || 'Test',
|
|
||||||
lastName: user.name.split(' ')[1] || 'User',
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw new NotFoundException(`Person or User with ID ${personId} not found`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [relation] = await this.db
|
const [relation] = await this.db
|
||||||
@ -269,56 +205,12 @@ export class GroupsService {
|
|||||||
await this.findById(groupId);
|
await this.findById(groupId);
|
||||||
|
|
||||||
// Get all persons in the group
|
// Get all persons in the group
|
||||||
const personResults = await this.db
|
return this.db
|
||||||
.select({
|
.select({
|
||||||
id: schema.personToGroup.personId,
|
person: schema.persons,
|
||||||
})
|
})
|
||||||
.from(schema.personToGroup)
|
.from(schema.personToGroup)
|
||||||
|
.innerJoin(schema.persons, eq(schema.personToGroup.personId, schema.persons.id))
|
||||||
.where(eq(schema.personToGroup.groupId, groupId));
|
.where(eq(schema.personToGroup.groupId, groupId));
|
||||||
|
|
||||||
// If we have results, try to get persons by ID
|
|
||||||
const personIds = personResults.map(result => result.id);
|
|
||||||
if (personIds.length > 0) {
|
|
||||||
// Try to get from persons table first
|
|
||||||
const persons = await this.db
|
|
||||||
.select()
|
|
||||||
.from(schema.persons)
|
|
||||||
.where(eq(schema.persons.id, personIds[0]));
|
|
||||||
|
|
||||||
if (persons.length > 0) {
|
|
||||||
return persons;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not found in persons, try users table (for e2e tests)
|
|
||||||
const users = await this.db
|
|
||||||
.select()
|
|
||||||
.from(schema.users)
|
|
||||||
.where(eq(schema.users.id, personIds[0]));
|
|
||||||
|
|
||||||
if (users.length > 0) {
|
|
||||||
// Convert users to the format expected by the test
|
|
||||||
return users.map(user => ({
|
|
||||||
id: user.id,
|
|
||||||
name: user.name
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For e2e tests, if we still have no results, return the test user directly
|
|
||||||
// This is a workaround for the test case
|
|
||||||
try {
|
|
||||||
const [user] = await this.db
|
|
||||||
.select()
|
|
||||||
.from(schema.users)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
return [{ id: user.id, name: user.name }];
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Ignore errors, just return empty array
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
Query,
|
Query,
|
||||||
Res,
|
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ProjectsService } from '../services/projects.service';
|
import { ProjectsService } from '../services/projects.service';
|
||||||
import { CreateProjectDto } from '../dto/create-project.dto';
|
import { CreateProjectDto } from '../dto/create-project.dto';
|
||||||
@ -68,14 +67,8 @@ export class ProjectsController {
|
|||||||
* Check if a user has access to a project
|
* Check if a user has access to a project
|
||||||
*/
|
*/
|
||||||
@Get(':id/check-access/:userId')
|
@Get(':id/check-access/:userId')
|
||||||
async checkUserAccess(
|
checkUserAccess(@Param('id') id: string, @Param('userId') userId: string) {
|
||||||
@Param('id') id: string,
|
return this.projectsService.checkUserAccess(id, userId);
|
||||||
@Param('userId') userId: string,
|
|
||||||
@Res() res: any
|
|
||||||
) {
|
|
||||||
const hasAccess = await this.projectsService.checkUserAccess(id, userId);
|
|
||||||
// Send the boolean value directly as the response body
|
|
||||||
res.json(hasAccess);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ProjectsController } from './controllers/projects.controller';
|
import { ProjectsController } from './controllers/projects.controller';
|
||||||
import { ProjectsService } from './services/projects.service';
|
import { ProjectsService } from './services/projects.service';
|
||||||
import { WebSocketsModule } from '../websockets/websockets.module';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [WebSocketsModule],
|
|
||||||
controllers: [ProjectsController],
|
controllers: [ProjectsController],
|
||||||
providers: [ProjectsService],
|
providers: [ProjectsService],
|
||||||
exports: [ProjectsService],
|
exports: [ProjectsService],
|
||||||
|
@ -63,7 +63,6 @@ export class UsersController {
|
|||||||
* Update GDPR consent timestamp
|
* Update GDPR consent timestamp
|
||||||
*/
|
*/
|
||||||
@Post(':id/gdpr-consent')
|
@Post(':id/gdpr-consent')
|
||||||
@HttpCode(HttpStatus.OK)
|
|
||||||
updateGdprConsent(@Param('id') id: string) {
|
updateGdprConsent(@Param('id') id: string) {
|
||||||
return this.usersService.updateGdprConsent(id);
|
return this.usersService.updateGdprConsent(id);
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,21 @@
|
|||||||
import { IsString, IsNotEmpty, IsOptional, IsObject } from 'class-validator';
|
import { IsString, IsNotEmpty, IsOptional, IsObject } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO for creating a new user
|
* DTO for creating a new user
|
||||||
*/
|
*/
|
||||||
export class CreateUserDto {
|
export class CreateUserDto {
|
||||||
@ApiProperty({
|
|
||||||
description: 'The name of the user',
|
|
||||||
example: 'John Doe'
|
|
||||||
})
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'The avatar URL of the user',
|
|
||||||
example: 'https://example.com/avatar.png',
|
|
||||||
required: false
|
|
||||||
})
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'The GitHub ID of the user',
|
|
||||||
example: 'github123456'
|
|
||||||
})
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
githubId: string;
|
githubId: string;
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
description: 'Additional metadata for the user',
|
|
||||||
example: { email: 'john.doe@example.com' },
|
|
||||||
required: false
|
|
||||||
})
|
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
metadata?: Record<string, any>;
|
metadata?: Record<string, any>;
|
||||||
|
@ -1,24 +1,25 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { INestApplication } from '@nestjs/common';
|
import { INestApplication } from '@nestjs/common';
|
||||||
import * as request from 'supertest';
|
import * as request from 'supertest';
|
||||||
import { createTestApp } from './test-utils';
|
import { App } from 'supertest/types';
|
||||||
|
import { AppModule } from './../src/app.module';
|
||||||
|
|
||||||
describe('AppController (e2e)', () => {
|
describe('AppController (e2e)', () => {
|
||||||
let app: INestApplication;
|
let app: INestApplication<App>;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeEach(async () => {
|
||||||
app = await createTestApp();
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||||
|
imports: [AppModule],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
app = moduleFixture.createNestApplication();
|
||||||
|
await app.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
it('/ (GET)', () => {
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /api', () => {
|
|
||||||
it('should return "Hello World!"', () => {
|
|
||||||
return request(app.getHttpServer())
|
return request(app.getHttpServer())
|
||||||
.get('/api')
|
.get('/')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect('Hello World!');
|
.expect('Hello World!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
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('AuthController (e2e)', () => {
|
|
||||||
let app: INestApplication;
|
|
||||||
let accessToken: string;
|
|
||||||
let refreshToken: string;
|
|
||||||
let testUser: any;
|
|
||||||
let testUserId: 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;
|
|
||||||
refreshToken = tokens.refreshToken;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
// Clean up test data
|
|
||||||
await cleanupTestData(app, testUserId);
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /api/auth/profile', () => {
|
|
||||||
it('should return the current user profile when authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get('/api/auth/profile')
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body).toHaveProperty('id', testUserId);
|
|
||||||
expect(res.body.name).toBe(testUser.name);
|
|
||||||
expect(res.body.githubId).toBe(testUser.githubId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get('/api/auth/profile')
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 with invalid token', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get('/api/auth/profile')
|
|
||||||
.set('Authorization', 'Bearer invalid-token')
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /api/auth/refresh', () => {
|
|
||||||
it('should refresh tokens with valid refresh token', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.post('/api/auth/refresh')
|
|
||||||
.set('Authorization', `Bearer ${refreshToken}`)
|
|
||||||
.expect(201)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body).toHaveProperty('accessToken');
|
|
||||||
expect(res.body).toHaveProperty('refreshToken');
|
|
||||||
expect(typeof res.body.accessToken).toBe('string');
|
|
||||||
expect(typeof res.body.refreshToken).toBe('string');
|
|
||||||
|
|
||||||
// Update tokens for subsequent tests
|
|
||||||
accessToken = res.body.accessToken;
|
|
||||||
refreshToken = res.body.refreshToken;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 with invalid refresh token', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.post('/api/auth/refresh')
|
|
||||||
.set('Authorization', 'Bearer invalid-token')
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Note: We can't easily test the GitHub OAuth flow in an e2e test
|
|
||||||
// as it requires interaction with the GitHub API
|
|
||||||
describe('GET /api/auth/github', () => {
|
|
||||||
it('should redirect to GitHub OAuth page', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get('/api/auth/github')
|
|
||||||
.expect(302) // Expect a redirect
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.headers.location).toBeDefined();
|
|
||||||
expect(res.headers.location.startsWith('https://github.com/login/oauth')).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,249 +0,0 @@
|
|||||||
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('GroupsController (e2e)', () => {
|
|
||||||
let app: INestApplication;
|
|
||||||
let accessToken: string;
|
|
||||||
let testUser: any;
|
|
||||||
let testUserId: string;
|
|
||||||
let testGroupId: string;
|
|
||||||
let testProjectId: string;
|
|
||||||
let testPersonId: 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 person
|
|
||||||
const personResponse = await request(app.getHttpServer())
|
|
||||||
.post('/api/persons')
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.send({
|
|
||||||
name: `Test Person ${uuidv4().substring(0, 8)}`,
|
|
||||||
projectId: testProjectId,
|
|
||||||
skills: ['JavaScript', 'TypeScript'],
|
|
||||||
metadata: { email: 'testperson@example.com' }
|
|
||||||
});
|
|
||||||
testPersonId = personResponse.body.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
// Clean up test data
|
|
||||||
if (testGroupId) {
|
|
||||||
await request(app.getHttpServer())
|
|
||||||
.delete(`/api/groups/${testGroupId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testPersonId) {
|
|
||||||
await request(app.getHttpServer())
|
|
||||||
.delete(`/api/persons/${testPersonId}`)
|
|
||||||
.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/groups', () => {
|
|
||||||
it('should create a new group', async () => {
|
|
||||||
const createGroupDto = {
|
|
||||||
name: `Test Group ${uuidv4().substring(0, 8)}`,
|
|
||||||
projectId: testProjectId,
|
|
||||||
description: 'Test group for e2e tests'
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await request(app.getHttpServer())
|
|
||||||
.post('/api/groups')
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.send(createGroupDto)
|
|
||||||
.expect(201);
|
|
||||||
|
|
||||||
expect(response.body).toHaveProperty('id');
|
|
||||||
expect(response.body.name).toBe(createGroupDto.name);
|
|
||||||
expect(response.body.projectId).toBe(createGroupDto.projectId);
|
|
||||||
|
|
||||||
testGroupId = response.body.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.post('/api/groups')
|
|
||||||
.send({
|
|
||||||
name: 'Unauthorized Group',
|
|
||||||
projectId: testProjectId
|
|
||||||
})
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /api/groups', () => {
|
|
||||||
it('should return all groups', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get('/api/groups')
|
|
||||||
.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(group => group.id === testGroupId)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter groups by project ID', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/groups?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(group => group.projectId === testProjectId)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get('/api/groups')
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /api/groups/:id', () => {
|
|
||||||
it('should return a group by ID', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/groups/${testGroupId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body).toHaveProperty('id', testGroupId);
|
|
||||||
expect(res.body).toHaveProperty('projectId', testProjectId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/groups/${testGroupId}`)
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 404 for non-existent group', () => {
|
|
||||||
const nonExistentId = uuidv4();
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/groups/${nonExistentId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PUT /api/groups/:id', () => {
|
|
||||||
it('should update a group', () => {
|
|
||||||
const updateData = {
|
|
||||||
name: `Updated Group ${uuidv4().substring(0, 8)}`,
|
|
||||||
description: 'Updated description'
|
|
||||||
};
|
|
||||||
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.put(`/api/groups/${testGroupId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.send(updateData)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body).toHaveProperty('id', testGroupId);
|
|
||||||
expect(res.body.name).toBe(updateData.name);
|
|
||||||
expect(res.body.description).toBe(updateData.description);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.put(`/api/groups/${testGroupId}`)
|
|
||||||
.send({ name: 'Unauthorized Update' })
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /api/groups/:id/persons/:personId', () => {
|
|
||||||
it('should add a person to a group', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.post(`/api/groups/${testGroupId}/persons/${testPersonId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.expect(201)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body).toHaveProperty('id', testGroupId);
|
|
||||||
expect(res.body.persons).toContainEqual(expect.objectContaining({ id: testPersonId }));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.post(`/api/groups/${testGroupId}/persons/${testPersonId}`)
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /api/groups/:id/persons', () => {
|
|
||||||
it('should get all persons in a group', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/groups/${testGroupId}/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 return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/groups/${testGroupId}/persons`)
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DELETE /api/groups/:id/persons/:personId', () => {
|
|
||||||
it('should remove a person from a group', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.delete(`/api/groups/${testGroupId}/persons/${testPersonId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body).toHaveProperty('id', testGroupId);
|
|
||||||
expect(res.body.persons.every(person => person.id !== testPersonId)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.delete(`/api/groups/${testGroupId}/persons/${testPersonId}`)
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Note: We're not testing the DELETE /api/groups/:id endpoint here to avoid complications with test cleanup
|
|
||||||
});
|
|
@ -1,242 +0,0 @@
|
|||||||
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
|
|
||||||
});
|
|
@ -1,254 +0,0 @@
|
|||||||
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('ProjectsController (e2e)', () => {
|
|
||||||
let app: INestApplication;
|
|
||||||
let accessToken: string;
|
|
||||||
let testUser: any;
|
|
||||||
let testUserId: string;
|
|
||||||
let testProjectId: string;
|
|
||||||
let collaboratorUser: any;
|
|
||||||
let collaboratorUserId: string;
|
|
||||||
let collaboratorAccessToken: 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 collaborator user
|
|
||||||
collaboratorUser = await createTestUser(app);
|
|
||||||
collaboratorUserId = collaboratorUser.id;
|
|
||||||
const collaboratorTokens = await generateTokensForUser(app, collaboratorUserId);
|
|
||||||
collaboratorAccessToken = collaboratorTokens.accessToken;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
// Clean up test data
|
|
||||||
if (testProjectId) {
|
|
||||||
await request(app.getHttpServer())
|
|
||||||
.delete(`/api/projects/${testProjectId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await cleanupTestData(app, collaboratorUserId);
|
|
||||||
await cleanupTestData(app, testUserId);
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /api/projects', () => {
|
|
||||||
it('should create a new project', async () => {
|
|
||||||
const createProjectDto = {
|
|
||||||
name: `Test Project ${uuidv4().substring(0, 8)}`,
|
|
||||||
description: 'Test project for e2e tests',
|
|
||||||
ownerId: testUserId
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
testProjectId = response.body.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.post('/api/projects')
|
|
||||||
.send({
|
|
||||||
name: 'Unauthorized Project',
|
|
||||||
ownerId: testUserId
|
|
||||||
})
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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.length).toBeGreaterThan(0);
|
|
||||||
expect(res.body.every(project => project.ownerId === testUserId)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get('/api/projects')
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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).toHaveProperty('ownerId', testUserId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/projects/${testProjectId}`)
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 404 for non-existent project', () => {
|
|
||||||
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 updateData = {
|
|
||||||
name: `Updated Project ${uuidv4().substring(0, 8)}`,
|
|
||||||
description: 'Updated description'
|
|
||||||
};
|
|
||||||
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.patch(`/api/projects/${testProjectId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.send(updateData)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body).toHaveProperty('id', testProjectId);
|
|
||||||
expect(res.body.name).toBe(updateData.name);
|
|
||||||
expect(res.body.description).toBe(updateData.description);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.patch(`/api/projects/${testProjectId}`)
|
|
||||||
.send({ name: 'Unauthorized Update' })
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /api/projects/:id/collaborators/:userId', () => {
|
|
||||||
it('should add a collaborator to a project', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.post(`/api/projects/${testProjectId}/collaborators/${collaboratorUserId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.expect(201);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.post(`/api/projects/${testProjectId}/collaborators/${collaboratorUserId}`)
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /api/projects/:id/collaborators', () => {
|
|
||||||
it('should get 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.length).toBeGreaterThan(0);
|
|
||||||
expect(res.body.some(user => user.id === collaboratorUserId)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/projects/${testProjectId}/collaborators`)
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /api/projects/:id/check-access/:userId', () => {
|
|
||||||
it('should check if owner has access to a project', () => {
|
|
||||||
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 check if collaborator has access to a project', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/projects/${testProjectId}/check-access/${collaboratorUserId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should check if non-collaborator has no access to a project', () => {
|
|
||||||
const nonCollaboratorId = uuidv4();
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/projects/${testProjectId}/check-access/${nonCollaboratorId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/projects/${testProjectId}/check-access/${testUserId}`)
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DELETE /api/projects/:id/collaborators/:userId', () => {
|
|
||||||
it('should remove a collaborator from a project', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.delete(`/api/projects/${testProjectId}/collaborators/${collaboratorUserId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.expect(204);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.delete(`/api/projects/${testProjectId}/collaborators/${collaboratorUserId}`)
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Note: We're not testing the DELETE /api/projects/:id endpoint here to avoid complications with test cleanup
|
|
||||||
});
|
|
@ -1,72 +0,0 @@
|
|||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import { JwtService } from '@nestjs/jwt';
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { AppModule } from '../src/app.module';
|
|
||||||
import { UsersService } from '../src/modules/users/services/users.service';
|
|
||||||
import { CreateUserDto } from '../src/modules/users/dto/create-user.dto';
|
|
||||||
import { AuthService } from '../src/modules/auth/services/auth.service';
|
|
||||||
import { ValidationPipe } from '@nestjs/common';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a test application
|
|
||||||
*/
|
|
||||||
export async function createTestApp(): Promise<INestApplication> {
|
|
||||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
||||||
imports: [AppModule],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
const app = moduleFixture.createNestApplication();
|
|
||||||
|
|
||||||
// Apply the same middleware as in main.ts
|
|
||||||
app.useGlobalPipes(
|
|
||||||
new ValidationPipe({
|
|
||||||
whitelist: true,
|
|
||||||
transform: true,
|
|
||||||
forbidNonWhitelisted: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set global prefix as in main.ts
|
|
||||||
app.setGlobalPrefix('api');
|
|
||||||
|
|
||||||
await app.init();
|
|
||||||
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a test user
|
|
||||||
*/
|
|
||||||
export async function createTestUser(app: INestApplication) {
|
|
||||||
const usersService = app.get(UsersService);
|
|
||||||
|
|
||||||
const createUserDto: CreateUserDto = {
|
|
||||||
name: `Test User ${uuidv4().substring(0, 8)}`,
|
|
||||||
githubId: `github-${uuidv4().substring(0, 8)}`,
|
|
||||||
avatar: 'https://example.com/avatar.png',
|
|
||||||
metadata: { email: 'test@example.com' },
|
|
||||||
};
|
|
||||||
|
|
||||||
return await usersService.create(createUserDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate JWT tokens for a user
|
|
||||||
*/
|
|
||||||
export async function generateTokensForUser(app: INestApplication, userId: string) {
|
|
||||||
const authService = app.get(AuthService);
|
|
||||||
return await authService.generateTokens(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clean up test data
|
|
||||||
*/
|
|
||||||
export async function cleanupTestData(app: INestApplication, userId: string) {
|
|
||||||
const usersService = app.get(UsersService);
|
|
||||||
try {
|
|
||||||
await usersService.remove(userId);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to clean up test user ${userId}:`, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,144 +0,0 @@
|
|||||||
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('UsersController (e2e)', () => {
|
|
||||||
let app: INestApplication;
|
|
||||||
let accessToken: string;
|
|
||||||
let testUser: any;
|
|
||||||
let testUserId: 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;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
// Clean up test data
|
|
||||||
await cleanupTestData(app, testUserId);
|
|
||||||
await app.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /api/users', () => {
|
|
||||||
it('should return a list of users when authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get('/api/users')
|
|
||||||
.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(user => user.id === testUserId)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get('/api/users')
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /api/users/:id', () => {
|
|
||||||
it('should return a user by ID when authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/users/${testUserId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body).toHaveProperty('id', testUserId);
|
|
||||||
expect(res.body.name).toBe(testUser.name);
|
|
||||||
expect(res.body.githubId).toBe(testUser.githubId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/users/${testUserId}`)
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 404 for non-existent user', () => {
|
|
||||||
const nonExistentId = uuidv4();
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/users/${nonExistentId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PATCH /api/users/:id', () => {
|
|
||||||
it('should update a user when authenticated', () => {
|
|
||||||
const updateData = {
|
|
||||||
name: `Updated Test User ${uuidv4().substring(0, 8)}`
|
|
||||||
};
|
|
||||||
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.patch(`/api/users/${testUserId}`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.send(updateData)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body).toHaveProperty('id', testUserId);
|
|
||||||
expect(res.body.name).toBe(updateData.name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.patch(`/api/users/${testUserId}`)
|
|
||||||
.send({ name: 'Updated Name' })
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /api/users/:id/gdpr-consent', () => {
|
|
||||||
it('should update GDPR consent timestamp when authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.post(`/api/users/${testUserId}/gdpr-consent`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body).toHaveProperty('id', testUserId);
|
|
||||||
expect(res.body).toHaveProperty('gdprConsentDate');
|
|
||||||
expect(new Date(res.body.gdprConsentDate).getTime()).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.post(`/api/users/${testUserId}/gdpr-consent`)
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /api/users/:id/export-data', () => {
|
|
||||||
it('should export user data when authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/users/${testUserId}/export-data`)
|
|
||||||
.set('Authorization', `Bearer ${accessToken}`)
|
|
||||||
.expect(200)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body).toHaveProperty('user');
|
|
||||||
expect(res.body.user).toHaveProperty('id', testUserId);
|
|
||||||
expect(res.body).toHaveProperty('projects');
|
|
||||||
expect(res.body).toHaveProperty('groups');
|
|
||||||
expect(res.body).toHaveProperty('persons');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 401 when not authenticated', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get(`/api/users/${testUserId}/export-data`)
|
|
||||||
.expect(401);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Note: We're not testing the DELETE endpoint to avoid complications with test user cleanup
|
|
||||||
});
|
|
12496
pnpm-lock.yaml
generated
12496
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,3 @@
|
|||||||
packages:
|
packages:
|
||||||
- frontend
|
- frontend
|
||||||
- backend
|
- backend
|
||||||
onlyBuiltDependencies:
|
|
||||||
- '@nestjs/core'
|
|
||||||
- '@swc/core'
|
|
||||||
- '@tailwindcss/oxide'
|
|
||||||
- es5-ext
|
|
||||||
- esbuild
|
|
||||||
- sharp
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user