mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
ci(r0.12): pipeline GitHub Actions — ci-contract, api (couverture ≥ 70 %), web
- ci-contract : régénère docs/openapi.json + schema.d.ts et échoue au moindre diff — la règle d'or (ADR-001) devient bloquante - api : services PostgreSQL 18 + Redis, prisma migrate deploy, typecheck, Jest avec coverageThreshold global 70 % (mesuré : 97,5 % stmts / 90,7 % branches) - web : typecheck + vitest + build de production - test unitaire PermissionsGuard (23 tests au total) ; badge CI au README Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
58
apps/api/src/permissions/permissions.guard.spec.ts
Normal file
58
apps/api/src/permissions/permissions.guard.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { ForbiddenException } from '@nestjs/common';
|
||||
import type { ExecutionContext } from '@nestjs/common';
|
||||
import type { Reflector } from '@nestjs/core';
|
||||
import { PermissionsGuard } from './permissions.guard';
|
||||
import type { PermissionsService } from './permissions.service';
|
||||
import type { RequiredPermission } from './require-permission.decorator';
|
||||
|
||||
function contextWithUser(user?: { userId: string; roleId: string }) {
|
||||
return {
|
||||
getHandler: () => ({}),
|
||||
getClass: () => ({}),
|
||||
switchToHttp: () => ({ getRequest: () => ({ user }) }),
|
||||
} as unknown as ExecutionContext;
|
||||
}
|
||||
|
||||
describe('PermissionsGuard (matrice relue en base — le JWT ne porte aucun droit)', () => {
|
||||
const reflector = (required?: RequiredPermission) =>
|
||||
({ getAllAndOverride: () => required }) as unknown as Reflector;
|
||||
const service = (allowed: boolean) =>
|
||||
({ can: jest.fn().mockResolvedValue(allowed) }) as unknown as PermissionsService;
|
||||
|
||||
it('laisse passer une route sans @RequirePermission', async () => {
|
||||
const guard = new PermissionsGuard(reflector(undefined), service(false));
|
||||
await expect(guard.canActivate(contextWithUser())).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('refuse si aucun utilisateur authentifié ne porte la requête', async () => {
|
||||
const guard = new PermissionsGuard(
|
||||
reflector({ category: 'WORK_ORDERS', right: 'view' }),
|
||||
service(true),
|
||||
);
|
||||
await expect(guard.canActivate(contextWithUser(undefined))).rejects.toThrow(
|
||||
ForbiddenException,
|
||||
);
|
||||
});
|
||||
|
||||
it('autorise quand la matrice accorde le droit', async () => {
|
||||
const permissions = service(true);
|
||||
const guard = new PermissionsGuard(
|
||||
reflector({ category: 'WORK_ORDERS', right: 'create' }),
|
||||
permissions,
|
||||
);
|
||||
await expect(
|
||||
guard.canActivate(contextWithUser({ userId: 'u1', roleId: 'r1' })),
|
||||
).resolves.toBe(true);
|
||||
expect(permissions.can).toHaveBeenCalledWith('r1', 'WORK_ORDERS', 'create');
|
||||
});
|
||||
|
||||
it('refuse (403, droit nommé) quand la matrice ne l’accorde pas', async () => {
|
||||
const guard = new PermissionsGuard(
|
||||
reflector({ category: 'SETTINGS', right: 'delete' }),
|
||||
service(false),
|
||||
);
|
||||
await expect(
|
||||
guard.canActivate(contextWithUser({ userId: 'u1', roleId: 'r1' })),
|
||||
).rejects.toThrow(/SETTINGS\.delete/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user