132 lines
3.9 KiB
TypeScript
132 lines
3.9 KiB
TypeScript
import { mkdtemp, rm } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
import { CatalogDatabase } from '../src/database/store.js';
|
|
import { runCatalog } from '../src/run/runCatalog.js';
|
|
|
|
let dir = '';
|
|
|
|
beforeEach(async () => {
|
|
dir = await mkdtemp(join(tmpdir(), 'nlc-run-'));
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(dir, { force: true, recursive: true });
|
|
});
|
|
|
|
describe('run orchestration', () => {
|
|
it('does not write output or state during dry run', async () => {
|
|
const stateFile = join(dir, 'state.json');
|
|
const writes: unknown[] = [];
|
|
const result = await runCatalog({
|
|
dryRun: 1,
|
|
skipEnrich: true,
|
|
config: {
|
|
gmail: { folder: 'Newsletters' },
|
|
output: { name: 'Catalog', excel: { enabled: true, path: join(dir, 'out.xlsx') } },
|
|
stateFile
|
|
},
|
|
messages: [
|
|
{
|
|
id: 'msg-1',
|
|
messageId: '<msg-1>',
|
|
from: 'A <a@example.com>',
|
|
date: '2026-05-16T00:00:00.000Z',
|
|
html: '<h2>Python</h2><p><a href="https://example.com?utm_source=x">Article</a></p>'
|
|
}
|
|
],
|
|
writers: [{ write: async (payload) => writes.push(payload) }]
|
|
});
|
|
|
|
expect(result.linksExtracted).toBe(1);
|
|
expect(writes).toHaveLength(0);
|
|
});
|
|
|
|
it('only sends locally marked sponsored links to the sponsored output', async () => {
|
|
const stateFile = join(dir, 'state.json');
|
|
const writes: any[] = [];
|
|
|
|
await runCatalog({
|
|
config: {
|
|
gmail: { folder: 'Newsletters' },
|
|
output: { name: 'Catalog', excel: { enabled: true, path: join(dir, 'out.xlsx') } },
|
|
stateFile
|
|
},
|
|
messages: [
|
|
{
|
|
id: 'msg-1',
|
|
messageId: '<msg-1>',
|
|
from: 'Web Tools Weekly <w@example.com>',
|
|
date: '2026-05-16T00:00:00.000Z',
|
|
html: `
|
|
<div>
|
|
<a href="https://cascade.example">Cascade</a> - CSS property icons.
|
|
<a href="https://frames.example">Fancy Frames</a> - Decorative borders.
|
|
SPONSORED
|
|
<a href="https://flexboxle.example">flexboxle</a> - A daily puzzle game.
|
|
<a href="https://types.example">Typescale AI</a> - A typescale generator.
|
|
</div>
|
|
`
|
|
}
|
|
],
|
|
writers: [{ write: async (payload) => writes.push(payload) }]
|
|
});
|
|
|
|
expect(writes[0].sponsors).toEqual([
|
|
{
|
|
Newsletter: 'Web Tools Weekly',
|
|
Sponsor: 'flexboxle',
|
|
Link: 'https://flexboxle.example/',
|
|
Description: 'A daily puzzle game.'
|
|
}
|
|
]);
|
|
expect(writes[0].rows.map((row: any) => row.Title)).toEqual([
|
|
'Cascade',
|
|
'Fancy Frames',
|
|
'Typescale AI'
|
|
]);
|
|
});
|
|
|
|
it('can persist run output to SQLite through a database writer', async () => {
|
|
const path = join(dir, 'catalog.sqlite');
|
|
const db = new CatalogDatabase(path);
|
|
db.migrate();
|
|
|
|
const result = await runCatalog({
|
|
config: {
|
|
gmail: { folder: 'Newsletters' },
|
|
output: { name: 'Catalog', excel: { enabled: false } },
|
|
stateFile: join(dir, 'state.json')
|
|
},
|
|
messages: [
|
|
{
|
|
id: 'msg-1',
|
|
messageId: '<msg-1>',
|
|
from: 'SQLite Weekly <sqlite@example.com>',
|
|
date: '2026-05-17T00:00:00.000Z',
|
|
html: '<h2>Databases</h2><a href="https://sqlite.example">SQLite Post</a> - Local data.'
|
|
}
|
|
],
|
|
writers: [
|
|
{
|
|
write: async (payload) =>
|
|
db.saveCatalogRun({
|
|
mode: 'test',
|
|
newslettersProcessed: 1,
|
|
linksExtracted: payload.rows.length,
|
|
sponsorCount: payload.sponsors.length,
|
|
deadLinkCount: payload.deadLinks.length,
|
|
errors: 0,
|
|
...payload
|
|
})
|
|
}
|
|
]
|
|
});
|
|
|
|
expect(result.linksExtracted).toBe(1);
|
|
expect(db.count('link_occurrences')).toBe(1);
|
|
db.close();
|
|
});
|
|
});
|