import { describe, it, expect } from 'vitest';
import { manageCart } from '../../src/tools/manage-cart.js';
import { negotiateCapabilities } from '../../src/ucp/negotiate.js';
import { CheckoutSessionManager } from '../../src/ucp/checkout-session.js';
import { MandateStore } from '../../src/ap2/mandate-store.js';
import { Guardrail } from '../../src/middleware/guardrail.js';
import { FeeCollector } from '../../src/middleware/fee-collector.js';
import type {
AgentProfile,
UCPCapability,
UCPPaymentHandler,
Mandate,
IntentPayload,
} from '../../src/types.js';
const V = '2026-01-11';
function cap(name: string): UCPCapability {
return { name, version: V };
}
describe('Full autonomous purchase flow (e2e)', () => {
it('creates a cart, adds items, negotiates, and attempts checkout', async () => {
// ─── Step 1: Create Cart ───
const cart = await manageCart({ action: 'create' });
expect(cart.cart_id).toBeDefined();
expect(cart.line_items).toHaveLength(0);
// ─── Step 2: Add Items ───
const cartWithItem = await manageCart({
action: 'add',
cart_id: cart.cart_id,
variant_id: 'gid://shopify/ProductVariant/42',
quantity: 2,
});
expect(cartWithItem.line_items).toHaveLength(1);
expect(cartWithItem.item_count).toBe(2);
const cartWithTwo = await manageCart({
action: 'add',
cart_id: cart.cart_id,
variant_id: 'gid://shopify/ProductVariant/43',
quantity: 1,
});
expect(cartWithTwo.line_items).toHaveLength(2);
expect(cartWithTwo.item_count).toBe(3);
// ─── Step 3: Negotiate Terms ───
const agentProfile: AgentProfile = {
profile_url: 'https://agent.example.com/.well-known/ucp',
capabilities: [
'dev.ucp.shopping.checkout',
'dev.ucp.shopping.catalog',
],
credentials: ['ap2-mandate-v1'],
};
const serverCaps: UCPCapability[] = [
cap('dev.ucp.shopping.checkout'),
cap('dev.ucp.shopping.catalog'),
cap('dev.ucp.shopping.order'),
cap('dev.ucp.shopping.fulfillment'),
];
const serverPaymentHandlers: UCPPaymentHandler[] = [
{ id: 'shopify_payments', type: 'processor_tokenizer' },
{ id: 'ap2_mandate', type: 'mandate' },
];
const negotiation = negotiateCapabilities(agentProfile, serverCaps, serverPaymentHandlers);
expect(negotiation.active_capabilities).toHaveLength(2);
expect(negotiation.shipping_options.length).toBeGreaterThan(0);
expect(negotiation.available_discounts.length).toBeGreaterThan(0);
// ─── Step 4: Build Checkout Session ───
const mgr = new CheckoutSessionManager();
const session = mgr.create('USD');
expect(session.status).toBe('incomplete');
// Add line items, buyer, shipping, payment
const readySession = mgr.update(session.id, {
line_items: cartWithTwo.line_items,
buyer: { email: 'buyer-agent@example.com', first_name: 'Bot', last_name: 'Alpha' },
shipping_address: {
line1: '1 Robot Blvd',
city: 'San Francisco',
province: 'CA',
postal_code: '94105',
country: 'US',
},
payment_instruments: [
{ handler_id: 'ap2_mandate', type: 'mandate' },
],
});
expect(readySession.status).toBe('ready_for_complete');
expect(readySession.messages).toHaveLength(0);
// ─── Step 5: Guardrail Checks ───
const guardrail = new Guardrail();
// The transition from incomplete to ready_for_complete should be valid
expect(
guardrail.validateCheckoutTransition('incomplete', 'ready_for_complete'),
).toBe(true);
// ─── Step 6: Fee Calculation ───
const feeCollector = new FeeCollector({
feeRate: 0.005,
walletAddress: '0xFEE_WALLET',
});
const feeRecord = feeCollector.calculateFee(
readySession.totals.total,
readySession.currency,
);
expect(feeRecord.status).toBe('pending');
expect(feeRecord.fee_rate).toBe(0.005);
// ─── Step 7: Mandate Store (error path — no valid JWS) ───
const mandateStore = new MandateStore();
// Simulate an intent mandate (would normally be JWS-signed)
const intentMandate: Mandate = {
id: 'mandate-intent-e2e',
type: 'intent',
status: 'active',
issuer: 'buyer-agent@example.com',
subject: 'buyer-agent@example.com',
payload: {
max_amount: 100000, // $1000.00 in cents
currency: 'USD',
} as IntentPayload,
signature: 'mock-jws-cannot-be-verified',
issued_at: new Date().toISOString(),
expires_at: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
};
await mandateStore.store(intentMandate);
const retrieved = await mandateStore.get(intentMandate.id);
expect(retrieved).not.toBeNull();
expect(retrieved!.status).toBe('active');
// Validate mandate amount against checkout total
const mandateAmountValid = guardrail.validateMandateAmount(
intentMandate,
readySession.totals.total,
);
expect(mandateAmountValid).toBe(true);
// ─── Step 8: Collect fee after "successful" order ───
const collected = await feeCollector.collect(feeRecord);
expect(collected.status).toBe('collected');
const totalCollected = await feeCollector.getTotalCollected('USD');
expect(totalCollected).toBe(feeRecord.fee_amount);
});
it('rejects checkout when mandate amount is exceeded', async () => {
const guardrail = new Guardrail();
const lowMandate: Mandate = {
id: 'mandate-low',
type: 'intent',
status: 'active',
issuer: 'buyer-agent',
subject: 'buyer@example.com',
payload: {
max_amount: 100, // very low: $1.00
currency: 'USD',
} as IntentPayload,
signature: 'mock',
issued_at: new Date().toISOString(),
expires_at: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
};
// $50.00 checkout should exceed $1.00 mandate
expect(guardrail.validateMandateAmount(lowMandate, 5000)).toBe(false);
});
it('rejects checkout when mandate is expired', async () => {
const guardrail = new Guardrail();
const expiredMandate: Mandate = {
id: 'mandate-expired',
type: 'intent',
status: 'active',
issuer: 'buyer-agent',
subject: 'buyer@example.com',
payload: {
max_amount: 100000,
currency: 'USD',
} as IntentPayload,
signature: 'mock',
issued_at: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
expires_at: new Date(Date.now() - 30 * 60 * 1000).toISOString(), // expired 30m ago
};
expect(guardrail.validateMandateAmount(expiredMandate, 1000)).toBe(false);
});
});