feat(import): manuelle URL-Importe von allen Domains zulassen
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m14s

Der User pastet bewusst eine URL und erwartet, dass der Import
klappt — die Whitelist-Prüfung (DOMAIN_BLOCKED) im previewRecipe
war da nur Reibung. Die Whitelist bleibt für die Web-Suche relevant
(dort muss das Crawl-Feld eingeschränkt werden), für Imports nicht
mehr.

Dropped: isDomainAllowed + whitelist.ts, DOMAIN_BLOCKED-Code in
ImporterError, die zugehörige Branch in mapImporterError. Tests
entsprechend angepasst: statt "DOMAIN_BLOCKED wenn nicht whitelisted"
prüft der Preview-Test jetzt "klappt auch ohne Whitelist-Eintrag".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-18 15:18:46 +02:00
parent 7233cc3a13
commit 2807dd1cab
5 changed files with 20 additions and 52 deletions

View File

@@ -7,7 +7,6 @@ import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { openInMemoryForTest } from '../../src/lib/server/db';
import { addDomain } from '../../src/lib/server/domains/repository';
import { importRecipe, previewRecipe, ImporterError } from '../../src/lib/server/recipes/importer';
const here = dirname(fileURLToPath(import.meta.url));
@@ -61,17 +60,9 @@ afterEach(async () => {
});
describe('previewRecipe', () => {
it('throws DOMAIN_BLOCKED if host not whitelisted', async () => {
it('accepts any domain — manuelle URL-Importe sind nicht auf die Whitelist beschränkt', async () => {
const db = openInMemoryForTest();
// note: no domain added
await expect(previewRecipe(db, `${baseUrl}/recipe`)).rejects.toMatchObject({
code: 'DOMAIN_BLOCKED'
});
});
it('returns parsed recipe for whitelisted domain', async () => {
const db = openInMemoryForTest();
addDomain(db, '127.0.0.1');
// keine Domain in der Whitelist — preview muss trotzdem klappen
const r = await previewRecipe(db, `${baseUrl}/recipe`);
expect(r.title.toLowerCase()).toContain('schupfnudel');
expect(r.source_url).toBe(`${baseUrl}/recipe`);
@@ -80,17 +71,22 @@ describe('previewRecipe', () => {
it('throws NO_RECIPE_FOUND when HTML has no Recipe JSON-LD', async () => {
const db = openInMemoryForTest();
addDomain(db, '127.0.0.1');
await expect(previewRecipe(db, `${baseUrl}/bare`)).rejects.toMatchObject({
code: 'NO_RECIPE_FOUND'
});
});
it('throws INVALID_URL for malformed input', async () => {
const db = openInMemoryForTest();
await expect(previewRecipe(db, 'not a url')).rejects.toMatchObject({
code: 'INVALID_URL'
});
});
});
describe('importRecipe', () => {
it('imports, persists, and is idempotent', async () => {
const db = openInMemoryForTest();
addDomain(db, '127.0.0.1');
const first = await importRecipe(db, imgDir, `${baseUrl}/recipe`);
expect(first.duplicate).toBe(false);
expect(first.id).toBeGreaterThan(0);
@@ -104,9 +100,9 @@ describe('importRecipe', () => {
expect(second.id).toBe(first.id);
});
it('surfaces ImporterError type', async () => {
it('surfaces ImporterError type when no recipe on page', async () => {
const db = openInMemoryForTest();
await expect(importRecipe(db, imgDir, `${baseUrl}/recipe`)).rejects.toBeInstanceOf(
await expect(importRecipe(db, imgDir, `${baseUrl}/bare`)).rejects.toBeInstanceOf(
ImporterError
);
});

View File

@@ -8,7 +8,6 @@ import {
updateDomain,
getDomainById
} from '../../src/lib/server/domains/repository';
import { isDomainAllowed } from '../../src/lib/server/domains/whitelist';
describe('allowed domains', () => {
it('round-trips domains', () => {
@@ -19,18 +18,10 @@ describe('allowed domains', () => {
expect(all.map((d) => d.domain).sort()).toEqual(['chefkoch.de', 'emmikochteinfach.de']);
});
it('normalizes www. and case', () => {
it('normalizes www. and case via addDomain', () => {
const db = openInMemoryForTest();
addDomain(db, 'WWW.Chefkoch.DE');
expect(isDomainAllowed(db, 'https://chefkoch.de/abc')).toBe(true);
expect(isDomainAllowed(db, 'https://www.chefkoch.de/abc')).toBe(true);
expect(isDomainAllowed(db, 'https://fake.de/abc')).toBe(false);
});
it('rejects invalid urls', () => {
const db = openInMemoryForTest();
addDomain(db, 'chefkoch.de');
expect(isDomainAllowed(db, 'not a url')).toBe(false);
expect(listDomains(db)[0].domain).toBe('chefkoch.de');
});
it('removes domains', () => {