Files
TREK/server/tests/unit/nest/wiring.test.ts
T
Maurice c956fe92ba Merge dev into feat/tours and port Tours onto MikroORM
dev now runs on MikroORM, so the positional migrations, seeds.ts and
DatabaseService the Tours server side was built on are gone. Behaviour
is unchanged; persistence moves onto dev's architecture:

- one MikroORM migration for tour_types, tours and tour_waypoints,
  idempotent so instances that ran the old positional steps keep
  their data; the hike tour type is seeded inside it, the addon row
  in the AddonSeeder
- entities and repositories for the three tables, tours domain on
  repositories and UnitOfWork
- tour facet joins in places, assignments and days re-applied on the
  repository code

Fixes found while porting:

- GPX import checks addon, trip access and permission in the handler
  instead of guards, so a refused upload gets a 403/404 instead of a
  connection reset
- copying a trip copies its tours and waypoints
- a tour cannot be moved onto a day that already has it
- updateTour checks existence inside its transaction

Adds tours e2e tests and rewrites the tours unit tests onto the
migrated test database.
2026-10-06 22:43:09 +02:00

65 lines
2.9 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest';
import { HttpException } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { AppModule } from '../../../src/nest/app.module';
import { FeaturesController } from '../../../src/nest/health/features.controller';
import { AdminGuard } from '../../../src/nest/auth/admin.guard';
vi.mock('../../../src/config', async () => {
const { readEnv } = await import('../../../src/app-config');
const env = readEnv();
return {
ENCRYPTION_KEY: 'wiring-test-inert-encryption-key',
JWT_SECRET: 'wiring-test-inert-jwt-secret',
updateJwtSecret: vi.fn(),
DEFAULT_LANGUAGE: env.app.defaultLanguage,
SESSION_DURATION: env.session.duration,
SESSION_DURATION_MS: env.session.durationMs,
SESSION_DURATION_SECONDS: env.session.durationSeconds,
SESSION_DURATION_REMEMBER: env.session.durationRemember,
SESSION_DURATION_REMEMBER_MS: env.session.durationRememberMs,
SESSION_DURATION_REMEMBER_SECONDS: env.session.durationRememberSeconds,
};
});
function ctx(user: unknown) {
return { switchToHttp: () => ({ getRequest: () => ({ user }) }) } as never;
}
describe('AppModule wiring', () => {
it('registers the upstream feature modules and Tours exactly once', () => {
// Only plain class imports: forRoot()/forFeature() entries are dynamic
// (MikroOrmModule legitimately appears twice) and some resolve async.
const imports = Reflect.getMetadata('imports', AppModule) as unknown[];
const names = imports
.filter((entry): entry is { name: string } => typeof entry === 'function')
.map(entry => entry.name);
expect(new Set(names).size).toBe(names.length);
expect(names).toEqual(expect.arrayContaining([
'GoogleQuotaModule', 'ReceiptScanModule', 'SchoolHolidaysModule', 'DocSyncModule',
'DawarichModule', 'NotificationsModule', 'ToursModule',
]));
expect(names.filter(name => name === 'ToursModule')).toHaveLength(1);
});
it('compiles with the global filter + DB provider and resolves the controller', async () => {
// Plan 4 Task 4: `DatabaseService` is gone — nothing left in the graph
// needs overriding for this to compile.
const moduleRef = await Test.createTestingModule({ imports: [AppModule] }).compile();
// The one test that builds the whole AppModule: resolving a controller out of
// it proves every module in the graph compiled, not just this one.
expect(moduleRef.get(FeaturesController)).toBeInstanceOf(FeaturesController);
});
});
describe('AdminGuard', () => {
const guard = new AdminGuard();
it('allows admins', () => {
expect(guard.canActivate(ctx({ role: 'admin' }))).toBe(true);
});
it('blocks non-admins and anonymous with 403 { error }', () => {
expect(() => guard.canActivate(ctx({ role: 'user' }))).toThrow(HttpException);
expect(() => guard.canActivate(ctx(undefined))).toThrow(HttpException);
});
});