Files
light 6ec9778d45 chore: initial commit
Laimiao agent v2 service: agent orchestration, sessions, billing,
gateway client, skills catalog, auth, config, plus tests.
2026-11-07 10:40:07 +08:00

112 lines
4.6 KiB
TypeScript

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/);
});
});