import assert from 'node:assert/strict'; import test from 'node:test'; import { applyRankingImagesToGroups, downloadNewsEngineMediaBytes, fetchLatestCollectionFromEngine, formatRankedNewsCandidates, isNewsEngineMediaUrl, rankNewsWithEngine, resolveNewsEngineEndpoint, shouldUseNewsEngineLatestCollection, } from './news-engine-client.mjs'; test('news engine client is disabled without an endpoint', async () => { assert.equal(resolveNewsEngineEndpoint({}), ''); const ranking = await rankNewsWithEngine([], { env: {}, fetchImpl: async () => { throw new Error('must not fetch'); }, }); assert.deepEqual(ranking.selected, []); }); test('news engine client sends groups and authentication to the independent service', async () => { const calls = []; const ranking = await rankNewsWithEngine([{ id: 'tech', results: [] }], { now: 123, env: { MEMIND_NEWS_ENGINE_URL: 'http://127.0.0.1:8092/', MEMIND_NEWS_ENGINE_API_TOKEN: 'service-token', MEMIND_NEWS_ENGINE_DAILY_LIMIT: '12', }, fetchImpl: async (url, init) => { calls.push({ url, init, body: JSON.parse(init.body) }); return { ok: true, async json() { return { candidateCount: 1, eventCount: 1, events: [], selected: [{ rank: 1, category: 'ai_tech', canonicalTitle: '模型发布', finalScore: 8.8, sourceCount: 2, mustInclude: true, primaryArticle: { sourceDomain: 'example.com', url: 'https://example.com/a' }, }], }; }, }; }, }); assert.equal(calls[0].url, 'http://127.0.0.1:8092/v1/rank'); assert.equal(calls[0].init.headers.authorization, 'Bearer service-token'); assert.equal(calls[0].body.options.limit, 12); assert.equal(ranking.selected[0].mustInclude, true); assert.match(formatRankedNewsCandidates(ranking), /模型发布/); }); test('isNewsEngineMediaUrl detects local news-engine media paths', () => { assert.equal(isNewsEngineMediaUrl('/media/abc.jpg'), true); assert.equal( isNewsEngineMediaUrl('http://127.0.0.1:8092/media/abc.jpg', { MEMIND_NEWS_ENGINE_URL: 'http://127.0.0.1:8092' }), true, ); assert.equal(isNewsEngineMediaUrl('https://cdn.example.com/a.jpg'), false); }); test('applyRankingImagesToGroups merges primaryArticle images by url', () => { const groups = applyRankingImagesToGroups([{ id: 'tech', results: [{ title: 'A', url: 'https://example.com/a' }], }], { selected: [{ primaryArticle: { url: 'https://example.com/a', image: 'http://127.0.0.1:8092/media/abc.jpg', }, }], }); assert.equal(groups[0].results[0].image, 'http://127.0.0.1:8092/media/abc.jpg'); assert.equal(groups[0].results[0].imageSource, 'news-engine'); }); test('shouldUseNewsEngineLatestCollection requires endpoint', () => { assert.equal(shouldUseNewsEngineLatestCollection({}), false); assert.equal( shouldUseNewsEngineLatestCollection({ MEMIND_NEWS_ENGINE_URL: 'http://127.0.0.1:8092' }), true, ); assert.equal( shouldUseNewsEngineLatestCollection({ MEMIND_NEWS_ENGINE_URL: 'http://127.0.0.1:8092', MEMIND_NEWS_ENGINE_USE_LATEST_COLLECTION: 'false', }), false, ); }); test('fetchLatestCollectionFromEngine returns latest snapshot', async () => { const collection = await fetchLatestCollectionFromEngine({ env: { MEMIND_NEWS_ENGINE_URL: 'http://127.0.0.1:8092', MEMIND_NEWS_ENGINE_API_TOKEN: 'token', }, fetchImpl: async (url, init) => { assert.equal(url, 'http://127.0.0.1:8092/v1/collections/latest'); assert.equal(init.headers.authorization, 'Bearer token'); return { ok: true, status: 200, async json() { return { id: 'c1', status: 'ok', dateLabel: '2026-09-22', groups: [{ id: 'tech', results: [{}] }] }; }, }; }, }); assert.equal(collection.id, 'c1'); }); test('downloadNewsEngineMediaBytes fetches /media assets from news-engine', async () => { const bytes = await downloadNewsEngineMediaBytes('http://127.0.0.1:8092/media/abc.jpg', { env: { MEMIND_NEWS_ENGINE_URL: 'http://127.0.0.1:8092' }, fetchImpl: async (url) => { assert.equal(url, 'http://127.0.0.1:8092/media/abc.jpg'); return { ok: true, arrayBuffer: async () => Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0, 0, 0, 0, 0, 0, 0, 0]), }; }, }); assert.ok(Buffer.isBuffer(bytes)); }); test('formatRankedNewsCandidates includes local image urls', () => { const block = formatRankedNewsCandidates({ selected: [{ rank: 1, category: 'tech', canonicalTitle: '测试', finalScore: 8, sourceCount: 1, mustInclude: false, primaryArticle: { url: 'https://example.com/a', image: 'http://127.0.0.1:8092/media/abc.jpg', sourceDomain: 'example.com', }, }], }); assert.match(block, /图:http:\/\/127\.0\.0\.1:8092\/media\/abc\.jpg/); }); test('news engine client fails open when the service is unavailable', async () => { const warnings = []; const ranking = await rankNewsWithEngine([], { env: { MEMIND_NEWS_ENGINE_URL: 'http://127.0.0.1:8092' }, fetchImpl: async () => { throw new Error('offline'); }, logger: { warn: (...args) => warnings.push(args) }, }); assert.deepEqual(ranking.selected, []); assert.match(String(warnings[0]), /offline/); });