import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import Fastify from 'fastify' import { testRoutes } from '../routes/testApi.js' async function buildApp() { const app = Fastify() await app.register(testRoutes, { prefix: '/test' }) return app } describe('GET /test/health', () => { it('returns ok status with binary availability map', async () => { const app = await buildApp() const res = await app.inject({ method: 'GET', url: '/test/health' }) expect(res.statusCode).toBe(200) const body = res.json() expect(body.status).toBe('ok') expect(body.binaries).toHaveProperty('chafa') expect(body.binaries).toHaveProperty('jp2a') expect(body.binaries).toHaveProperty('imagemagick') expect(typeof body.binaries.chafa.available).toBe('boolean') expect(body.timestamp).toBeTruthy() }) }) describe('GET /test/flags/:tool', () => { it('returns flag list for chafa with width/height not size', async () => { const app = await buildApp() const res = await app.inject({ method: 'GET', url: '/test/flags/chafa' }) expect(res.statusCode).toBe(200) const body = res.json() expect(body.tool).toBe('chafa') expect(Array.isArray(body.flags)).toBe(true) const names = body.flags.map(f => f.name) expect(names).toContain('width') expect(names).toContain('height') expect(names).not.toContain('size') expect(names).toContain('colors') expect(names).toContain('threshold') expect(names).toContain('format') }) it('returns flag list for jp2a', async () => { const app = await buildApp() const res = await app.inject({ method: 'GET', url: '/test/flags/jp2a' }) expect(res.statusCode).toBe(200) const body = res.json() expect(body.tool).toBe('jp2a') const names = body.flags.map(f => f.name) expect(names).toContain('width') expect(names).toContain('background') }) it('returns 404 for unknown tool', async () => { const app = await buildApp() const res = await app.inject({ method: 'GET', url: '/test/flags/unknown-tool' }) expect(res.statusCode).toBe(404) }) it('includes enum options for enum-type flags', async () => { const app = await buildApp() const res = await app.inject({ method: 'GET', url: '/test/flags/chafa' }) const body = res.json() const colorsFlag = body.flags.find(f => f.name === 'colors') expect(colorsFlag.type).toBe('enum') expect(Array.isArray(colorsFlag.options)).toBe(true) expect(colorsFlag.options).toContain('256') }) it('marks bool flags as boolean type', async () => { const app = await buildApp() const res = await app.inject({ method: 'GET', url: '/test/flags/chafa' }) const body = res.json() const zoomFlag = body.flags.find(f => f.name === 'zoom') expect(zoomFlag.type).toBe('boolean') }) }) describe('POST /test/convert — URL validation', () => { it('rejects missing imageUrl', async () => { const app = await buildApp() const res = await app.inject({ method: 'POST', url: '/test/convert', headers: { 'content-type': 'application/json' }, payload: { tool: 'chafa', flags: {} }, }) expect(res.statusCode).toBe(400) }) it('rejects missing tool', async () => { const app = await buildApp() const res = await app.inject({ method: 'POST', url: '/test/convert', headers: { 'content-type': 'application/json' }, payload: { imageUrl: 'https://example.com/img.png' }, }) expect(res.statusCode).toBe(400) }) it('rejects non-http URL schemes', async () => { const app = await buildApp() const res = await app.inject({ method: 'POST', url: '/test/convert', headers: { 'content-type': 'application/json' }, payload: { imageUrl: 'file:///etc/passwd', tool: 'chafa', flags: {} }, }) expect(res.statusCode).toBe(400) expect(res.json().error).toMatch(/http/) }) it('rejects unknown tool', async () => { const app = await buildApp() const res = await app.inject({ method: 'POST', url: '/test/convert', headers: { 'content-type': 'application/json' }, payload: { imageUrl: 'https://example.com/img.png', tool: 'evil-tool', flags: {} }, }) expect(res.statusCode).toBe(400) }) }) describe('POST /test/imagemagick — URL validation', () => { it('rejects missing imageUrl', async () => { const app = await buildApp() const res = await app.inject({ method: 'POST', url: '/test/imagemagick', headers: { 'content-type': 'application/json' }, payload: {}, }) expect(res.statusCode).toBe(400) }) it('rejects file:// scheme', async () => { const app = await buildApp() const res = await app.inject({ method: 'POST', url: '/test/imagemagick', headers: { 'content-type': 'application/json' }, payload: { imageUrl: 'file:///tmp/img.png' }, }) expect(res.statusCode).toBe(400) expect(res.json().error).toMatch(/http/) }) })