Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions tests/mcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ describe('BazaarMcpServer — server identity', () => {

describe('BazaarMcpServer', () => {
it('should map signature expiration ledger error to ERR_EXPIRED_AUTHORISATION', async () => {
const mockFetch = vi.fn().mockRejectedValue(new Error('Transaction signature expired. Signature expiration ledger 123 is less than current ledger 125'));
// What the payment client actually surfaces: @x402/fetch rethrows a scheme
// failure as `Failed to create payment payload: <cause>`, and the cause for a
// lapsed signature is the SDK's own `The transaction has expired`
// (@stellar/stellar-sdk lib/webauth/utils.js).
const expired = new Error('Failed to create payment payload: The transaction has expired');
const mockFetch = vi.fn().mockRejectedValue(expired);
const server = new BazaarMcpServer({ fetchWithPayment: mockFetch, paidCallAllowlist: ['example.com'] });

// The SDK server doesn't expose the router directly without a client, but we can test the handlePaidCallError method directly,
Expand Down Expand Up @@ -80,11 +85,15 @@ describe('BazaarMcpServer', () => {
});

it('should map 402 rejected error to ERR_PAYMENT_REJECTED', async () => {
const mockFetch = vi.fn().mockResolvedValue({
ok: false,
status: 402,
text: async () => 'Payment Required'
});
// fetchWithPayment does not throw on a rejected payment: it pays, retries,
// and hands back the seller's second response, so this is a real Response
// carrying the x402 error body rather than a hand-rolled object.
const mockFetch = vi.fn().mockResolvedValue(
new Response(
JSON.stringify({ x402Version: 1, error: 'Payment failed: signature invalid' }),
{ status: 402, headers: { 'Content-Type': 'application/json' } },
),
);

const server = new BazaarMcpServer({ fetchWithPayment: mockFetch, paidCallAllowlist: ['example.com'] });

Expand All @@ -111,11 +120,19 @@ describe('BazaarMcpServer', () => {
});

it('should return successfully on a successful call', async () => {
const mockFetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
text: async () => JSON.stringify({ success: true, data: "hello" })
});
// A settled call is a real Response whose body is the resource's payload and
// whose X-PAYMENT-RESPONSE header carries the seller's settlement echo; the
// ledger decodes that header, so a stand-in without Headers would skip the
// only code path that reads it.
const settlement = Buffer.from(
JSON.stringify({ success: true, network: 'stellar:testnet' }),
).toString('base64url');
const mockFetch = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ success: true, data: 'hello' }), {
status: 200,
headers: { 'Content-Type': 'application/json', 'X-PAYMENT-RESPONSE': settlement },
}),
);

const server = new BazaarMcpServer({ fetchWithPayment: mockFetch, paidCallAllowlist: ['example.com'] });

Expand Down
Loading