first commit

This commit is contained in:
2026-08-13 18:36:31 +09:00
commit cee36bf12d
48 changed files with 4212 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { recipeDraftSchema, recipePatchSchema } from '../src/schemas/recipe.schema.js';
import {
normalizeModelJson,
normalizeRecipePayload,
RecipeParserService,
} from '../src/services/recipe-parser.service.js';
import { recipeDraft } from './helpers/fakes.js';
test('유효한 AI recipe 결과를 허용한다', () => {
assert.deepEqual(recipeDraftSchema.parse(recipeDraft), recipeDraft);
});
test('재료 이름이나 단계가 비어 있으면 거부한다', () => {
const invalid = structuredClone(recipeDraft);
invalid.ingredientGroups[0].items[0].name = '';
assert.equal(recipeDraftSchema.safeParse(invalid).success, false);
});
test('MiniMax reasoning 및 markdown fence에서 JSON만 분리한다', () => {
const value = normalizeModelJson('<think>reasoning</think>\n```json\n{"title":"test"}\n```');
assert.equal(value, '{"title":"test"}');
});
test('부분 수정은 보내지 않은 nullable/default 필드를 만들지 않는다', () => {
assert.deepEqual(recipePatchSchema.parse({ title: '새 제목' }), { title: '새 제목' });
});
test('태그를 표준 태그로 정리하고 최대 3개만 유지한다', () => {
const input = structuredClone(recipeDraft);
input.tags = [' #한식 ', '한식', ' QUICK MEAL ', '감자요리', '중화요리', '샐러드'];
assert.deepEqual(recipeDraftSchema.parse(input).tags, ['한식', '간단요리', '중식']);
assert.deepEqual(
recipePatchSchema.parse({ tags: ['##찌개', ' 샐러드 ', '식단', '고기'] }).tags,
['국물', '샐러드', '다이어트'],
);
});
test('AI가 비어 있는 재료 그룹 라벨을 반환하면 구조 라벨만 보완한다', () => {
const input = structuredClone(recipeDraft);
input.ingredientGroups[0].name = null;
const normalized = normalizeRecipePayload(input);
assert.equal(normalized.ingredientGroups[0].name, '재료');
assert.deepEqual(normalized.ingredientGroups[0].items, recipeDraft.ingredientGroups[0].items);
});
test('MiniMax 사고 과정 분리와 충분한 출력 한도를 요청한다', async () => {
let request;
const client = {
chat: {
completions: {
async create(parameters) {
request = parameters;
const output = structuredClone(recipeDraft);
output.ingredientGroups[0].name = null;
return { choices: [{ message: { content: JSON.stringify(output) } }] };
},
},
},
};
const service = new RecipeParserService({ client, model: 'MiniMax-M2.7' });
const result = await service.parse({
platform: 'youtube',
title: '김치찌개',
rawText: '[12.4] 김치를 볶는다.',
});
assert.equal(request.reasoning_split, true);
assert.equal(request.max_completion_tokens, 8192);
assert.match(request.messages[0].content, /최대 3개/);
assert.match(request.messages[0].content, /한식, 중식/);
assert.equal(result.ingredientGroups[0].name, '재료');
});