import { execFileSync } from 'node:child_process';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { closeDatabase, getDatabase } from './database.js';
import {
discardFeedEntries,
fetchDueFeeds,
fetchFeed,
importFeedEntries,
listFeedEntries,
saveFeedSource
} from './feeds.js';
import { getPiece } from './content.js';
vi.mock('node:dns/promises', () => ({
lookup: vi.fn(async () => [{ address: '93.184.216.34', family: 4 }])
}));
const validFeed = `Test-
entry-1Ein Beitraghttps://example.org/post
Kurz und gut.
]]>
Sat, 24 Oct 2026 12:00:00 GMT`;
let directory: string;
beforeEach(() => {
directory = mkdtempSync(path.join(tmpdir(), 'sendewerk-feed-test-'));
process.env.SENDEWERK_DB_PATH = path.join(directory, 'test.sqlite');
process.env.SENDEWERK_UPLOAD_DIR = path.join(directory, 'uploads');
execFileSync(process.execPath, ['scripts/migrate.mjs'], { cwd: process.cwd(), env: process.env });
execFileSync(process.execPath, ['scripts/seed.mjs'], { cwd: process.cwd(), env: process.env });
});
afterEach(() => {
vi.unstubAllGlobals();
closeDatabase();
delete process.env.SENDEWERK_DB_PATH;
delete process.env.SENDEWERK_UPLOAD_DIR;
rmSync(directory, { recursive: true, force: true });
});
describe('RSS import inbox', () => {
it('keeps one entry across fetches and preserves a discard decision', async () => {
vi.stubGlobal(
'fetch',
vi.fn(
async () => new Response(validFeed, { headers: { 'content-type': 'application/rss+xml' } })
)
);
const feedId = saveFeedSource(null, {
name: 'Feed',
url: 'https://example.org/feed.xml',
isEnabled: true,
refreshIntervalMinutes: 60,
defaultCategoryId: null
});
await fetchFeed(feedId);
const [entry] = listFeedEntries();
expect(entry.title).toBe('Ein Beitrag');
expect(discardFeedEntries([entry.id])).toBe(1);
await fetchFeed(feedId);
expect(listFeedEntries()).toHaveLength(0);
expect(listFeedEntries('discarded')).toHaveLength(1);
});
it('sanitizes foreign HTML and retains the original source on explicit import', async () => {
vi.stubGlobal(
'fetch',
vi.fn(async () => new Response(validFeed))
);
const feedId = saveFeedSource(null, {
name: 'Feed',
url: 'https://example.org/feed.xml',
isEnabled: true,
refreshIntervalMinutes: 60,
defaultCategoryId: 'cat-jc'
});
await fetchFeed(feedId);
const [entry] = listFeedEntries();
const [pieceId] = importFeedEntries([entry.id], []);
const piece = getPiece(pieceId)!;
expect(piece.summary).toContain('Kurz und gut.');
expect(piece.summary).not.toContain('