/* Unit tests for capability covering its documented public contract and regression-sensitive edge cases. Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only. RFC-0288: initial capability resolution tests. RFC-1186: steps 1-2 — capability contracts, catalog records, resolver, propsSchema */ import { test, expect } from "vitest"; import { resolveActiveCapabilities, capabilityToActionRef, type CapabilityRecord, } from "../capability.ts"; function makeCap(overrides: Partial = {}): CapabilityRecord { return { id: "lead.submit", version: 1, kind: "action", title: { de: "x", en: "x" }, description: { de: "x", en: "x" }, input: { type: "object", additionalProperties: false, properties: {} }, output: { type: "object", additionalProperties: false, properties: {} }, integration: { eventKind: "lead", source: "agent" }, requires: { entitlements: [], sections: ["send-message"] }, humanEquivalent: { sectionType: "send-message" }, limits: { perMinutePerIp: 10, maxPayloadBytes: 16384 }, ...overrides, }; } test("resolveActiveCapabilities: empty when agent.actions is not entitled", () => { const result = resolveActiveCapabilities({ catalog: [makeCap()], entitlements: [], renderedSectionTypes: ["send-message"], actionsDisabled: [], }); expect(result).toEqual([]); }); test("resolveActiveCapabilities: active when agent.actions held + section renders", () => { const result = resolveActiveCapabilities({ catalog: [makeCap()], entitlements: ["agent.actions"], renderedSectionTypes: ["send-message", "hero"], actionsDisabled: [], }); expect(result.length).toBe(1); expect(result[0]!.id).toBe("lead.submit"); }); test("resolveActiveCapabilities: inactive when required section does not render", () => { const result = resolveActiveCapabilities({ catalog: [makeCap()], entitlements: ["agent.actions"], renderedSectionTypes: ["hero"], actionsDisabled: [], }); expect(result).toEqual([]); }); test("resolveActiveCapabilities: inactive when an extra required entitlement is missing", () => { const cap = makeCap({ id: "appointment.request", requires: { entitlements: ["booking"], sections: ["booking"] }, }); const withoutBooking = resolveActiveCapabilities({ catalog: [cap], entitlements: ["agent.actions"], renderedSectionTypes: ["booking"], actionsDisabled: [], }); expect(withoutBooking).toEqual([]); const withBooking = resolveActiveCapabilities({ catalog: [cap], entitlements: ["agent.actions", "booking"], renderedSectionTypes: ["booking"], actionsDisabled: [], }); expect(withBooking.length).toBe(1); }); test("resolveActiveCapabilities: actionsDisabled withholds an otherwise-active capability", () => { const result = resolveActiveCapabilities({ catalog: [makeCap()], entitlements: ["agent.actions"], renderedSectionTypes: ["send-message"], actionsDisabled: ["lead.submit"], }); expect(result).toEqual([]); }); test("resolveActiveCapabilities: formBinding capability inactive without a bound block", () => { const cap = makeCap({ id: "recommendation.submit", requires: { entitlements: ["agent.actions"], sections: ["send-message"], formBinding: true }, }); const result = resolveActiveCapabilities({ catalog: [cap], entitlements: ["agent.actions"], renderedSectionTypes: ["send-message"], actionsDisabled: [], boundCapabilityIds: [], }); expect(result).toEqual([]); }); test("resolveActiveCapabilities: formBinding capability active when bound", () => { const cap = makeCap({ id: "recommendation.submit", requires: { entitlements: ["agent.actions"], sections: ["send-message"], formBinding: true }, }); const result = resolveActiveCapabilities({ catalog: [cap], entitlements: ["agent.actions"], renderedSectionTypes: ["send-message"], actionsDisabled: [], boundCapabilityIds: ["recommendation.submit"], }); expect(result.length).toBe(1); }); test("resolveActiveCapabilities: preview resolves binding through nextAction sibling", () => { const preview = makeCap({ id: "recommendation.prepare", sideEffect: "none", integration: undefined, humanEquivalent: undefined, nextAction: { capability: "recommendation.submit", sideEffect: "Sends the recommendation." }, requires: { entitlements: ["agent.actions"], sections: ["send-message"], formBinding: true }, }); const bound = resolveActiveCapabilities({ catalog: [preview], entitlements: ["agent.actions"], renderedSectionTypes: ["send-message"], actionsDisabled: [], boundCapabilityIds: ["recommendation.submit"], }); expect(bound.length).toBe(1); const unbound = resolveActiveCapabilities({ catalog: [preview], entitlements: ["agent.actions"], renderedSectionTypes: ["send-message"], actionsDisabled: [], boundCapabilityIds: ["other.capability"], }); expect(unbound).toEqual([]); }); test("resolveActiveCapabilities: absent boundCapabilityIds leaves non-formBinding caps unaffected", () => { const result = resolveActiveCapabilities({ catalog: [makeCap()], entitlements: ["agent.actions"], renderedSectionTypes: ["send-message"], actionsDisabled: [], }); expect(result.length).toBe(1); }); test("capabilityToActionRef: projects id/url/title/entitlement", () => { const ref = capabilityToActionRef(makeCap()); expect(ref.id).toBe("lead.submit"); expect(ref.url).toBe("/api/agent/v1/actions/lead.submit"); expect(ref.entitlement).toBe("agent.actions"); expect(ref.title).toEqual({ de: "x", en: "x" }); }); test("capabilityToActionRef: metered capability projects resolved pricing (AC-2)", () => { const ref = capabilityToActionRef( makeCap({ metering: { product: "agent-services.quote-run", unit: "invocation", pricePerUnit: { amount: 0.05, currency: "EUR" }, freeUnitsPerWindow: 3, window: "month", }, }), ); expect(ref.pricing).toEqual({ metered: true, product: "agent-services.quote-run", unit: "invocation", pricePerUnit: { amount: 0.05, currency: "EUR" }, freeUnitsPerWindow: 3, window: "month", }); }); test("capabilityToActionRef: unmetered capability projects no pricing", () => { expect(capabilityToActionRef(makeCap()).pricing).toBeUndefined(); });