feat(ai): Extraction-Prompt + Gemini-Schema + Zod-Validator

This commit is contained in:
hsiegeln
2026-04-21 10:40:03 +02:00
parent 0cca9a699c
commit d479fd61d8
2 changed files with 156 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
import { describe, it, expect } from 'vitest';
import {
RECIPE_EXTRACTION_SYSTEM_PROMPT,
GEMINI_RESPONSE_SCHEMA,
extractionResponseSchema,
type ExtractionResponse
} from '../../src/lib/server/ai/recipe-extraction-prompt';
describe('recipe-extraction-prompt', () => {
it('system prompt is in German and mentions Rezept + Zutaten + Zubereitung', () => {
const p = RECIPE_EXTRACTION_SYSTEM_PROMPT.toLowerCase();
expect(p).toContain('rezept');
expect(p).toContain('zutaten');
expect(p).toContain('zubereitung');
});
it('Gemini response schema has required top-level keys', () => {
expect(GEMINI_RESPONSE_SCHEMA.type).toBeDefined();
expect(Object.keys(GEMINI_RESPONSE_SCHEMA.properties)).toEqual(
expect.arrayContaining(['title', 'ingredients', 'steps'])
);
});
it('Zod validator accepts a well-formed response', () => {
const good: ExtractionResponse = {
title: 'Testrezept',
servings_default: 4,
servings_unit: 'Portionen',
prep_time_min: 15,
cook_time_min: 30,
total_time_min: null,
ingredients: [{ quantity: 100, unit: 'g', name: 'Mehl', note: null }],
steps: [{ text: 'Mehl in eine Schüssel geben.' }]
};
expect(() => extractionResponseSchema.parse(good)).not.toThrow();
});
it('Zod validator rejects missing title', () => {
const bad = { servings_default: 4, ingredients: [], steps: [] };
expect(() => extractionResponseSchema.parse(bad)).toThrow();
});
it('Zod validator accepts quantity=null and unit=null', () => {
const ok: ExtractionResponse = {
title: 'Prise-Rezept',
servings_default: null,
servings_unit: null,
prep_time_min: null,
cook_time_min: null,
total_time_min: null,
ingredients: [{ quantity: null, unit: null, name: 'Salz', note: 'nach Geschmack' }],
steps: [{ text: 'Einfach so.' }]
};
expect(() => extractionResponseSchema.parse(ok)).not.toThrow();
});
it('Zod validator rejects unexpected extra top-level keys (strict)', () => {
const bad = {
title: 'x',
servings_default: null,
servings_unit: null,
prep_time_min: null,
cook_time_min: null,
total_time_min: null,
ingredients: [],
steps: [],
malicious_extra_field: 'pwned'
};
expect(() => extractionResponseSchema.parse(bad)).toThrow();
});
});