import { beforeEach, describe, expect, it, vi } from 'vitest'; vi.mock('../../lib/sql.js', () => ({ sql: vi.fn(), })); vi.mock('../../lib/permission-middleware.js', () => ({ getUserFromRequest: vi.fn(), })); vi.mock('../../lib/rate-limit.js', () => ({ checkStoresRateLimit: vi.fn(), })); vi.mock('../../lib/pricing/geo-cache.js', () => ({ getZipCoords: vi.fn(), getStoresNear: vi.fn(), })); import { sql } from '../../lib/sql.js'; import { getUserFromRequest } from '../../lib/permission-middleware.js'; import { checkStoresRateLimit } from '../../lib/rate-limit.js'; import { getZipCoords, getStoresNear } from '../../lib/pricing/geo-cache.js'; import handler from '../../pages/api/stores/nearby.js'; function createRes() { const res = { statusCode: 200, headers: {}, body: null, status(code) { res.statusCode = code; return res; }, setHeader(name, value) { res.headers[name] = value; return res; }, json(data) { res.body = data; return res; }, }; return res; } describe('GET /api/stores/nearby', () => { beforeEach(() => { vi.clearAllMocks(); process.env.PRICING_LOCAL_ENABLED = 'true'; getUserFromRequest.mockResolvedValue({ userId: 1, email: 'a@b.c', role: 'user' }); checkStoresRateLimit.mockResolvedValue({ allowed: true, remaining: 0, reset: Date.now() + 3600000 }); sql.mockResolvedValue({ rows: [{ home_zip: '10001' }] }); getZipCoords.mockResolvedValue({ lat: 40.7, lng: -74.0, displayName: 'NYC', cached: true }); getStoresNear.mockResolvedValue({ stores: [{ name: 'Game Shop', distanceMiles: 1.2 }], cached: true, }); }); it('returns 401 without authentication', async () => { getUserFromRequest.mockResolvedValue(null); const res = createRes(); await handler({ method: 'GET', query: {} }, res); expect(res.statusCode).toBe(401); }); it('returns 503 when local pricing is disabled', async () => { process.env.PRICING_LOCAL_ENABLED = 'false'; const res = createRes(); await handler({ method: 'GET', query: {} }, res); expect(res.statusCode).toBe(503); }); it('returns 429 with Retry-After when rate limited', async () => { checkStoresRateLimit.mockResolvedValue({ allowed: false, remaining: 0, reset: Date.now() + 1800000, }); const res = createRes(); await handler({ method: 'GET', query: {} }, res); expect(res.statusCode).toBe(429); expect(res.headers['Retry-After']).toBeTruthy(); }); it('returns stores for authenticated user with saved zip', async () => { const res = createRes(); await handler({ method: 'GET', query: { zip: '90210' } }, res); expect(res.statusCode).toBe(200); expect(res.body.homeZip).toBe('10001'); expect(res.body.stores).toHaveLength(1); expect(getZipCoords).toHaveBeenCalledWith('10001'); expect(getStoresNear).toHaveBeenCalled(); }); it('returns 400 when home zip is not configured', async () => { sql.mockResolvedValue({ rows: [{ home_zip: null }] }); const res = createRes(); await handler({ method: 'GET', query: {} }, res); expect(res.statusCode).toBe(400); expect(res.body.code).toBe('NO_HOME_ZIP'); }); });