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