test(server): suites record broadcasts on a FakeRealtimeService instead of mocking src/websocket

A hand-built service gets a FakeRealtimeService where it took a RealtimeService, a Nest testing module overrides the provider, and a suite over buildApp() spies on the container's instance (spyOnRealtime). The 167 vi.mock factories on src/websocket are gone, RealtimeService reads the socket registry in ws-state.ts directly, and the re-export stub src/websocket.ts is deleted. The facade's own suite now checks what reaches a socket instead of which module function it called.
This commit is contained in:
Maurice
2026-10-10 18:23:04 +02:00
parent 00cbc5aae3
commit fa0edfd714
181 changed files with 584 additions and 561 deletions
+1 -1
View File
@@ -85,7 +85,7 @@ Nest owns everything: every domain is a DI module under `server/src/nest/<domain
- **Auth is default-deny**: global guards run as `APP_GUARD`s; public routes opt out with `@Public()`, and a boot-time ratchet (`validate-route-guards.ts`) refuses any public route not on its allow-list.
- **Trip-scoped routes** verify trip access (404) and the permission (403) — `@UseGuards(JwtAuthGuard, TripAccessGuard)` + `@RequirePermission('<action>')` per handler — and forward `X-Socket-Id` to the broadcast so the originating client doesn't echo its own change. **Never gate a multipart upload with a guard** (the client sees ECONNRESET instead of 403 — check in the handler).
- **DB** (`server/src/db/`): `better-sqlite3`, WAL, FK on. MikroORM's migrations (`db/migrations/`) are the only schema source, **including for tests** — the hand-kept `schema.ts`/`migrations.ts`/`seeds.ts` test builders are gone. Seeding is MikroORM's too (`db/seeders/`), applied at boot from `buildApp()`; the ORM shares the one better-sqlite3 connection (bound in `server/src/db/orm-driver.ts`) rather than opening its own. Queries go through repositories (`server/src/db/repositories/`) and `UnitOfWork` (`server/src/nest/database/unit-of-work.ts`) for transactions — there is no `DatabaseService`. `better-sqlite3` itself is ESLint-restricted to `src/db/` plus a short, named allow-list (`server/eslint.config.mjs`), so a domain reaching for the driver directly fails lint. `NODE_ENV=test` gives each vitest worker an isolated `:memory:` DB.
- **Realtime**: `/ws` is a Nest gateway in `server/src/nest/realtime/` — inject `RealtimeService`; `server/src/websocket.ts` is a legacy re-export stub.
- **Realtime**: `/ws` is a Nest gateway in `server/src/nest/realtime/` — inject `RealtimeService`. Tests observe broadcasts through a `FakeRealtimeService` (`server/tests/helpers/fake-realtime.ts`), never by mocking the transport module.
- **MCP**: tools are declared in `<domain>.mcp.ts` files beside each module, on the `server/src/nest-mcp/` decorator layer. A validation or permission change on a REST route must land in the parallel MCP tool in the same change. See `MCP.md`.
- **Addons** are admin-toggleable feature modules keyed by `ADDON_IDS` in `server/src/addons.ts`.
- **Plugins** (`server/src/nest/plugins/`): one forked child process per plugin, permission-gated RPC (a handler is registered only if the plugin holds the unlocking permission), install-time manifest/signature/egress/version-range gates. The protocol tables are **generated** into `plugin-sdk/` and `shared/` by `gen:plugin-facts`; `check:plugin-facts` is the CI gate. Author tooling is `plugin-sdk/`; the `trek-plugin-dev` skill covers authoring.
+1 -1
View File
@@ -66,7 +66,7 @@ Single test: `npx vitest run tests/unit/nest/weather.controller.test.ts`, or `np
Nest owns everything. Every domain is a DI module under `src/nest/<domain>/` (`controller` + `service` + `module`, registered in `src/nest/app.module.ts`); business logic lives in the injectable `.service.ts`, pure halves in `.helpers.ts`, SQL in a `.repository.ts` where one exists. There is no legacy service layer — ESLint errors on any `services/` import. **`weather/` is the reference module shape; `collections.controller.ts` is the reference for body validation and enumeration-safe access checks.**
- **`src/bootstrap.ts` — `buildApp()`** is the single builder for production and the test harness. Everything on the raw Express instance (global middleware, uploads, discovery metadata, statics, the named `jsonParser`/`urlencodedParser` wrappers) is registered **before `app.init()`**, because Nest throws `NotFoundException` on unmatched routes and nothing after init is reachable. The wrappers exempt `/mcp` so the MCP SDK reads the raw stream — **never `@Body()` a `/mcp` route**. Don't add routes to that pre-init shell; it bypasses every global guard, interceptor and filter.
- **Realtime**: `/ws` is a Nest gateway in `src/nest/realtime/`. Inject `RealtimeService`; `src/websocket.ts` is a re-export stub for old call sites. Trip mutations forward `X-Socket-Id` so the originating client doesn't echo its own change.
- **Realtime**: `/ws` is a Nest gateway in `src/nest/realtime/`. Inject `RealtimeService`; tests hand in a `FakeRealtimeService` (`tests/helpers/fake-realtime.ts`) or spy on the container's instance instead of mocking the transport module. Trip mutations forward `X-Socket-Id` so the originating client doesn't echo its own change.
- **MCP**: `src/nest-mcp/` is the decorator/registry layer (`@McpController`/`@Tool`/`@Resource`/`@Prompt`; see its `README.md`); tools live in `<domain>.mcp.ts` files beside each module; the HTTP transport is `src/nest/mcp-transport/`; `src/mcp/` holds process-wide state only (scopes, session manager, boot policy). Every tool invocation writes an audit row through the registry's `onInvoke` seam. The demo-mode write block is the registry's `toolGate` (`trekDemoToolGate`): every tool not annotated read-only refuses a demo account there, so a tool never checks demo mode itself (ESLint refuses it in `*.mcp.ts`). MCP tools are adapters over the same services and permission checks as REST — a change on one side lands on the other in the same change.
- **Scheduling**: `src/nest/scheduling/` (`CronRegistrarService`) is the one way to schedule a cron. Jobs are `*.job.ts` providers in their owning domain. The registrar refuses to schedule under `NODE_ENV=test` (an integration test pins it). **Never use a `@Cron` decorator**: it bypasses that gate. Every tick first takes the job's row in `scheduler_leases` (`SchedulerLeasesRepository.acquire`, one conditional upsert that decides the race), renews it while it runs and keeps it for a short settle window afterwards, so two processes on one database never run the same tick; a job needs nothing of its own for this. Claims on individual rows (the reminder jobs' `claimReminder`) stay where they are: the lease decides who runs a tick, the claim what a tick may send.
- **Plugins** (`src/nest/plugins/`): the child-process runtime (`supervisor/`, `runtime/plugin-host-entry.ts`, `protocol/envelope.ts`) is **intentionally not Nest**; it is the security boundary. `host/rpc-host.ts` registers an RPC method at spawn only if the plugin holds the unlocking permission (registration = authorization). Install-time gates live in `install/`; the manifest's `trek` version range fails closed, and `TREK_PLUGINS_IGNORE_TREK_RANGE` downgrades that to a warning. The protocol tables in `protocol/envelope.ts` (+ `src/plugin-event-sink.ts`) are **generated** into `plugin-sdk/` and `shared/`: run `gen:plugin-facts` after touching them and commit the output. What a plugin gets back is `protocol/output-contract.ts`: every wire method has an entry, and an entity result (`trips.getById`, `todos.list`, `vacay.mine`, ...) is cut down to its published fields in `rpc-host.ts` on the way out, together with every child row it carries whose field is listed in `PLUGIN_ENTITY_NESTED` (a day's `notes_items`, a reservation's `endpoints`). A new column on any whole-row table behind those entities, child tables included, fails `tests/unit/plugins/output-contract.schema.test.ts` until it is listed as published or withheld (the SDK's `PLUGIN_ENTITY_FIELDS` and `PLUGIN_ENTITY_NESTED` are generated from it); a method left as `readModel` must return a value built field by field from named columns. A plugin's own SQL (`host/plugin-data.service.ts`) is held to a row cap, and `query`, `tx` and an `exec` script to a time budget checked between rows and statements (`migrate` is deliberately not timed). `runtime/egress-policy.ts` is a hand-kept mirror guarded by a parity test in `plugin-sdk/`; change both sides together.
@@ -12,7 +12,7 @@ import type { TripEventSink, TripWriter } from '../common/trip-writer';
* The impure MCP tool guards that used to live as plain functions in
* src/mcp/tools/_shared.ts, reaching the db Proxy, a permissions.bridge
* instance (no longer exists — see auth.service.ts:116) and the
* src/websocket stub as module globals. The @McpController domain classes are
* websocket broadcast functions as module globals. The @McpController domain classes are
* ordinary Nest providers, so they inject this instead. The pure result
* helpers (noAccess/permissionDenied/adminRequired and the
* src/nest-mcp re-exports) stay in _shared.ts — they carry no dependencies.
@@ -50,8 +50,8 @@ export class McpToolGuardsService {
try {
// Legacy MCP call sites pass event names as plain strings; route through
// the facade's implementation signature (its typed overloads are for new
// call sites). Runtime is a pure pass-through to src/websocket's
// broadcast, so the 100+ per-file vi.mock stubs keep intercepting.
// call sites). Runtime is a pure pass-through to the transport's
// broadcast.
const send = this.realtime.broadcast as (
t: number | string, e: string, p: Record<string, unknown>, sid?: number | string, uid?: number,
) => void;
@@ -33,7 +33,7 @@ import type { JourneyPhotosRepository } from '../../db/repositories/JourneyPhoto
*
* File/test renamed to match (Plan 4 Task 8a — 3e Task 6 renamed only the
* class). The old path, `trek-photos.repository.ts`, was kept as a thin
* re-export stub (the same `src/websocket.ts` shape) while its ~20
* re-export stub while its ~20
* importers sat in sibling tasks' exclusively-owned windows on this shared
* branch; Plan 4 Task 4 repointed every one of them at this file directly
* and deleted the stub.
+1 -1
View File
@@ -47,7 +47,7 @@ const HEARTBEAT_INTERVAL = 30_000;
* What did NOT move is the socket registry. It stays one process-wide
* instance in ws-state.ts (the RoomRegistry port, installed in `roomsSlot` by
* RealtimeGatewayModule) because out-of-container code (the no-Nest test
* harnesses, the vi.mock'd src/websocket seam) must see the same rooms; see
* harnesses, hand-built RealtimeService instances) must see the same rooms; see
* the note there.
*
* The wire protocol is unchanged, down to the frame names. TrekWsAdapter maps
+15 -20
View File
@@ -5,31 +5,28 @@ import type {
TrekWsTripEventName,
TrekWsUserEventName,
} from '@trek/shared';
import { broadcast, broadcastToUser, getOnlineUserIds } from '../../websocket';
import { broadcast, broadcastToUser, getOnlineUserIds } from './ws-state';
/**
* Injectable facade over the websocket module singleton (roadmap Phase 0 item 3).
* Injectable facade over the socket registry in ws-state.ts.
*
* Migrated Nest services inject this instead of importing `broadcast`/
* `broadcastToUser` module globals. The actual transport (rooms, auth,
* heartbeat, rate limiting) stays in src/websocket.ts — a future
* @WebSocketGateway swap happens behind this facade, invisibly.
* Nest services inject this instead of importing the `broadcast`/
* `broadcastToUser` module functions. The transport itself (rooms, the
* handshake, the wire protocol) is RealtimeGateway, TrekWsAdapter and
* ws-state.ts next door.
*
* Both methods delegate to the live module exports *at call time* (same
* pattern as DatabaseService.canAccessTrip): tests vi.mock src/websocket
* per file, and the stubs must keep flowing through here. Never capture
* the functions at construction, and never dereference an export the
* method wasn't asked for — many test mocks provide `broadcast` only.
* Tests observe broadcasts through DI: a FakeRealtimeService
* (tests/helpers/fake-realtime.ts) takes this class's place, so nothing has to
* mock the transport module.
*
* The service is deliberately dependency-free so out-of-container code (the
* auth.bridge graph, the no-Nest test harnesses) can construct it with a bare
* `new RealtimeService()`.
* no-Nest test harnesses) can construct it with a bare `new RealtimeService()`.
*/
@Injectable()
export class RealtimeService {
/**
* Broadcast an event to all sockets in a trip room. Mirrors the
* websocket.ts signature exactly: `excludeSid` is the X-Socket-Id
* ws-state.ts signature exactly: `excludeSid` is the X-Socket-Id
* echo-suppression contract (the originating client is skipped);
* `onlyUserId` narrows delivery to one user's sockets (#858 private
* packing items).
@@ -40,9 +37,8 @@ export class RealtimeService {
* ships silence. The `plugin:` overload is the deliberate escape hatch for
* the host's force-namespaced `plugin:<id>:<event>` broadcasts.
*
* Rest-spread keeps the caller's exact argument arity — dozens of test
* mocks assert the precise call shape (3, 4 or 5 args), so the facade
* must not pad omitted optionals with explicit `undefined`s.
* Rest-spread keeps the caller's exact argument arity: the facade must not
* pad omitted optionals with explicit `undefined`s.
*/
broadcast<E extends TrekWsTripEventName>(
tripId: number | string,
@@ -94,12 +90,11 @@ export class RealtimeService {
/**
* Which users currently hold an open socket.
*
* AdminService read this through a lazy `require('../../websocket')` inside a
* AdminService read this through a lazy `require()` of the transport inside a
* try/catch, which is what the facade exists to avoid. Deliberately NOT
* guarded here: a guard on the facade would swallow the failure for every
* future caller, and the one caller that wants a degraded answer rather than
* an error keeps its own catch. Delegates at call time, like its two
* siblings, so per-file vi.mock stubs keep flowing through.
* an error keeps its own catch.
*/
getOnlineUserIds(): Set<number> {
return getOnlineUserIds();
+4 -5
View File
@@ -9,11 +9,10 @@ import { ProcessStoreSlot } from '../common/process-store-slot';
*
* MODULE state, not provider state, and that is load-bearing: the rooms live
* in the registry `roomsSlot` holds, which the container's provider replaces
* when RealtimeGatewayModule is built and nothing else does. The no-Nest test
* harnesses hand-build RealtimeService instances (mcp-test-controllers.ts and
* ~60 unit suites), and the 115 vi.mock('src/websocket') seams assert on these
* exact exports. If the rooms lived on a provider instance, an out-of-container
* broadcast would go to an empty map: no error, no log, just a client that
* when RealtimeGatewayModule is built and nothing else does. Hand-built
* RealtimeService instances (the no-Nest test harnesses, out-of-container
* consumers) deliver through these same functions. If the rooms lived on a
* provider instance, an out-of-container broadcast would go to an empty map: no error, no log, just a client that
* stops updating. Same reasoning as the geo throttle cursor and the
* permissions cache.
*
-14
View File
@@ -1,14 +0,0 @@
/**
* The transport's public surface, for code outside the Nest container.
*
* The implementation moved to nest/realtime: the connection lifecycle and the
* handshake are RealtimeGateway, the wire protocol is TrekWsAdapter, and the
* socket registry is ws-state.ts. This file stays because it is the import path
* the rest of the tree knows, including the more than 160 test files that `vi.mock` it to
* assert on broadcasts. Repointing them all would be a large diff that proves
* nothing, and this re-export keeps every one of those mocks working.
*
* `setupWebSocket` is gone: the server is created by the adapter now, when Nest
* initialises the gateway, rather than by index.ts after listen().
*/
export { broadcast, broadcastToUser, getOnlineUserIds } from './nest/realtime/ws-state';
+9 -4
View File
@@ -10,8 +10,8 @@
* to resolve a hidden inverse relation this DDL never created — the same
* class of failure `days.e2e.test.ts`'s own conversion (Task 2) fixed for
* that file. AssignmentsService runs its real SQL (DI-injected, no service
* mock); journeyService, the permission check and the WebSocket broadcast
* stay mocked. Every `it(...)` body below is unchanged from before this
* mock); journeyService and the permission check stay mocked, and the
* broadcast goes to a FakeRealtimeService. Every `it(...)` body below is unchanged from before this
* conversion — only the DB bootstrap changed.
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach, vi, type MockInstance } from 'vitest';
@@ -19,6 +19,7 @@ import request from 'supertest';
import cookieParser from 'cookie-parser';
import type { Server } from 'http';
import { RealtimeModule } from '../../src/nest/realtime/realtime.module';
import { RealtimeService } from '../../src/nest/realtime/realtime.service';
import { Test } from '@nestjs/testing';
import { sessionCookie } from './harness';
@@ -26,8 +27,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
const { broadcast } = vi.hoisted(() => ({ broadcast: vi.fn() }));
vi.mock('../../src/websocket', () => ({ broadcast }));
import { db } from '../../src/db/database';
@@ -55,6 +54,10 @@ import { Days } from '../../src/db/entities/Days.entity';
import { Places } from '../../src/db/entities/Places.entity';
import { Tours } from '../../src/db/entities/Tours.entity';
import { Trips } from '../../src/db/entities/Trips.entity';
import { FakeRealtimeService } from '../helpers/fake-realtime';
const realtime = new FakeRealtimeService();
const broadcast = realtime.broadcastMock;
let orm: TestOrm;
@@ -78,6 +81,8 @@ describe('Assignments e2e (real auth guard + temp SQLite)', () => {
async function build() {
const moduleRef = await Test.createTestingModule({ imports: [await TestUnitOfWorkModule.forRoot(db), await createTestMikroOrmModule(db), RealtimeModule, AssignmentsModule] })
.overrideProvider(RealtimeService)
.useValue(realtime)
.overrideProvider(JourneyDomainService)
.useValue({ reconcileTripSkeletons })
.compile();
-2
View File
@@ -27,8 +27,6 @@ vi.mock('../../src/db/database', async () => {
};
});
vi.mock('../../src/websocket', () => ({ broadcastToUser: vi.fn(), broadcast: vi.fn() }));
import { db } from '../../src/db/database';
import { createUser, createTrip } from '../helpers/factories';
import { AtlasModule } from '../../src/nest/atlas/atlas.module';
-1
View File
@@ -27,7 +27,6 @@ vi.mock('../../src/db/database', async () => {
};
});
vi.mock('../../src/websocket', () => ({ broadcastToUser: vi.fn(), broadcast: vi.fn() }));
// The audit domain is DI-native: writeAudit runs for real against the temp
// db's audit_log table; only the file logger is silenced.
vi.mock('../../src/nest/audit/audit-log.logger', () => ({ LOG_LEVEL: 'error', logInfo: vi.fn(), logDebug: vi.fn(), logError: vi.fn(), logWarn: vi.fn() }));
@@ -35,7 +35,6 @@ vi.mock('../../src/db/database', async () => {
reinitialize: () => {},
};
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
vi.mock('../../src/utils/ssrfGuard', async (orig) => ({ ...(await orig<Record<string, unknown>>()), safeFetchLlm }));
import { db } from '../../src/db/database';
+2 -3
View File
@@ -2,8 +2,8 @@
* Budget module e2e — exercises the migrated /api/trips/:tripId/budget endpoints
* through the real JwtAuthGuard against a temp SQLite db carrying the full real,
* migrated schema (createSnapshotTestDb), so the folded BudgetService runs its
* real SQL. Only the db singleton (trip access) and the WebSocket broadcast are
* mocked; the permission check is a spy on the container's PermissionsService.
* real SQL. Only the db singleton (trip access) is mocked, and broadcasts reach
* the real transport, which has no sockets here; the permission check is a spy on the container's PermissionsService.
* ReservationsModule is mounted beside it because an expense can be linked to a
* booking (#2084), and deleting that booking has to take the expense with it.
*/
@@ -21,7 +21,6 @@ vi.mock('../../src/db/database', async () => {
// Trip access reads through TripsRepository now; the module only hands out the handle.
return { db, closeDb: () => {}, reinitialize: () => {} };
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
import { PermissionsService } from '../../src/nest/permissions/permissions.service';
+2 -3
View File
@@ -2,8 +2,8 @@
* Collab module e2e — exercises the migrated /api/trips/:tripId/collab endpoints
* through the real JwtAuthGuard against a real migrated temp SQLite db
* (createSnapshotTestDb(), no hand-rolled CREATE TABLEs), running CollabService's
* real queries (DI-injected, no service mock). The permission check, the
* WebSocket broadcast and the chat/note notification are mocked; this focuses
* real queries (DI-injected, no service mock). The permission check and
* the chat/note notification are mocked; this focuses
* on auth, trip-access 404, permission 403, the create-201 status codes, the
* vote/react 200 overrides and the persisted rows. Rows are seeded and read
* through the factories in tests/helpers/factories.
@@ -20,7 +20,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
import { PermissionsService } from '../../src/nest/permissions/permissions.service';
+1 -2
View File
@@ -2,7 +2,7 @@
* Collections e2e — drives /api/addons/collections through the REAL JwtAuthGuard
* AND the real DI-native CollectionsService (DatabaseModule + RealtimeModule +
* CollectionsModule) against a temp SQLite db (full schema). Only the addon
* flag, websocket and notification send are mocked. Covers: the addon gate
* flag and notification send are mocked. Covers: the addon gate
* (404 before auth), auth, CRUD happy paths, invite/accept/decline, copy-to-trip,
* cross-user 404s, the non-owner 403 on /:id/available-users (no enumeration), and
* a list out as GPX and back in through the reader and the import (#2301).
@@ -27,7 +27,6 @@ vi.mock('../../src/db/database', async () => {
});
const { isAddonEnabled } = vi.hoisted(() => ({ isAddonEnabled: vi.fn(() => true) }));
vi.mock('../../src/websocket', () => ({ broadcastToUser: vi.fn(), broadcast: vi.fn() }));
import { db } from '../../src/db/database';
import { createUser, createTrip, createCategory, createDay, createPlace, createDayAssignment } from '../helpers/factories';
-1
View File
@@ -37,7 +37,6 @@ vi.mock('../../src/db/database', async () => {
});
const { isAddonEnabled } = vi.hoisted(() => ({ isAddonEnabled: vi.fn(() => true) }));
vi.mock('../../src/websocket', () => ({ broadcastToUser: vi.fn(), broadcast: vi.fn() }));
import { db } from '../../src/db/database';
import { createUser } from '../helpers/factories';
+1 -2
View File
@@ -15,7 +15,7 @@
* `canAccessTrip` mock (the legacy override this file used to export from
* `db/database.ts` — deleted there since Plan 3c Task 0b; a stale mock here
* would silently do nothing, not fail loudly, which is worse than removing
* it). Only the permission check and the WebSocket broadcast stay mocked.
* it). Only the permission check stays mocked.
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach, vi, type MockInstance } from 'vitest';
import request from 'supertest';
@@ -29,7 +29,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
import { db } from '../../src/db/database';
import { PermissionsService } from '../../src/nest/permissions/permissions.service';
-1
View File
@@ -37,7 +37,6 @@ vi.mock('../../src/db/database', async () => {
});
const { isAddonEnabled } = vi.hoisted(() => ({ isAddonEnabled: vi.fn(() => true) }));
vi.mock('../../src/websocket', () => ({ broadcastToUser: vi.fn(), broadcast: vi.fn() }));
/**
* The SSRF guard resolves DNS, and `paperless.example.com` does not exist. In
-1
View File
@@ -29,7 +29,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
import { db } from '../../src/db/database';
import { resetTestDb } from '../helpers/test-db';
+2 -3
View File
@@ -2,8 +2,8 @@
* Files + photos e2e — exercises the migrated /api/trips/:tripId/files and
* /api/photos endpoints through the real JwtAuthGuard against a temp SQLite db.
* FilesService is DI-native (no service mock): the file rows live in the temp
* db and the SQL runs for real; only canAccessTrip, the permission check, the
* photo services and the broadcast are mocked. Focuses on auth (incl. the
* db and the SQL runs for real; only canAccessTrip, the permission check and the
* photo services are mocked. Focuses on auth (incl. the
* unguarded download's own token auth), trip-access 404, permission 403, the
* photo id/access guards and status codes.
*/
@@ -56,7 +56,6 @@ const { canAccessTrip } = vi.hoisted(() => ({ canAccessTrip: vi.fn() }));
vi.mock('../../src/db/database', () => ({
db, canAccessTrip, getPlaceWithTags: vi.fn(), closeDb: () => {}, reinitialize: () => {},
}));
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
vi.mock('../../src/nest/common/demo', () => ({ isDemoEmail: vi.fn(() => false) }));
import { PermissionsService } from '../../src/nest/permissions/permissions.service';
-1
View File
@@ -27,7 +27,6 @@ vi.mock('../../src/db/database', async () => {
isOwner: () => false,
};
});
vi.mock('../../src/websocket', () => ({ broadcastToUser: vi.fn(), broadcast: vi.fn() }));
vi.mock('../../src/nest/audit/audit-log.logger', () => ({ LOG_LEVEL: 'error', logInfo: vi.fn(), logDebug: vi.fn(), logError: vi.fn(), logWarn: vi.fn() }));
vi.mock('../../src/app-config', async (importOriginal) => {
const actual = await importOriginal<typeof import('../../src/app-config')>();
-1
View File
@@ -30,7 +30,6 @@ const { db } = vi.hoisted(() => {
});
vi.mock('../../src/db/database', () => ({ db, canAccessTrip: vi.fn(), closeDb: () => {}, reinitialize: () => {} }));
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
// Provider services — fully mocked. fail/success/canAccessUserPhoto from the
// helper module are kept real except canAccessUserPhoto which we override.
-1
View File
@@ -40,7 +40,6 @@ vi.mock('../../src/db/database', () => ({
canAccessTrip: () => undefined,
isOwner: () => false,
}));
vi.mock('../../src/websocket', () => ({ broadcastToUser: vi.fn(), broadcast: vi.fn() }));
vi.mock('../../src/nest/audit/audit-log.logger', () => ({ LOG_LEVEL: 'error', logInfo: vi.fn(), logDebug: vi.fn(), logError: vi.fn(), logWarn: vi.fn() }));
const toggles = { oidc_login: true };
+2 -3
View File
@@ -3,8 +3,8 @@
* endpoints through the real JwtAuthGuard against a migrated temp SQLite db.
* PackingService runs its real SQL via DatabaseModule (the DATABASE_CONNECTION
* factory picks up the mocked db singleton); trip access resolves through the
* real repositories over the temp db. Only the permission check, the
* WebSocket broadcast and the notification sender stay mocked.
* real repositories over the temp db. Only the permission check and
* the notification sender stay mocked.
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach, vi, type MockInstance } from 'vitest';
import request from 'supertest';
@@ -17,7 +17,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
import { PermissionsService } from '../../src/nest/permissions/permissions.service';
+9 -4
View File
@@ -10,8 +10,8 @@
* uses — the same class of failure `days.e2e.test.ts` (Task 2) and
* `assignments.e2e.test.ts` (Task 3) already fixed for their own files.
* PlacesService runs its real SQL (DI-injected, no service mock);
* journeyService, the permission check and the WebSocket broadcast stay
* mocked. Every `it(...)` body below is unchanged from before this
* journeyService and the permission check stay mocked, and the broadcast
* goes to a FakeRealtimeService. Every `it(...)` body below is unchanged from before this
* conversion — only the DB bootstrap changed.
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach, vi, type MockInstance } from 'vitest';
@@ -19,6 +19,7 @@ import request from 'supertest';
import cookieParser from 'cookie-parser';
import type { Server } from 'http';
import { RealtimeModule } from '../../src/nest/realtime/realtime.module';
import { RealtimeService } from '../../src/nest/realtime/realtime.service';
import { Test } from '@nestjs/testing';
import { sessionCookie } from './harness';
@@ -26,8 +27,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
const { broadcast } = vi.hoisted(() => ({ broadcast: vi.fn() }));
vi.mock('../../src/websocket', () => ({ broadcast }));
import { db } from '../../src/db/database';
@@ -60,6 +59,10 @@ import { PlaceRatings } from '../../src/db/entities/PlaceRatings.entity';
import { Places } from '../../src/db/entities/Places.entity';
import { Tours } from '../../src/db/entities/Tours.entity';
import { Trips } from '../../src/db/entities/Trips.entity';
import { FakeRealtimeService } from '../helpers/fake-realtime';
const realtime = new FakeRealtimeService();
const broadcast = realtime.broadcastMock;
let orm: TestOrm;
@@ -69,6 +72,8 @@ describe('Places e2e (real auth guard + temp SQLite)', () => {
async function build() {
const moduleRef = await Test.createTestingModule({ imports: [await TestUnitOfWorkModule.forRoot(db), await createTestMikroOrmModule(db), RealtimeModule, PlacesModule] })
.overrideProvider(RealtimeService)
.useValue(realtime)
.overrideProvider(JourneyDomainService)
.useValue({ onPlaceCreated, onPlaceUpdated, onPlaceDeleted })
.compile();
-1
View File
@@ -37,7 +37,6 @@ vi.mock('../../src/db/database', async () => {
reinitialize: () => {},
};
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
vi.mock('../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled: () => pluginsEnabled.value }));
import { db } from '../../src/db/database';
@@ -38,7 +38,6 @@ vi.mock('../../src/db/database', async () => {
reinitialize: () => {},
};
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
vi.mock('../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled: () => pluginsEnabled.value }));
import { db } from '../../src/db/database';
@@ -23,7 +23,6 @@ vi.mock('../../src/db/database', async () => {
const tmp = createSnapshotTestDb();
return { db: tmp, canAccessTrip: () => undefined, isOwner: () => false, getPlaceWithTags: () => null, closeDb: () => {}, reinitialize: () => {} };
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
vi.mock('../../src/utils/ssrfGuard', async (orig) => ({ ...(await orig<Record<string, unknown>>()), safeFetchLlm }));
import { db } from '../../src/db/database';
+2 -3
View File
@@ -3,8 +3,8 @@
* through the real JwtAuthGuard against a temp SQLite db. Reservation SQL runs
* for real (ReservationsService is DI-native, no mock — the temp db carries the
* full, real migrated schema via createSnapshotTestDb), and so does the accommodation
* SQL (the injected DaysService is DI-native too); the budget service, the
* permission check and the WebSocket broadcast stay mocked.
* SQL (the injected DaysService is DI-native too); the budget service and the
* permission check stay mocked.
*/
import { TrekExceptionFilter } from '../../src/nest/common/trek-exception.filter';
import { ZodValidationPipe } from '../../src/nest/common/zod-validation.pipe';
@@ -36,7 +36,6 @@ vi.mock('../../src/db/database', async () => {
reinitialize: () => {},
};
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
const { notificationSend } = vi.hoisted(() => ({ notificationSend: vi.fn().mockResolvedValue(undefined) }));
import { PermissionsService } from '../../src/nest/permissions/permissions.service';
+1 -2
View File
@@ -13,7 +13,7 @@ import { ChargingService } from '../../src/nest/roadtrip/charging.service';
* fixed for that file). Every service in this module now runs its real SQL
* through repositories (DI-injected, no service mock) against the real
* request-scoped `EntityManager` `createTestMikroOrmModule` wires in; only
* the permission check and the WebSocket broadcast stay mocked.
* the permission check stays mocked.
*
* The unit test next door pins the handler bodies. What only a booted container
* can show is the thing the controller's own comment calls load-bearing: that
@@ -47,7 +47,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
import { db } from '../../src/db/database';
import { Addons } from '../../src/db/entities/Addons.entity';
@@ -20,7 +20,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
-1
View File
@@ -34,7 +34,6 @@ vi.mock('../../src/db/database', async () => {
};
});
vi.mock('../../src/websocket', () => ({ broadcastToUser: vi.fn(), broadcast: vi.fn() }));
vi.mock('../../src/nest/audit/audit-log.logger', () => ({ LOG_LEVEL: 'error', logInfo: vi.fn(), logDebug: vi.fn(), logError: vi.fn(), logWarn: vi.fn() }));
import { MailerService } from '../../src/nest/notifications/mailer/mailer.service';
+2 -3
View File
@@ -3,8 +3,8 @@
* through the real JwtAuthGuard against a migrated temp SQLite db
* (createSnapshotTestDb()). TodoService and the trip access check run their
* real queries through the repositories; rows are seeded and read through the
* factories in tests/helpers/factories. Only the permission check and the
* WebSocket broadcast stay mocked.
* factories in tests/helpers/factories. Only the permission check stays
* mocked.
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach, vi, type MockInstance } from 'vitest';
import request from 'supertest';
@@ -17,7 +17,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
import { db } from '../../src/db/database';
import { PermissionsService } from '../../src/nest/permissions/permissions.service';
+9 -4
View File
@@ -3,8 +3,8 @@
* JwtAuthGuard, AddonGuard and TripAccessGuard against a real migrated and
* seeded temp SQLite db (createSnapshotTestDb()). ToursService, PlacesService,
* AssignmentsService and TripsService run their real SQL; only the permission
* check (a container spy), the journey hooks and the WebSocket broadcast are
* stubbed.
* check (a container spy) and the journey hooks are stubbed, and the broadcast
* goes to a FakeRealtimeService.
*
* Fixtures: user 1 owns trips 5 and 6, user 2 is a member of trip 5 whom the
* permission spy denies 'place_edit', user 3 has no access to either trip.
@@ -16,6 +16,7 @@ import request from 'supertest';
import cookieParser from 'cookie-parser';
import type { Server } from 'http';
import { RealtimeModule } from '../../src/nest/realtime/realtime.module';
import { RealtimeService } from '../../src/nest/realtime/realtime.service';
import { Test } from '@nestjs/testing';
import { sessionCookie } from './harness';
@@ -23,8 +24,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
const { broadcast } = vi.hoisted(() => ({ broadcast: vi.fn() }));
vi.mock('../../src/websocket', () => ({ broadcast }));
vi.mock('../../src/nest/audit/audit-log.logger', () => ({ LOG_LEVEL: 'error', logInfo: vi.fn(), logDebug: vi.fn(), logError: vi.fn(), logWarn: vi.fn() }));
import { db } from '../../src/db/database';
@@ -59,6 +58,10 @@ import { Places } from '../../src/db/entities/Places.entity';
import { TourWaypoints } from '../../src/db/entities/TourWaypoints.entity';
import { Tours } from '../../src/db/entities/Tours.entity';
import { Trips } from '../../src/db/entities/Trips.entity';
import { FakeRealtimeService } from '../helpers/fake-realtime';
const realtime = new FakeRealtimeService();
const broadcast = realtime.broadcastMock;
let orm: TestOrm;
@@ -110,6 +113,8 @@ describe('Tours e2e (real guards + temp SQLite)', () => {
TripsModule,
],
})
.overrideProvider(RealtimeService)
.useValue(realtime)
.overrideProvider(JourneyDomainService)
.useValue(journeyHooks)
.compile();
+1 -2
View File
@@ -18,7 +18,7 @@
* not fail loudly, which is worse than removing it — the same reasoning
* `days.e2e.test.ts`'s identical comment gives). Only the permission check,
* `BudgetService` (the budget fold's own container spy, unrelated to this
* conversion) and the WebSocket broadcast stay mocked.
* conversion) stay mocked.
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach, vi, type MockInstance } from 'vitest';
import request from 'supertest';
@@ -33,7 +33,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
// The audit domain is DI-native now: writeAudit runs for real against the temp
// db's audit_log table; only the file logger is silenced.
vi.mock('../../src/nest/audit/audit-log.logger', () => ({ LOG_LEVEL: 'error', logInfo: vi.fn(), logDebug: vi.fn(), logError: vi.fn(), logWarn: vi.fn() }));
+10 -6
View File
@@ -1,8 +1,8 @@
/**
* Vacay module e2e — exercises the migrated /api/addons/vacay endpoints through
* the real JwtAuthGuard against a migrated temp SQLite db. DI-native: VacayService runs
* its real SQL over the temp db (no legacy service mock); only the websocket
* and notification side channels stay mocked. Focuses on auth, status codes
* its real SQL over the temp db (no legacy service mock); the notification side
* channel stays mocked and the broadcast goes to a FakeRealtimeService. Focuses on auth, status codes
* (POSTs stay 200), the Zod-pipe 400 envelope and a 403 body.
*/
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
@@ -16,7 +16,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcastToUser: vi.fn() }));
import { db } from '../../src/db/database';
import { VacayEntries } from '../../src/db/entities/VacayEntries.entity';
@@ -25,20 +24,25 @@ import { RealtimeModule } from '../../src/nest/realtime/realtime.module';
import { VacayModule } from '../../src/nest/vacay/vacay.module';
import { TrekExceptionFilter } from '../../src/nest/common/trek-exception.filter';
import { ZodValidationPipe } from '../../src/nest/common/zod-validation.pipe';
import { broadcastToUser } from '../../src/websocket';
import { RealtimeService } from '../../src/nest/realtime/realtime.service';
import { FakeRealtimeService } from '../helpers/fake-realtime';
import { TestUnitOfWorkModule } from '../helpers/test-uow';
import { createTestMikroOrmModule, createTestOrm, type TestOrm } from '../helpers/test-orm';
import { makeUser } from '../helpers/factories/users';
import { findRow } from '../helpers/factories/rows';
let orm: TestOrm;
const realtime = new FakeRealtimeService();
describe('Vacay e2e (real auth guard + migrated temp SQLite)', () => {
let server: Server;
let app: Awaited<ReturnType<typeof build>>;
async function build() {
const moduleRef = await Test.createTestingModule({ imports: [await TestUnitOfWorkModule.forRoot(db), await createTestMikroOrmModule(db), RealtimeModule, VacayModule] }).compile();
const moduleRef = await Test.createTestingModule({ imports: [await TestUnitOfWorkModule.forRoot(db), await createTestMikroOrmModule(db), RealtimeModule, VacayModule] })
.overrideProvider(RealtimeService)
.useValue(realtime)
.compile();
const nest = moduleRef.createNestApplication();
nest.use(cookieParser());
nest.useGlobalFilters(new TrekExceptionFilter());
@@ -81,7 +85,7 @@ describe('Vacay e2e (real auth guard + migrated temp SQLite)', () => {
expect(res.status).toBe(200);
expect(res.body).toEqual({ action: 'added', fraction: 1, kind: 'vacation' });
expect(await findRow(orm, VacayEntries, { user: 1, date: '2026-07-01' })).not.toBeNull();
expect(vi.mocked(broadcastToUser)).toHaveBeenCalledWith(1, { type: 'vacay:update' }, 'sock-7');
expect(realtime.broadcastToUserMock).toHaveBeenCalledWith(1, { type: 'vacay:update' }, 'sock-7');
});
it('400 from the Zod pipe on entries/toggle without a date', async () => {
@@ -36,8 +36,11 @@ import { TripAccessService } from '../../src/nest/trip-membership/trip-access.se
* instance). Kept as two functions for caller-shape continuity, though they
* are now identical.
*/
export async function makeAccommodationsService(conn: Database): Promise<AccommodationsService> {
return accommodationsOver(conn);
export async function makeAccommodationsService(
conn: Database,
realtime: RealtimeService = new RealtimeService(),
): Promise<AccommodationsService> {
return accommodationsOver(conn, realtime);
}
/**
@@ -47,9 +50,11 @@ export async function makeAccommodationsService(conn: Database): Promise<Accommo
* the same stop a night entered under Days does, and every suite that builds
* that service by hand needs one to hand it.
*/
export async function accommodationsOver(conn: Database): Promise<AccommodationsService> {
export async function accommodationsOver(
conn: Database,
realtime: RealtimeService = new RealtimeService(),
): Promise<AccommodationsService> {
const permissions = new PermissionsService(await createTestAppSettingsRepo(conn), await createTestUnitOfWork(conn));
const realtime = new RealtimeService();
const t = await sharedTestOrm(conn);
const assignments = new AssignmentsService(
new TripAccessService(await createTestTripsRepo(conn)), permissions, realtime,
+88
View File
@@ -0,0 +1,88 @@
/**
* A RealtimeService that records instead of delivering.
*
* Suites observe broadcasts through DI rather than by mocking a module path:
* a hand-built service gets `new FakeRealtimeService()` where it took a
* RealtimeService, and a booted Nest app gets its container instance swapped
* (`.overrideProvider(RealtimeService).useValue(fake)`) or spied on with
* `spyOnRealtime(app)`. Each method records into a `vi.fn`, so the assertions read the
* same as they did against the old module mock:
*
* const realtime = new FakeRealtimeService();
* const service = new TodoService(permissions, realtime, ...);
* expect(realtime.broadcastMock).toHaveBeenCalledWith(tripId, 'todo:created', ...);
*
* The fake receives exactly the arguments the service handed RealtimeService
* (the facade passes them through without padding), so call-shape assertions
* keep their meaning.
*/
import { RealtimeService } from '../../src/nest/realtime/realtime.service';
import type { INestApplicationContext } from '@nestjs/common';
import { vi, type Mock } from 'vitest';
type BroadcastArgs = [
tripId: number | string,
eventType: string,
payload: Record<string, unknown>,
excludeSid?: number | string,
onlyUserId?: number,
];
type BroadcastToUserArgs = [userId: number, payload: Record<string, unknown>, excludeSid?: number | string];
export class FakeRealtimeService extends RealtimeService {
readonly broadcastMock: Mock<(...args: BroadcastArgs) => void> = vi.fn();
readonly broadcastToUserMock: Mock<(...args: BroadcastToUserArgs) => void> = vi.fn();
/** Nobody is online unless a case says otherwise. */
readonly getOnlineUserIdsMock: Mock<() => Set<number>> = vi.fn(() => new Set<number>());
constructor() {
super();
// Some callers take the method off the instance before calling it
// (McpToolGuardsService.safeBroadcast does), as the real facade allows.
this.broadcast = this.broadcast.bind(this);
this.broadcastToUser = this.broadcastToUser.bind(this);
this.getOnlineUserIds = this.getOnlineUserIds.bind(this);
}
override broadcast(...args: BroadcastArgs): void {
this.broadcastMock(...args);
}
override broadcastToUser(...args: BroadcastToUserArgs): void {
this.broadcastToUserMock(...args);
}
override getOnlineUserIds(): Set<number> {
return this.getOnlineUserIdsMock();
}
/** Forget every recorded call (and any per-case return value). */
reset(): void {
this.broadcastMock.mockReset();
this.broadcastToUserMock.mockReset();
this.getOnlineUserIdsMock.mockReset();
this.getOnlineUserIdsMock.mockImplementation(() => new Set<number>());
}
}
export interface RealtimeSpies {
broadcast: Mock<(...args: BroadcastArgs) => void>;
broadcastToUser: Mock<(...args: BroadcastToUserArgs) => void>;
}
/**
* Spy on the RealtimeService a booted app resolved, for suites that build the
* whole app (buildApp) and cannot override a provider. The spies do not
* deliver: a test socket would otherwise receive every broadcast the case
* caused, which is what the old module mock prevented too.
*/
export function spyOnRealtime(app: INestApplicationContext): RealtimeSpies {
const realtime = app.get(RealtimeService);
const broadcast = vi.spyOn(realtime, 'broadcast').mockImplementation(() => undefined);
const broadcastToUser = vi.spyOn(realtime, 'broadcastToUser').mockImplementation(() => undefined);
return {
broadcast: broadcast as unknown as RealtimeSpies['broadcast'],
broadcastToUser: broadcastToUser as unknown as RealtimeSpies['broadcastToUser'],
};
}
+9 -2
View File
@@ -16,6 +16,7 @@ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory';
import { registerTools } from '../../src/mcp/tools';
import type { McpAttachOptions } from '../../src/nest-mcp';
import { createMcpTestRegistry } from './mcp-test-controllers';
import type { RealtimeService } from '../../src/nest/realtime/realtime.service';
export interface McpHarness {
client: Client;
@@ -47,10 +48,16 @@ export interface McpHarnessOptions {
* every suite that does not register one.
*/
dynamicTools?: McpAttachOptions['dynamicTools'];
/**
* The RealtimeService every tool broadcasts through. A suite that asserts on
* broadcasts hands in a FakeRealtimeService (tests/helpers/fake-realtime.ts);
* left out, the tools get a real one, whose transport has no sockets here.
*/
realtime?: RealtimeService;
}
export async function createMcpHarness(options: McpHarnessOptions): Promise<McpHarness> {
const { userId, withTools = true, scopes = null, isStaticToken = false, getDeprecationNotice, dynamicTools } = options;
const { userId, withTools = true, scopes = null, isStaticToken = false, getDeprecationNotice, dynamicTools, realtime } = options;
const server = new McpServer({ name: 'trek-test', version: '1.0.0' });
@@ -59,7 +66,7 @@ export async function createMcpHarness(options: McpHarnessOptions): Promise<McpH
// McpRegistryService to registerTools; the harness has no Nest app, so it
// builds the same registry by hand (see mcp-test-controllers.ts).
// registerTools' own ctx construction stays exercised.
await registerTools(await createMcpTestRegistry(), server, userId, scopes ?? null, isStaticToken, getDeprecationNotice, undefined, dynamicTools);
await registerTools(await createMcpTestRegistry(realtime), server, userId, scopes ?? null, isStaticToken, getDeprecationNotice, undefined, dynamicTools);
}
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
+1 -2
View File
@@ -194,7 +194,7 @@ import { TripAccessService } from '../../src/nest/trip-membership/trip-access.se
* fan-out. Constructing against the `db` Proxy keeps per-file vi.mock's of
* src/db/database flowing through (same pattern as todo.bridge.ts).
*/
export async function createMcpTestRegistry(): Promise<McpRegistry> {
export async function createMcpTestRegistry(realtimeService: RealtimeService = new RealtimeService()): Promise<McpRegistry> {
// Plan 3c Task 0b / Plan 4 Task 4: `mcpOrm` gives every repository below
// its `EntityManager` — `canAccessTrip`/`isOwner`/`rosterUserIds`/
// `getPlaceWithTags` resolve `TripsRepository`/`TripMembersRepository`/
@@ -207,7 +207,6 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
const auditLogRepo = mcpOrm.repo(AuditLog);
const usersRepo = mcpOrm.repo(Users);
const permissionsService = new PermissionsService(await createTestAppSettingsRepo(db), await createTestUnitOfWork(db));
const realtimeService = new RealtimeService();
// Plan 4 Task 1 constructor-ripple: the trip `user_id`/user `role` reads
// moved off `DatabaseService` onto `TripsRepository`/`UsersRepository`.
const guards = new McpToolGuardsService(mcpOrm.repo(Trips), usersRepo, permissionsService, realtimeService);
-1
View File
@@ -11,7 +11,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { MikroORM } from '@mikro-orm/core';
@@ -30,7 +30,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn(), getOnlineUserIds: vi.fn(() => []) }));
import { MikroORM } from '@mikro-orm/core';
import { SchedulerRegistry } from '@nestjs/schedule';
@@ -12,7 +12,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { resetTestDb } from '../helpers/test-db';
@@ -11,7 +11,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { resetTestDb } from '../helpers/test-db';
@@ -11,7 +11,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -87,7 +87,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn(), getOnlineUserIds: vi.fn(() => []) }));
import request from 'supertest';
import type { Application } from 'express';
@@ -28,7 +28,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
vi.mock('../../src/nest/atlas/atlas-geo', async (orig) => {
const actual = await orig<typeof import('../../src/nest/atlas/atlas-geo')>();
return {
-1
View File
@@ -12,7 +12,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
-1
View File
@@ -17,7 +17,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
-1
View File
@@ -15,7 +15,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
// Mock filesystem-dependent service functions to avoid real disk I/O in tests
vi.mock('../../src/nest/backup/backup.impl', async () => {
@@ -63,7 +63,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn(), getOnlineUserIds: vi.fn(() => []) }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -14,7 +14,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
// BOOT-007 boots with NODE_ENV=production, which opens the cron gate — keep the
// registrar inert so a bootstrap test never schedules real jobs or runs boot
// sweeps against the real uploads/data dirs. The gate itself is covered by
-1
View File
@@ -11,7 +11,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -11,7 +11,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { MikroORM } from '@mikro-orm/core';
import { db as testDb } from '../../src/db/database';
-1
View File
@@ -16,7 +16,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -17,7 +17,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn(), getOnlineUserIds: vi.fn(() => []) }));
import { db as testDb } from '../../src/db/database';
import { MikroORM } from '@mikro-orm/core';
@@ -11,7 +11,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
-1
View File
@@ -15,7 +15,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -44,7 +44,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn(), getOnlineUserIds: vi.fn(() => []) }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -48,7 +48,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn(), getOnlineUserIds: vi.fn(() => []) }));
import { randomUUID } from 'node:crypto';
import { MikroORM } from '@mikro-orm/core';
-1
View File
@@ -18,7 +18,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { MikroORM } from '@mikro-orm/core';
-1
View File
@@ -14,7 +14,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
-1
View File
@@ -14,7 +14,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
// Mock SSRF guard: block loopback and private IPs, allow external hostnames without DNS.
vi.mock('../../src/utils/ssrfGuard', async () => {
@@ -25,11 +25,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({
broadcast: vi.fn(),
broadcastToUser: vi.fn(),
getOnlineUserIds: vi.fn(() => []),
}));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
-5
View File
@@ -14,11 +14,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({
broadcast: vi.fn(),
broadcastToUser: vi.fn(),
getOnlineUserIds: vi.fn(() => []),
}));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -17,7 +17,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb2 } from '../../src/db/database';
import { MikroORM } from '@mikro-orm/core';
@@ -16,11 +16,6 @@ vi.mock('../../src/db/database', async () => {
const { openLegacyFixture } = await import('../helpers/legacy-fixture');
return buildDbMock(openLegacyFixture('legacy-v066'));
});
vi.mock('../../src/websocket', () => ({
broadcast: vi.fn(),
broadcastToUser: vi.fn(),
getOnlineUserIds: vi.fn(() => []),
}));
describeLegacyUpgrade(
{
@@ -13,11 +13,6 @@ vi.mock('../../src/db/database', async () => {
const { openLegacyFixture } = await import('../helpers/legacy-fixture');
return buildDbMock(openLegacyFixture('legacy-v082'));
});
vi.mock('../../src/websocket', () => ({
broadcast: vi.fn(),
broadcastToUser: vi.fn(),
getOnlineUserIds: vi.fn(() => []),
}));
describeLegacyUpgrade(
{ fixture: 'legacy-v082', version: 82, reseated: 1, nightOrderIndex: 1, id: 'LEGACYUP-082' },
@@ -13,11 +13,6 @@ vi.mock('../../src/db/database', async () => {
const { openLegacyFixture } = await import('../helpers/legacy-fixture');
return buildDbMock(openLegacyFixture('legacy-v205'));
});
vi.mock('../../src/websocket', () => ({
broadcast: vi.fn(),
broadcastToUser: vi.fn(),
getOnlineUserIds: vi.fn(() => []),
}));
describeLegacyUpgrade(
{ fixture: 'legacy-v205', version: 205, reseated: 1, nightOrderIndex: 1, id: 'LEGACYUP-205' },
@@ -13,11 +13,6 @@ vi.mock('../../src/db/database', async () => {
const { openLegacyFixture } = await import('../helpers/legacy-fixture');
return buildDbMock(openLegacyFixture('legacy-v242'));
});
vi.mock('../../src/websocket', () => ({
broadcast: vi.fn(),
broadcastToUser: vi.fn(),
getOnlineUserIds: vi.fn(() => []),
}));
describeLegacyUpgrade(
{ fixture: 'legacy-v242', version: 242, reseated: 0, nightOrderIndex: 3, id: 'LEGACYUP-242' },
@@ -19,11 +19,6 @@ vi.mock('../../src/db/database', async () => {
const { openLegacyFixture } = await import('../helpers/legacy-fixture');
return buildDbMock(openLegacyFixture('legacy-v244'));
});
vi.mock('../../src/websocket', () => ({
broadcast: vi.fn(),
broadcastToUser: vi.fn(),
getOnlineUserIds: vi.fn(() => []),
}));
// Off by default under NODE_ENV=test; this suite is the one that asks for it.
const previousSwitch = process.env.TREK_DB_PRE_MIGRATE_SNAPSHOT;
-1
View File
@@ -14,7 +14,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
-1
View File
@@ -14,7 +14,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -41,7 +41,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
// ── SSRF guard mock — routes all Immich API calls to fake responses ───────────
vi.mock('../../src/utils/ssrfGuard', async () => {
@@ -19,7 +19,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
// ── SSRF guard mock — routes all Synology API calls to fake responses ─────────
vi.mock('../../src/utils/ssrfGuard', async () => {
@@ -4,7 +4,6 @@
* /unified/trips/:tripId/album-links routes.
*
* No real HTTP is made — safeFetch is mocked to never be called.
* The broadcast WebSocket call is no-op mocked.
*/
import { describe, it, expect, vi, beforeAll, beforeEach, afterAll } from 'vitest';
import request from 'supertest';
@@ -18,7 +17,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
vi.mock('../../src/utils/ssrfGuard', async () => {
const actual = await vi.importActual<typeof import('../../src/utils/ssrfGuard')>('../../src/utils/ssrfGuard');
return {
-1
View File
@@ -11,7 +11,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -15,7 +15,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
// The two test-send endpoints must not reach a real SMTP server or URL. Since
// the fold they are provider methods, so they are stubbed on the prototype
// rather than by module path — everything else in the domain stays real.
@@ -24,7 +24,6 @@ vi.mock('../../src/app-config', async (importOriginal) => {
return { ...actual, getMcpSafeUrl: () => 'https://trek.example.com' };
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
vi.mock('../../src/mcp/sessionManager', () => ({ revokeUserSessions: vi.fn(), revokeUserSessionsForClient: vi.fn(), sessions: new Map() }));
import { db as testDb } from '../../src/db/database';
-1
View File
@@ -19,7 +19,6 @@ vi.mock('../../src/app-config', async (importOriginal) => {
return { ...actual, getMcpSafeUrl: () => 'https://trek.example.com' };
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
vi.mock('../../src/mcp/sessionManager', () => ({ revokeUserSessions: vi.fn(), revokeUserSessionsForClient: vi.fn(), sessions: new Map() }));
import { db as testDb } from '../../src/db/database';
@@ -27,7 +27,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
// The provider's names are answered here; every other name goes to the real resolver.
vi.mock('dns/promises', async (importOriginal) => {
const real = await importOriginal<typeof import('dns/promises')>();
-1
View File
@@ -19,7 +19,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import type { MockInstance } from 'vitest';
@@ -36,7 +36,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn(), getOnlineUserIds: vi.fn(() => []) }));
import { db as testDb } from '../../src/db/database';
import { MikroORM } from '@mikro-orm/core';
-1
View File
@@ -11,7 +11,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { MikroORM } from '@mikro-orm/core';
-1
View File
@@ -27,7 +27,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
+4 -3
View File
@@ -17,12 +17,10 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { MikroORM } from '@mikro-orm/core';
import { buildApp } from '../../src/bootstrap';
import { DomainError } from '../../src/nest/common/domain-error';
import { resetTestDb, resetRateLimits } from '../helpers/test-db';
import type { FactoryOrm } from '../helpers/factories/context';
import { createUser, createAdmin, createTrip, createPlace, addTripMember } from '../helpers/factories';
@@ -38,7 +36,8 @@ import { PlaceRatings } from '../../src/db/entities/PlaceRatings.entity';
import { Places } from '../../src/db/entities/Places.entity';
import { PlacesService } from '../../src/nest/places/places.service';
import { invalidatePermissionsCache } from '../../src/nest/permissions/permissions-cache';
import { broadcast } from '../../src/websocket';
import { spyOnRealtime, type RealtimeSpies } from '../helpers/fake-realtime';
import { DomainError } from '../../src/nest/common/domain-error';
let nestApp: INestApplication;
let app: Application;
@@ -49,6 +48,7 @@ let orm: FactoryOrm;
// implementation, so only the *Once overrides below change behaviour.
let importGoogleList: MockInstance;
let searchPlaceImage: MockInstance;
let broadcast: RealtimeSpies['broadcast'];
const GPX_FIXTURE = path.join(__dirname, '../fixtures/test.gpx');
const KML_FIXTURE = path.join(__dirname, '../fixtures/test.kml');
const KML_NESTED_FIXTURE = path.join(__dirname, '../fixtures/test-nested.kml');
@@ -69,6 +69,7 @@ beforeEach(async () => {
// afterEach, which would otherwise strip these for every later test.
importGoogleList = vi.spyOn(nestApp.get(PlacesService), 'importGoogleList');
searchPlaceImage = vi.spyOn(nestApp.get(PlacesService), 'searchImage');
broadcast = spyOnRealtime(nestApp).broadcast;
});
afterAll(async () => {
@@ -28,7 +28,6 @@ vi.mock('../../../src/db/database', async () => {
const db = createSnapshotTestDb();
return { db, canAccessTrip: () => undefined };
});
vi.mock('../../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../../src/db/database';
import { UnitOfWork } from '../../../src/nest/database/unit-of-work';
@@ -18,7 +18,6 @@ import path from 'node:path';
// (`createSnapshotTestDb` + `createTestOrm`) replaces the old hand-rolled table set,
// same fix `registry.test.ts` needed.
import { createSnapshotTestDb } from '../../helpers/db-mock';
vi.mock('../../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { PluginRuntimeService } from '../../../src/nest/plugins/plugin-runtime.service';
import { createPluginRuntime } from '../../helpers/plugin-host';
@@ -44,7 +44,6 @@ const { testDb } = vi.hoisted(() => {
});
vi.mock('../../../src/db/database', () => ({ db: testDb, canAccessTrip: () => undefined }));
import { db as dbConn } from '../../../src/db/database';
vi.mock('../../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { PluginRuntimeService, PluginDependencyError } from '../../../src/nest/plugins/plugin-runtime.service';
import { createPluginRuntime } from '../../helpers/plugin-host';
-1
View File
@@ -12,7 +12,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -11,7 +11,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -15,7 +15,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { SchedulerRegistry } from '@nestjs/schedule';
@@ -19,7 +19,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -11,7 +11,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
-1
View File
@@ -11,7 +11,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { MikroORM } from '@mikro-orm/core';
@@ -27,7 +27,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn(), getOnlineUserIds: vi.fn(() => []) }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
@@ -14,7 +14,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { MikroORM } from '@mikro-orm/core';
-1
View File
@@ -11,7 +11,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { buildApp } from '../../src/bootstrap';
-1
View File
@@ -11,7 +11,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { MikroORM } from '@mikro-orm/core';
@@ -30,7 +30,6 @@ vi.mock('../../src/db/database', async () => {
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
return buildDbMock(createSnapshotTestDb());
});
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
import { db as testDb } from '../../src/db/database';
import { MikroORM } from '@mikro-orm/core';

Some files were not shown because too many files have changed in this diff Show More