import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { AddressInfo } from 'node:net'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { CatalogDatabase } from '../src/database/store.js'; import { createWebApp } from '../src/web/app.js'; let dir = ''; beforeEach(async () => { dir = await mkdtemp(join(tmpdir(), 'nlc-web-')); }); afterEach(async () => { await rm(dir, { force: true, recursive: true }); }); async function withServer(path: string, pathname: string): Promise { const app = createWebApp(path); const server = app.listen(0); try { const { port } = server.address() as AddressInfo; const response = await fetch(`http://127.0.0.1:${port}${pathname}`); return response.text(); } finally { server.close(); } } function fixtureDatabase(): string { const path = join(dir, 'catalog.sqlite'); const db = new CatalogDatabase(path); db.saveCatalogRun({ mode: 'test', newslettersProcessed: 1, linksExtracted: 1, sponsorCount: 1, deadLinkCount: 1, errors: 0, rows: [ { 'Issue Date': '2026-05-17', Category: 'SQLite', 'Link URL': 'https://sqlite.example', Title: 'SQLite Post', Description: 'A local database post', 'Page Title + Meta': '', 'Source Newsletter': 'DB Weekly', 'Also In': '' } ], sponsors: [ { Newsletter: 'DB Weekly', Sponsor: 'Acme', Link: 'https://sponsor.example', Description: 'Blurb' } ], deadLinks: [ { URL: 'https://dead.example', Status: '404', Source: 'DB Weekly', Date: '2026-05-17' } ] }); db.close(); return path; } describe('web app', () => { it('renders dashboard and catalog pages from SQLite', async () => { const path = fixtureDatabase(); await expect(withServer(path, '/')).resolves.toContain('Newsletter Link Catalog'); await expect(withServer(path, '/links')).resolves.toContain('SQLite Post'); await expect(withServer(path, '/sponsors')).resolves.toContain('Acme'); await expect(withServer(path, '/dead-links')).resolves.toContain('https://dead.example'); await expect(withServer(path, '/runs')).resolves.toContain('test'); }); });