import { describe, expect, it } from "vitest"; import { articleInputSchema, confirmedFactCardSchema, candidateFactCardSchema, copyOptimizationRequestSchema, copyOptimizationResultSchema, optimizationFactCardSchema, optimizedArticleSchema, qaReportSchema, } from "../validation"; describe("domain validation", () => { it("accepts article input with title, body, images, platform, and instructions", () => { const parsed = articleInputSchema.parse({ title: "How GEO Optimization Improves Brand Visibility", body: "A practical overview of GEO optimization for marketing teams.", images: [ { type: "description", content: "Dashboard screenshot" }, { type: "link", content: "https://example.com/image.png" }, ], platform: "official_site", user_instructions: "Keep the article factual and concise.", }); expect(parsed.images).toHaveLength(2); expect(parsed.platform).toBe("official_site"); }); it("accepts article input with an empty optional title", () => { const parsed = articleInputSchema.parse({ title: " ", body: "完整文章正文可以直接粘贴在这里。", images: [], platform: "official_site", user_instructions: "", }); expect(parsed.title).toBe(""); expect(parsed.body).toBe("完整文章正文可以直接粘贴在这里。"); }); it("still rejects article input with an empty body", () => { expect(() => articleInputSchema.parse({ title: "", body: " ", images: [], platform: "official_site", user_instructions: "", }), ).toThrow(); }); it("accepts a trimmed copy optimization request", () => { const parsed = copyOptimizationRequestSchema.parse({ source_text: " 我写了一段有点卡的文案 ", goal: "", intensity: "light", user_instructions: " 保留口语感 ", }); expect(parsed).toEqual({ source_text: "我写了一段有点卡的文案", goal: "保留原意,减少 AI 味", intensity: "light", user_instructions: "保留口语感", }); }); it("rejects empty copy optimization source text", () => { expect(() => copyOptimizationRequestSchema.parse({ source_text: " ", intensity: "light", }), ).toThrow(); }); it("accepts structured copy optimization results", () => { const parsed = copyOptimizationResultSchema.parse({ optimized_text: "我把句子顺了一下。", change_notes: [ { original: "我把句子顺顺。", revised: "我把句子顺了一下。", reason: "修正口语里不顺的重复。", confidence: "confident", revertible: false, }, ], ai_taste_checks: [ { rule_id: "promotion_tone", status: "pass", evidence: "没有新增宣传词。", suggestion: "", }, ], warnings: [], }); expect(parsed.optimized_text).toBe("我把句子顺了一下。"); expect(parsed.change_notes[0]?.confidence).toBe("confident"); expect(parsed.ai_taste_checks[0]?.rule_id).toBe("promotion_tone"); }); it("accepts an unconfirmed optimization fact card with unresolved items", () => { const parsed = optimizationFactCardSchema.parse({ company_full_name: "", company_short_names: ["示例科技"], brand_names: [], product_names: ["GEO内容优化平台"], target_industry: "", target_audience: "市场团队", experience_years: "", core_claims: ["提供GEO内容优化服务"], forbidden_claims: [], image_topics: [], uncertain_items: ["客户案例需要确认"], confirmed_by_user: false, }); expect(parsed.company_full_name).toBe(""); expect(parsed.experience_years).toBeNull(); expect(parsed.confirmed_by_user).toBe(false); expect(parsed.is_ready_for_optimization).toBe(false); expect(parsed.uncertain_items).toEqual(["客户案例需要确认"]); }); it("marks a fact card with unresolved uncertain items as not ready for optimization", () => { const parsed = candidateFactCardSchema.parse({ company_full_name: "Example Technology Co., Ltd.", company_short_names: ["Example Tech"], brand_names: ["Example"], product_names: ["Example GEO"], target_industry: "GEO optimization", target_audience: "Marketing teams", experience_years: 8, core_claims: ["Eight years of GEO optimization experience"], forbidden_claims: [], image_topics: ["Product dashboard"], uncertain_items: ["Conflicting product names found"], }); expect(parsed.is_ready_for_optimization).toBe(false); }); it("normalizes near-valid LLM fact card field types", () => { const emptyExperience = candidateFactCardSchema.parse({ company_full_name: "Example Technology Co., Ltd.", company_short_names: ["Example Tech"], brand_names: ["Example"], product_names: ["Example GEO"], target_industry: "GEO optimization", target_audience: ["Marketing teams", "Brand teams"], experience_years: "", core_claims: ["GEO optimization experience"], forbidden_claims: [], image_topics: [], uncertain_items: [], }); const numericExperience = candidateFactCardSchema.parse({ company_full_name: "Example Technology Co., Ltd.", company_short_names: ["Example Tech"], brand_names: ["Example"], product_names: ["Example GEO"], target_industry: "GEO optimization", target_audience: "Marketing teams", experience_years: "8年", core_claims: ["GEO optimization experience"], forbidden_claims: [], image_topics: [], uncertain_items: [], }); expect(emptyExperience.target_audience).toBe("Marketing teams、Brand teams"); expect(emptyExperience.experience_years).toBeNull(); expect(numericExperience.experience_years).toBe(8); }); it("normalizes object-shaped and single-string LLM fact card fields", () => { const parsed = candidateFactCardSchema.parse({ company_full_name: { name: "示例科技有限公司" }, company_short_names: "示例科技", brand_names: [{ name: "示例品牌" }], product_names: [{ product: "GEO内容优化平台" }], target_industry: { industry: "GEO内容优化" }, target_audience: { audience: "市场团队" }, experience_years: { years: "8年" }, core_claims: [ { claim: "提供GEO内容优化服务", source: "原文明确出现" }, ], forbidden_claims: [ { claim: "行业第一", reason: "缺少第三方依据" }, ], image_topics: [{ topic: "产品后台截图" }], uncertain_items: [ { item: "客户案例", reason: "原文没有给出客户名称" }, { claim: "出海能力", evidence: "只出现营销表述" }, ], }); expect(parsed.company_full_name).toBe("示例科技有限公司"); expect(parsed.company_short_names).toEqual(["示例科技"]); expect(parsed.brand_names).toEqual(["示例品牌"]); expect(parsed.product_names).toEqual(["GEO内容优化平台"]); expect(parsed.target_industry).toBe("GEO内容优化"); expect(parsed.target_audience).toBe("市场团队"); expect(parsed.experience_years).toBe(8); expect(parsed.core_claims).toEqual(["提供GEO内容优化服务"]); expect(parsed.forbidden_claims).toEqual(["行业第一"]); expect(parsed.image_topics).toEqual(["产品后台截图"]); expect(parsed.uncertain_items).toEqual(["客户案例", "出海能力"]); expect(parsed.is_ready_for_optimization).toBe(false); }); it("keeps incomplete candidate fact cards editable and ready", () => { const parsed = candidateFactCardSchema.parse({ company_full_name: "", company_short_names: [], brand_names: [], product_names: [], target_industry: "", target_audience: "", experience_years: "", core_claims: [], forbidden_claims: [], image_topics: [], uncertain_items: [], }); expect(parsed.target_industry).toBe(""); expect(parsed.target_audience).toBe(""); expect(parsed.experience_years).toBeNull(); expect(parsed.uncertain_items).toEqual([]); expect(parsed.is_ready_for_optimization).toBe(true); }); it("rejects a confirmed fact card with an empty company full name", () => { expect(() => confirmedFactCardSchema.parse({ company_full_name: "", company_short_names: ["Example"], brand_names: ["Example"], product_names: ["Example GEO"], target_industry: "GEO optimization", target_audience: "Marketing teams", experience_years: 8, core_claims: ["Eight years of GEO optimization experience"], forbidden_claims: [], image_topics: [], uncertain_items: [], confirmed_by_user: true, }), ).toThrow(); }); it("accepts QA reports only with pass, warn, or fail statuses", () => { const valid = qaReportSchema.parse({ job_id: "job_123", revision: 1, overall_status: "warn", checks: [ { rule_id: "title_quality", status: "pass", evidence: "Title reads naturally.", reason: "No grammar issue detected.", suggested_fix: "", target_agent: null, }, ], }); expect(valid.checks[0]?.status).toBe("pass"); expect(() => qaReportSchema.parse({ job_id: "job_123", revision: 1, overall_status: "blocked", checks: [], }), ).toThrow(); }); it("normalizes near-valid LLM QA report values", () => { const parsed = qaReportSchema.parse({ overall_status: "警告", checks: [ { rule_id: "标题质量", status: "警告", evidence: { detail: "标题仍然偏营销化" }, reason: { reason: "官网标题需要更克制" }, suggested_fix: null, target_agent: "", }, { rule_id: "company_name_integrity", status: "通过", evidence: "公司全称一致", reason: "正文保留了事实卡中的公司全称", target_agent: "无", }, ], }); expect(parsed.overall_status).toBe("warn"); expect(parsed.checks[0]).toEqual({ rule_id: "title_quality", status: "warn", evidence: "标题仍然偏营销化", reason: "官网标题需要更克制", suggested_fix: "", target_agent: null, }); expect(parsed.checks[1]?.target_agent).toBeNull(); }); it("normalizes object-shaped changed sections from LLM output", () => { const parsed = optimizedArticleSchema.parse({ title: "Optimized article", summary: "Summary constrained by the confirmed fact card.", body_markdown: "## Body\nUpdated body.", image_suggestions: [], changed_sections: [ { section: "title", change: "Improved keyword clarity." }, { name: "body", reason: "Fixed sentence flow." }, ], requires_user_confirmation: [], }); expect(parsed.changed_sections).toEqual([ "title: Improved keyword clarity.", "body: Fixed sentence flow.", ]); }); it("normalizes near-valid optimized article LLM optional fields", () => { const parsed = optimizedArticleSchema.parse({ title: { text: "示例科技 GEO 内容优化方案" }, summary: ["围绕事实卡重写官网文章摘要"], body_markdown: { markdown: "## 服务能力\n示例科技有限公司提供GEO内容优化服务。", }, image_suggestions: [ "当前版本不生成图片建议", { source: "image_1" }, { suggestion: "使用产品后台截图" }, { source: "image_2", suggestion: "保留原文截图说明" }, ], changed_sections: [ { section: "title", change: "改成中文官网标题" }, ], requires_user_confirmation: [ { claim: "客户案例", reason: "原文没有给出客户名称" }, ], }); expect(parsed.title).toBe("示例科技 GEO 内容优化方案"); expect(parsed.summary).toBe("围绕事实卡重写官网文章摘要"); expect(parsed.body_markdown).toContain("## 服务能力"); expect(parsed.image_suggestions).toEqual([ { source: "image_2", suggestion: "保留原文截图说明" }, ]); expect(parsed.changed_sections).toEqual(["title: 改成中文官网标题"]); expect(parsed.requires_user_confirmation).toEqual(["客户案例"]); }); });