76 lines
2.9 KiB
JavaScript
76 lines
2.9 KiB
JavaScript
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, '재료');
|
|
});
|