@coinbase/agentkit
Version:
Coinbase AgentKit core primitives
323 lines (322 loc) • 13.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const x402ActionProvider_1 = require("./x402ActionProvider");
const axios_1 = __importDefault(require("axios"));
const x402axios = __importStar(require("x402-axios"));
// Mock modules
jest.mock("axios");
jest.mock("x402-axios");
// Create mock functions
const mockRequest = jest.fn();
// Create a complete mock axios instance
const mockAxiosInstance = {
request: mockRequest,
get: jest.fn(),
delete: jest.fn(),
head: jest.fn(),
options: jest.fn(),
post: jest.fn(),
put: jest.fn(),
patch: jest.fn(),
getUri: jest.fn(),
defaults: {},
interceptors: {
request: { use: jest.fn(), eject: jest.fn(), clear: jest.fn() },
response: { use: jest.fn(), eject: jest.fn(), clear: jest.fn() },
},
};
// Create a complete mock axios static
const mockAxios = {
create: jest.fn().mockReturnValue(mockAxiosInstance),
request: mockRequest,
get: jest.fn(),
delete: jest.fn(),
head: jest.fn(),
options: jest.fn(),
post: jest.fn(),
put: jest.fn(),
patch: jest.fn(),
all: jest.fn(),
spread: jest.fn(),
isAxiosError: jest.fn(),
isCancel: jest.fn(),
CancelToken: {
source: jest.fn(),
},
VERSION: "1.x",
};
const mockWithPaymentInterceptor = jest.fn().mockReturnValue(mockAxiosInstance);
const mockDecodeXPaymentResponse = jest.fn();
// Override the mocked modules
axios_1.default.create = mockAxios.create;
axios_1.default.request = mockRequest;
axios_1.default.isAxiosError = mockAxios.isAxiosError;
// Mock x402-axios functions
jest.mocked(x402axios.withPaymentInterceptor).mockImplementation(mockWithPaymentInterceptor);
jest.mocked(x402axios.decodeXPaymentResponse).mockImplementation(mockDecodeXPaymentResponse);
// Mock wallet provider
const mockWalletProvider = {
toSigner: jest.fn().mockReturnValue("mock-signer"),
};
// Sample responses based on real examples
const MOCK_PAYMENT_INFO_RESPONSE = {
paymentRequired: true,
url: "https://www.x402.org/protected",
status: 402,
data: {
x402Version: 1,
error: "X-PAYMENT header is required",
accepts: [
{
scheme: "exact",
network: "base-sepolia",
maxAmountRequired: "10000",
resource: "https://www.x402.org/protected",
description: "Access to protected content",
mimeType: "application/json",
payTo: "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
maxTimeoutSeconds: 300,
asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
extra: {
name: "USDC",
version: "2",
},
},
],
},
};
const MOCK_PAYMENT_RESPONSE = {
success: true,
transaction: "0xcbc385789d3744b52af5106c32809534f64adcbe097e050ec03d6b53fed5d305",
network: "base-sepolia",
payer: "0xa8c1a5D3C372C65c04f91f87a43F549619A9483f",
};
describe("X402ActionProvider", () => {
let provider;
beforeEach(() => {
provider = new x402ActionProvider_1.X402ActionProvider();
jest.clearAllMocks();
// Setup mocks
mockAxios.create.mockReturnValue(mockAxiosInstance);
mockWithPaymentInterceptor.mockReturnValue(mockAxiosInstance);
// Setup axios.isAxiosError mock
jest
.mocked(axios_1.default.isAxiosError)
.mockImplementation((error) => Boolean(error &&
typeof error === "object" &&
("isAxiosError" in error || "response" in error || "request" in error)));
});
afterEach(() => {
jest.clearAllMocks();
});
describe("supportsNetwork", () => {
it("should support base-mainnet", () => {
const network = { protocolFamily: "evm", networkId: "base-mainnet" };
expect(provider.supportsNetwork(network)).toBe(true);
});
it("should support base-sepolia", () => {
const network = { protocolFamily: "evm", networkId: "base-sepolia" };
expect(provider.supportsNetwork(network)).toBe(true);
});
it("should not support unsupported EVM networks", () => {
const network = { protocolFamily: "evm", networkId: "ethereum" };
expect(provider.supportsNetwork(network)).toBe(false);
});
it("should not support non-EVM networks", () => {
const network = { protocolFamily: "solana", networkId: "mainnet" };
expect(provider.supportsNetwork(network)).toBe(false);
});
});
describe("makeHttpRequest", () => {
it("should handle successful non-payment requests", async () => {
mockRequest.mockResolvedValue({
status: 200,
data: { message: "Success" },
headers: {},
config: {},
});
const result = await provider.makeHttpRequest(mockWalletProvider, {
url: "https://api.example.com/free",
method: "GET",
});
const parsedResult = JSON.parse(result);
expect(parsedResult.success).toBe(true);
expect(parsedResult.status).toBe(200);
expect(parsedResult.data).toEqual({ message: "Success" });
});
it("should handle 402 responses with payment options", async () => {
mockRequest.mockResolvedValue({
status: 402,
data: MOCK_PAYMENT_INFO_RESPONSE.data,
headers: {},
config: {},
});
const result = await provider.makeHttpRequest(mockWalletProvider, {
url: "https://www.x402.org/protected",
method: "GET",
});
const parsedResult = JSON.parse(result);
expect(parsedResult.status).toBe("error_402_payment_required");
expect(parsedResult.acceptablePaymentOptions).toEqual(MOCK_PAYMENT_INFO_RESPONSE.data.accepts);
expect(parsedResult.nextSteps).toBeDefined();
});
it("should handle network errors", async () => {
const error = new Error("Network error");
error.isAxiosError = true;
error.request = {};
mockRequest.mockRejectedValue(error);
const result = await provider.makeHttpRequest(mockWalletProvider, {
url: "https://api.example.com/endpoint",
method: "GET",
});
const parsedResult = JSON.parse(result);
expect(parsedResult.error).toBe(true);
expect(parsedResult.message).toContain("Network error");
});
});
describe("retryHttpRequestWithX402", () => {
it("should successfully retry with payment", async () => {
mockDecodeXPaymentResponse.mockReturnValue(MOCK_PAYMENT_RESPONSE);
mockRequest.mockResolvedValue({
status: 200,
statusText: "OK",
data: { message: "Paid content" },
headers: {
"x-payment-response": "encoded-payment-data",
},
config: {},
});
const result = await provider.retryWithX402(mockWalletProvider, {
url: "https://www.x402.org/protected",
method: "GET",
selectedPaymentOption: {
scheme: "exact",
network: "base-sepolia",
maxAmountRequired: "10000",
asset: "0x456",
},
});
// Update expectation to accept the payment selector function
expect(mockWithPaymentInterceptor).toHaveBeenCalledWith(mockAxiosInstance, "mock-signer", expect.any(Function));
const parsedResult = JSON.parse(result);
expect(parsedResult.status).toBe("success");
expect(parsedResult.details.paymentProof).toEqual({
transaction: MOCK_PAYMENT_RESPONSE.transaction,
network: MOCK_PAYMENT_RESPONSE.network,
payer: MOCK_PAYMENT_RESPONSE.payer,
});
});
it("should handle network errors during payment", async () => {
const error = new Error("Network error");
error.isAxiosError = true;
error.request = {};
mockRequest.mockRejectedValue(error);
const result = await provider.retryWithX402(mockWalletProvider, {
url: "https://www.x402.org/protected",
method: "GET",
selectedPaymentOption: {
scheme: "exact",
network: "base-sepolia",
maxAmountRequired: "10000",
asset: "0x456",
},
});
const parsedResult = JSON.parse(result);
expect(parsedResult.error).toBe(true);
expect(parsedResult.message).toContain("Network error");
});
});
describe("makeHttpRequestWithX402", () => {
it("should handle successful direct payment requests", async () => {
mockDecodeXPaymentResponse.mockReturnValue(MOCK_PAYMENT_RESPONSE);
mockRequest.mockResolvedValue({
status: 200,
statusText: "OK",
data: { message: "Paid content" },
headers: {
"x-payment-response": "encoded-payment-data",
},
config: {},
});
const result = await provider.makeHttpRequestWithX402(mockWalletProvider, {
url: "https://www.x402.org/protected",
method: "GET",
});
expect(mockWithPaymentInterceptor).toHaveBeenCalledWith(mockAxiosInstance, "mock-signer");
const parsedResult = JSON.parse(result);
expect(parsedResult.success).toBe(true);
expect(parsedResult.data).toEqual({ message: "Paid content" });
expect(parsedResult.paymentProof).toEqual({
transaction: MOCK_PAYMENT_RESPONSE.transaction,
network: MOCK_PAYMENT_RESPONSE.network,
payer: MOCK_PAYMENT_RESPONSE.payer,
});
});
it("should handle successful non-payment requests", async () => {
mockRequest.mockResolvedValue({
status: 200,
statusText: "OK",
data: { message: "Free content" },
headers: {},
config: {},
});
const result = await provider.makeHttpRequestWithX402(mockWalletProvider, {
url: "https://api.example.com/free",
method: "GET",
});
const parsedResult = JSON.parse(result);
expect(parsedResult.success).toBe(true);
expect(parsedResult.data).toEqual({ message: "Free content" });
expect(parsedResult.paymentProof).toBeNull();
});
it("should handle network errors", async () => {
const error = new Error("Network error");
error.isAxiosError = true;
error.request = {};
mockRequest.mockRejectedValue(error);
const result = await provider.makeHttpRequestWithX402(mockWalletProvider, {
url: "https://api.example.com/endpoint",
method: "GET",
});
const parsedResult = JSON.parse(result);
expect(parsedResult.error).toBe(true);
expect(parsedResult.message).toContain("Network error");
});
});
});