import { describe, expect, it } from "vitest"; import { PricingError, quoteSkill, repriceForQuantity } from "../src/billing/pricing.js"; import type { SkillDefinition } from "../src/types.js"; const skill: SkillDefinition = { key: "demo", name: "demo", displayName: "Demo Skill", description: "demo", content: "# demo", triggerKeywords: "demo", usesDefaults: false, manifest: { key: "demo", version: "1.0.0", industry: "test", model: "seedream@5.0", defaults: { size: "1024x1536", n: 1 }, price: { currency: "credits", per_image: 30, size_multipliers: { "2048x3072": 2 } }, capabilities: [], tags: [], max_images: 4, enabled: true, }, }; describe("pricing", () => { it("quotes one image using defaults", () => { expect(quoteSkill(skill, { size: "1024x1536", quantity: 1 }, {}, "dry_run")).toEqual({ skill_id: "demo", skill_name: "Demo Skill", model: "seedream@5.0", size: "1024x1536", quantity: 1, unit_credits: 30, size_multiplier: 1, total_credits: 30, currency: "credits", billing_mode: "dry_run", }); }); it("applies size multipliers and quantity", () => { const quote = quoteSkill( skill, { size: "1024x1536", quantity: 1 }, { size: "2048x3072", quantity: 3 }, "off", ); expect(quote.size_multiplier).toBe(2); expect(quote.total_credits).toBe(180); expect(quote.billing_mode).toBe("off"); }); it("rejects over-limit, non-integer, and malformed sizes", () => { expect(() => quoteSkill(skill, { size: "1024x1536", quantity: 1 }, { quantity: 5 }, "dry_run")).toThrow( PricingError, ); expect(() => quoteSkill(skill, { size: "1024x1536", quantity: 1 }, { quantity: 1.5 }, "dry_run")).toThrow( /integer/, ); expect(() => quoteSkill(skill, { size: "1024x1536", quantity: 1 }, { size: "large" }, "dry_run")).toThrow( /size/, ); }); it("reprices to the actually returned image count", () => { const quote = quoteSkill(skill, { size: "1024x1536", quantity: 1 }, { quantity: 3 }, "dry_run"); const repriced = repriceForQuantity(quote, 1); expect(repriced.quantity).toBe(1); expect(repriced.total_credits).toBe(30); expect(() => repriceForQuantity(quote, -1)).toThrow(PricingError); }); });