Compare commits
No commits in common. "7eae25d5de5b9be4e7ff46361e772524e6c7f81b" and "9792110560f3d16ad665ec2aecef98ce1f7dd4eb" have entirely different histories.
7eae25d5de
...
9792110560
@ -1,117 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { UnauthorizedException } from '@nestjs/common';
|
|
||||||
import { AuthController } from './auth.controller';
|
|
||||||
import { AuthService } from '../services/auth.service';
|
|
||||||
|
|
||||||
describe('AuthController', () => {
|
|
||||||
let controller: AuthController;
|
|
||||||
let authService: AuthService;
|
|
||||||
let configService: ConfigService;
|
|
||||||
|
|
||||||
const mockAuthService = {
|
|
||||||
validateGithubUser: jest.fn(),
|
|
||||||
generateTokens: jest.fn(),
|
|
||||||
refreshTokens: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockConfigService = {
|
|
||||||
get: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [AuthController],
|
|
||||||
providers: [
|
|
||||||
{ provide: AuthService, useValue: mockAuthService },
|
|
||||||
{ provide: ConfigService, useValue: mockConfigService },
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
controller = module.get<AuthController>(AuthController);
|
|
||||||
authService = module.get<AuthService>(AuthService);
|
|
||||||
configService = module.get<ConfigService>(ConfigService);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(controller).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('githubAuth', () => {
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(controller.githubAuth).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('githubAuthCallback', () => {
|
|
||||||
it('should redirect to frontend with tokens', async () => {
|
|
||||||
const req = {
|
|
||||||
user: { id: 'user1', name: 'Test User' },
|
|
||||||
};
|
|
||||||
const res = {
|
|
||||||
redirect: jest.fn(),
|
|
||||||
};
|
|
||||||
const tokens = {
|
|
||||||
accessToken: 'access-token',
|
|
||||||
refreshToken: 'refresh-token',
|
|
||||||
};
|
|
||||||
const frontendUrl = 'http://localhost:3001';
|
|
||||||
const expectedRedirectUrl = `${frontendUrl}/auth/callback?accessToken=${tokens.accessToken}&refreshToken=${tokens.refreshToken}`;
|
|
||||||
|
|
||||||
mockAuthService.generateTokens.mockResolvedValue(tokens);
|
|
||||||
mockConfigService.get.mockReturnValue(frontendUrl);
|
|
||||||
|
|
||||||
await controller.githubAuthCallback(req as any, res as any);
|
|
||||||
|
|
||||||
expect(mockAuthService.generateTokens).toHaveBeenCalledWith('user1');
|
|
||||||
expect(mockConfigService.get).toHaveBeenCalledWith('FRONTEND_URL');
|
|
||||||
expect(res.redirect).toHaveBeenCalledWith(expectedRedirectUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw UnauthorizedException if user is not provided', async () => {
|
|
||||||
const req = {};
|
|
||||||
const res = {
|
|
||||||
redirect: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
await expect(controller.githubAuthCallback(req as any, res as any)).rejects.toThrow(
|
|
||||||
UnauthorizedException,
|
|
||||||
);
|
|
||||||
expect(res.redirect).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('refreshTokens', () => {
|
|
||||||
it('should refresh tokens', async () => {
|
|
||||||
const user = {
|
|
||||||
id: 'user1',
|
|
||||||
refreshToken: 'refresh-token',
|
|
||||||
};
|
|
||||||
const tokens = {
|
|
||||||
accessToken: 'new-access-token',
|
|
||||||
refreshToken: 'new-refresh-token',
|
|
||||||
};
|
|
||||||
|
|
||||||
mockAuthService.refreshTokens.mockResolvedValue(tokens);
|
|
||||||
|
|
||||||
const result = await controller.refreshTokens(user);
|
|
||||||
|
|
||||||
expect(mockAuthService.refreshTokens).toHaveBeenCalledWith('user1', 'refresh-token');
|
|
||||||
expect(result).toEqual(tokens);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getProfile', () => {
|
|
||||||
it('should return user profile', () => {
|
|
||||||
const user = { id: 'user1', name: 'Test User' };
|
|
||||||
|
|
||||||
const result = controller.getProfile(user);
|
|
||||||
|
|
||||||
expect(result).toEqual(user);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,82 +0,0 @@
|
|||||||
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
|
||||||
import { Reflector } from '@nestjs/core';
|
|
||||||
import { JwtAuthGuard } from './jwt-auth.guard';
|
|
||||||
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
|
|
||||||
|
|
||||||
describe('JwtAuthGuard', () => {
|
|
||||||
let guard: JwtAuthGuard;
|
|
||||||
let reflector: Reflector;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
reflector = new Reflector();
|
|
||||||
guard = new JwtAuthGuard(reflector);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('canActivate', () => {
|
|
||||||
it('should return true if the route is public', () => {
|
|
||||||
const context = {
|
|
||||||
getHandler: jest.fn(),
|
|
||||||
getClass: jest.fn(),
|
|
||||||
switchToHttp: jest.fn().mockReturnValue({
|
|
||||||
getRequest: jest.fn().mockReturnValue({}),
|
|
||||||
getResponse: jest.fn().mockReturnValue({}),
|
|
||||||
}),
|
|
||||||
} as unknown as ExecutionContext;
|
|
||||||
|
|
||||||
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(true);
|
|
||||||
|
|
||||||
expect(guard.canActivate(context)).toBe(true);
|
|
||||||
expect(reflector.getAllAndOverride).toHaveBeenCalledWith(IS_PUBLIC_KEY, [
|
|
||||||
context.getHandler(),
|
|
||||||
context.getClass(),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call super.canActivate if the route is not public', () => {
|
|
||||||
const context = {
|
|
||||||
getHandler: jest.fn(),
|
|
||||||
getClass: jest.fn(),
|
|
||||||
switchToHttp: jest.fn().mockReturnValue({
|
|
||||||
getRequest: jest.fn().mockReturnValue({}),
|
|
||||||
getResponse: jest.fn().mockReturnValue({}),
|
|
||||||
}),
|
|
||||||
} as unknown as ExecutionContext;
|
|
||||||
|
|
||||||
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(false);
|
|
||||||
|
|
||||||
// Mock the AuthGuard's canActivate method
|
|
||||||
const canActivateSpy = jest.spyOn(guard, 'canActivate');
|
|
||||||
|
|
||||||
// We can't easily test the super.canActivate call directly,
|
|
||||||
// so we'll just verify our method was called with the right context
|
|
||||||
guard.canActivate(context);
|
|
||||||
|
|
||||||
expect(reflector.getAllAndOverride).toHaveBeenCalledWith(IS_PUBLIC_KEY, [
|
|
||||||
context.getHandler(),
|
|
||||||
context.getClass(),
|
|
||||||
]);
|
|
||||||
expect(canActivateSpy).toHaveBeenCalledWith(context);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handleRequest', () => {
|
|
||||||
it('should return the user if no error and user exists', () => {
|
|
||||||
const user = { id: 'user1', name: 'Test User' };
|
|
||||||
|
|
||||||
const result = guard.handleRequest(null, user, null);
|
|
||||||
|
|
||||||
expect(result).toBe(user);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw the error if an error exists', () => {
|
|
||||||
const error = new Error('Test error');
|
|
||||||
|
|
||||||
expect(() => guard.handleRequest(error, null, null)).toThrow(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw UnauthorizedException if no error but user does not exist', () => {
|
|
||||||
expect(() => guard.handleRequest(null, null, null)).toThrow(UnauthorizedException);
|
|
||||||
expect(() => guard.handleRequest(null, null, null)).toThrow('Authentication required');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,208 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { JwtService } from '@nestjs/jwt';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { AuthService } from './auth.service';
|
|
||||||
import { UsersService } from '../../users/services/users.service';
|
|
||||||
import { UnauthorizedException } from '@nestjs/common';
|
|
||||||
|
|
||||||
describe('AuthService', () => {
|
|
||||||
let service: AuthService;
|
|
||||||
let usersService: UsersService;
|
|
||||||
let jwtService: JwtService;
|
|
||||||
let configService: ConfigService;
|
|
||||||
|
|
||||||
const mockUsersService = {
|
|
||||||
findByGithubId: jest.fn(),
|
|
||||||
findById: jest.fn(),
|
|
||||||
create: jest.fn(),
|
|
||||||
update: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockJwtService = {
|
|
||||||
signAsync: jest.fn(),
|
|
||||||
verifyAsync: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockConfigService = {
|
|
||||||
get: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [
|
|
||||||
AuthService,
|
|
||||||
{ provide: UsersService, useValue: mockUsersService },
|
|
||||||
{ provide: JwtService, useValue: mockJwtService },
|
|
||||||
{ provide: ConfigService, useValue: mockConfigService },
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<AuthService>(AuthService);
|
|
||||||
usersService = module.get<UsersService>(UsersService);
|
|
||||||
jwtService = module.get<JwtService>(JwtService);
|
|
||||||
configService = module.get<ConfigService>(ConfigService);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('validateGithubUser', () => {
|
|
||||||
it('should create a new user if one does not exist', async () => {
|
|
||||||
const githubId = 'github123';
|
|
||||||
const email = 'test@example.com';
|
|
||||||
const name = 'Test User';
|
|
||||||
const avatarUrl = 'https://example.com/avatar.jpg';
|
|
||||||
const newUser = { id: 'user1', githubId, name, avatar: avatarUrl };
|
|
||||||
|
|
||||||
mockUsersService.findByGithubId.mockResolvedValue(null);
|
|
||||||
mockUsersService.create.mockResolvedValue(newUser);
|
|
||||||
|
|
||||||
const result = await service.validateGithubUser(githubId, email, name, avatarUrl);
|
|
||||||
|
|
||||||
expect(mockUsersService.findByGithubId).toHaveBeenCalledWith(githubId);
|
|
||||||
expect(mockUsersService.create).toHaveBeenCalledWith({
|
|
||||||
githubId,
|
|
||||||
name,
|
|
||||||
avatar: avatarUrl,
|
|
||||||
metadata: { email },
|
|
||||||
});
|
|
||||||
expect(result).toEqual(newUser);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an existing user if one exists', async () => {
|
|
||||||
const githubId = 'github123';
|
|
||||||
const email = 'test@example.com';
|
|
||||||
const name = 'Test User';
|
|
||||||
const avatarUrl = 'https://example.com/avatar.jpg';
|
|
||||||
const existingUser = { id: 'user1', githubId, name, avatar: avatarUrl };
|
|
||||||
|
|
||||||
mockUsersService.findByGithubId.mockResolvedValue(existingUser);
|
|
||||||
|
|
||||||
const result = await service.validateGithubUser(githubId, email, name, avatarUrl);
|
|
||||||
|
|
||||||
expect(mockUsersService.findByGithubId).toHaveBeenCalledWith(githubId);
|
|
||||||
expect(mockUsersService.create).not.toHaveBeenCalled();
|
|
||||||
expect(result).toEqual(existingUser);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('generateTokens', () => {
|
|
||||||
it('should generate access and refresh tokens', async () => {
|
|
||||||
const userId = 'user1';
|
|
||||||
const accessToken = 'access-token';
|
|
||||||
const refreshToken = 'refresh-token';
|
|
||||||
const refreshExpiration = '7d';
|
|
||||||
const refreshSecret = 'refresh-secret';
|
|
||||||
|
|
||||||
mockJwtService.signAsync.mockResolvedValueOnce(accessToken);
|
|
||||||
mockJwtService.signAsync.mockResolvedValueOnce(refreshToken);
|
|
||||||
mockConfigService.get.mockReturnValueOnce(refreshExpiration);
|
|
||||||
mockConfigService.get.mockReturnValueOnce(refreshSecret);
|
|
||||||
|
|
||||||
const result = await service.generateTokens(userId);
|
|
||||||
|
|
||||||
expect(mockJwtService.signAsync).toHaveBeenCalledWith({ sub: userId });
|
|
||||||
expect(mockJwtService.signAsync).toHaveBeenCalledWith(
|
|
||||||
{ sub: userId, isRefreshToken: true },
|
|
||||||
{
|
|
||||||
expiresIn: refreshExpiration,
|
|
||||||
secret: refreshSecret,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
expect(result).toEqual({
|
|
||||||
accessToken,
|
|
||||||
refreshToken,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('refreshTokens', () => {
|
|
||||||
it('should refresh tokens if refresh token is valid', async () => {
|
|
||||||
const userId = 'user1';
|
|
||||||
const refreshToken = 'valid-refresh-token';
|
|
||||||
const newAccessToken = 'new-access-token';
|
|
||||||
const newRefreshToken = 'new-refresh-token';
|
|
||||||
const payload = { sub: userId, isRefreshToken: true };
|
|
||||||
|
|
||||||
mockJwtService.verifyAsync.mockResolvedValue(payload);
|
|
||||||
mockJwtService.signAsync.mockResolvedValueOnce(newAccessToken);
|
|
||||||
mockJwtService.signAsync.mockResolvedValueOnce(newRefreshToken);
|
|
||||||
|
|
||||||
const result = await service.refreshTokens(userId, refreshToken);
|
|
||||||
|
|
||||||
expect(mockJwtService.verifyAsync).toHaveBeenCalledWith(refreshToken, {
|
|
||||||
secret: undefined,
|
|
||||||
});
|
|
||||||
expect(result).toEqual({
|
|
||||||
accessToken: newAccessToken,
|
|
||||||
refreshToken: newRefreshToken,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw UnauthorizedException if refresh token is invalid', async () => {
|
|
||||||
const userId = 'user1';
|
|
||||||
const refreshToken = 'invalid-refresh-token';
|
|
||||||
|
|
||||||
mockJwtService.verifyAsync.mockRejectedValue(new Error('Invalid token'));
|
|
||||||
|
|
||||||
await expect(service.refreshTokens(userId, refreshToken)).rejects.toThrow(
|
|
||||||
UnauthorizedException,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw UnauthorizedException if token is not a refresh token', async () => {
|
|
||||||
const userId = 'user1';
|
|
||||||
const refreshToken = 'not-a-refresh-token';
|
|
||||||
const payload = { sub: userId }; // Missing isRefreshToken: true
|
|
||||||
|
|
||||||
mockJwtService.verifyAsync.mockResolvedValue(payload);
|
|
||||||
|
|
||||||
await expect(service.refreshTokens(userId, refreshToken)).rejects.toThrow(
|
|
||||||
UnauthorizedException,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw UnauthorizedException if user ID does not match', async () => {
|
|
||||||
const userId = 'user1';
|
|
||||||
const refreshToken = 'wrong-user-token';
|
|
||||||
const payload = { sub: 'user2', isRefreshToken: true }; // Different user ID
|
|
||||||
|
|
||||||
mockJwtService.verifyAsync.mockResolvedValue(payload);
|
|
||||||
|
|
||||||
await expect(service.refreshTokens(userId, refreshToken)).rejects.toThrow(
|
|
||||||
UnauthorizedException,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('validateJwtUser', () => {
|
|
||||||
it('should return user if user exists', async () => {
|
|
||||||
const userId = 'user1';
|
|
||||||
const user = { id: userId, name: 'Test User' };
|
|
||||||
const payload = { sub: userId };
|
|
||||||
|
|
||||||
mockUsersService.findById.mockResolvedValue(user);
|
|
||||||
|
|
||||||
const result = await service.validateJwtUser(payload);
|
|
||||||
|
|
||||||
expect(mockUsersService.findById).toHaveBeenCalledWith(userId);
|
|
||||||
expect(result).toEqual(user);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw UnauthorizedException if user does not exist', async () => {
|
|
||||||
const userId = 'nonexistent';
|
|
||||||
const payload = { sub: userId };
|
|
||||||
|
|
||||||
mockUsersService.findById.mockResolvedValue(null);
|
|
||||||
|
|
||||||
await expect(service.validateJwtUser(payload)).rejects.toThrow(
|
|
||||||
UnauthorizedException,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,194 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { GroupsController } from './groups.controller';
|
|
||||||
import { GroupsService } from '../services/groups.service';
|
|
||||||
import { CreateGroupDto } from '../dto/create-group.dto';
|
|
||||||
import { UpdateGroupDto } from '../dto/update-group.dto';
|
|
||||||
|
|
||||||
describe('GroupsController', () => {
|
|
||||||
let controller: GroupsController;
|
|
||||||
let service: GroupsService;
|
|
||||||
|
|
||||||
// Mock data
|
|
||||||
const mockGroup = {
|
|
||||||
id: 'group1',
|
|
||||||
name: 'Test Group',
|
|
||||||
projectId: 'project1',
|
|
||||||
metadata: {},
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockPerson = {
|
|
||||||
id: 'person1',
|
|
||||||
name: 'Test Person',
|
|
||||||
projectId: 'project1',
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mock service
|
|
||||||
const mockGroupsService = {
|
|
||||||
create: jest.fn(),
|
|
||||||
findAll: jest.fn(),
|
|
||||||
findByProjectId: jest.fn(),
|
|
||||||
findById: jest.fn(),
|
|
||||||
update: jest.fn(),
|
|
||||||
remove: jest.fn(),
|
|
||||||
addPersonToGroup: jest.fn(),
|
|
||||||
removePersonFromGroup: jest.fn(),
|
|
||||||
getPersonsInGroup: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [GroupsController],
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: GroupsService,
|
|
||||||
useValue: mockGroupsService,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
controller = module.get<GroupsController>(GroupsController);
|
|
||||||
service = module.get<GroupsService>(GroupsService);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(controller).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('create', () => {
|
|
||||||
it('should create a new group', async () => {
|
|
||||||
const createGroupDto: CreateGroupDto = {
|
|
||||||
name: 'Test Group',
|
|
||||||
projectId: 'project1',
|
|
||||||
metadata: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
mockGroupsService.create.mockResolvedValue(mockGroup);
|
|
||||||
|
|
||||||
const result = await controller.create(createGroupDto);
|
|
||||||
|
|
||||||
expect(mockGroupsService.create).toHaveBeenCalledWith(createGroupDto);
|
|
||||||
expect(result).toEqual(mockGroup);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('findAll', () => {
|
|
||||||
it('should return all groups when no projectId is provided', async () => {
|
|
||||||
mockGroupsService.findAll.mockResolvedValue([mockGroup]);
|
|
||||||
|
|
||||||
const result = await controller.findAll();
|
|
||||||
|
|
||||||
expect(mockGroupsService.findAll).toHaveBeenCalled();
|
|
||||||
expect(mockGroupsService.findByProjectId).not.toHaveBeenCalled();
|
|
||||||
expect(result).toEqual([mockGroup]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return groups for a specific project when projectId is provided', async () => {
|
|
||||||
const projectId = 'project1';
|
|
||||||
mockGroupsService.findByProjectId.mockResolvedValue([mockGroup]);
|
|
||||||
|
|
||||||
const result = await controller.findAll(projectId);
|
|
||||||
|
|
||||||
expect(mockGroupsService.findByProjectId).toHaveBeenCalledWith(projectId);
|
|
||||||
expect(mockGroupsService.findAll).not.toHaveBeenCalled();
|
|
||||||
expect(result).toEqual([mockGroup]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('findOne', () => {
|
|
||||||
it('should return a group by id', async () => {
|
|
||||||
const id = 'group1';
|
|
||||||
mockGroupsService.findById.mockResolvedValue(mockGroup);
|
|
||||||
|
|
||||||
const result = await controller.findOne(id);
|
|
||||||
|
|
||||||
expect(mockGroupsService.findById).toHaveBeenCalledWith(id);
|
|
||||||
expect(result).toEqual(mockGroup);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('update', () => {
|
|
||||||
it('should update a group', async () => {
|
|
||||||
const id = 'group1';
|
|
||||||
const updateGroupDto: UpdateGroupDto = {
|
|
||||||
name: 'Updated Group',
|
|
||||||
};
|
|
||||||
|
|
||||||
mockGroupsService.update.mockResolvedValue({
|
|
||||||
...mockGroup,
|
|
||||||
name: 'Updated Group',
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await controller.update(id, updateGroupDto);
|
|
||||||
|
|
||||||
expect(mockGroupsService.update).toHaveBeenCalledWith(id, updateGroupDto);
|
|
||||||
expect(result).toEqual({
|
|
||||||
...mockGroup,
|
|
||||||
name: 'Updated Group',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('remove', () => {
|
|
||||||
it('should remove a group', async () => {
|
|
||||||
const id = 'group1';
|
|
||||||
mockGroupsService.remove.mockResolvedValue(mockGroup);
|
|
||||||
|
|
||||||
const result = await controller.remove(id);
|
|
||||||
|
|
||||||
expect(mockGroupsService.remove).toHaveBeenCalledWith(id);
|
|
||||||
expect(result).toEqual(mockGroup);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('addPersonToGroup', () => {
|
|
||||||
it('should add a person to a group', async () => {
|
|
||||||
const groupId = 'group1';
|
|
||||||
const personId = 'person1';
|
|
||||||
const mockRelation = { groupId, personId };
|
|
||||||
|
|
||||||
mockGroupsService.addPersonToGroup.mockResolvedValue(mockRelation);
|
|
||||||
|
|
||||||
const result = await controller.addPersonToGroup(groupId, personId);
|
|
||||||
|
|
||||||
expect(mockGroupsService.addPersonToGroup).toHaveBeenCalledWith(groupId, personId);
|
|
||||||
expect(result).toEqual(mockRelation);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('removePersonFromGroup', () => {
|
|
||||||
it('should remove a person from a group', async () => {
|
|
||||||
const groupId = 'group1';
|
|
||||||
const personId = 'person1';
|
|
||||||
const mockRelation = { groupId, personId };
|
|
||||||
|
|
||||||
mockGroupsService.removePersonFromGroup.mockResolvedValue(mockRelation);
|
|
||||||
|
|
||||||
const result = await controller.removePersonFromGroup(groupId, personId);
|
|
||||||
|
|
||||||
expect(mockGroupsService.removePersonFromGroup).toHaveBeenCalledWith(groupId, personId);
|
|
||||||
expect(result).toEqual(mockRelation);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getPersonsInGroup', () => {
|
|
||||||
it('should get all persons in a group', async () => {
|
|
||||||
const groupId = 'group1';
|
|
||||||
const mockPersons = [{ person: mockPerson }];
|
|
||||||
|
|
||||||
mockGroupsService.getPersonsInGroup.mockResolvedValue(mockPersons);
|
|
||||||
|
|
||||||
const result = await controller.getPersonsInGroup(groupId);
|
|
||||||
|
|
||||||
expect(mockGroupsService.getPersonsInGroup).toHaveBeenCalledWith(groupId);
|
|
||||||
expect(result).toEqual(mockPersons);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,317 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { GroupsService } from './groups.service';
|
|
||||||
import { NotFoundException } from '@nestjs/common';
|
|
||||||
import { DRIZZLE } from '../../../database/database.module';
|
|
||||||
|
|
||||||
describe('GroupsService', () => {
|
|
||||||
let service: GroupsService;
|
|
||||||
let mockDb: any;
|
|
||||||
|
|
||||||
// Mock data
|
|
||||||
const mockGroup = {
|
|
||||||
id: 'group1',
|
|
||||||
name: 'Test Group',
|
|
||||||
projectId: 'project1',
|
|
||||||
metadata: {},
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockPerson = {
|
|
||||||
id: 'person1',
|
|
||||||
name: 'Test Person',
|
|
||||||
projectId: 'project1',
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockPersonToGroup = {
|
|
||||||
personId: 'person1',
|
|
||||||
groupId: 'group1',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mock database operations
|
|
||||||
const mockDbOperations = {
|
|
||||||
select: jest.fn().mockReturnThis(),
|
|
||||||
from: jest.fn().mockReturnThis(),
|
|
||||||
where: jest.fn().mockReturnThis(),
|
|
||||||
insert: jest.fn().mockReturnThis(),
|
|
||||||
values: jest.fn().mockReturnThis(),
|
|
||||||
update: jest.fn().mockReturnThis(),
|
|
||||||
set: jest.fn().mockReturnThis(),
|
|
||||||
delete: jest.fn().mockReturnThis(),
|
|
||||||
innerJoin: jest.fn().mockReturnThis(),
|
|
||||||
returning: jest.fn().mockImplementation(() => {
|
|
||||||
return [mockGroup];
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
mockDb = {
|
|
||||||
...mockDbOperations,
|
|
||||||
};
|
|
||||||
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [
|
|
||||||
GroupsService,
|
|
||||||
{
|
|
||||||
provide: DRIZZLE,
|
|
||||||
useValue: mockDb,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<GroupsService>(GroupsService);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('create', () => {
|
|
||||||
it('should create a new group', async () => {
|
|
||||||
const createGroupDto = {
|
|
||||||
name: 'Test Group',
|
|
||||||
projectId: 'project1',
|
|
||||||
metadata: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await service.create(createGroupDto);
|
|
||||||
|
|
||||||
expect(mockDb.insert).toHaveBeenCalled();
|
|
||||||
expect(mockDb.values).toHaveBeenCalledWith({
|
|
||||||
...createGroupDto,
|
|
||||||
});
|
|
||||||
expect(result).toEqual(mockGroup);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('findAll', () => {
|
|
||||||
it('should return all groups', async () => {
|
|
||||||
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.from.mockImplementationOnce(() => [mockGroup]);
|
|
||||||
|
|
||||||
const result = await service.findAll();
|
|
||||||
|
|
||||||
expect(mockDb.select).toHaveBeenCalled();
|
|
||||||
expect(mockDb.from).toHaveBeenCalled();
|
|
||||||
expect(result).toEqual([mockGroup]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('findByProjectId', () => {
|
|
||||||
it('should return groups for a specific project', async () => {
|
|
||||||
const projectId = 'project1';
|
|
||||||
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => [mockGroup]);
|
|
||||||
|
|
||||||
const result = await service.findByProjectId(projectId);
|
|
||||||
|
|
||||||
expect(mockDb.select).toHaveBeenCalled();
|
|
||||||
expect(mockDb.from).toHaveBeenCalled();
|
|
||||||
expect(mockDb.where).toHaveBeenCalled();
|
|
||||||
expect(result).toEqual([mockGroup]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('findById', () => {
|
|
||||||
it('should return a group by id', async () => {
|
|
||||||
const id = 'group1';
|
|
||||||
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => [mockGroup]);
|
|
||||||
|
|
||||||
const result = await service.findById(id);
|
|
||||||
|
|
||||||
expect(mockDb.select).toHaveBeenCalled();
|
|
||||||
expect(mockDb.from).toHaveBeenCalled();
|
|
||||||
expect(mockDb.where).toHaveBeenCalled();
|
|
||||||
expect(result).toEqual(mockGroup);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw NotFoundException if group not found', async () => {
|
|
||||||
const id = 'nonexistent';
|
|
||||||
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => [undefined]);
|
|
||||||
|
|
||||||
await expect(service.findById(id)).rejects.toThrow(NotFoundException);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('update', () => {
|
|
||||||
it('should update a group', async () => {
|
|
||||||
const id = 'group1';
|
|
||||||
const updateGroupDto = {
|
|
||||||
name: 'Updated Group',
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await service.update(id, updateGroupDto);
|
|
||||||
|
|
||||||
expect(mockDb.update).toHaveBeenCalled();
|
|
||||||
expect(mockDb.set).toHaveBeenCalled();
|
|
||||||
expect(mockDb.where).toHaveBeenCalled();
|
|
||||||
expect(result).toEqual(mockGroup);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw NotFoundException if group not found', async () => {
|
|
||||||
const id = 'nonexistent';
|
|
||||||
const updateGroupDto = {
|
|
||||||
name: 'Updated Group',
|
|
||||||
};
|
|
||||||
|
|
||||||
mockDb.update.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.set.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.returning.mockImplementationOnce(() => [undefined]);
|
|
||||||
|
|
||||||
await expect(service.update(id, updateGroupDto)).rejects.toThrow(NotFoundException);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('remove', () => {
|
|
||||||
it('should remove a group', async () => {
|
|
||||||
const id = 'group1';
|
|
||||||
|
|
||||||
const result = await service.remove(id);
|
|
||||||
|
|
||||||
expect(mockDb.delete).toHaveBeenCalled();
|
|
||||||
expect(mockDb.where).toHaveBeenCalled();
|
|
||||||
expect(result).toEqual(mockGroup);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw NotFoundException if group not found', async () => {
|
|
||||||
const id = 'nonexistent';
|
|
||||||
|
|
||||||
mockDb.delete.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.returning.mockImplementationOnce(() => [undefined]);
|
|
||||||
|
|
||||||
await expect(service.remove(id)).rejects.toThrow(NotFoundException);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('addPersonToGroup', () => {
|
|
||||||
it('should add a person to a group', async () => {
|
|
||||||
const groupId = 'group1';
|
|
||||||
const personId = 'person1';
|
|
||||||
|
|
||||||
// Mock findById to return the group
|
|
||||||
jest.spyOn(service, 'findById').mockResolvedValueOnce(mockGroup);
|
|
||||||
|
|
||||||
// Reset and setup mocks for this test
|
|
||||||
jest.clearAllMocks();
|
|
||||||
|
|
||||||
// Mock person lookup
|
|
||||||
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => [[mockPerson]]);
|
|
||||||
|
|
||||||
// Mock relation lookup
|
|
||||||
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => [undefined]);
|
|
||||||
|
|
||||||
// Mock relation creation
|
|
||||||
mockDb.insert.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.values.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.returning.mockImplementationOnce(() => [mockPersonToGroup]);
|
|
||||||
|
|
||||||
const result = await service.addPersonToGroup(groupId, personId);
|
|
||||||
|
|
||||||
expect(service.findById).toHaveBeenCalledWith(groupId);
|
|
||||||
expect(mockDb.select).toHaveBeenCalled();
|
|
||||||
expect(mockDb.from).toHaveBeenCalled();
|
|
||||||
expect(mockDb.insert).toHaveBeenCalled();
|
|
||||||
expect(mockDb.values).toHaveBeenCalledWith({
|
|
||||||
personId,
|
|
||||||
groupId,
|
|
||||||
});
|
|
||||||
expect(result).toEqual(mockPersonToGroup);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw NotFoundException if person not found', async () => {
|
|
||||||
const groupId = 'group1';
|
|
||||||
const personId = 'nonexistent';
|
|
||||||
|
|
||||||
// Mock findById to return the group
|
|
||||||
jest.spyOn(service, 'findById').mockResolvedValueOnce(mockGroup);
|
|
||||||
|
|
||||||
// Reset and setup mocks for this test
|
|
||||||
jest.clearAllMocks();
|
|
||||||
|
|
||||||
// Mock person lookup to return no person
|
|
||||||
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => [undefined]);
|
|
||||||
|
|
||||||
await expect(service.addPersonToGroup(groupId, personId)).rejects.toThrow(NotFoundException);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('removePersonFromGroup', () => {
|
|
||||||
it('should remove a person from a group', async () => {
|
|
||||||
const groupId = 'group1';
|
|
||||||
const personId = 'person1';
|
|
||||||
|
|
||||||
// Reset and setup mocks for this test
|
|
||||||
jest.clearAllMocks();
|
|
||||||
|
|
||||||
mockDb.delete.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.returning.mockImplementationOnce(() => [mockPersonToGroup]);
|
|
||||||
|
|
||||||
const result = await service.removePersonFromGroup(groupId, personId);
|
|
||||||
|
|
||||||
expect(mockDb.delete).toHaveBeenCalled();
|
|
||||||
expect(mockDb.where).toHaveBeenCalled();
|
|
||||||
expect(result).toEqual(mockPersonToGroup);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw NotFoundException if relation not found', async () => {
|
|
||||||
const groupId = 'group1';
|
|
||||||
const personId = 'nonexistent';
|
|
||||||
|
|
||||||
// Reset and setup mocks for this test
|
|
||||||
jest.clearAllMocks();
|
|
||||||
|
|
||||||
mockDb.delete.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.returning.mockImplementationOnce(() => [undefined]);
|
|
||||||
|
|
||||||
await expect(service.removePersonFromGroup(groupId, personId)).rejects.toThrow(NotFoundException);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getPersonsInGroup', () => {
|
|
||||||
it('should get all persons in a group', async () => {
|
|
||||||
const groupId = 'group1';
|
|
||||||
const mockPersons = [{ person: mockPerson }];
|
|
||||||
|
|
||||||
// Mock findById to return the group
|
|
||||||
jest.spyOn(service, 'findById').mockResolvedValueOnce(mockGroup);
|
|
||||||
|
|
||||||
mockDb.select.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.from.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.innerJoin.mockImplementationOnce(() => mockDbOperations);
|
|
||||||
mockDbOperations.where.mockImplementationOnce(() => mockPersons);
|
|
||||||
|
|
||||||
const result = await service.getPersonsInGroup(groupId);
|
|
||||||
|
|
||||||
expect(service.findById).toHaveBeenCalledWith(groupId);
|
|
||||||
expect(mockDb.select).toHaveBeenCalled();
|
|
||||||
expect(mockDb.from).toHaveBeenCalled();
|
|
||||||
expect(mockDb.innerJoin).toHaveBeenCalled();
|
|
||||||
expect(mockDb.where).toHaveBeenCalled();
|
|
||||||
expect(result).toEqual(mockPersons);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -27,14 +27,14 @@ Nous avons élaboré un plan de bataille complet pour l'implémentation du backe
|
|||||||
- ✅ Système de migrations de base de données avec DrizzleORM
|
- ✅ Système de migrations de base de données avec DrizzleORM
|
||||||
|
|
||||||
#### Composants Non Implémentés
|
#### Composants Non Implémentés
|
||||||
- ⏳ Module d'authentification avec GitHub OAuth
|
- ❌ Module d'authentification avec GitHub OAuth
|
||||||
- ⏳ Stratégies JWT pour la gestion des sessions
|
- ❌ Stratégies JWT pour la gestion des sessions
|
||||||
- ✅ Guards et décorateurs pour la protection des routes
|
- ❌ Guards et décorateurs pour la protection des routes
|
||||||
- ❌ Module groupes
|
- ❌ Module groupes
|
||||||
- ❌ Module tags
|
- ❌ Module tags
|
||||||
- ❌ Communication en temps réel avec Socket.IO
|
- ❌ Communication en temps réel avec Socket.IO
|
||||||
- ❌ Fonctionnalités de conformité RGPD
|
- ❌ Fonctionnalités de conformité RGPD
|
||||||
- ⏳ Tests unitaires et e2e
|
- ❌ Tests unitaires et e2e
|
||||||
- ❌ Documentation API avec Swagger
|
- ❌ Documentation API avec Swagger
|
||||||
|
|
||||||
### Frontend
|
### Frontend
|
||||||
@ -65,11 +65,11 @@ Nous avons élaboré un plan de bataille complet pour l'implémentation du backe
|
|||||||
- [x] Créer un script pour exécuter les migrations automatiquement au démarrage
|
- [x] Créer un script pour exécuter les migrations automatiquement au démarrage
|
||||||
|
|
||||||
##### Authentification
|
##### Authentification
|
||||||
- [x] Implémenter le module d'authentification
|
- [ ] Implémenter le module d'authentification
|
||||||
- [x] Configurer l'authentification OAuth avec GitHub
|
- [ ] Configurer l'authentification OAuth avec GitHub
|
||||||
- [x] Implémenter les stratégies JWT pour la gestion des sessions
|
- [ ] Implémenter les stratégies JWT pour la gestion des sessions
|
||||||
- [x] Créer les guards et décorateurs pour la protection des routes
|
- [ ] Créer les guards et décorateurs pour la protection des routes
|
||||||
- [x] Implémenter le refresh token
|
- [ ] Implémenter le refresh token
|
||||||
|
|
||||||
##### Modules Manquants
|
##### Modules Manquants
|
||||||
- [ ] Implémenter le module groupes (contrôleurs, services, DTOs)
|
- [ ] Implémenter le module groupes (contrôleurs, services, DTOs)
|
||||||
@ -164,10 +164,10 @@ Nous avons élaboré un plan de bataille complet pour l'implémentation du backe
|
|||||||
## Prochaines Étapes Prioritaires
|
## Prochaines Étapes Prioritaires
|
||||||
|
|
||||||
### Backend (Priorité Haute)
|
### Backend (Priorité Haute)
|
||||||
1. **Authentification** ✅
|
1. **Authentification**
|
||||||
- Implémenter le module d'authentification avec GitHub OAuth ✅
|
- Implémenter le module d'authentification avec GitHub OAuth
|
||||||
- Configurer les stratégies JWT pour la gestion des sessions ✅
|
- Configurer les stratégies JWT pour la gestion des sessions
|
||||||
- Créer les guards et décorateurs pour la protection des routes ✅
|
- Créer les guards et décorateurs pour la protection des routes
|
||||||
|
|
||||||
2. **Modules Manquants**
|
2. **Modules Manquants**
|
||||||
- Implémenter le module groupes
|
- Implémenter le module groupes
|
||||||
@ -192,9 +192,9 @@ Nous avons élaboré un plan de bataille complet pour l'implémentation du backe
|
|||||||
| Backend - Structure de Base | 90% |
|
| Backend - Structure de Base | 90% |
|
||||||
| Backend - Base de Données | 100% |
|
| Backend - Base de Données | 100% |
|
||||||
| Backend - Modules Fonctionnels | 60% |
|
| Backend - Modules Fonctionnels | 60% |
|
||||||
| Backend - Authentification | 90% |
|
| Backend - Authentification | 0% |
|
||||||
| Backend - WebSockets | 0% |
|
| Backend - WebSockets | 0% |
|
||||||
| Backend - Tests et Documentation | 20% |
|
| Backend - Tests et Documentation | 0% |
|
||||||
| Frontend - Structure de Base | 70% |
|
| Frontend - Structure de Base | 70% |
|
||||||
| Frontend - Pages et Composants | 10% |
|
| Frontend - Pages et Composants | 10% |
|
||||||
| Frontend - Authentification | 0% |
|
| Frontend - Authentification | 0% |
|
||||||
@ -205,8 +205,8 @@ Nous avons élaboré un plan de bataille complet pour l'implémentation du backe
|
|||||||
|
|
||||||
Basé sur l'état d'avancement actuel et les tâches restantes, l'estimation du temps nécessaire pour compléter le projet est la suivante:
|
Basé sur l'état d'avancement actuel et les tâches restantes, l'estimation du temps nécessaire pour compléter le projet est la suivante:
|
||||||
|
|
||||||
- **Backend**: ~3-4 semaines
|
- **Backend**: ~4-5 semaines
|
||||||
- Authentification: ✅ Terminé
|
- Authentification: 1 semaine
|
||||||
- Modules manquants: 1-2 semaines
|
- Modules manquants: 1-2 semaines
|
||||||
- WebSockets: 1 semaine
|
- WebSockets: 1 semaine
|
||||||
- Tests et documentation: 1 semaine
|
- Tests et documentation: 1 semaine
|
||||||
@ -219,7 +219,7 @@ Basé sur l'état d'avancement actuel et les tâches restantes, l'estimation du
|
|||||||
|
|
||||||
- **Intégration et Tests**: ~1-2 semaines
|
- **Intégration et Tests**: ~1-2 semaines
|
||||||
|
|
||||||
**Temps total estimé**: 9-12 semaines
|
**Temps total estimé**: 10-13 semaines
|
||||||
|
|
||||||
## Recommandations
|
## Recommandations
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user