mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 22:31:46 +00:00
Finish the NestJS migration — drop the legacy Express app
NestJS now serves the whole surface: every /api domain plus the platform
routes (uploads, /mcp, the OAuth/MCP SDK + /.well-known metadata and the
production SPA fallback). Removed server/src/app.ts, all of
server/src/routes/* and the strangler dispatcher; index.ts and the
integration suite share a single buildApp() bootstrap so prod and tests
can't drift.
- Platform/transport routes extracted to nest/platform/platform.routes.ts
and mounted before app.init() — Nest's router answers an unmatched
request with a 404, so a route registered after init is never reached.
The SPA fallback is a NotFoundException filter and the catch-all uses a
RegExp (Express 5's path-to-regexp rejects a bare '*').
- New modules: memories (/api/integrations/memories — the Journey
gallery's Immich/Synology proxy), addons (GET /api/addons) and the
cross-trip GET /api/reservations/upcoming.
- TrekExceptionFilter reproduces the old multer / err.statusCode handling
so upload rejections keep their 400/413 { error } body and non-ASCII
filenames survive (defParamCharset).
- addTripToJourney and the MCP get_journey_share_link tool gained the
trip-access check they were missing.
- Re-pointed the 34 integration tests + the websocket test onto the Nest
app; removed the now-meaningless Express-vs-Nest parity tests and a few
orphaned client components.
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest';
|
||||
import request from 'supertest';
|
||||
import type { Application } from 'express';
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
|
||||
const { testDb, dbMock } = vi.hoisted(() => {
|
||||
const Database = require('better-sqlite3');
|
||||
@@ -35,30 +36,34 @@ vi.mock('../../src/config', () => ({
|
||||
JWT_SECRET: 'test-jwt-secret-for-trek-testing-only',
|
||||
ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2',
|
||||
updateJwtSecret: () => {},
|
||||
DEFAULT_LANGUAGE: 'en',
|
||||
}));
|
||||
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
|
||||
|
||||
import { createApp } from '../../src/app';
|
||||
import { buildApp } from '../../src/bootstrap';
|
||||
import { createTables } from '../../src/db/schema';
|
||||
import { runMigrations } from '../../src/db/migrations';
|
||||
import { resetTestDb } from '../helpers/test-db';
|
||||
import { resetTestDb, resetRateLimits } from '../helpers/test-db';
|
||||
import { createUser } from '../helpers/factories';
|
||||
import { authCookie } from '../helpers/auth';
|
||||
import { loginAttempts, mfaAttempts } from '../../src/routes/auth';
|
||||
|
||||
const app: Application = createApp();
|
||||
let nestApp: INestApplication;
|
||||
let app: Application;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
createTables(testDb);
|
||||
runMigrations(testDb);
|
||||
nestApp = await buildApp();
|
||||
app = nestApp.getHttpAdapter().getInstance();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
resetTestDb(testDb);
|
||||
loginAttempts.clear();
|
||||
mfaAttempts.clear();
|
||||
resetRateLimits(nestApp);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
afterAll(async () => {
|
||||
await nestApp.close();
|
||||
testDb.close();
|
||||
});
|
||||
|
||||
@@ -95,33 +100,36 @@ describe('Photo endpoint auth', () => {
|
||||
|
||||
describe('Force HTTPS redirect', () => {
|
||||
it('MISC-004 — FORCE_HTTPS redirect sends 301 for HTTP requests on non-health paths', async () => {
|
||||
// createApp() reads FORCE_HTTPS at call time, so we need a fresh app instance
|
||||
// applyGlobalMiddleware reads FORCE_HTTPS when buildApp() composes the app, so
|
||||
// we need a fresh Nest instance built with the flag set.
|
||||
process.env.FORCE_HTTPS = 'true';
|
||||
let httpsApp: Express;
|
||||
let httpsApp: INestApplication | undefined;
|
||||
try {
|
||||
httpsApp = createApp();
|
||||
httpsApp = await buildApp();
|
||||
const res = await request(httpsApp.getHttpAdapter().getInstance())
|
||||
.get('/api/addons')
|
||||
.set('X-Forwarded-Proto', 'http');
|
||||
expect(res.status).toBe(301);
|
||||
} finally {
|
||||
if (httpsApp) await httpsApp.close();
|
||||
delete process.env.FORCE_HTTPS;
|
||||
}
|
||||
const res = await request(httpsApp)
|
||||
.get('/api/addons')
|
||||
.set('X-Forwarded-Proto', 'http');
|
||||
expect(res.status).toBe(301);
|
||||
});
|
||||
|
||||
it('MISC-008 — FORCE_HTTPS does not redirect /api/health (probes must reach it over HTTP)', async () => {
|
||||
process.env.FORCE_HTTPS = 'true';
|
||||
let httpsApp: Express;
|
||||
let httpsApp: INestApplication | undefined;
|
||||
try {
|
||||
httpsApp = createApp();
|
||||
httpsApp = await buildApp();
|
||||
const res = await request(httpsApp.getHttpAdapter().getInstance())
|
||||
.get('/api/health')
|
||||
.set('X-Forwarded-Proto', 'http');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.status).toBe('ok');
|
||||
} finally {
|
||||
if (httpsApp) await httpsApp.close();
|
||||
delete process.env.FORCE_HTTPS;
|
||||
}
|
||||
const res = await request(httpsApp)
|
||||
.get('/api/health')
|
||||
.set('X-Forwarded-Proto', 'http');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.status).toBe('ok');
|
||||
});
|
||||
|
||||
it('MISC-004 — no redirect when FORCE_HTTPS is not set', async () => {
|
||||
|
||||
Reference in New Issue
Block a user