|
1 | | -import { writeFileSync } from "node:fs" |
2 | | -import { tmpdir } from "node:os" |
3 | | -import { join } from "node:path" |
4 | 1 | import { afterEach, describe, expect, it, vi } from "vitest" |
5 | 2 |
|
6 | 3 | // Hardhat/Anvil account #0 — deterministic test key, never holds real funds |
@@ -117,212 +114,3 @@ describe("pay command", () => { |
117 | 114 | logSpy.mockRestore() |
118 | 115 | }) |
119 | 116 | }) |
120 | | - |
121 | | -describe("pay --auth eip3009", () => { |
122 | | - it("uses paidAuthenticatedFetch when --auth siwe is set (deprecated, maps to EIP-3009)", async () => { |
123 | | - const calls: { url: string; headers: Record<string, string> }[] = [] |
124 | | - |
125 | | - const fetchMock = vi.fn(async (url: string, init?: RequestInit) => { |
126 | | - const headers = Object.fromEntries( |
127 | | - Object.entries(init?.headers ?? {}), |
128 | | - ) as Record<string, string> |
129 | | - calls.push({ url: url as string, headers }) |
130 | | - |
131 | | - // First call has no auth headers → return 402 |
132 | | - if (!headers["X-Payment"]) { |
133 | | - return new Response( |
134 | | - JSON.stringify({ |
135 | | - x402Version: 1, |
136 | | - error: "Payment required", |
137 | | - accepts: [PAYMENT_REQUIREMENTS], |
138 | | - }), |
139 | | - { status: 402 }, |
140 | | - ) |
141 | | - } |
142 | | - |
143 | | - // Second call has X-Payment → success |
144 | | - if (headers["X-Payment"]) { |
145 | | - return new Response(JSON.stringify({ result: "authenticated-paid" }), { |
146 | | - status: 200, |
147 | | - }) |
148 | | - } |
149 | | - |
150 | | - return new Response("Unexpected", { status: 500 }) |
151 | | - }) |
152 | | - |
153 | | - vi.stubGlobal("fetch", fetchMock) |
154 | | - |
155 | | - const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}) |
156 | | - process.env.PRIVATE_KEY = PRIVATE_KEY |
157 | | - process.env.RPC_URL = "http://localhost:8545" |
158 | | - |
159 | | - const { payCommand } = await import("../cli/commands/pay.js") |
160 | | - |
161 | | - await payCommand.parseAsync([ |
162 | | - "node", |
163 | | - "pay", |
164 | | - "https://tool.example.com/api", |
165 | | - "--body", |
166 | | - '{"query":"test"}', |
167 | | - "--auth", |
168 | | - "siwe", |
169 | | - ]) |
170 | | - |
171 | | - expect(fetchMock).toHaveBeenCalledTimes(2) |
172 | | - |
173 | | - // First call: no auth headers |
174 | | - expect(calls[0].headers.Authorization).toBeUndefined() |
175 | | - expect(calls[0].headers["X-Payment"]).toBeUndefined() |
176 | | - |
177 | | - // Second call: X-Payment only |
178 | | - expect(calls[1].headers.Authorization).toBeUndefined() |
179 | | - expect(calls[1].headers["X-Payment"]).toBeDefined() |
180 | | - |
181 | | - logSpy.mockRestore() |
182 | | - }) |
183 | | - |
184 | | - it("returns directly when --auth siwe response is not 402", async () => { |
185 | | - vi.stubGlobal( |
186 | | - "fetch", |
187 | | - vi.fn( |
188 | | - async () => |
189 | | - new Response(JSON.stringify({ result: "auth-only" }), { |
190 | | - status: 200, |
191 | | - }), |
192 | | - ), |
193 | | - ) |
194 | | - |
195 | | - const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}) |
196 | | - process.env.PRIVATE_KEY = PRIVATE_KEY |
197 | | - process.env.RPC_URL = "http://localhost:8545" |
198 | | - |
199 | | - const { payCommand } = await import("../cli/commands/pay.js") |
200 | | - |
201 | | - await payCommand.parseAsync([ |
202 | | - "node", |
203 | | - "pay", |
204 | | - "https://tool.example.com/api", |
205 | | - "--auth", |
206 | | - "siwe", |
207 | | - "--body", |
208 | | - "{}", |
209 | | - ]) |
210 | | - |
211 | | - // Only one fetch call — no 402 so no payment replay |
212 | | - expect(vi.mocked(fetch)).toHaveBeenCalledTimes(1) |
213 | | - |
214 | | - logSpy.mockRestore() |
215 | | - }) |
216 | | - |
217 | | - it("auto-enables EIP-3009 auth when manifest declares an access block", async () => { |
218 | | - const manifest = { |
219 | | - type: "https://ercs.ethereum.org/ERCS/erc-8257#tool-manifest-v1", |
220 | | - name: "Test Tool", |
221 | | - description: "A test tool", |
222 | | - endpoint: "https://tool.example.com/api", |
223 | | - inputs: { type: "object", properties: {} }, |
224 | | - outputs: { type: "object", properties: {} }, |
225 | | - creatorAddress: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", |
226 | | - access: { |
227 | | - logic: "AND", |
228 | | - requirements: [ |
229 | | - { |
230 | | - kind: "0xdeadbeef", |
231 | | - data: "0x", |
232 | | - label: "Test gate", |
233 | | - }, |
234 | | - ], |
235 | | - }, |
236 | | - } |
237 | | - |
238 | | - const manifestPath = join(tmpdir(), `test-manifest-${Date.now()}.json`) |
239 | | - writeFileSync(manifestPath, JSON.stringify(manifest)) |
240 | | - |
241 | | - const calls: { url: string; headers: Record<string, string> }[] = [] |
242 | | - |
243 | | - const fetchMock = vi.fn(async (url: string, init?: RequestInit) => { |
244 | | - const headers = Object.fromEntries( |
245 | | - Object.entries(init?.headers ?? {}), |
246 | | - ) as Record<string, string> |
247 | | - calls.push({ url: url as string, headers }) |
248 | | - |
249 | | - return new Response(JSON.stringify({ result: "ok" }), { status: 200 }) |
250 | | - }) |
251 | | - |
252 | | - vi.stubGlobal("fetch", fetchMock) |
253 | | - |
254 | | - const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}) |
255 | | - process.env.PRIVATE_KEY = PRIVATE_KEY |
256 | | - process.env.RPC_URL = "http://localhost:8545" |
257 | | - |
258 | | - const { payCommand } = await import("../cli/commands/pay.js") |
259 | | - |
260 | | - await payCommand.parseAsync([ |
261 | | - "node", |
262 | | - "pay", |
263 | | - "https://tool.example.com/api", |
264 | | - "--manifest", |
265 | | - manifestPath, |
266 | | - "--body", |
267 | | - "{}", |
268 | | - ]) |
269 | | - |
270 | | - // Should have used paidAuthenticatedFetch (no Authorization, identity via X-Payment on 402) |
271 | | - expect(calls[0].headers.Authorization).toBeUndefined() |
272 | | - |
273 | | - logSpy.mockRestore() |
274 | | - }) |
275 | | - |
276 | | - it("does not auto-enable auth when manifest has no access block", async () => { |
277 | | - const manifest = { |
278 | | - type: "https://ercs.ethereum.org/ERCS/erc-8257#tool-manifest-v1", |
279 | | - name: "Test Tool", |
280 | | - description: "A test tool", |
281 | | - endpoint: "https://tool.example.com/api", |
282 | | - inputs: { type: "object", properties: {} }, |
283 | | - outputs: { type: "object", properties: {} }, |
284 | | - creatorAddress: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", |
285 | | - } |
286 | | - |
287 | | - const manifestPath = join( |
288 | | - tmpdir(), |
289 | | - `test-manifest-no-access-${Date.now()}.json`, |
290 | | - ) |
291 | | - writeFileSync(manifestPath, JSON.stringify(manifest)) |
292 | | - |
293 | | - const calls: { url: string; headers: Record<string, string> }[] = [] |
294 | | - |
295 | | - vi.stubGlobal( |
296 | | - "fetch", |
297 | | - vi.fn(async (url: string, init?: RequestInit) => { |
298 | | - const headers = Object.fromEntries( |
299 | | - Object.entries(init?.headers ?? {}), |
300 | | - ) as Record<string, string> |
301 | | - calls.push({ url: url as string, headers }) |
302 | | - |
303 | | - return new Response(JSON.stringify({ result: "free" }), { status: 200 }) |
304 | | - }), |
305 | | - ) |
306 | | - |
307 | | - const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}) |
308 | | - process.env.PRIVATE_KEY = PRIVATE_KEY |
309 | | - process.env.RPC_URL = "http://localhost:8545" |
310 | | - |
311 | | - const { payCommand } = await import("../cli/commands/pay.js") |
312 | | - |
313 | | - await payCommand.parseAsync([ |
314 | | - "node", |
315 | | - "pay", |
316 | | - "https://tool.example.com/api", |
317 | | - "--manifest", |
318 | | - manifestPath, |
319 | | - "--body", |
320 | | - "{}", |
321 | | - ]) |
322 | | - |
323 | | - // Should NOT have used auth — no Authorization header |
324 | | - expect(calls[0].headers.Authorization).toBeUndefined() |
325 | | - |
326 | | - logSpy.mockRestore() |
327 | | - }) |
328 | | -}) |
0 commit comments