mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 06:26:26 +00:00
04f2ec72c6
Add "guest" trip participants — people without a Trek account who can still be assigned to costs, packing, to-dos and day-plan activities. A guest is a credential-less users row (is_guest=1) joined into trip_members, so it is assignable everywhere a real member is, with the cost-splitting, settlement, packing and assignment paths working unchanged. Guests are firewalled from everything account-related: they can never sign in (password, OIDC and reset lookups skip them), never appear in the global user directory, the member-add picker or admin user management, are never resolved as notification recipients, can't be invited to another trip, and can't be made owner. The trip owner manages guests from the share dialog in a dedicated, clearly-labelled section (add / rename / remove), and guests carry a "Guest" badge wherever members are picked. All 22 locales stay in parity.
95 lines
5.4 KiB
TypeScript
95 lines
5.4 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
const { dbMock } = vi.hoisted(() => {
|
|
const stmt = { get: vi.fn(() => ({ id: 42 })), all: vi.fn(() => []), run: vi.fn() };
|
|
return { dbMock: { prepare: vi.fn(() => stmt), _stmt: stmt } };
|
|
});
|
|
const { canAccessTrip } = vi.hoisted(() => ({ canAccessTrip: vi.fn(() => ({ user_id: 1 })) }));
|
|
vi.mock('../../../src/db/database', () => ({ db: dbMock, canAccessTrip, closeDb: () => {}, reinitialize: () => {} }));
|
|
|
|
const { broadcast } = vi.hoisted(() => ({ broadcast: vi.fn() }));
|
|
vi.mock('../../../src/websocket', () => ({ broadcast }));
|
|
const { checkPermission } = vi.hoisted(() => ({ checkPermission: vi.fn(() => true) }));
|
|
vi.mock('../../../src/services/permissions', () => ({ checkPermission }));
|
|
|
|
const { tripSvc } = vi.hoisted(() => ({
|
|
tripSvc: {
|
|
listTrips: vi.fn(), createTrip: vi.fn(), getTrip: vi.fn(), updateTrip: vi.fn(), deleteTrip: vi.fn(),
|
|
getTripRaw: vi.fn(), getTripOwner: vi.fn(), deleteOldCover: vi.fn(), updateCoverImage: vi.fn(),
|
|
listMembers: vi.fn(() => ({ owner: { id: 1 }, members: [] })), addMember: vi.fn(), removeMember: vi.fn(),
|
|
transferOwnership: vi.fn(),
|
|
createGuest: vi.fn(), renameGuest: vi.fn(), deleteGuest: vi.fn(),
|
|
exportICS: vi.fn(), copyTripById: vi.fn(), TRIP_SELECT: 'SELECT * FROM trips t',
|
|
},
|
|
}));
|
|
vi.mock('../../../src/services/tripService', () => tripSvc);
|
|
vi.mock('../../../src/services/dayService', () => ({ listDays: () => ({ days: [1] }), listAccommodations: () => [] }));
|
|
vi.mock('../../../src/services/placeService', () => ({ listPlaces: () => [] }));
|
|
vi.mock('../../../src/services/packingService', () => ({ listItems: () => [] }));
|
|
vi.mock('../../../src/services/todoService', () => ({ listItems: () => [] }));
|
|
vi.mock('../../../src/services/budgetService', () => ({ listBudgetItems: () => [] }));
|
|
vi.mock('../../../src/services/reservationService', () => ({ listReservations: () => [] }));
|
|
vi.mock('../../../src/services/fileService', () => ({ listFiles: () => [] }));
|
|
|
|
import { TripsService } from '../../../src/nest/trips/trips.service';
|
|
|
|
function svc() { return new TripsService(); }
|
|
beforeEach(() => vi.clearAllMocks());
|
|
|
|
describe('TripsService (wrapper delegation + bundle/copy/notify helpers)', () => {
|
|
it('delegates the simple wrappers to tripService', () => {
|
|
const s = svc();
|
|
s.list(1, 0); expect(tripSvc.listTrips).toHaveBeenCalledWith(1, 0);
|
|
s.create(1, { title: 'T' } as never); expect(tripSvc.createTrip).toHaveBeenCalledWith(1, { title: 'T' });
|
|
s.get('9', 1); expect(tripSvc.getTrip).toHaveBeenCalledWith('9', 1);
|
|
s.getRaw('9'); expect(tripSvc.getTripRaw).toHaveBeenCalledWith('9');
|
|
s.getOwner('9'); expect(tripSvc.getTripOwner).toHaveBeenCalledWith('9');
|
|
s.update('9', 1, {} as never, 'user'); expect(tripSvc.updateTrip).toHaveBeenCalledWith('9', 1, {}, 'user');
|
|
s.remove('9', 1, 'user'); expect(tripSvc.deleteTrip).toHaveBeenCalledWith('9', 1, 'user');
|
|
s.deleteOldCover('/old.jpg'); expect(tripSvc.deleteOldCover).toHaveBeenCalledWith('/old.jpg');
|
|
s.updateCoverImage('9', '/n.jpg'); expect(tripSvc.updateCoverImage).toHaveBeenCalledWith('9', '/n.jpg');
|
|
s.copy('9', 1, 'C'); expect(tripSvc.copyTripById).toHaveBeenCalledWith('9', 1, 'C');
|
|
s.listMembers('9', 1); expect(tripSvc.listMembers).toHaveBeenCalledWith('9', 1);
|
|
s.addMember('9', 'b@x.y', 1, 1); expect(tripSvc.addMember).toHaveBeenCalledWith('9', 'b@x.y', 1, 1);
|
|
s.removeMember('9', 2); expect(tripSvc.removeMember).toHaveBeenCalledWith('9', 2);
|
|
s.transferOwnership('9', 2, 1); expect(tripSvc.transferOwnership).toHaveBeenCalledWith('9', 2, 1);
|
|
s.createGuest('9', 'Anna', 1); expect(tripSvc.createGuest).toHaveBeenCalledWith('9', 'Anna', 1);
|
|
s.renameGuest('9', 7, 'Bob'); expect(tripSvc.renameGuest).toHaveBeenCalledWith('9', 7, 'Bob');
|
|
s.deleteGuest('9', 7); expect(tripSvc.deleteGuest).toHaveBeenCalledWith('9', 7);
|
|
s.exportICS('9'); expect(tripSvc.exportICS).toHaveBeenCalledWith('9');
|
|
});
|
|
|
|
it('canAccessTrip delegates to the db helper', () => {
|
|
canAccessTrip.mockReturnValueOnce({ user_id: 7 });
|
|
expect(svc().canAccessTrip('9', 7)).toEqual({ user_id: 7 });
|
|
expect(canAccessTrip).toHaveBeenCalledWith('9', 7);
|
|
});
|
|
|
|
it('can() delegates to checkPermission; broadcast forwards', () => {
|
|
svc().can('trip_edit', 'user', 1, 1, false);
|
|
expect(checkPermission).toHaveBeenCalledWith('trip_edit', 'user', 1, 1, false);
|
|
svc().broadcast('9', 'trip:updated', { a: 1 }, 'sock');
|
|
expect(broadcast).toHaveBeenCalledWith('9', 'trip:updated', { a: 1 }, 'sock');
|
|
});
|
|
|
|
it('getCopiedTrip re-reads via the TRIP_SELECT query', () => {
|
|
expect(svc().getCopiedTrip(42, 1)).toEqual({ id: 42 });
|
|
expect(dbMock.prepare).toHaveBeenCalledWith(expect.stringContaining('SELECT * FROM trips t'));
|
|
});
|
|
|
|
it('bundle aggregates every sub-collection + the member list', () => {
|
|
const result = svc().bundle('9', { user_id: 1 });
|
|
expect(result).toMatchObject({ trip: { user_id: 1 }, days: [1], places: [], members: [{ id: 1 }] });
|
|
});
|
|
|
|
it('bundle tolerates a null member list', () => {
|
|
tripSvc.listMembers.mockReturnValueOnce({ owner: { id: 1 }, members: null });
|
|
const result = svc().bundle('9', { user_id: 1 });
|
|
expect(result).toMatchObject({ members: [{ id: 1 }] });
|
|
});
|
|
|
|
it('notifyInvite is fire-and-forget (no throw)', () => {
|
|
expect(() => svc().notifyInvite('9', { id: 1, email: 'a@b.c' } as never, 2, 'T', 'b@x.y')).not.toThrow();
|
|
});
|
|
});
|