import 'reflect-metadata'; import { Reflector } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; import request from 'supertest'; import { vi } from 'vitest'; import { TeamController } from './TeamController'; import { TeamService } from './TeamService'; import { CreateTeamInputDTO } from './dtos/CreateTeamInputDTO'; import { UpdateTeamInput } from './dtos/TeamDto'; import { AuthenticationGuard } from '../auth/AuthenticationGuard'; import { AuthorizationGuard } from '../auth/AuthorizationGuard'; import type { AuthorizationService } from '../auth/AuthorizationService'; import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; import type { PolicyService, PolicySnapshot } from '../policy/PolicyService'; describe('TeamController', () => { let controller: TeamController; let service: ReturnType>; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [TeamController], providers: [ { provide: TeamService, useValue: { getAll: vi.fn(), getDetails: vi.fn(), getMembers: vi.fn(), getJoinRequests: vi.fn(), create: vi.fn(), update: vi.fn(), getDriverTeam: vi.fn(), getMembership: vi.fn(), }, }, ], }).compile(); controller = module.get(TeamController); service = vi.mocked(module.get(TeamService)); }); describe('getAll', () => { it('should return all teams', async () => { const result = { teams: [], totalCount: 0 }; service.getAll.mockResolvedValue(result); const response = await controller.getAll(); expect(service.getAll).toHaveBeenCalled(); expect(response).toEqual(result); }); }); describe('getDetails', () => { it('should return team details', async () => { const teamId = 'team-123'; const userId = 'user-456'; const result = { team: { id: teamId, name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [], isRecruiting: false }, membership: null, canManage: false }; service.getDetails.mockResolvedValue(result); const mockReq = { user: { userId } } as never; const response = await controller.getDetails(teamId, mockReq); expect(service.getDetails).toHaveBeenCalledWith(teamId, userId); expect(response).toEqual(result); }); }); describe('getMembers', () => { it('should return team members', async () => { const teamId = 'team-123'; const result = { members: [], totalCount: 0, ownerCount: 0, managerCount: 0, memberCount: 0 }; service.getMembers.mockResolvedValue(result); const response = await controller.getMembers(teamId); expect(service.getMembers).toHaveBeenCalledWith(teamId); expect(response).toEqual(result); }); }); describe('getJoinRequests', () => { it('should return join requests', async () => { const teamId = 'team-123'; const result = { requests: [], pendingCount: 0, totalCount: 0 }; service.getJoinRequests.mockResolvedValue(result); const response = await controller.getJoinRequests(teamId); expect(service.getJoinRequests).toHaveBeenCalledWith(teamId); expect(response).toEqual(result); }); }); describe('create', () => { it('should create team', async () => { const input: CreateTeamInputDTO = { name: 'New Team', tag: 'TAG' }; const userId = 'user-123'; const result = { id: 'team-456', success: true }; service.create.mockResolvedValue(result); const mockReq = { user: { userId } } as never; const response = await controller.create(input, mockReq); expect(service.create).toHaveBeenCalledWith(input, userId); expect(response).toEqual(result); }); }); describe('update', () => { it('should update team', async () => { const teamId = 'team-123'; const userId = 'user-456'; const input: UpdateTeamInput = { name: 'Updated Team', updatedBy: userId }; const result = { success: true }; service.update.mockResolvedValue(result); const mockReq = { user: { userId } } as never; const response = await controller.update(teamId, input, mockReq); expect(service.update).toHaveBeenCalledWith(teamId, input, userId); expect(response).toEqual(result); }); }); describe('getDriverTeam', () => { it('should return driver team', async () => { const driverId = 'driver-123'; const result = { team: { id: 'team-456', name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [], isRecruiting: false }, membership: { role: 'member' as const, joinedAt: '2023-01-01', isActive: true }, isOwner: false, canManage: false }; service.getDriverTeam.mockResolvedValue(result); const response = await controller.getDriverTeam(driverId); expect(service.getDriverTeam).toHaveBeenCalledWith(driverId); expect(response).toEqual(result); }); }); describe('getMembership', () => { it('should return team membership', async () => { const teamId = 'team-123'; const driverId = 'driver-456'; const result = { role: 'member' as const, joinedAt: '2023-01-01', isActive: true }; service.getMembership.mockResolvedValue(result); const response = await controller.getMembership(teamId, driverId); expect(service.getMembership).toHaveBeenCalledWith(teamId, driverId); expect(response).toEqual(result); }); }); describe('auth guards (HTTP)', () => { let app: import("@nestjs/common").INestApplication; const sessionPort: { getCurrentSession: () => Promise } = { getCurrentSession: vi.fn(async () => null), }; const authorizationService: Pick = { getRolesForUser: vi.fn(() => []), }; const policyService: Pick = { getSnapshot: vi.fn(async (): Promise => ({ policyVersion: 1, operationalMode: 'normal', maintenanceAllowlist: { view: [], mutate: [] }, capabilities: {}, loadedFrom: 'defaults', loadedAtIso: new Date(0).toISOString(), })), }; beforeEach(async () => { const module = await Test.createTestingModule({ controllers: [TeamController], providers: [ { provide: TeamService, useValue: { getAll: vi.fn(async () => ({ teams: [], totalCount: 0 })), getJoinRequests: vi.fn(async () => ({ requests: [], pendingCount: 0, totalCount: 0 })), }, }, ], }).compile(); app = module.createNestApplication(); const reflector = new Reflector(); app.useGlobalGuards( new AuthenticationGuard(sessionPort as never), new AuthorizationGuard(reflector, authorizationService as never), new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); }); afterEach(async () => { await app?.close(); vi.clearAllMocks(); }); it('allows @Public() endpoint without a session', async () => { await request(app.getHttpServer()).get('/teams/all').expect(200); }); it('denies non-public endpoint by default when not authenticated (401)', async () => { await request(app.getHttpServer()).get('/teams/t1/join-requests').expect(401); }); it('allows non-public endpoint when authenticated via session port', async () => { vi.mocked(sessionPort.getCurrentSession).mockResolvedValueOnce({ token: 't', user: { id: 'user-1' }, }); await request(app.getHttpServer()).get('/teams/t1/join-requests').expect(200); }); }); });