diff --git a/app/__tests__/ipuzApi.test.ts b/app/__tests__/ipuzApi.test.ts new file mode 100644 index 00000000..6d33be40 --- /dev/null +++ b/app/__tests__/ipuzApi.test.ts @@ -0,0 +1,58 @@ +/** + * @jest-environment node + */ + +import { jest } from '@jest/globals'; +import type { NextApiRequest, NextApiResponse } from 'next'; + +const getPuzzle = jest.fn<(id: string) => Promise>(); + +jest.unstable_mockModule('../lib/serverOnly.js', () => ({ getPuzzle })); +jest.unstable_mockModule('../lib/converter.js', () => ({ + exportIpuz: () => new TextEncoder().encode('{}'), +})); + +const { default: handler } = await import('../pages/api/ipuz/[puzzleId].js'); + +function mockResponse() { + const headers: Record = {}; + const res = { + headers, + statusCode: 0, + setHeader: jest.fn((k: string, v: string) => { + headers[k.toLowerCase()] = v; + }), + writeHead: jest.fn((status: number, h: Record) => { + res.statusCode = status; + for (const [k, v] of Object.entries(h)) { + headers[k.toLowerCase()] = v; + } + }), + end: jest.fn(), + status: jest.fn((s: number) => { + res.statusCode = s; + return res; + }), + json: jest.fn(), + }; + return res; +} + +test('ipuz download is not served as application/json', async () => { + // Android appends .json to downloads served as application/json, which + // turns "puzzle.ipuz" into "puzzle.ipuz.json". + getPuzzle.mockResolvedValue({ id: 'abc', t: 'My Puzzle!' }); + const res = mockResponse(); + + await handler( + { query: { puzzleId: 'abc' } } as unknown as NextApiRequest, + res as unknown as NextApiResponse + ); + + expect(res.statusCode).toBe(200); + expect(res.headers['content-type']).toBe('application/octet-stream'); + expect(res.headers['content-disposition']).toBe( + 'inline; filename="My Puzzle.ipuz"' + ); + expect(res.end).toHaveBeenCalled(); +}); diff --git a/app/pages/api/ipuz/[puzzleId].ts b/app/pages/api/ipuz/[puzzleId].ts index d4c14c3b..83ca22eb 100644 --- a/app/pages/api/ipuz/[puzzleId].ts +++ b/app/pages/api/ipuz/[puzzleId].ts @@ -25,6 +25,6 @@ export default async function ipuz(req: NextApiRequest, res: NextApiResponse) { 'Content-Disposition', 'inline; filename="' + puzzle.t.replace(/[^\w ]/g, '') + '.ipuz"' ); - res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.writeHead(200, { 'Content-Type': 'application/octet-stream' }); res.end(Buffer.from(exportIpuz(puzzle))); }