Spaces:
Sleeping
Sleeping
File size: 5,650 Bytes
1f5ea39 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | /**
* Unit tests for requireTripAccess and requireTripOwner middleware.
* TRIP-ACCESS-001 through TRIP-ACCESS-010.
* canAccessTrip and isOwner are mocked; no DB required.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { Request, Response, NextFunction } from 'express';
const mockCanAccessTrip = vi.fn();
const mockIsOwner = vi.fn();
vi.mock('../../../src/db/database', () => ({
canAccessTrip: (...args: any[]) => mockCanAccessTrip(...args),
isOwner: (...args: any[]) => mockIsOwner(...args),
}));
vi.mock('../../../src/config', () => ({ JWT_SECRET: 'test-secret' }));
import { requireTripAccess, requireTripOwner } from '../../../src/middleware/tripAccess';
function makeRes(): { res: Response; status: ReturnType<typeof vi.fn>; json: ReturnType<typeof vi.fn> } {
const json = vi.fn();
const status = vi.fn(() => ({ json }));
const res = { status } as unknown as Response;
return { res, status, json };
}
function makeReq(params: Record<string, string> = {}, userId = 1): Request {
return {
params,
user: { id: userId },
} as unknown as Request;
}
beforeEach(() => {
mockCanAccessTrip.mockReset();
mockIsOwner.mockReset();
});
// ββ requireTripAccess βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
describe('requireTripAccess', () => {
it('TRIP-ACCESS-001: returns 400 when no tripId param', () => {
const next = vi.fn() as unknown as NextFunction;
const { res, status, json } = makeRes();
requireTripAccess(makeReq({}), res, next);
expect(next).not.toHaveBeenCalled();
expect(status).toHaveBeenCalledWith(400);
expect(json).toHaveBeenCalledWith(expect.objectContaining({ error: expect.any(String) }));
});
it('TRIP-ACCESS-002: returns 404 when canAccessTrip returns null (not a member)', () => {
mockCanAccessTrip.mockReturnValue(null);
const next = vi.fn() as unknown as NextFunction;
const { res, status, json } = makeRes();
requireTripAccess(makeReq({ tripId: '42' }), res, next);
expect(next).not.toHaveBeenCalled();
expect(status).toHaveBeenCalledWith(404);
expect(json).toHaveBeenCalledWith(expect.objectContaining({ error: expect.any(String) }));
});
it('TRIP-ACCESS-003: calls next and attaches trip when user has access', () => {
const fakeTrip = { id: 42, user_id: 1 };
mockCanAccessTrip.mockReturnValue(fakeTrip);
const next = vi.fn() as unknown as NextFunction;
const { res } = makeRes();
const req = makeReq({ tripId: '42' }, 1);
requireTripAccess(req, res, next);
expect(next).toHaveBeenCalledOnce();
expect((req as any).trip).toEqual(fakeTrip);
});
it('TRIP-ACCESS-004: accepts req.params.id as fallback when tripId is absent', () => {
const fakeTrip = { id: 7, user_id: 2 };
mockCanAccessTrip.mockReturnValue(fakeTrip);
const next = vi.fn() as unknown as NextFunction;
const { res } = makeRes();
requireTripAccess(makeReq({ id: '7' }), res, next);
expect(mockCanAccessTrip).toHaveBeenCalledWith(7, expect.any(Number));
expect(next).toHaveBeenCalledOnce();
});
it('TRIP-ACCESS-005: passes numeric tripId to canAccessTrip', () => {
mockCanAccessTrip.mockReturnValue({ id: 99, user_id: 3 });
const next = vi.fn() as unknown as NextFunction;
const { res } = makeRes();
requireTripAccess(makeReq({ tripId: '99' }, 3), res, next);
expect(mockCanAccessTrip).toHaveBeenCalledWith(99, 3);
});
});
// ββ requireTripOwner ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
describe('requireTripOwner', () => {
it('TRIP-ACCESS-006: returns 400 when no tripId param', () => {
const next = vi.fn() as unknown as NextFunction;
const { res, status, json } = makeRes();
requireTripOwner(makeReq({}), res, next);
expect(next).not.toHaveBeenCalled();
expect(status).toHaveBeenCalledWith(400);
expect(json).toHaveBeenCalledWith(expect.objectContaining({ error: expect.any(String) }));
});
it('TRIP-ACCESS-007: returns 403 when user is not the owner', () => {
mockIsOwner.mockReturnValue(false);
const next = vi.fn() as unknown as NextFunction;
const { res, status, json } = makeRes();
requireTripOwner(makeReq({ tripId: '10' }, 2), res, next);
expect(next).not.toHaveBeenCalled();
expect(status).toHaveBeenCalledWith(403);
expect(json).toHaveBeenCalledWith(expect.objectContaining({ error: expect.any(String) }));
});
it('TRIP-ACCESS-008: calls next when user is the owner', () => {
mockIsOwner.mockReturnValue(true);
const next = vi.fn() as unknown as NextFunction;
const { res } = makeRes();
requireTripOwner(makeReq({ tripId: '10' }, 1), res, next);
expect(next).toHaveBeenCalledOnce();
});
it('TRIP-ACCESS-009: accepts req.params.id as fallback when tripId is absent', () => {
mockIsOwner.mockReturnValue(true);
const next = vi.fn() as unknown as NextFunction;
const { res } = makeRes();
requireTripOwner(makeReq({ id: '5' }, 1), res, next);
expect(mockIsOwner).toHaveBeenCalledWith(5, 1);
expect(next).toHaveBeenCalledOnce();
});
it('TRIP-ACCESS-010: passes numeric tripId to isOwner', () => {
mockIsOwner.mockReturnValue(true);
const next = vi.fn() as unknown as NextFunction;
const { res } = makeRes();
requireTripOwner(makeReq({ tripId: '77' }, 4), res, next);
expect(mockIsOwner).toHaveBeenCalledWith(77, 4);
});
});
|