This commit is contained in:
2026-01-08 16:30:15 +01:00
parent 52e9a2f6a7
commit 064fdd1b0a
25 changed files with 3068 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
import 'reflect-metadata';
import { ValidationPipe } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { requestContextMiddleware } from '@adapters/http/RequestContext';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import { IDENTITY_SESSION_PORT_TOKEN } from '../auth/AuthProviders';
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Admin domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
beforeAll(async () => {
vi.resetModules();
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
process.env.GRIDPILOT_API_BOOTSTRAP = 'true';
delete process.env.DATABASE_URL;
const { AppModule } = await import('../../app.module');
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
// Ensure AsyncLocalStorage request context is present for getActorFromRequestContext()
app.use(requestContextMiddleware);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
const reflector = new Reflector();
const sessionPort = module.get(IDENTITY_SESSION_PORT_TOKEN);
const authorizationService = {
getRolesForUser: () => [],
};
const policyService = {
getSnapshot: async () => ({
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date(0).toISOString(),
}),
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
);
await app.init();
}, 20_000);
afterAll(async () => {
await app?.close();
process.env = originalEnv;
vi.restoreAllMocks();
});
it('module compiles and app is initialized', () => {
expect(app).toBeDefined();
expect(app.getHttpServer()).toBeDefined();
});
it('rejects unauthenticated actor on admin endpoints (401)', async () => {
await request(app.getHttpServer())
.get('/admin/users')
.expect(401);
await request(app.getHttpServer())
.get('/admin/dashboard/stats')
.expect(401);
});
it('rejects authenticated non-admin actor (403)', async () => {
const agent = request.agent(app.getHttpServer());
await agent
.post('/auth/signup')
.send({ email: 'user-admin-test@gridpilot.local', password: 'Password123!', displayName: 'Regular User' })
.expect(201);
await agent.get('/admin/users').expect(403);
await agent.get('/admin/dashboard/stats').expect(403);
});
});

View File

@@ -0,0 +1,220 @@
import { describe, it, expect, vi } from 'vitest';
import { AdminController } from './AdminController';
import { ListUsersRequestDto } from './dtos/ListUsersRequestDto';
import { UserListResponseDto, UserResponseDto } from './dtos/UserResponseDto';
import { DashboardStatsResponseDto } from './dto/DashboardStatsResponseDto';
describe('AdminController', () => {
let controller: AdminController;
let mockService: {
listUsers: ReturnType<typeof vi.fn>;
getDashboardStats: ReturnType<typeof vi.fn>;
};
beforeEach(() => {
mockService = {
listUsers: vi.fn(),
getDashboardStats: vi.fn(),
};
controller = new AdminController(mockService as never);
});
describe('listUsers', () => {
it('should list users with basic query params', async () => {
const mockUser: UserResponseDto = {
id: 'user-1',
email: 'test@example.com',
displayName: 'Test User',
roles: ['admin'],
status: 'active',
isSystemAdmin: false,
createdAt: new Date(),
updatedAt: new Date(),
};
const mockResponse: UserListResponseDto = {
users: [mockUser],
total: 1,
page: 1,
limit: 10,
totalPages: 1,
};
mockService.listUsers.mockResolvedValue(mockResponse);
const query: ListUsersRequestDto = {
page: 1,
limit: 10,
};
const req = { user: { userId: 'admin-1' } } as any;
const result = await controller.listUsers(query, req);
expect(mockService.listUsers).toHaveBeenCalledWith({
actorId: 'admin-1',
page: 1,
limit: 10,
});
expect(result).toEqual(mockResponse);
});
it('should list users with all query params', async () => {
const mockResponse: UserListResponseDto = {
users: [],
total: 0,
page: 2,
limit: 20,
totalPages: 0,
};
mockService.listUsers.mockResolvedValue(mockResponse);
const query: ListUsersRequestDto = {
page: 2,
limit: 20,
role: 'owner',
status: 'active',
email: 'admin',
search: 'test',
sortBy: 'email',
sortDirection: 'desc',
};
const req = { user: { userId: 'owner-1' } } as any;
const result = await controller.listUsers(query, req);
expect(mockService.listUsers).toHaveBeenCalledWith({
actorId: 'owner-1',
page: 2,
limit: 20,
role: 'owner',
status: 'active',
email: 'admin',
search: 'test',
sortBy: 'email',
sortDirection: 'desc',
});
expect(result).toEqual(mockResponse);
});
it('should handle missing user ID from request', async () => {
const mockResponse: UserListResponseDto = {
users: [],
total: 0,
page: 1,
limit: 10,
totalPages: 0,
};
mockService.listUsers.mockResolvedValue(mockResponse);
const query: ListUsersRequestDto = { page: 1, limit: 10 };
const req = {} as any;
await controller.listUsers(query, req);
expect(mockService.listUsers).toHaveBeenCalledWith({
actorId: 'current-user',
page: 1,
limit: 10,
});
});
it('should handle optional query params being undefined', async () => {
const mockResponse: UserListResponseDto = {
users: [],
total: 0,
page: 1,
limit: 10,
totalPages: 0,
};
mockService.listUsers.mockResolvedValue(mockResponse);
const query: ListUsersRequestDto = {
page: 1,
limit: 10,
};
const req = { user: { userId: 'admin-1' } } as any;
await controller.listUsers(query, req);
expect(mockService.listUsers).toHaveBeenCalledWith({
actorId: 'admin-1',
page: 1,
limit: 10,
});
});
});
describe('getDashboardStats', () => {
it('should return dashboard stats', async () => {
const mockStats: DashboardStatsResponseDto = {
totalUsers: 150,
activeUsers: 120,
suspendedUsers: 20,
deletedUsers: 10,
systemAdmins: 5,
recentLogins: 25,
newUsersToday: 8,
userGrowth: [],
roleDistribution: [],
statusDistribution: {
active: 120,
suspended: 20,
deleted: 10,
},
activityTimeline: [],
};
mockService.getDashboardStats.mockResolvedValue(mockStats);
const req = { user: { userId: 'admin-1' } } as any;
const result = await controller.getDashboardStats(req);
expect(mockService.getDashboardStats).toHaveBeenCalledWith({
actorId: 'admin-1',
});
expect(result).toEqual(mockStats);
});
it('should handle missing user ID from request', async () => {
const mockStats: DashboardStatsResponseDto = {
totalUsers: 0,
activeUsers: 0,
suspendedUsers: 0,
deletedUsers: 0,
systemAdmins: 0,
recentLogins: 0,
newUsersToday: 0,
userGrowth: [],
roleDistribution: [],
statusDistribution: {
active: 0,
suspended: 0,
deleted: 0,
},
activityTimeline: [],
};
mockService.getDashboardStats.mockResolvedValue(mockStats);
const req = {} as any;
const result = await controller.getDashboardStats(req);
expect(mockService.getDashboardStats).toHaveBeenCalledWith({
actorId: 'current-user',
});
expect(result).toEqual(mockStats);
});
it('should handle service errors gracefully', async () => {
mockService.getDashboardStats.mockRejectedValue(new Error('Database connection failed'));
const req = { user: { userId: 'admin-1' } } as any;
await expect(controller.getDashboardStats(req)).rejects.toThrow('Database connection failed');
});
});
});

View File

@@ -0,0 +1,95 @@
import 'reflect-metadata';
import { ValidationPipe } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { requestContextMiddleware } from '@adapters/http/RequestContext';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import { IDENTITY_SESSION_PORT_TOKEN } from '../auth/AuthProviders';
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Analytics domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
beforeAll(async () => {
vi.resetModules();
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
process.env.GRIDPILOT_API_BOOTSTRAP = 'true';
delete process.env.DATABASE_URL;
const { AppModule } = await import('../../app.module');
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
// Ensure AsyncLocalStorage request context is present for getActorFromRequestContext()
app.use(requestContextMiddleware);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
const reflector = new Reflector();
const sessionPort = module.get(IDENTITY_SESSION_PORT_TOKEN);
const authorizationService = {
getRolesForUser: () => [],
};
const policyService = {
getSnapshot: async () => ({
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date(0).toISOString(),
}),
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
);
await app.init();
}, 20_000);
afterAll(async () => {
await app?.close();
process.env = originalEnv;
vi.restoreAllMocks();
});
it('module compiles and app is initialized', () => {
expect(app).toBeDefined();
expect(app.getHttpServer()).toBeDefined();
});
it('rejects unauthenticated actor on internal dashboard endpoints (401)', async () => {
await request(app.getHttpServer())
.get('/analytics/dashboard')
.expect(401);
await request(app.getHttpServer())
.get('/analytics/metrics')
.expect(401);
});
});

View File

@@ -0,0 +1,90 @@
import 'reflect-metadata';
import { ValidationPipe } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { requestContextMiddleware } from '@adapters/http/RequestContext';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import { IDENTITY_SESSION_PORT_TOKEN } from '../auth/AuthProviders';
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Dashboard domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
beforeAll(async () => {
vi.resetModules();
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
process.env.GRIDPILOT_API_BOOTSTRAP = 'true';
delete process.env.DATABASE_URL;
const { AppModule } = await import('../../app.module');
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
// Ensure AsyncLocalStorage request context is present for getActorFromRequestContext()
app.use(requestContextMiddleware);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
const reflector = new Reflector();
const sessionPort = module.get(IDENTITY_SESSION_PORT_TOKEN);
const authorizationService = {
getRolesForUser: () => [],
};
const policyService = {
getSnapshot: async () => ({
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date(0).toISOString(),
}),
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
);
await app.init();
}, 20_000);
afterAll(async () => {
await app?.close();
process.env = originalEnv;
vi.restoreAllMocks();
});
it('module compiles and app is initialized', () => {
expect(app).toBeDefined();
expect(app.getHttpServer()).toBeDefined();
});
it('rejects unauthenticated actor (401)', async () => {
await request(app.getHttpServer())
.get('/dashboard/overview')
.expect(401);
});
});

View File

@@ -0,0 +1,91 @@
import 'reflect-metadata';
import { ValidationPipe } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { requestContextMiddleware } from '@adapters/http/RequestContext';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import { IDENTITY_SESSION_PORT_TOKEN } from '../auth/AuthProviders';
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Driver domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
beforeAll(async () => {
vi.resetModules();
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
process.env.GRIDPILOT_API_BOOTSTRAP = 'true';
delete process.env.DATABASE_URL;
const { AppModule } = await import('../../app.module');
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
// Ensure AsyncLocalStorage request context is present for getActorFromRequestContext()
app.use(requestContextMiddleware);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
const reflector = new Reflector();
const sessionPort = module.get(IDENTITY_SESSION_PORT_TOKEN);
const authorizationService = {
getRolesForUser: () => [],
};
const policyService = {
getSnapshot: async () => ({
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date(0).toISOString(),
}),
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
);
await app.init();
}, 20_000);
afterAll(async () => {
await app?.close();
process.env = originalEnv;
vi.restoreAllMocks();
});
it('module compiles and app is initialized', () => {
expect(app).toBeDefined();
expect(app.getHttpServer()).toBeDefined();
});
it('rejects unauthenticated actor on current driver endpoint (401)', async () => {
await request(app.getHttpServer())
.get('/drivers/current')
.expect(401);
});
});

View File

@@ -0,0 +1,93 @@
import 'reflect-metadata';
import { ValidationPipe } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { requestContextMiddleware } from '@adapters/http/RequestContext';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import { IDENTITY_SESSION_PORT_TOKEN } from '../auth/AuthProviders';
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Hello domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
beforeAll(async () => {
vi.resetModules();
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
process.env.GRIDPILOT_API_BOOTSTRAP = 'true';
delete process.env.DATABASE_URL;
const { AppModule } = await import('../../app.module');
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
// Ensure AsyncLocalStorage request context is present for getActorFromRequestContext()
app.use(requestContextMiddleware);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
const reflector = new Reflector();
const sessionPort = module.get(IDENTITY_SESSION_PORT_TOKEN);
const authorizationService = {
getRolesForUser: () => [],
};
const policyService = {
getSnapshot: async () => ({
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date(0).toISOString(),
}),
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
);
await app.init();
}, 20_000);
afterAll(async () => {
await app?.close();
process.env = originalEnv;
vi.restoreAllMocks();
});
it('module compiles and app is initialized', () => {
expect(app).toBeDefined();
expect(app.getHttpServer()).toBeDefined();
});
it('allows public access to health endpoint (happy path)', async () => {
await request(app.getHttpServer())
.get('/health')
.expect(200)
.expect((res) => {
expect(res.body).toEqual({ status: 'ok' });
});
});
});

View File

@@ -0,0 +1,52 @@
import { describe, it, expect, vi } from 'vitest';
import { HelloController } from './HelloController';
describe('HelloController', () => {
let controller: HelloController;
let mockService: { getHello: ReturnType<typeof vi.fn> };
beforeEach(() => {
mockService = {
getHello: vi.fn(),
};
controller = new HelloController(mockService as never);
});
describe('health', () => {
it('should return health status', async () => {
const result = await controller.health();
expect(result).toEqual({ status: 'ok' });
});
});
describe('getHello', () => {
it('should return hello message from service', async () => {
const helloMessage = 'Hello World';
mockService.getHello.mockReturnValue(helloMessage);
const result = await controller.getHello();
expect(mockService.getHello).toHaveBeenCalledTimes(1);
expect(result).toBe(helloMessage);
});
it('should return custom hello message', async () => {
const customMessage = 'Hello from Test';
mockService.getHello.mockReturnValue(customMessage);
const result = await controller.getHello();
expect(result).toBe(customMessage);
});
it('should handle empty string from service', async () => {
mockService.getHello.mockReturnValue('');
const result = await controller.getHello();
expect(result).toBe('');
});
});
});

View File

@@ -0,0 +1,72 @@
import { describe, expect, it } from 'vitest';
import { CreateLeagueSeasonScheduleRacePresenter } from './LeagueSeasonScheduleMutationPresenters';
describe('CreateLeagueSeasonScheduleRacePresenter', () => {
it('presents create result with raceId', () => {
const presenter = new CreateLeagueSeasonScheduleRacePresenter();
presenter.present({
raceId: 'race-123',
});
const vm = presenter.getResponseModel();
expect(vm).not.toBeNull();
expect(vm!.raceId).toBe('race-123');
});
it('returns null before present is called', () => {
const presenter = new CreateLeagueSeasonScheduleRacePresenter();
const vm = presenter.getResponseModel();
expect(vm).toBeNull();
});
it('can be reset after presenting', () => {
const presenter = new CreateLeagueSeasonScheduleRacePresenter();
presenter.present({
raceId: 'race-123',
});
expect(presenter.getResponseModel()).not.toBeNull();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
it('can present multiple times with different race IDs', () => {
const presenter = new CreateLeagueSeasonScheduleRacePresenter();
// First presentation
presenter.present({
raceId: 'race-123',
});
let vm = presenter.getResponseModel();
expect(vm!.raceId).toBe('race-123');
// Second presentation (simulating reuse)
presenter.present({
raceId: 'race-456',
});
vm = presenter.getResponseModel();
expect(vm!.raceId).toBe('race-456');
});
it('handles raceId with special characters', () => {
const presenter = new CreateLeagueSeasonScheduleRacePresenter();
presenter.present({
raceId: 'race-abc-123_xyz',
});
const vm = presenter.getResponseModel();
expect(vm).not.toBeNull();
expect(vm!.raceId).toBe('race-abc-123_xyz');
});
});

View File

@@ -0,0 +1,59 @@
import { describe, expect, it } from 'vitest';
import { DeleteLeagueSeasonScheduleRacePresenter } from './LeagueSeasonScheduleMutationPresenters';
describe('DeleteLeagueSeasonScheduleRacePresenter', () => {
it('presents delete result with success=true', () => {
const presenter = new DeleteLeagueSeasonScheduleRacePresenter();
presenter.present({
success: true,
});
const vm = presenter.getResponseModel();
expect(vm).not.toBeNull();
expect(vm!.success).toBe(true);
});
it('returns null before present is called', () => {
const presenter = new DeleteLeagueSeasonScheduleRacePresenter();
const vm = presenter.getResponseModel();
expect(vm).toBeNull();
});
it('can be reset after presenting', () => {
const presenter = new DeleteLeagueSeasonScheduleRacePresenter();
presenter.present({
success: true,
});
expect(presenter.getResponseModel()).not.toBeNull();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
it('can present multiple times', () => {
const presenter = new DeleteLeagueSeasonScheduleRacePresenter();
// First presentation
presenter.present({
success: true,
});
let vm = presenter.getResponseModel();
expect(vm!.success).toBe(true);
// Second presentation (simulating reuse)
presenter.present({
success: true,
});
vm = presenter.getResponseModel();
expect(vm!.success).toBe(true);
});
});

View File

@@ -0,0 +1,68 @@
import { describe, expect, it } from 'vitest';
import { PublishLeagueSeasonSchedulePresenter } from './LeagueSeasonScheduleMutationPresenters';
describe('PublishLeagueSeasonSchedulePresenter', () => {
it('presents publish result with success and published=true', () => {
const presenter = new PublishLeagueSeasonSchedulePresenter();
presenter.present({
success: true,
seasonId: 'season-1',
published: true,
});
const vm = presenter.getResponseModel();
expect(vm).not.toBeNull();
expect(vm!.success).toBe(true);
expect(vm!.published).toBe(true);
});
it('returns null before present is called', () => {
const presenter = new PublishLeagueSeasonSchedulePresenter();
const vm = presenter.getResponseModel();
expect(vm).toBeNull();
});
it('can be reset after presenting', () => {
const presenter = new PublishLeagueSeasonSchedulePresenter();
presenter.present({
success: true,
seasonId: 'season-1',
published: true,
});
expect(presenter.getResponseModel()).not.toBeNull();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
it('can present multiple times with different results', () => {
const presenter = new PublishLeagueSeasonSchedulePresenter();
// First presentation
presenter.present({
success: true,
seasonId: 'season-1',
published: true,
});
let vm = presenter.getResponseModel();
expect(vm!.published).toBe(true);
// Second presentation (simulating reuse)
presenter.present({
success: true,
seasonId: 'season-2',
published: true,
});
vm = presenter.getResponseModel();
expect(vm!.published).toBe(true);
});
});

View File

@@ -0,0 +1,68 @@
import { describe, expect, it } from 'vitest';
import { UnpublishLeagueSeasonSchedulePresenter } from './LeagueSeasonScheduleMutationPresenters';
describe('UnpublishLeagueSeasonSchedulePresenter', () => {
it('presents unpublish result with success and published=false', () => {
const presenter = new UnpublishLeagueSeasonSchedulePresenter();
presenter.present({
success: true,
seasonId: 'season-1',
published: false,
});
const vm = presenter.getResponseModel();
expect(vm).not.toBeNull();
expect(vm!.success).toBe(true);
expect(vm!.published).toBe(false);
});
it('returns null before present is called', () => {
const presenter = new UnpublishLeagueSeasonSchedulePresenter();
const vm = presenter.getResponseModel();
expect(vm).toBeNull();
});
it('can be reset after presenting', () => {
const presenter = new UnpublishLeagueSeasonSchedulePresenter();
presenter.present({
success: true,
seasonId: 'season-1',
published: false,
});
expect(presenter.getResponseModel()).not.toBeNull();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
it('can present multiple times with different results', () => {
const presenter = new UnpublishLeagueSeasonSchedulePresenter();
// First presentation
presenter.present({
success: true,
seasonId: 'season-1',
published: false,
});
let vm = presenter.getResponseModel();
expect(vm!.published).toBe(false);
// Second presentation (simulating reuse)
presenter.present({
success: true,
seasonId: 'season-2',
published: false,
});
vm = presenter.getResponseModel();
expect(vm!.published).toBe(false);
});
});

View File

@@ -0,0 +1,59 @@
import { describe, expect, it } from 'vitest';
import { UpdateLeagueSeasonScheduleRacePresenter } from './LeagueSeasonScheduleMutationPresenters';
describe('UpdateLeagueSeasonScheduleRacePresenter', () => {
it('presents update result with success=true', () => {
const presenter = new UpdateLeagueSeasonScheduleRacePresenter();
presenter.present({
success: true,
});
const vm = presenter.getResponseModel();
expect(vm).not.toBeNull();
expect(vm!.success).toBe(true);
});
it('returns null before present is called', () => {
const presenter = new UpdateLeagueSeasonScheduleRacePresenter();
const vm = presenter.getResponseModel();
expect(vm).toBeNull();
});
it('can be reset after presenting', () => {
const presenter = new UpdateLeagueSeasonScheduleRacePresenter();
presenter.present({
success: true,
});
expect(presenter.getResponseModel()).not.toBeNull();
presenter.reset();
expect(presenter.getResponseModel()).toBeNull();
});
it('can present multiple times', () => {
const presenter = new UpdateLeagueSeasonScheduleRacePresenter();
// First presentation
presenter.present({
success: true,
});
let vm = presenter.getResponseModel();
expect(vm!.success).toBe(true);
// Second presentation (simulating reuse)
presenter.present({
success: true,
});
vm = presenter.getResponseModel();
expect(vm!.success).toBe(true);
});
});

View File

@@ -0,0 +1,101 @@
import 'reflect-metadata';
import { ValidationPipe } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { requestContextMiddleware } from '@adapters/http/RequestContext';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import { IDENTITY_SESSION_PORT_TOKEN } from '../auth/AuthProviders';
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Payments domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
beforeAll(async () => {
vi.resetModules();
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
process.env.GRIDPILOT_API_BOOTSTRAP = 'true';
delete process.env.DATABASE_URL;
const { AppModule } = await import('../../app.module');
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
// Ensure AsyncLocalStorage request context is present for getActorFromRequestContext()
app.use(requestContextMiddleware);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
const reflector = new Reflector();
const sessionPort = module.get(IDENTITY_SESSION_PORT_TOKEN);
const authorizationService = {
getRolesForUser: () => [],
};
const policyService = {
getSnapshot: async () => ({
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date(0).toISOString(),
}),
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
);
await app.init();
}, 20_000);
afterAll(async () => {
await app?.close();
process.env = originalEnv;
vi.restoreAllMocks();
});
it('module compiles and app is initialized', () => {
expect(app).toBeDefined();
expect(app.getHttpServer()).toBeDefined();
});
it('rejects unauthenticated actor (401)', async () => {
await request(app.getHttpServer())
.get('/payments')
.expect(401);
});
it('rejects authenticated non-admin actor (403)', async () => {
const agent = request.agent(app.getHttpServer());
await agent
.post('/auth/signup')
.send({ email: 'user-payments-test@gridpilot.local', password: 'Password123!', displayName: 'Regular User' })
.expect(201);
await agent.get('/payments').expect(403);
});
});

View File

@@ -0,0 +1,84 @@
import 'reflect-metadata';
import { ValidationPipe } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { requestContextMiddleware } from '@adapters/http/RequestContext';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import { IDENTITY_SESSION_PORT_TOKEN } from '../auth/AuthProviders';
import { FeatureAvailabilityGuard } from './FeatureAvailabilityGuard';
describe('Policy domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
beforeAll(async () => {
vi.resetModules();
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
process.env.GRIDPILOT_API_BOOTSTRAP = 'true';
delete process.env.DATABASE_URL;
const { AppModule } = await import('../../app.module');
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
// Ensure AsyncLocalStorage request context is present for getActorFromRequestContext()
app.use(requestContextMiddleware);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
const reflector = new Reflector();
const sessionPort = module.get(IDENTITY_SESSION_PORT_TOKEN);
const authorizationService = {
getRolesForUser: () => [],
};
const policyService = {
getSnapshot: async () => ({
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date(0).toISOString(),
}),
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
);
await app.init();
}, 20_000);
afterAll(async () => {
await app?.close();
process.env = originalEnv;
vi.restoreAllMocks();
});
it('module compiles and app is initialized', () => {
expect(app).toBeDefined();
expect(app.getHttpServer()).toBeDefined();
});
});

View File

@@ -0,0 +1,134 @@
import { describe, it, expect, vi } from 'vitest';
import { PolicyController } from './PolicyController';
import { PolicySnapshot } from './PolicyService';
describe('PolicyController', () => {
let controller: PolicyController;
let mockService: { getSnapshot: ReturnType<typeof vi.fn> };
beforeEach(() => {
mockService = {
getSnapshot: vi.fn(),
};
controller = new PolicyController(mockService as never);
});
describe('getSnapshot', () => {
it('should return policy snapshot from service', async () => {
const mockSnapshot: PolicySnapshot = {
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: {
view: ['health'],
mutate: ['admin'],
},
capabilities: {
'feature-a': 'enabled',
'feature-b': 'disabled',
},
loadedFrom: 'defaults',
loadedAtIso: new Date().toISOString(),
};
mockService.getSnapshot.mockResolvedValue(mockSnapshot);
const result = await controller.getSnapshot();
expect(mockService.getSnapshot).toHaveBeenCalledTimes(1);
expect(result).toEqual(mockSnapshot);
});
it('should return snapshot with maintenance mode', async () => {
const mockSnapshot: PolicySnapshot = {
policyVersion: 2,
operationalMode: 'maintenance',
maintenanceAllowlist: {
view: ['health', 'status'],
mutate: ['admin'],
},
capabilities: {
'dashboard': 'enabled',
'payments': 'disabled',
},
loadedFrom: 'file',
loadedAtIso: new Date().toISOString(),
};
mockService.getSnapshot.mockResolvedValue(mockSnapshot);
const result = await controller.getSnapshot();
expect(result).toEqual(mockSnapshot);
expect(result.operationalMode).toBe('maintenance');
});
it('should return snapshot with test mode', async () => {
const mockSnapshot: PolicySnapshot = {
policyVersion: 1,
operationalMode: 'test',
maintenanceAllowlist: {
view: [],
mutate: [],
},
capabilities: {
'all-features': 'enabled',
},
loadedFrom: 'env',
loadedAtIso: new Date().toISOString(),
};
mockService.getSnapshot.mockResolvedValue(mockSnapshot);
const result = await controller.getSnapshot();
expect(result).toEqual(mockSnapshot);
expect(result.operationalMode).toBe('test');
});
it('should return snapshot with empty capabilities', async () => {
const mockSnapshot: PolicySnapshot = {
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: {
view: [],
mutate: [],
},
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date().toISOString(),
};
mockService.getSnapshot.mockResolvedValue(mockSnapshot);
const result = await controller.getSnapshot();
expect(result).toEqual(mockSnapshot);
expect(Object.keys(result.capabilities)).toHaveLength(0);
});
it('should return snapshot with coming_soon features', async () => {
const mockSnapshot: PolicySnapshot = {
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: {
view: [],
mutate: [],
},
capabilities: {
'new-feature': 'coming_soon',
'beta-feature': 'hidden',
},
loadedFrom: 'file',
loadedAtIso: new Date().toISOString(),
};
mockService.getSnapshot.mockResolvedValue(mockSnapshot);
const result = await controller.getSnapshot();
expect(result.capabilities['new-feature']).toBe('coming_soon');
expect(result.capabilities['beta-feature']).toBe('hidden');
});
});
});

View File

@@ -0,0 +1,123 @@
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { PolicyService } from './PolicyService';
describe('PolicyService', () => {
let service: PolicyService;
let originalEnv: NodeJS.ProcessEnv;
beforeEach(() => {
originalEnv = process.env;
process.env = { ...originalEnv };
service = new PolicyService();
});
afterEach(() => {
process.env = originalEnv;
vi.restoreAllMocks();
});
describe('getSnapshot', () => {
it('should return cached snapshot when not expired', async () => {
// Set a long cache time
process.env.GRIDPILOT_POLICY_CACHE_MS = '60000';
const snapshot1 = await service.getSnapshot();
const snapshot2 = await service.getSnapshot();
expect(snapshot1).toEqual(snapshot2);
expect(snapshot1.policyVersion).toBeDefined();
expect(snapshot1.loadedAtIso).toBeDefined();
});
it('should return new snapshot when cache expires', async () => {
// Set cache to 1ms to force expiration
process.env.GRIDPILOT_POLICY_CACHE_MS = '1';
const snapshot1 = await service.getSnapshot();
// Wait for cache to expire
await new Promise(resolve => setTimeout(resolve, 10));
const snapshot2 = await service.getSnapshot();
expect(snapshot1.loadedAtIso).not.toBe(snapshot2.loadedAtIso);
});
it('should load from file when GRIDPILOT_POLICY_PATH is set', async () => {
// We can't easily mock readFile in this context, so we'll test the default path
// This test verifies the service structure works
const snapshot = await service.getSnapshot();
expect(snapshot).toBeDefined();
expect(snapshot.policyVersion).toBeGreaterThanOrEqual(1);
expect(snapshot.operationalMode).toBeDefined();
expect(snapshot.capabilities).toBeDefined();
});
it('should use default values when no env vars are set', async () => {
// Clear all policy-related env vars
delete process.env.GRIDPILOT_POLICY_PATH;
delete process.env.GRIDPILOT_POLICY_CACHE_MS;
delete process.env.GRIDPILOT_OPERATIONAL_MODE;
delete process.env.GRIDPILOT_MAINTENANCE_ALLOW_VIEW;
delete process.env.GRIDPILOT_MAINTENANCE_ALLOW_MUTATE;
const snapshot = await service.getSnapshot();
expect(snapshot.policyVersion).toBe(1);
expect(snapshot.operationalMode).toBe('normal');
expect(snapshot.maintenanceAllowlist.view).toEqual([]);
expect(snapshot.maintenanceAllowlist.mutate).toEqual([]);
expect(snapshot.capabilities).toBeDefined();
expect(snapshot.loadedFrom).toBeDefined();
expect(snapshot.loadedAtIso).toBeDefined();
});
it('should parse operational mode from env', async () => {
process.env.GRIDPILOT_OPERATIONAL_MODE = 'maintenance';
process.env.GRIDPILOT_POLICY_CACHE_MS = '5000';
const snapshot = await service.getSnapshot();
expect(snapshot.operationalMode).toBe('maintenance');
});
it('should handle invalid operational mode gracefully', async () => {
process.env.GRIDPILOT_OPERATIONAL_MODE = 'invalid-mode';
const snapshot = await service.getSnapshot();
expect(snapshot.operationalMode).toBe('normal');
});
it('should parse maintenance allowlist from env', async () => {
process.env.GRIDPILOT_MAINTENANCE_ALLOW_VIEW = 'health, status, api';
process.env.GRIDPILOT_MAINTENANCE_ALLOW_MUTATE = 'admin, config';
const snapshot = await service.getSnapshot();
expect(snapshot.maintenanceAllowlist.view).toEqual(['health', 'status', 'api']);
expect(snapshot.maintenanceAllowlist.mutate).toEqual(['admin', 'config']);
});
it('should handle empty maintenance allowlist', async () => {
process.env.GRIDPILOT_MAINTENANCE_ALLOW_VIEW = '';
process.env.GRIDPILOT_MAINTENANCE_ALLOW_MUTATE = '';
const snapshot = await service.getSnapshot();
expect(snapshot.maintenanceAllowlist.view).toEqual([]);
expect(snapshot.maintenanceAllowlist.mutate).toEqual([]);
});
it('should handle missing maintenance allowlist', async () => {
delete process.env.GRIDPILOT_MAINTENANCE_ALLOW_VIEW;
delete process.env.GRIDPILOT_MAINTENANCE_ALLOW_MUTATE;
const snapshot = await service.getSnapshot();
expect(snapshot.maintenanceAllowlist.view).toEqual([]);
expect(snapshot.maintenanceAllowlist.mutate).toEqual([]);
});
});
});

View File

@@ -0,0 +1,94 @@
import 'reflect-metadata';
import { ValidationPipe } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterAll, beforeAll, describe, it, vi } from 'vitest';
import { requestContextMiddleware } from '@adapters/http/RequestContext';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import { IDENTITY_SESSION_PORT_TOKEN } from '../auth/AuthProviders';
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Protests domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
beforeAll(async () => {
vi.resetModules();
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
process.env.GRIDPILOT_API_BOOTSTRAP = 'true';
delete process.env.DATABASE_URL;
const { AppModule } = await import('../../app.module');
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
// Ensure AsyncLocalStorage request context is present for getActorFromRequestContext()
app.use(requestContextMiddleware);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
const reflector = new Reflector();
const sessionPort = module.get(IDENTITY_SESSION_PORT_TOKEN);
const authorizationService = {
getRolesForUser: () => [],
};
const policyService = {
getSnapshot: async () => ({
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date(0).toISOString(),
}),
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
);
await app.init();
}, 20_000);
afterAll(async () => {
await app?.close();
process.env = originalEnv;
vi.restoreAllMocks();
});
it('module compiles and app is initialized', () => {
expect(app).toBeDefined();
expect(app.getHttpServer()).toBeDefined();
});
it('rejects unauthenticated actor (401)', async () => {
await request(app.getHttpServer())
.post('/protests/protest-123/review')
.send({
decision: 'approved',
notes: 'Test review',
})
.expect(401);
});
});

View File

@@ -0,0 +1,99 @@
import 'reflect-metadata';
import { ValidationPipe } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { requestContextMiddleware } from '@adapters/http/RequestContext';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import { IDENTITY_SESSION_PORT_TOKEN } from '../auth/AuthProviders';
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Race domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
beforeAll(async () => {
vi.resetModules();
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
process.env.GRIDPILOT_API_BOOTSTRAP = 'true';
delete process.env.DATABASE_URL;
const { AppModule } = await import('../../app.module');
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
// Ensure AsyncLocalStorage request context is present for getActorFromRequestContext()
app.use(requestContextMiddleware);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
const reflector = new Reflector();
const sessionPort = module.get(IDENTITY_SESSION_PORT_TOKEN);
const authorizationService = {
getRolesForUser: () => [],
};
const policyService = {
getSnapshot: async () => ({
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date(0).toISOString(),
}),
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
);
await app.init();
}, 20_000);
afterAll(async () => {
await app?.close();
process.env = originalEnv;
vi.restoreAllMocks();
});
it('module compiles and app is initialized', () => {
expect(app).toBeDefined();
expect(app.getHttpServer()).toBeDefined();
});
it('allows public access to all races (happy path)', async () => {
await request(app.getHttpServer())
.get('/races/all')
.expect(200);
});
it('rejects unauthenticated actor on register endpoint (401)', async () => {
await request(app.getHttpServer())
.post('/races/race-123/register')
.send({
driverId: 'driver-123',
})
.expect(401);
});
});

View File

@@ -0,0 +1,91 @@
import 'reflect-metadata';
import { ValidationPipe } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { requestContextMiddleware } from '@adapters/http/RequestContext';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import { IDENTITY_SESSION_PORT_TOKEN } from '../auth/AuthProviders';
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Sponsor domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
beforeAll(async () => {
vi.resetModules();
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
process.env.GRIDPILOT_API_BOOTSTRAP = 'true';
delete process.env.DATABASE_URL;
const { AppModule } = await import('../../app.module');
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
// Ensure AsyncLocalStorage request context is present for getActorFromRequestContext()
app.use(requestContextMiddleware);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
const reflector = new Reflector();
const sessionPort = module.get(IDENTITY_SESSION_PORT_TOKEN);
const authorizationService = {
getRolesForUser: () => [],
};
const policyService = {
getSnapshot: async () => ({
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date(0).toISOString(),
}),
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
);
await app.init();
}, 20_000);
afterAll(async () => {
await app?.close();
process.env = originalEnv;
vi.restoreAllMocks();
});
it('module compiles and app is initialized', () => {
expect(app).toBeDefined();
expect(app.getHttpServer()).toBeDefined();
});
it('rejects unauthenticated actor on admin endpoints (401)', async () => {
await request(app.getHttpServer())
.get('/sponsors')
.expect(401);
});
});

View File

@@ -0,0 +1,100 @@
import 'reflect-metadata';
import { ValidationPipe } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { requestContextMiddleware } from '@adapters/http/RequestContext';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import { IDENTITY_SESSION_PORT_TOKEN } from '../auth/AuthProviders';
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Team domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
beforeAll(async () => {
vi.resetModules();
process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory';
process.env.GRIDPILOT_API_BOOTSTRAP = 'true';
delete process.env.DATABASE_URL;
const { AppModule } = await import('../../app.module');
const module = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
// Ensure AsyncLocalStorage request context is present for getActorFromRequestContext()
app.use(requestContextMiddleware);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
const reflector = new Reflector();
const sessionPort = module.get(IDENTITY_SESSION_PORT_TOKEN);
const authorizationService = {
getRolesForUser: () => [],
};
const policyService = {
getSnapshot: async () => ({
policyVersion: 1,
operationalMode: 'normal',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: new Date(0).toISOString(),
}),
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
);
await app.init();
}, 20_000);
afterAll(async () => {
await app?.close();
process.env = originalEnv;
vi.restoreAllMocks();
});
it('module compiles and app is initialized', () => {
expect(app).toBeDefined();
expect(app.getHttpServer()).toBeDefined();
});
it('allows public access to all teams (happy path)', async () => {
await request(app.getHttpServer())
.get('/teams/all')
.expect(200);
});
it('rejects unauthenticated actor on create team (401)', async () => {
await request(app.getHttpServer())
.post('/teams')
.send({
name: 'Test Team',
tag: 'TST',
})
.expect(401);
});
});