deckhearth/test/api/public-games.test.js
Randall Stillwell 5b9a278ca2 feat(designer): inline symbol icons, frame textures, community sharing, print sheets
- {CODE} tokens in description/actions/flavor render as inline symbol
  icons (RichText), including inside cost pips
- custom frames gain an optional background texture (upload/replace/
  remove via /api/custom-frames/[id]/texture); renders behind panels
- custom games can be shared to the community (is_public): toggle in the
  game space, public listing at /community/games, read-only game view,
  /api/public/games endpoints (no auth, public rows only)
- /designer/print: multi-card print sheets on US Letter at 300dpi
  (63x88mm cards, 3x3 or 2x2, dashed cut guides, full-sheet PNG export)
- migration 1787711511000
2026-08-24 21:48:52 -05:00

104 lines
2.6 KiB
JavaScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
vi.mock('../../lib/sql.js', () => ({ sql: vi.fn() }));
import { sql } from '../../lib/sql.js';
import listHandler from '../../pages/api/public/games/index.js';
import itemHandler from '../../pages/api/public/games/[id].js';
function createRes() {
const res = {
statusCode: 200,
body: null,
status(code) {
res.statusCode = code;
return res;
},
json(data) {
res.body = data;
return res;
},
};
return res;
}
describe('/api/public/games', () => {
beforeEach(() => {
vi.clearAllMocks();
sql.mockResolvedValue({ rows: [] });
});
it('rejects non-GET methods', async () => {
const res = createRes();
await listHandler({ method: 'POST' }, res);
expect(res.statusCode).toBe(405);
expect(sql).not.toHaveBeenCalled();
});
it('lists public games with author and card counts', async () => {
sql.mockResolvedValueOnce({
rows: [{ id: 9, name: 'Aetherfall', author: 'rstillw', card_count: 4 }],
});
const res = createRes();
await listHandler({ method: 'GET' }, res);
expect(res.statusCode).toBe(200);
expect(res.body.games).toHaveLength(1);
const listed = sql.mock.calls[0][0].join('');
expect(listed).toContain('is_public = true');
});
});
describe('/api/public/games/[id]', () => {
beforeEach(() => {
vi.clearAllMocks();
sql.mockResolvedValue({ rows: [] });
});
it('rejects invalid ids', async () => {
const res = createRes();
await itemHandler({ method: 'GET', query: { id: 'abc' } }, res);
expect(res.statusCode).toBe(400);
});
it('404s private or missing games', async () => {
sql.mockResolvedValueOnce({ rows: [] }); // public filter misses
const res = createRes();
await itemHandler({ method: 'GET', query: { id: '9' } }, res);
expect(res.statusCode).toBe(404);
});
it('returns the game and its designs with resolved frames', async () => {
sql
.mockResolvedValueOnce({
rows: [{ id: 9, name: 'Aetherfall', author: 'rstillw' }],
})
.mockResolvedValueOnce({
rows: [
{
id: 3,
name: 'Stormsage',
frame_pk: 2,
frame_name: 'Molten',
frame_palette: { outer: '#111111' },
frame_texture: null,
},
],
});
const res = createRes();
await itemHandler({ method: 'GET', query: { id: '9' } }, res);
expect(res.statusCode).toBe(200);
expect(res.body.game.name).toBe('Aetherfall');
expect(res.body.designs[0].custom_frame.name).toBe('Molten');
expect(res.body.designs[0].frame_pk).toBeUndefined();
});
});