Files
TREK/server/tests/unit/nest/trip-writer.test.ts
T

117 lines
4.4 KiB
TypeScript

import { DomainError } from '../../../src/nest/common/domain-error';
import {
requireTripWrite,
restTripEvents,
restTripWriter,
tripNotFound,
tripPermissionDenied,
type TripWriter,
} from '../../../src/nest/common/trip-writer';
import { describe, it, expect, vi } from 'vitest';
describe('restTripEvents', () => {
it('sends a room event skipping the sender, with the four arguments the routes always passed', () => {
const broadcast = vi.fn();
restTripEvents(broadcast, '5', 'sock').emit('packing:deleted', { itemId: 3 });
expect(broadcast.mock.calls).toEqual([['5', 'packing:deleted', { itemId: 3 }, 'sock']]);
});
it('treats null as the room too', () => {
const broadcast = vi.fn();
restTripEvents(broadcast, 5, undefined).emit('packing:deleted', { itemId: 3 }, null);
expect(broadcast.mock.calls).toEqual([[5, 'packing:deleted', { itemId: 3 }, undefined]]);
});
it('scopes a restricted event to each named user once', () => {
const broadcast = vi.fn();
restTripEvents(broadcast, '5', 'sock').emit('packing:deleted', { itemId: 3 }, [1, 2, 1]);
expect(broadcast.mock.calls).toEqual([
['5', 'packing:deleted', { itemId: 3 }, 'sock', 1],
['5', 'packing:deleted', { itemId: 3 }, 'sock', 2],
]);
});
it('sends emitAll to every socket, the sender included', () => {
const broadcast = vi.fn();
restTripEvents(broadcast, '5', 'sock').emitAll('reservation:created', {});
expect(broadcast.mock.calls).toEqual([['5', 'reservation:created', {}, undefined]]);
});
});
describe('restTripWriter', () => {
it('takes the user, the socket and RealtimeService.broadcast bound to its instance', () => {
const realtime = {
calls: [] as unknown[][],
broadcast(this: { calls: unknown[][] }, ...args: unknown[]) {
this.calls.push(args);
},
};
const writer = restTripWriter(realtime, '5', { id: 7, role: 'admin' }, 'sock');
expect(writer).toMatchObject({ userId: 7, role: 'admin', socketId: 'sock', surface: 'rest' });
writer.events.emit('packing:deleted', { itemId: 1 });
expect(realtime.calls).toEqual([['5', 'packing:deleted', { itemId: 1 }, 'sock']]);
});
});
describe('requireTripWrite', () => {
const writer = (userId = 1): TripWriter => ({
userId,
role: 'user',
surface: 'mcp',
events: { emit: vi.fn(), emitAll: vi.fn() },
});
it('answers the trip when it is visible and the action is allowed', async () => {
const trip = { user_id: 1 };
const checkPermission = vi.fn().mockResolvedValue(true);
const findAccessible = vi.fn().mockResolvedValue(trip);
expect(
await requireTripWrite(
{ access: { findAccessible }, permissions: { checkPermission } },
'day_edit',
'5',
writer(),
),
).toBe(trip);
// The id reaches the lookup as given; the owner is no member of their own trip.
expect(findAccessible).toHaveBeenCalledWith('5', 1);
expect(checkPermission).toHaveBeenCalledWith('day_edit', 'user', 1, 1, false);
});
it('refuses an invisible trip with 404, before asking about the permission', async () => {
const checkPermission = vi.fn();
const err = await requireTripWrite(
{ access: { findAccessible: vi.fn().mockResolvedValue(undefined) }, permissions: { checkPermission } },
'day_edit',
5,
writer(),
).catch((e: unknown) => e);
expect(err).toBeInstanceOf(DomainError);
expect((err as DomainError).getStatus()).toBe(404);
expect(checkPermission).not.toHaveBeenCalled();
});
it('refuses a member without the action with 403', async () => {
const err = await requireTripWrite(
{
access: { findAccessible: vi.fn().mockResolvedValue({ user_id: 2 }) },
permissions: { checkPermission: vi.fn().mockResolvedValue(false) },
},
'packing_edit',
5,
writer(),
).catch((e: unknown) => e);
expect((err as DomainError).getStatus()).toBe(403);
});
});
describe('trip refusals', () => {
it("carry REST's guard bodies and the wording MCP always answered with", () => {
expect(tripNotFound().toBody()).toEqual({ error: 'Trip not found' });
expect(tripNotFound().mcpMessage).toBe('Trip not found or access denied.');
expect(tripPermissionDenied().toBody()).toEqual({ error: 'No permission' });
expect(tripPermissionDenied().mcpMessage).toBe('You do not have permission to perform this action on this trip.');
});
});