import { describe, expect, it } from "vitest"; import { SkillCatalog } from "../src/skills/catalog.js"; import { FakeSkillRepository, makeCatalog, silentLogger, skillFixture } from "./helpers.js"; describe("SkillCatalog (database)", () => { it("loads valid skills and exposes pricing metadata", async () => { const catalog = await makeCatalog([ skillFixture({ key: "poster", config: { pricePerImage: 30 }, content: "---\ndefault_size: 1024x1536\ndefault_n: 2\nwatermark: false\n---\n# Skill", }), skillFixture({ key: "space", config: { pricePerImage: 50 } }), ]); expect(catalog.diagnostics()).toEqual([]); expect(catalog.list().map((skill) => skill.key)).toEqual(["poster", "space"]); expect(catalog.get("poster")?.displayName).toBe("Test poster"); expect(catalog.toPublic(catalog.get("poster")!)).toMatchObject({ key: "poster", display_name: "Test poster", price: { per_image: 30 }, defaults: { size: "1024x1536", n: 2, watermark: false }, }); expect(catalog.promptCatalog()).toContain("skill_id: poster"); expect(catalog.promptCatalog()).toContain("单价: 30 credits/张"); }); it("parses size, quantity, watermark and tags from skill body", async () => { const catalog = await makeCatalog([ skillFixture({ key: "body-params", content: "---\ndefault_size: 1536x864\ndefault_n: 1\nmax_images: 3\ntags: [旅游, 海报]\n---\n# Body", }), skillFixture({ key: "inline-params", content: "# 技能\n默认尺寸:1024x1820\n生成数量:2\n水印:否\n标签:电商、海报", }), ]); const body = catalog.get("body-params"); expect(body?.manifest.defaults).toMatchObject({ size: "1536x864", n: 1 }); expect(body?.manifest.max_images).toBe(3); expect(body?.manifest.tags).toEqual(["旅游", "海报"]); const inline = catalog.get("inline-params"); expect(inline?.manifest.defaults).toMatchObject({ size: "1024x1820", n: 2, watermark: false }); expect(inline?.manifest.tags).toEqual(["电商", "海报"]); }); it("falls back to defaults when agent_skill_config is absent", async () => { const catalog = await makeCatalog([skillFixture({ key: "no-config", config: null })], { defaultPrice: 17 }); const skill = catalog.get("no-config"); expect(skill?.usesDefaults).toBe(true); expect(skill?.manifest.price.per_image).toBe(17); expect(skill?.manifest.model).toBe("gpt-image-2"); expect(catalog.diagnostics().join("\n")).toContain("no agent_skill_config"); }); it("rejects disabled, missing content, and invalid keys", async () => { const catalog = await makeCatalog([ skillFixture({ key: "good" }), skillFixture({ key: "no-description", description: "" }), skillFixture({ key: "disabled", config: { enabled: false } }), skillFixture({ key: "library-disabled", libraryEnabled: false }), skillFixture({ key: "empty", content: "" }), skillFixture({ key: "bad_name" }), ]); expect(catalog.list().map((skill) => skill.key)).toEqual(["good", "no-description"]); expect(catalog.get("disabled")).toBeUndefined(); expect(catalog.get("library-disabled")).toBeUndefined(); const diagnostics = catalog.diagnostics().join("\n"); expect(diagnostics).toContain("content is empty"); expect(diagnostics).toContain("description is empty"); expect(diagnostics).toContain("lowercase kebab-case"); }); it("rejects invalid database config without crashing", async () => { const catalog = await makeCatalog([ skillFixture({ key: "bad-price", config: { pricePerImage: -1 } }), ]); expect(catalog.list()).toHaveLength(0); expect(catalog.diagnostics().join("\n")).toContain("bad-price"); }); it("refreshes the in-memory catalog from the repository", async () => { const repository = new FakeSkillRepository([skillFixture({ key: "first" })]); const catalog = new SkillCatalog({ repository, defaultPrice: 10, defaultModel: "seedream@5.0", defaultSize: "1024x1024", currency: "credits", logger: silentLogger, }); await catalog.load(); expect(catalog.list().map((skill) => skill.key)).toEqual(["first"]); repository.setSkills([skillFixture({ key: "second", config: { pricePerImage: 20 } })]); await catalog.refresh(); expect(catalog.list().map((skill) => skill.key)).toEqual(["second"]); expect(catalog.get("first")).toBeUndefined(); }); it("requires at least one enabled skill", async () => { const catalog = await makeCatalog([]); expect(() => catalog.requireReady()).toThrow(/no enabled skills/); }); });