deckhearth/test/api/stores-nearby.test.js

102 lines
3.2 KiB
JavaScript
Raw Permalink Normal View History

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');
});
});