refactor(trip-membership): domains ask TripAccessService who may see a trip

Thirty services, controllers, MCP tools and RPC handlers outside the trips
domain injected TripsRepository for one call, findAccessible. They now
inject TripAccessService from the trip-membership leaf, in the same
constructor slot under the same field name; it answers with the same
access row (or undefined) and binds the id exactly as given, so each
caller keeps its own coercion and its own 404, 403 or tool error.
This commit is contained in:
Maurice
2026-10-10 13:11:10 +02:00
parent 734cbc2a94
commit fe35e26313
70 changed files with 219 additions and 195 deletions
@@ -20,6 +20,7 @@ import {
createTestJourneyPhotosRepo, createTestJourneyEntryPhotosRepo,
} from './journey-repos';
import { createTestToursRepo } from './tours-repos';
import { TripAccessService } from '../../src/nest/trip-membership/trip-access.service';
/**
* AccommodationsService over a test connection.
@@ -51,7 +52,7 @@ export async function accommodationsOver(conn: Database): Promise<Accommodations
const realtime = new RealtimeService();
const t = await sharedTestOrm(conn);
const assignments = new AssignmentsService(
await createTestTripsRepo(conn), permissions, realtime,
new TripAccessService(await createTestTripsRepo(conn)), permissions, realtime,
new QueryHelpersService(await createTestTagsRepo(conn), await createTestPlaceRatingsRepo(conn), await createTestAssignmentParticipantsRepo(conn)),
new JourneyDomainService(
realtime, new TrekPhotoRegistrationService(t.repo(TrekPhotos), t.repo(TripPhotos), await createTestJourneyPhotosRepo(conn)), await createTestUnitOfWork(conn),
@@ -75,7 +76,7 @@ export async function accommodationsOver(conn: Database): Promise<Accommodations
// TripsRepository.findAccessible. Plan 4 Task 3 — stampLodging's
// getPlaceWithTags is PlacesRepository.findWithTagsAndRatings directly
// (`placesRepo` below, already injected).
await createTestTripsRepo(conn),
new TripAccessService(await createTestTripsRepo(conn)),
await createTestDayAccommodationsRepo(conn),
await createTestDayAssignmentsRepo(conn),
await createTestPlacesRepo(conn),
+13 -12
View File
@@ -183,6 +183,7 @@ import { noGoogleQuota } from './google-quota';
import { createTestToursRepo } from './tours-repos';
import { createTestBudgetSettlementsRepo } from './budget-repos';
import { MaintenanceRepository } from '../../src/db/repositories/MaintenanceRepository';
import { TripAccessService } from '../../src/nest/trip-membership/trip-access.service';
/**
* Hand-wired counterpart of the boot-time discovery in McpRegistryService,
@@ -226,7 +227,7 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
await createTestRoadtripViasRepo(db),
await createTestRoadtripDayBoundariesRepo(db),
);
const todoService = new TodoService(permissionsService, realtimeService, await createTestUnitOfWork(db), await createTestTodoItemsRepo(db), await createTestTodoCategoryAssigneesRepo(db), await createTestTripsRepo(db), await createTestTripMembersRepo(db));
const todoService = new TodoService(permissionsService, realtimeService, await createTestUnitOfWork(db), await createTestTodoItemsRepo(db), await createTestTodoCategoryAssigneesRepo(db), new TripAccessService(await createTestTripsRepo(db)), await createTestTripMembersRepo(db));
const packingService = new PackingService(
permissionsService, realtimeService, notificationsStub(), await createTestUnitOfWork(db),
await createTestPackingItemsRepo(db), await createTestPackingItemContributorsRepo(db), await createTestPackingBagsRepo(db),
@@ -266,7 +267,7 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
// One instance, four consumers: AssignmentsMcp, ReservationsMcp, PlacesMcp and
// AccommodationsService, which writes the day stop a booked night implies.
const assignmentsService = new AssignmentsService(
await createTestTripsRepo(db), permissionsService, realtimeService, queryHelpersService, journeyDomain, await createTestUnitOfWork(db),
new TripAccessService(await createTestTripsRepo(db)), permissionsService, realtimeService, queryHelpersService, journeyDomain, await createTestUnitOfWork(db),
await createTestDayAssignmentsRepo(db),
await createTestAssignmentParticipantsRepo(db),
await createTestDaysRepo(db),
@@ -277,7 +278,7 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
);
const accommodationsService = new AccommodationsService(
permissionsService, realtimeService, assignmentsService, await createTestUnitOfWork(db),
await createTestTripsRepo(db),
new TripAccessService(await createTestTripsRepo(db)),
await createTestDayAccommodationsRepo(db),
await createTestDayAssignmentsRepo(db),
await createTestPlacesRepo(db),
@@ -338,7 +339,7 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
// once and shared by both consumers below (readModelService, FilesMcp)
// rather than reconstructed twice with the same seven-repository tail.
const filesService = new FilesService(
await createTestTripsRepo(db), permissionsService, realtimeService, new EphemeralTokenService(), generalStorage, mcpOrm.em,
new TripAccessService(await createTestTripsRepo(db)), permissionsService, realtimeService, new EphemeralTokenService(), generalStorage, mcpOrm.em,
await createTestUnitOfWork(db),
await createTestTripFilesRepo(db),
await createTestFileLinksRepo(db),
@@ -388,7 +389,7 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
new PackingMcp(packingService, addonsService, guards),
new BudgetMcp(budgetService, exchangeRatesService, new RuntimeEnvService(), new TripMembershipService(await createTestTripsRepo(db), await createTestTripMembersRepo(db)), addonsService, guards, await createTestUnitOfWork(db), await createTestPlacesRepo(db), await createTestTripsRepo(db), await createTestTripMembersRepo(db)),
new ReservationsMcp(reservationsService, daysService, budgetService, assignmentsService, guards),
new DayNotesMcp(new DayNotesService(await createTestTripsRepo(db), permissionsService, realtimeService, await createTestDayNotesRepo(db), await createTestDaysRepo(db)), guards),
new DayNotesMcp(new DayNotesService(new TripAccessService(await createTestTripsRepo(db)), permissionsService, realtimeService, await createTestDayNotesRepo(db), await createTestDaysRepo(db)), guards),
new DaysMcp(daysService, guards, dayRemovalService),
new RoadtripMcp(
new RoadtripService(
@@ -399,13 +400,13 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
await createTestRoadtripViasRepo(db),
await createTestRoadtripDayTracksRepo(db),
),
await createTestTripsRepo(db),
new TripAccessService(await createTestTripsRepo(db)),
guards,
addonsService,
),
new RoadtripPreferencesMcp(
new RoadtripPreferencesService(realtimeService, await createTestUnitOfWork(db), await createTestRoadtripPreferencesRepo(db)),
addonsService, await createTestTripsRepo(db), guards,
addonsService, new TripAccessService(await createTestTripsRepo(db)), guards,
),
new FilesMcp(filesService, guards, new AllowedFileTypesService(appSettings)),
new AccommodationsMcp(accommodationsService, placesService, guards, await createTestUnitOfWork(db)),
@@ -434,9 +435,9 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
new TripPromptsMcp(tripsService, readModelService, packingService, addonsService),
new ShareMcp(new ShareService(new SettingsService(await createTestUnitOfWork(db), appSettings, await createTestSettingsRepo(db)), permissionsService, queryHelpersService, placePhotoCache, await createTestUnitOfWork(db), ...(await shareServiceRepoArgs(db))), guards),
new FeedsMcp(new FeedsService(await createTestTripsRepo(db), usersRepo, calendarService), await createTestTripsRepo(db), new RuntimeEnvService(), guards),
new TripInviteMcp(new TripInviteService(await createTestTripsRepo(db), permissionsService, new TripMembershipService(await createTestTripsRepo(db), await createTestTripMembersRepo(db)), await createTestUnitOfWork(db), await createTestTripInviteTokensRepo(db)), new RuntimeEnvService(), guards, new AuditService(auditLogRepo, usersRepo)),
new TripInviteMcp(new TripInviteService(new TripAccessService(await createTestTripsRepo(db)), permissionsService, new TripMembershipService(await createTestTripsRepo(db), await createTestTripMembersRepo(db)), await createTestUnitOfWork(db), await createTestTripInviteTokensRepo(db)), new RuntimeEnvService(), guards, new AuditService(auditLogRepo, usersRepo)),
new MapsMcp(mapsService),
new PlacesMcp(placesService, mapsService, await createTestTripsRepo(db), journeyDomain, assignmentsService, guards, await createTestUnitOfWork(db)),
new PlacesMcp(placesService, mapsService, new TripAccessService(await createTestTripsRepo(db)), journeyDomain, assignmentsService, guards, await createTestUnitOfWork(db)),
new CollectionsMcp(
new CollectionsService(
permissionsService, realtimeService, notificationsStub(), generalStorage, await createTestUnitOfWork(db),
@@ -460,7 +461,7 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
// (`findUsernameEmail`, UM11's precedent) added in its place.
usersRepo, addonsService,
),
new TransitMcp(new TransitService(new GoogleTransitProvider(appSettings, usersRepo, noGoogleQuota), transitConfig()), daysService, reservationsService, await createTestTripsRepo(db), guards),
new TransitMcp(new TransitService(new GoogleTransitProvider(appSettings, usersRepo, noGoogleQuota), transitConfig()), daysService, reservationsService, new TripAccessService(await createTestTripsRepo(db)), guards),
new AtlasMcp(new AtlasService(
await createTestBucketListRepo(db), await createTestHiddenCountriesRepo(db),
await createTestHiddenRegionsRepo(db), await createTestVisitedCountriesRepo(db),
@@ -488,10 +489,10 @@ export async function createMcpTestRegistry(): Promise<McpRegistry> {
await createTestReservationsRepo(db), await createTestReservationEndpointsRepo(db), await createTestDaysRepo(db),
realtimeService, reservationsService, new AirtrailClient(), new AirtrailService(await createTestUserAirtrailRepo(db), new AuditService(auditLogRepo, usersRepo), new AirtrailClient()),
await createTestUnitOfWork(db),
), await createTestTripsRepo(db), guards, addonsService),
), new TripAccessService(await createTestTripsRepo(db)), guards, addonsService),
new SettingsMcp(new SettingsService(await createTestUnitOfWork(db), appSettings, await createTestSettingsRepo(db))),
new HelpMcp(), new AddonsMcp(addonsService),
new TripWarningsMcp(new PluginHooks({ providersOf: () => [], invokeHook: async () => [] } as unknown as PluginRuntimeService), await createTestTripsRepo(db)),
new TripWarningsMcp(new PluginHooks({ providersOf: () => [], invokeHook: async () => [] } as unknown as PluginRuntimeService), new TripAccessService(await createTestTripsRepo(db))),
new PluginSearchMcp(new PluginHooks({ providersOf: () => [], invokeHook: async () => [] } as unknown as PluginRuntimeService)),
new PluginPoisMcp(new PluginPoisService(new PluginHooks({ providersOf: () => [], invokeHook: async () => [] } as unknown as PluginRuntimeService), await createTestPluginsRepo(db))),
],
+7 -6
View File
@@ -143,6 +143,7 @@ import { noGoogleQuota } from './google-quota';
import { createTestToursRepo } from './tours-repos';
import { createTestBudgetSettlementsRepo } from './budget-repos';
import { MaintenanceRepository } from '../../src/db/repositories/MaintenanceRepository';
import { TripAccessService } from '../../src/nest/trip-membership/trip-access.service';
/**
* Hand-wired counterpart of the PluginsModule DI graph for no-Nest tests
@@ -178,7 +179,7 @@ export async function createPluginRpcHostParts(
const budget = new BudgetService(permissions, exchangeRates, realtime, await createTestUnitOfWork(db), ...(await budgetRepoArgs(db)));
const addons = await createTestAddonsService(db);
const queryHelpers = new QueryHelpersService(await createTestTagsRepo(db), await createTestPlaceRatingsRepo(db), await createTestAssignmentParticipantsRepo(db));
const todos = new TodoService(permissions, realtime, await createTestUnitOfWork(db), await createTestTodoItemsRepo(db), await createTestTodoCategoryAssigneesRepo(db), await createTestTripsRepo(db), await createTestTripMembersRepo(db));
const todos = new TodoService(permissions, realtime, await createTestUnitOfWork(db), await createTestTodoItemsRepo(db), await createTestTodoCategoryAssigneesRepo(db), new TripAccessService(await createTestTripsRepo(db)), await createTestTripMembersRepo(db));
const packing = new PackingService(
permissions, realtime, notificationsStub(), await createTestUnitOfWork(db),
await createTestPackingItemsRepo(db), await createTestPackingItemContributorsRepo(db), await createTestPackingBagsRepo(db),
@@ -188,7 +189,7 @@ export async function createPluginRpcHostParts(
// Plan 3e Task 1 (files): FilesService now also takes uow + the repositories
// its R2 transactions and R12 cross-object trip-scoping guard need.
const files = new FilesService(
await createTestTripsRepo(db), permissions, realtime, new EphemeralTokenService(), generalStorage, (await sharedTestOrm(db)).em,
new TripAccessService(await createTestTripsRepo(db)), permissions, realtime, new EphemeralTokenService(), generalStorage, (await sharedTestOrm(db)).em,
await createTestUnitOfWork(db),
await createTestTripFilesRepo(db),
await createTestFileLinksRepo(db),
@@ -267,9 +268,9 @@ export async function createPluginRpcHostParts(
await createTestTripsRepo(db), await createTestPlacesRepo(db),
await createTestReservationEndpointsRepo(db), await createTestUnitOfWork(db), (await sharedTestOrm(db)).orm,
);
const dayNotes = new DayNotesService(await createTestTripsRepo(db), permissions, realtime, await createTestDayNotesRepo(db), await createTestDaysRepo(db));
const dayNotes = new DayNotesService(new TripAccessService(await createTestTripsRepo(db)), permissions, realtime, await createTestDayNotesRepo(db), await createTestDaysRepo(db));
const assignments = new AssignmentsService(
await createTestTripsRepo(db), permissions, realtime, queryHelpers, journey, await createTestUnitOfWork(db),
new TripAccessService(await createTestTripsRepo(db)), permissions, realtime, queryHelpers, journey, await createTestUnitOfWork(db),
await createTestDayAssignmentsRepo(db),
await createTestAssignmentParticipantsRepo(db),
await createTestDaysRepo(db),
@@ -287,7 +288,7 @@ export async function createPluginRpcHostParts(
const oauth = new PluginOAuthService(pluginOrm.repo(Plugins), pluginOrm.repo(PluginOauthTokens), pluginOrm.repo(PluginOauthState), pluginOrm.repo(PluginSettingsFields), await createTestUnitOfWork(db));
const accommodations = new AccommodationsService(
permissions, realtime, assignments, await createTestUnitOfWork(db),
await createTestTripsRepo(db),
new TripAccessService(await createTestTripsRepo(db)),
await createTestDayAccommodationsRepo(db),
await createTestDayAssignmentsRepo(db),
await createTestPlacesRepo(db),
@@ -339,7 +340,7 @@ export async function createPluginRpcHostParts(
new AccommodationsRpc(accommodations, realtime, guards),
new ItineraryRpc(assignments, realtime, guards),
new TripsRpc(trips, reservations, days, membership, realtime, guards, accommodations, members, (await sharedTestOrm(db)).em),
new CostsRpc(budget, await createTestTripsRepo(db), realtime, guards, membership),
new CostsRpc(budget, new TripAccessService(await createTestTripsRepo(db)), realtime, guards, membership),
new ReservationsRpc(reservations, realtime, guards),
new CollabRpc(collab, realtime, guards),
new AtlasRpc(atlas, guards),
+2 -1
View File
@@ -21,6 +21,7 @@ import type { UsersRepository } from '../../../src/db/repositories/Users.reposit
import type { TripMembershipService } from '../../../src/nest/trip-membership/trip-membership.service';
import type { RpcRequest, RpcError } from '../../../src/nest/plugins/protocol/envelope';
import { makeDeps } from '../../helpers/rpc-host-deps';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
const req = (method: string, params: Record<string, unknown> = {}): RpcRequest => ({ k: 'req', id: 'x', method, params });
@@ -55,7 +56,7 @@ function build(opts: { addonOn?: boolean; canEdit?: boolean; missing?: boolean;
);
// The leaf membership read replaced the deleted trips.bridge for listMine.
const membership = { listAccessibleTripIds: vi.fn(() => [1, 2]) } as unknown as TripMembershipService;
const rpc = new CostsRpc(budget, db, realtime, guards, membership);
const rpc = new CostsRpc(budget, new TripAccessService(db), realtime, guards, membership);
const host = (...grants: string[]) =>
new PluginRpcHost('p', new Set(grants.length ? grants : ['db:read:costs', 'db:write:costs']), makeDeps(), createTestPluginRegistry([rpc]));
return { budget, realtime, host };
@@ -60,6 +60,7 @@ import { Days } from '../../../src/db/entities/Days.entity';
import { Places } from '../../../src/db/entities/Places.entity';
import { RoadtripVias } from '../../../src/db/entities/RoadtripVias.entity';
import { Users } from '../../../src/db/entities/Users.entity';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
let svc: AssignmentsService;
beforeAll(async () => {
@@ -67,7 +68,7 @@ beforeAll(async () => {
svc = new AssignmentsService(
// Plan 4 Task 2 — AssignmentsService's own canAccessTrip delegate is now
// TripsRepository.findAccessible, in the same constructor slot.
await createTestTripsRepo(testDb),
new TripAccessService(await createTestTripsRepo(testDb)),
new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)),
realtime,
new QueryHelpersService(await createTestTagsRepo(testDb), await createTestPlaceRatingsRepo(testDb), await createTestAssignmentParticipantsRepo(testDb)),
@@ -39,13 +39,14 @@ import { createTestUnitOfWork, createTestAppSettingsRepo, createTestDayNotesRepo
import { sharedTestOrm } from '../../helpers/test-uow';
import { findRow } from '../../helpers/factories/rows';
import { DayNotes } from '../../../src/db/entities/DayNotes.entity';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
let svc: DayNotesService;
beforeAll(async () => {
// Plan 4 Task 2 — DayNotesService's own canAccessTrip delegate is now
// TripsRepository.findAccessible, in the same constructor slot.
svc = new DayNotesService(
await createTestTripsRepo(testDb),
new TripAccessService(await createTestTripsRepo(testDb)),
new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)),
new RealtimeService(),
await createTestDayNotesRepo(testDb),
@@ -279,7 +280,7 @@ describe('DayNotesService.canEdit', () => {
it('DAYNOTE-SVC-090 asks for day_edit and flags a non-owner as shared', async () => {
const checkPermission = vi.fn(() => true);
const permissions = { checkPermission } as unknown as PermissionsService;
const withStub = new DayNotesService(await createTestTripsRepo(testDb), permissions, new RealtimeService(), await createTestDayNotesRepo(testDb), await createTestDaysRepo(testDb));
const withStub = new DayNotesService(new TripAccessService(await createTestTripsRepo(testDb)), permissions, new RealtimeService(), await createTestDayNotesRepo(testDb), await createTestDaysRepo(testDb));
const trip = { id: 1, user_id: 1 } as never;
expect(await withStub.canEdit(trip, { id: 1, role: 'user' } as never)).toBe(true);
@@ -62,6 +62,7 @@ import { ReservationEndpoints } from '../../../src/db/entities/ReservationEndpoi
import { RoadtripDayBoundaries } from '../../../src/db/entities/RoadtripDayBoundaries.entity';
import { Trips } from '../../../src/db/entities/Trips.entity';
import { legacyBoundIntegerText } from '../../../src/nest/common/row-id';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
let days: DaysService;
let journey: JourneyDomainService;
@@ -96,7 +97,7 @@ beforeAll(async () => {
await createTestJourneyPhotosRepo(testDb), await createTestJourneyEntryPhotosRepo(testDb), await createTestPlacesRepo(testDb),
);
assignments = new AssignmentsService(
await createTestTripsRepo(testDb), permissions, realtime, queryHelpers, journey,
new TripAccessService(await createTestTripsRepo(testDb)), permissions, realtime, queryHelpers, journey,
await createTestUnitOfWork(testDb),
await createTestDayAssignmentsRepo(testDb),
await createTestAssignmentParticipantsRepo(testDb),
@@ -108,7 +109,7 @@ beforeAll(async () => {
);
accommodations = new AccommodationsService(
permissions, realtime, assignments, await createTestUnitOfWork(testDb),
await createTestTripsRepo(testDb),
new TripAccessService(await createTestTripsRepo(testDb)),
await createTestDayAccommodationsRepo(testDb),
await createTestDayAssignmentsRepo(testDb),
await createTestPlacesRepo(testDb),
+2 -1
View File
@@ -79,6 +79,7 @@ import { EphemeralTokenService } from '../../../src/nest/auth-core/ephemeral-tok
import type { EntityManager } from '@mikro-orm/core';
import { Users } from '../../../src/db/entities/Users.entity';
import { UserSessions } from '../../../src/db/entities/UserSessions.entity';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
const storageDelete = vi.fn();
const storageStub = { delete: storageDelete } as unknown as import('../../../src/nest/storage/storage.service').StorageService;
@@ -114,7 +115,7 @@ beforeAll(async () => {
tripFilesRepo = await createTestTripFilesRepo(testDb);
fileLinksRepo = await createTestFileLinksRepo(testDb);
svc = new FilesService(
await createTestTripsRepo(testDb),
new TripAccessService(await createTestTripsRepo(testDb)),
permissionsStub,
new RealtimeService(),
new EphemeralTokenService(),
+17 -16
View File
@@ -43,6 +43,7 @@ import type { JourneyDomainService } from '../../../src/nest/journey/journey-dom
import type { UsersRepository } from '../../../src/db/repositories/Users.repository';
import type { AppSettingsRepository } from '../../../src/db/repositories/AppSettings.repository';
import type { User } from '../../../src/types';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
interface FakeSocket extends TrekWebSocket {
sent: string[];
@@ -88,7 +89,7 @@ const canAccessJourney = vi.fn((journeyId: number) => (journeyId === 4 ? null :
const journeys = { canAccessJourney } as unknown as JourneyDomainService;
async function connect(url: string) {
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
const ws = socket();
await gw.handleConnection(ws, { url } as never);
return { gw, ws };
@@ -158,7 +159,7 @@ describe('RealtimeGateway handshake', () => {
});
it('WSGW-007b: survives an upgrade request with no url, rather than throwing at it', async () => {
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
const ws = socket();
await expect(gw.handleConnection(ws, {} as never)).resolves.not.toThrow();
expect(ws.closedWith).toEqual([4001, 'Authentication required']);
@@ -198,7 +199,7 @@ describe('RealtimeGateway handshake', () => {
const throwingUsers = {
findForWsHandshake: () => { throw new Error('db exploded'); },
} as unknown as UsersRepository;
const gw = new RealtimeGateway(db, tokens, journeys, throwingUsers, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, throwingUsers, appSettings);
const ws = socket();
await gw.handleConnection(ws, { url: '/ws?token=x' } as never);
@@ -211,7 +212,7 @@ describe('RealtimeGateway handshake', () => {
const throwingUsers = {
findForWsHandshake: () => { throw 'db string'; }, // non-Error throw, pinning the ternary's String(err) fallback
} as unknown as UsersRepository;
const gw = new RealtimeGateway(db, tokens, journeys, throwingUsers, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, throwingUsers, appSettings);
const ws = socket();
await gw.handleConnection(ws, { url: '/ws?token=x' } as never);
@@ -248,7 +249,7 @@ describe('RealtimeGateway heartbeat', () => {
alive.isAlive = true;
const stale = socket();
stale.isAlive = false;
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
gw.afterInit({ clients: new Set([alive, stale]) } as never);
vi.advanceTimersByTime(30_000);
@@ -269,7 +270,7 @@ describe('RealtimeGateway heartbeat', () => {
try {
const ws = socket();
ws.isAlive = true;
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
gw.afterInit({ clients: new Set([ws]) } as never);
gw.onModuleDestroy();
@@ -378,7 +379,7 @@ describe('book rooms', () => {
let nextJourney = 100;
async function joined(journeyId: number) {
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
const ws = socket();
registerSocket(ws, { id: 3, username: 'm' } as User);
const reply = await gw.handleBookJoin({ journeyId }, ws);
@@ -394,7 +395,7 @@ describe('book rooms', () => {
/* Same shape as the trip room's refusal, and for the same reason. */
it('WSGW-BOOK-002: refuses a journey the user cannot see, and adds nobody', async () => {
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
const ws = socket();
registerSocket(ws, { id: 3, username: 'm' } as User);
@@ -445,7 +446,7 @@ describe('book pointers', () => {
async function pair() {
const journeyId = nextJourney++;
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
const mine = socket();
const theirs = socket();
registerSocket(mine, { id: 3, username: 'm' } as User);
@@ -510,7 +511,7 @@ describe('book messages that are refused', () => {
let nextJourney = 300;
it('WSGW-BOOK-006: a join without a journey id is ignored', async () => {
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
const ws = socket();
registerSocket(ws, { id: 3, username: 'm' } as User);
@@ -520,7 +521,7 @@ describe('book messages that are refused', () => {
/* An unauthenticated socket never got as far as the registry. */
it('WSGW-BOOK-007: a join from a socket with no user is ignored', async () => {
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
const j = nextJourney++;
expect(await gw.handleBookJoin({ journeyId: j }, socket())).toBeUndefined();
@@ -528,7 +529,7 @@ describe('book messages that are refused', () => {
});
it('WSGW-BOOK-008: a journey id that is not a number is refused, not joined', async () => {
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
const ws = socket();
registerSocket(ws, { id: 3, username: 'm' } as User);
@@ -537,7 +538,7 @@ describe('book messages that are refused', () => {
});
it('WSGW-BOOK-009: a leave without a journey id is ignored', () => {
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
const ws = socket();
registerSocket(ws, { id: 3, username: 'm' } as User);
@@ -546,7 +547,7 @@ describe('book messages that are refused', () => {
/* Leaving a room nobody is in must not create one on the way out. */
it('WSGW-BOOK-010: leaving a book that was never joined does nothing', () => {
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
const ws = socket();
registerSocket(ws, { id: 3, username: 'm' } as User);
const j = nextJourney++;
@@ -556,7 +557,7 @@ describe('book messages that are refused', () => {
});
it('WSGW-CUR-005: a pointer without a journey id reaches nobody', async () => {
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
const mine = socket();
const theirs = socket();
registerSocket(mine, { id: 3, username: 'm' } as User);
@@ -571,7 +572,7 @@ describe('book messages that are refused', () => {
});
it('WSGW-CUR-006: a pointer from a socket with no user reaches nobody', async () => {
const gw = new RealtimeGateway(db, tokens, journeys, users, appSettings);
const gw = new RealtimeGateway(new TripAccessService(db), tokens, journeys, users, appSettings);
const theirs = socket();
registerSocket(theirs, { id: 4, username: 'o' } as User);
const j = nextJourney++;
@@ -7,6 +7,7 @@ import type { TripsRepository } from '../../../src/db/repositories/Trips.reposit
import type { PermissionsService } from '../../../src/nest/permissions/permissions.service';
import type { AddonsService } from '../../../src/nest/addons/addons.service';
import type { User } from '../../../src/types';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
const user = { id: 3, role: 'user' } as User;
const photo = (name = 'r.jpg') => ({ originalname: name, buffer: Buffer.from('x') } as Express.Multer.File);
@@ -17,7 +18,7 @@ function make(over: { readsImages?: boolean; trip?: unknown; addon?: boolean; al
const c = new ReceiptScanController(
{ startReceipt } as unknown as ImportJobsService,
{ readsImages: vi.fn(async () => over.readsImages ?? true) } as unknown as LlmParseService,
{ findAccessible: vi.fn(async () => ('trip' in over ? over.trip : { user_id: 5 })) } as unknown as TripsRepository,
new TripAccessService({ findAccessible: vi.fn(async () => ('trip' in over ? over.trip : { user_id: 5 })) } as unknown as TripsRepository),
{ checkPermission } as unknown as PermissionsService,
{ isAddonEnabled: vi.fn(async () => over.addon ?? true) } as unknown as AddonsService,
);
@@ -31,6 +31,7 @@ import { bookendAssignmentId, type RoadtripStop } from '@trek/shared/roadtrip';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { legacyBoundIntegerText } from '../../../src/nest/common/row-id';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
/** Every leg an hour and 60 km, whatever it joins. */
function hourlyRouter() {
@@ -61,7 +62,7 @@ function hourlyRouter() {
/** The repositories the service reads through, all on the worker's one test handle. */
async function planRepos() {
return [
await createTestTripsRepo(db),
new TripAccessService(await createTestTripsRepo(db)),
await createTestDaysRepo(db),
await createTestDayAssignmentsRepo(db),
await createTestDayAccommodationsRepo(db),
+3 -2
View File
@@ -37,6 +37,7 @@ import { TodoItems } from '../../../src/db/entities/TodoItems.entity';
import { TodoCategoryAssignees } from '../../../src/db/entities/TodoCategoryAssignees.entity';
import { Users } from '../../../src/db/entities/Users.entity';
import { createTestTodoItemsRepo, createTestTodoCategoryAssigneesRepo } from '../../helpers/todo-repos';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
let svc: TodoService;
let todoItemsRepoDirect: Awaited<ReturnType<typeof createTestTodoItemsRepo>>;
@@ -48,7 +49,7 @@ beforeAll(async () => {
todoItemsRepoDirect, await createTestTodoCategoryAssigneesRepo(testDb),
// Plan 4 Task 2 — TodoService's own canAccessTrip delegate is now
// TripsRepository.findAccessible, a new trailing constructor param.
await createTestTripsRepo(testDb),
new TripAccessService(await createTestTripsRepo(testDb)),
// Plan 4 Task 3 — DatabaseService.rosterUserIds inlined onto
// TripMembersRepository.rosterUserIds directly.
await createTestTripMembersRepo(testDb),
@@ -404,7 +405,7 @@ describe('TodoService.canEdit', () => {
const withStub = new TodoService(
permissions, new RealtimeService(), await createTestUnitOfWork(testDb),
await createTestTodoItemsRepo(testDb), await createTestTodoCategoryAssigneesRepo(testDb),
await createTestTripsRepo(testDb), await createTestTripMembersRepo(testDb),
new TripAccessService(await createTestTripsRepo(testDb)), await createTestTripMembersRepo(testDb),
);
const trip = { id: 1, user_id: 1 } as never;
@@ -36,6 +36,7 @@ import { createTestUnitOfWork, createTestAppSettingsRepo, createTestTripsRepo, c
import { sharedTestOrm } from '../../helpers/test-uow';
import { countRows, updateRows } from '../../helpers/factories/rows';
import { TripInviteTokens } from '../../../src/db/entities/TripInviteTokens.entity';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
let svc: TripInviteService;
beforeAll(async () => {
@@ -43,7 +44,7 @@ beforeAll(async () => {
svc = new TripInviteService(
// Plan 4 Task 2 — TripInviteService's own canAccessTrip delegate is now
// TripsRepository.findAccessible, in the same constructor slot.
await createTestTripsRepo(testDb),
new TripAccessService(await createTestTripsRepo(testDb)),
new PermissionsService(await createTestAppSettingsRepo(testDb), uow),
new TripMembershipService(await createTestTripsRepo(testDb), await createTestTripMembersRepo(testDb)),
uow,
@@ -108,6 +108,7 @@ import { createTestCollectionPlacesRepo } from '../../helpers/test-uow';
import { noGoogleQuota } from '../../helpers/google-quota';
import { createTestBudgetSettlementsRepo } from '../../helpers/budget-repos';
import { MaintenanceRepository } from '../../../src/db/repositories/MaintenanceRepository';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
// Real sibling services over the same in-memory DB — the aggregation runs the
// actual SQL of every domain it fans out to, so a shape change downstream shows
@@ -200,7 +201,7 @@ const buildReadModel = async (tripsRepo: TripsRepository, roster: TripMembersSer
await createTestTodoItemsRepo(testDb), await createTestTodoCategoryAssigneesRepo(testDb),
// Plan 4 Task 2 — TodoService's own canAccessTrip delegate is now
// TripsRepository.findAccessible, a new trailing constructor param.
await createTestTripsRepo(testDb),
new TripAccessService(await createTestTripsRepo(testDb)),
// Plan 4 Task 3 — DatabaseService.rosterUserIds inlined onto
// TripMembersRepository.rosterUserIds directly.
await createTestTripMembersRepo(testDb),
@@ -208,7 +209,7 @@ const buildReadModel = async (tripsRepo: TripsRepository, roster: TripMembersSer
new FilesService(
// Plan 4 Task 2 — FilesService's own canAccessTrip delegate is now
// TripsRepository.findAccessible, in the same constructor slot.
await createTestTripsRepo(testDb),
new TripAccessService(await createTestTripsRepo(testDb)),
new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)),
new RealtimeService(),
new EphemeralTokenService(),
+3 -2
View File
@@ -153,6 +153,7 @@ import { TripMembers } from '../../../src/db/entities/TripMembers.entity';
import { Trips } from '../../../src/db/entities/Trips.entity';
import { Users } from '../../../src/db/entities/Users.entity';
import { legacyBoundIntegerText } from '../../../src/nest/common/row-id';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
const orm = () => sharedTestOrm(testDb);
@@ -322,13 +323,13 @@ beforeAll(async () => {
new TodoService(
new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)), new RealtimeService(), await createTestUnitOfWork(testDb),
await createTestTodoItemsRepo(testDb), await createTestTodoCategoryAssigneesRepo(testDb),
await createTestTripsRepo(testDb),
new TripAccessService(await createTestTripsRepo(testDb)),
await createTestTripMembersRepo(testDb),
),
new FilesService(
// Plan 4 Task 2 — FilesService's own canAccessTrip delegate is now
// TripsRepository.findAccessible, in the same constructor slot.
await createTestTripsRepo(testDb),
new TripAccessService(await createTestTripsRepo(testDb)),
new PermissionsService(await createTestAppSettingsRepo(testDb), await createTestUnitOfWork(testDb)),
new RealtimeService(),
new EphemeralTokenService(),
@@ -26,6 +26,7 @@ import type { AddonsService } from '../../../src/nest/addons/addons.service';
import type { UsersRepository } from '../../../src/db/repositories/Users.repository';
import type { RpcRequest, RpcError } from '../../../src/nest/plugins/protocol/envelope';
import { makeDeps } from '../../helpers/rpc-host-deps';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
const req = (method: string, params: Record<string, unknown> = {}): RpcRequest => ({ k: 'req', id: 'x', method, params });
@@ -81,7 +82,7 @@ function build(addonOn: boolean) {
// SV8 — Plan 3i: DemoService.isDemoUserId, the shared demo-gate primitive.
{ isDemoUserId: async () => false } as never),
new CollectionsRpc(spyService(calls, 'collections'), guards),
new CostsRpc(spyService(calls, 'budget'), db, { broadcast: vi.fn() } as never, guards, spyService(calls, 'membership')),
new CostsRpc(spyService(calls, 'budget'), new TripAccessService(db), { broadcast: vi.fn() } as never, guards, spyService(calls, 'membership')),
]);
const granted = new Set([
'db:read:collab', 'db:write:collab', 'db:read:journal', 'db:write:journal',
@@ -22,6 +22,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
import { DayScheduleController } from '../../../src/nest/plugins/contributions/day-schedule.controller';
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
import type { DaysRepository } from '../../../src/db/repositories/Days.repository';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
@@ -32,7 +33,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
} as unknown as PluginHooks;
// CT1 (Plan 3j Task 5) — the day-id-set read is now DaysRepository.listIdsByTrip.
const days = { listIdsByTrip: vi.fn(async () => tripDays.value.map((d) => d.id)) } as unknown as DaysRepository;
return { c: new DayScheduleController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository, days), runtime };
return { c: new DayScheduleController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository), days), runtime };
}
const item = (over: Record<string, unknown> = {}) => ({ id: 's1', dayId: 10, label: 'Charging', ...over });
@@ -22,6 +22,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
import { DayTintsController } from '../../../src/nest/plugins/contributions/day-tints.controller';
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
import type { DaysRepository } from '../../../src/db/repositories/Days.repository';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
@@ -32,7 +33,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
} as unknown as PluginHooks;
// CT2 (Plan 3j Task 5) — the day-id-set read is now DaysRepository.listIdsByTrip.
const days = { listIdsByTrip: vi.fn(async () => tripDays.value.map((d) => d.id)) } as unknown as DaysRepository;
return { c: new DayTintsController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository, days), runtime };
return { c: new DayTintsController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository), days), runtime };
}
const tint = (over: Record<string, unknown> = {}) => ({ dayId: 10, tone: 'success', ...over });
@@ -17,6 +17,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
import { MapLayersController } from '../../../src/nest/plugins/contributions/map-layers.controller';
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
import type { TripsRepository } from '../../../src/db/repositories/Trips.repository';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
@@ -25,7 +26,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
providersOf: vi.fn(() => providers),
mapLayers: vi.fn(async (id: string) => invoke(id)),
} as unknown as PluginHooks;
return { c: new MapLayersController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository), runtime };
return { c: new MapLayersController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository)), runtime };
}
const line = (n = 3, over: Record<string, unknown> = {}) => ({
type: 'polyline',
@@ -16,6 +16,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
import { MapMarkersController } from '../../../src/nest/plugins/contributions/map-markers.controller';
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
import type { TripsRepository } from '../../../src/db/repositories/Trips.repository';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
@@ -24,7 +25,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
providersOf: vi.fn(() => providers),
mapMarkers: vi.fn(async (id: string) => invoke(id)),
} as unknown as PluginHooks;
return { c: new MapMarkersController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository), runtime };
return { c: new MapMarkersController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository)), runtime };
}
const mk = (over: Record<string, unknown> = {}) => ({ id: 'm1', lat: 48.85, lng: 2.35, ...over });
@@ -17,6 +17,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
import { PdfSectionsController } from '../../../src/nest/plugins/contributions/pdf-sections.controller';
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
import type { TripsRepository } from '../../../src/db/repositories/Trips.repository';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
@@ -25,7 +26,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
providersOf: vi.fn(() => providers),
pdfSections: vi.fn(async (id: string) => invoke(id)),
} as unknown as PluginHooks;
return { c: new PdfSectionsController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository), runtime };
return { c: new PdfSectionsController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository)), runtime };
}
const sec = (over: Record<string, unknown> = {}) => ({ title: 'Weather', ...over });
@@ -15,6 +15,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
import { PlaceDetailsController } from '../../../src/nest/plugins/contributions/place-details.controller';
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
import type { PlacesRepository } from '../../../src/db/repositories/Places.repository';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
@@ -26,7 +27,7 @@ function controller(over: Partial<PluginHooks> = {}) {
} as unknown as PluginHooks;
// CT7 (Plan 3j Task 5) — the place's owning trip id is now Places.repository.ts#findTripId.
const places = { findTripId: vi.fn(async (placeId: number) => placeTrip(placeId)?.trip_id) } as unknown as PlacesRepository;
return { c: new PlaceDetailsController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository, places), runtime };
return { c: new PlaceDetailsController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository), places), runtime };
}
describe('PlaceDetailsController', () => {
@@ -22,6 +22,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
import { PluginRoutesController } from '../../../src/nest/plugins/contributions/plugin-routes.controller';
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
import type { PluginsRepository } from '../../../src/db/repositories/Plugins.repository';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
@@ -36,7 +37,7 @@ function controller(invoke: () => unknown, providers = ['ev-plug']) {
const plugins = {
findCapabilities: vi.fn(async () => (capabilitiesRow.value === undefined ? null : capabilitiesRow.value)),
} as unknown as PluginsRepository;
return { c: new PluginRoutesController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository, plugins), runtime };
return { c: new PluginRoutesController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository), plugins), runtime };
}
const wp = (n = 3) => Array.from({ length: n }, (_, i) => ({ lat: 48 + i * 0.1, lng: 2 + i * 0.1 }));
const goodRoute = (n = 3, over: Record<string, unknown> = {}) => ({
@@ -18,6 +18,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
import { TripCardContributionsController } from '../../../src/nest/plugins/contributions/trip-card-contributions.controller';
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
import type { TripsRepository } from '../../../src/db/repositories/Trips.repository';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
@@ -26,7 +27,7 @@ function controller(invoke: (id: string, args: unknown[]) => unknown, providers
providersOf: vi.fn(() => providers),
tripCards: vi.fn(async (id: string, _h: string, _fn: string, args: unknown[]) => invoke(id, args)),
} as unknown as PluginHooks;
return { c: new TripCardContributionsController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository), runtime };
return { c: new TripCardContributionsController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository)), runtime };
}
const badge = (over: Record<string, unknown> = {}) => ({ tripId: 1, id: 'b1', label: 'Visa', ...over });
@@ -10,6 +10,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
import { TripWarningsController } from '../../../src/nest/plugins/contributions/trip-warnings.controller';
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
import type { TripsRepository } from '../../../src/db/repositories/Trips.repository';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
@@ -20,7 +21,7 @@ function controller(over: Partial<PluginHooks> = {}) {
id === 'p2' ? [{ level: 'error', message: 'Day 3 is overpacked', dayId: 3 }] : [{ level: 'warning', message: 'Museum closed Mon', placeId: 7 }]),
...over,
} as unknown as PluginHooks;
return { c: new TripWarningsController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository), runtime };
return { c: new TripWarningsController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository)), runtime };
}
describe('TripWarningsController', () => {
@@ -17,6 +17,7 @@ vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
import { ViewContributionsController } from '../../../src/nest/plugins/contributions/view-contributions.controller';
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
import type { TripsRepository } from '../../../src/db/repositories/Trips.repository';
import { TripAccessService } from '../../../src/nest/trip-membership/trip-access.service';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) as any;
@@ -25,7 +26,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
providersOf: vi.fn(() => providers),
tableContributions: vi.fn(async (id: string) => invoke(id)),
} as unknown as PluginHooks;
return { c: new ViewContributionsController(runtime, { findAccessible: canAccessTrip } as unknown as TripsRepository), runtime };
return { c: new ViewContributionsController(runtime, new TripAccessService({ findAccessible: canAccessTrip } as unknown as TripsRepository)), runtime };
}
const col = (over: Record<string, unknown> = {}) => ({ kind: 'column', entityId: 1, id: 'c1', label: 'X', ...over });
const act = (over: Record<string, unknown> = {}) => ({ kind: 'action', entityId: 1, id: 'a1', label: 'Go', target: { kind: 'frame', sub: '/ui' }, ...over });