import { FastifyRequest, FastifyReply } from 'fastify'; import { requireTier } from './require-tier'; // Mock logger to suppress output during tests jest.mock('../logging/logger', () => ({ logger: { error: jest.fn(), warn: jest.fn(), debug: jest.fn(), info: jest.fn(), }, })); const createRequest = (subscriptionTier?: string): Partial => { if (subscriptionTier === undefined) { return { userContext: undefined }; } return { userContext: { userId: '550e8400-e29b-41d4-a716-446655440000', email: 'user@example.com', emailVerified: true, onboardingCompleted: true, isAdmin: false, subscriptionTier: subscriptionTier as any, }, }; }; const createReply = (): Partial & { statusCode?: number; payload?: unknown } => { const reply: any = { sent: false, code: jest.fn(function (this: any, status: number) { this.statusCode = status; return this; }), send: jest.fn(function (this: any, payload: unknown) { this.payload = payload; this.sent = true; return this; }), }; return reply; }; describe('requireTier middleware', () => { afterEach(() => { jest.clearAllMocks(); }); describe('pro user passes fuelLog.receiptScan check', () => { it('allows pro user through without sending a response', async () => { const handler = requireTier('fuelLog.receiptScan'); const request = createRequest('pro'); const reply = createReply(); await handler(request as FastifyRequest, reply as FastifyReply); expect(reply.code).not.toHaveBeenCalled(); expect(reply.send).not.toHaveBeenCalled(); }); }); describe('enterprise user passes all checks (tier inheritance)', () => { it('allows enterprise user access to pro-gated features', async () => { const handler = requireTier('fuelLog.receiptScan'); const request = createRequest('enterprise'); const reply = createReply(); await handler(request as FastifyRequest, reply as FastifyReply); expect(reply.code).not.toHaveBeenCalled(); expect(reply.send).not.toHaveBeenCalled(); }); it('allows enterprise user access to document.scanMaintenanceSchedule', async () => { const handler = requireTier('document.scanMaintenanceSchedule'); const request = createRequest('enterprise'); const reply = createReply(); await handler(request as FastifyRequest, reply as FastifyReply); expect(reply.code).not.toHaveBeenCalled(); expect(reply.send).not.toHaveBeenCalled(); }); it('allows enterprise user access to vehicle.vinDecode', async () => { const handler = requireTier('vehicle.vinDecode'); const request = createRequest('enterprise'); const reply = createReply(); await handler(request as FastifyRequest, reply as FastifyReply); expect(reply.code).not.toHaveBeenCalled(); expect(reply.send).not.toHaveBeenCalled(); }); }); describe('free user blocked with 403 and correct response body', () => { it('blocks free user from fuelLog.receiptScan', async () => { const handler = requireTier('fuelLog.receiptScan'); const request = createRequest('free'); const reply = createReply(); await handler(request as FastifyRequest, reply as FastifyReply); expect(reply.code).toHaveBeenCalledWith(403); expect(reply.send).toHaveBeenCalledWith( expect.objectContaining({ error: 'TIER_REQUIRED', requiredTier: 'pro', currentTier: 'free', featureName: 'Receipt Scan', upgradePrompt: expect.any(String), }), ); }); it('blocks free user from document.scanMaintenanceSchedule', async () => { const handler = requireTier('document.scanMaintenanceSchedule'); const request = createRequest('free'); const reply = createReply(); await handler(request as FastifyRequest, reply as FastifyReply); expect(reply.code).toHaveBeenCalledWith(403); expect(reply.send).toHaveBeenCalledWith( expect.objectContaining({ error: 'TIER_REQUIRED', requiredTier: 'pro', currentTier: 'free', featureName: 'Scan for Maintenance Schedule', upgradePrompt: expect.any(String), }), ); }); it('response body includes all required fields', async () => { const handler = requireTier('fuelLog.receiptScan'); const request = createRequest('free'); const reply = createReply(); await handler(request as FastifyRequest, reply as FastifyReply); const body = (reply.send as jest.Mock).mock.calls[0][0]; expect(body).toHaveProperty('requiredTier', 'pro'); expect(body).toHaveProperty('currentTier', 'free'); expect(body).toHaveProperty('featureName', 'Receipt Scan'); expect(body).toHaveProperty('upgradePrompt'); expect(typeof body.upgradePrompt).toBe('string'); expect(body.upgradePrompt.length).toBeGreaterThan(0); }); }); describe('unknown feature key returns 500', () => { it('returns 500 INTERNAL_ERROR for unregistered feature', async () => { const handler = requireTier('unknown.nonexistent.feature'); const request = createRequest('pro'); const reply = createReply(); await handler(request as FastifyRequest, reply as FastifyReply); expect(reply.code).toHaveBeenCalledWith(500); expect(reply.send).toHaveBeenCalledWith( expect.objectContaining({ error: 'INTERNAL_ERROR', message: 'Unknown feature configuration', }), ); }); }); describe('missing user.tier on request returns 403', () => { it('defaults to free tier when userContext is undefined', async () => { const handler = requireTier('fuelLog.receiptScan'); const request = createRequest(); // no tier = undefined userContext const reply = createReply(); await handler(request as FastifyRequest, reply as FastifyReply); expect(reply.code).toHaveBeenCalledWith(403); expect(reply.send).toHaveBeenCalledWith( expect.objectContaining({ error: 'TIER_REQUIRED', currentTier: 'free', requiredTier: 'pro', }), ); }); }); });