@ophirai/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -0
- package/dist/__tests__/buyer.test.d.ts +1 -0
- package/dist/__tests__/buyer.test.js +664 -0
- package/dist/__tests__/discovery.test.d.ts +1 -0
- package/dist/__tests__/discovery.test.js +188 -0
- package/dist/__tests__/escrow.test.d.ts +1 -0
- package/dist/__tests__/escrow.test.js +385 -0
- package/dist/__tests__/identity.test.d.ts +1 -0
- package/dist/__tests__/identity.test.js +222 -0
- package/dist/__tests__/integration.test.d.ts +1 -0
- package/dist/__tests__/integration.test.js +681 -0
- package/dist/__tests__/lockstep.test.d.ts +1 -0
- package/dist/__tests__/lockstep.test.js +320 -0
- package/dist/__tests__/messages.test.d.ts +1 -0
- package/dist/__tests__/messages.test.js +976 -0
- package/dist/__tests__/negotiation.test.d.ts +1 -0
- package/dist/__tests__/negotiation.test.js +667 -0
- package/dist/__tests__/seller.test.d.ts +1 -0
- package/dist/__tests__/seller.test.js +767 -0
- package/dist/__tests__/server.test.d.ts +1 -0
- package/dist/__tests__/server.test.js +239 -0
- package/dist/__tests__/signing.test.d.ts +1 -0
- package/dist/__tests__/signing.test.js +713 -0
- package/dist/__tests__/sla.test.d.ts +1 -0
- package/dist/__tests__/sla.test.js +342 -0
- package/dist/__tests__/transport.test.d.ts +1 -0
- package/dist/__tests__/transport.test.js +197 -0
- package/dist/__tests__/x402.test.d.ts +1 -0
- package/dist/__tests__/x402.test.js +141 -0
- package/dist/buyer.d.ts +190 -0
- package/dist/buyer.js +555 -0
- package/dist/discovery.d.ts +47 -0
- package/dist/discovery.js +51 -0
- package/dist/escrow.d.ts +177 -0
- package/dist/escrow.js +434 -0
- package/dist/identity.d.ts +60 -0
- package/dist/identity.js +108 -0
- package/dist/index.d.ts +122 -0
- package/dist/index.js +43 -0
- package/dist/lockstep.d.ts +94 -0
- package/dist/lockstep.js +127 -0
- package/dist/messages.d.ts +172 -0
- package/dist/messages.js +262 -0
- package/dist/negotiation.d.ts +113 -0
- package/dist/negotiation.js +214 -0
- package/dist/seller.d.ts +127 -0
- package/dist/seller.js +395 -0
- package/dist/server.d.ts +52 -0
- package/dist/server.js +149 -0
- package/dist/signing.d.ts +98 -0
- package/dist/signing.js +165 -0
- package/dist/sla.d.ts +95 -0
- package/dist/sla.js +187 -0
- package/dist/transport.d.ts +41 -0
- package/dist/transport.js +127 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.js +1 -0
- package/dist/x402.d.ts +25 -0
- package/dist/x402.js +54 -0
- package/package.json +40 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach, beforeEach } from 'vitest';
|
|
2
|
+
import { NegotiationServer } from '../server.js';
|
|
3
|
+
import { OphirError, OphirErrorCode } from '@ophirai/protocol';
|
|
4
|
+
function jsonrpcRequest(url, body) {
|
|
5
|
+
return fetch(url, {
|
|
6
|
+
method: 'POST',
|
|
7
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8
|
+
body: JSON.stringify(body),
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
describe('NegotiationServer', () => {
|
|
12
|
+
let server;
|
|
13
|
+
let baseUrl;
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
server = new NegotiationServer();
|
|
16
|
+
});
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
await server.close();
|
|
19
|
+
});
|
|
20
|
+
async function startServer() {
|
|
21
|
+
await server.listen(0);
|
|
22
|
+
const port = server.getPort();
|
|
23
|
+
return `http://localhost:${port}`;
|
|
24
|
+
}
|
|
25
|
+
// 1. Returns -32600 for invalid jsonrpc version
|
|
26
|
+
it('returns -32600 for invalid jsonrpc version', async () => {
|
|
27
|
+
baseUrl = await startServer();
|
|
28
|
+
const res = await jsonrpcRequest(baseUrl, {
|
|
29
|
+
jsonrpc: '1.0',
|
|
30
|
+
method: 'test',
|
|
31
|
+
id: 1,
|
|
32
|
+
});
|
|
33
|
+
const json = await res.json();
|
|
34
|
+
expect(json).toEqual({
|
|
35
|
+
jsonrpc: '2.0',
|
|
36
|
+
id: 1,
|
|
37
|
+
error: { code: -32600, message: 'Invalid Request: jsonrpc must be "2.0"' },
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
// 2. Returns -32601 for unknown method
|
|
41
|
+
it('returns -32601 for unknown method', async () => {
|
|
42
|
+
baseUrl = await startServer();
|
|
43
|
+
const res = await jsonrpcRequest(baseUrl, {
|
|
44
|
+
jsonrpc: '2.0',
|
|
45
|
+
method: 'nonexistent',
|
|
46
|
+
id: 42,
|
|
47
|
+
});
|
|
48
|
+
const json = await res.json();
|
|
49
|
+
expect(json).toEqual({
|
|
50
|
+
jsonrpc: '2.0',
|
|
51
|
+
id: 42,
|
|
52
|
+
error: { code: -32601, message: 'Method not found: nonexistent' },
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
// 3. Returns 204 for notifications (no id) with unknown method
|
|
56
|
+
it('returns 204 for notification with unknown method', async () => {
|
|
57
|
+
baseUrl = await startServer();
|
|
58
|
+
const res = await jsonrpcRequest(baseUrl, {
|
|
59
|
+
jsonrpc: '2.0',
|
|
60
|
+
method: 'nonexistent',
|
|
61
|
+
});
|
|
62
|
+
expect(res.status).toBe(204);
|
|
63
|
+
const body = await res.text();
|
|
64
|
+
expect(body).toBe('');
|
|
65
|
+
});
|
|
66
|
+
// 4. Returns 204 for notifications with known method
|
|
67
|
+
it('returns 204 for notification with known method', async () => {
|
|
68
|
+
baseUrl = await startServer();
|
|
69
|
+
server.handle('ping', async () => 'pong');
|
|
70
|
+
const res = await jsonrpcRequest(baseUrl, {
|
|
71
|
+
jsonrpc: '2.0',
|
|
72
|
+
method: 'ping',
|
|
73
|
+
});
|
|
74
|
+
expect(res.status).toBe(204);
|
|
75
|
+
const body = await res.text();
|
|
76
|
+
expect(body).toBe('');
|
|
77
|
+
});
|
|
78
|
+
// 5. Returns result for valid request with handler
|
|
79
|
+
it('returns result for valid request with handler', async () => {
|
|
80
|
+
server.handle('add', async (params) => {
|
|
81
|
+
const { a, b } = params;
|
|
82
|
+
return a + b;
|
|
83
|
+
});
|
|
84
|
+
baseUrl = await startServer();
|
|
85
|
+
const res = await jsonrpcRequest(baseUrl, {
|
|
86
|
+
jsonrpc: '2.0',
|
|
87
|
+
method: 'add',
|
|
88
|
+
params: { a: 3, b: 7 },
|
|
89
|
+
id: 1,
|
|
90
|
+
});
|
|
91
|
+
const json = await res.json();
|
|
92
|
+
expect(json).toEqual({
|
|
93
|
+
jsonrpc: '2.0',
|
|
94
|
+
id: 1,
|
|
95
|
+
result: 10,
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
// 6. Returns OphirError with code -32000 when handler throws OphirError
|
|
99
|
+
it('returns -32000 with OphirError code when handler throws OphirError', async () => {
|
|
100
|
+
server.handle('fail', async () => {
|
|
101
|
+
throw new OphirError(OphirErrorCode.INVALID_MESSAGE, 'bad message');
|
|
102
|
+
});
|
|
103
|
+
baseUrl = await startServer();
|
|
104
|
+
const res = await jsonrpcRequest(baseUrl, {
|
|
105
|
+
jsonrpc: '2.0',
|
|
106
|
+
method: 'fail',
|
|
107
|
+
id: 5,
|
|
108
|
+
});
|
|
109
|
+
const json = await res.json();
|
|
110
|
+
expect(json).toEqual({
|
|
111
|
+
jsonrpc: '2.0',
|
|
112
|
+
id: 5,
|
|
113
|
+
error: {
|
|
114
|
+
code: -32000,
|
|
115
|
+
message: 'bad message',
|
|
116
|
+
data: { ophir_code: OphirErrorCode.INVALID_MESSAGE },
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
// 7. Returns generic error with code -32603 when handler throws non-OphirError
|
|
121
|
+
it('returns -32603 when handler throws a generic Error', async () => {
|
|
122
|
+
server.handle('boom', async () => {
|
|
123
|
+
throw new Error('something broke');
|
|
124
|
+
});
|
|
125
|
+
baseUrl = await startServer();
|
|
126
|
+
const res = await jsonrpcRequest(baseUrl, {
|
|
127
|
+
jsonrpc: '2.0',
|
|
128
|
+
method: 'boom',
|
|
129
|
+
id: 6,
|
|
130
|
+
});
|
|
131
|
+
const json = await res.json();
|
|
132
|
+
expect(json).toEqual({
|
|
133
|
+
jsonrpc: '2.0',
|
|
134
|
+
id: 6,
|
|
135
|
+
error: {
|
|
136
|
+
code: -32603,
|
|
137
|
+
message: 'something broke',
|
|
138
|
+
data: undefined,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
// 8. handle() registers method handlers
|
|
143
|
+
it('handle() registers method handlers that are callable', async () => {
|
|
144
|
+
server.handle('echo', async (params) => params);
|
|
145
|
+
baseUrl = await startServer();
|
|
146
|
+
const res = await jsonrpcRequest(baseUrl, {
|
|
147
|
+
jsonrpc: '2.0',
|
|
148
|
+
method: 'echo',
|
|
149
|
+
params: { hello: 'world' },
|
|
150
|
+
id: 1,
|
|
151
|
+
});
|
|
152
|
+
const json = await res.json();
|
|
153
|
+
expect(json.result).toEqual({ hello: 'world' });
|
|
154
|
+
});
|
|
155
|
+
// 9. getPort() returns the bound port after listen
|
|
156
|
+
it('getPort() returns the bound port after listen', async () => {
|
|
157
|
+
await server.listen(0);
|
|
158
|
+
const port = server.getPort();
|
|
159
|
+
expect(port).toBeDefined();
|
|
160
|
+
expect(typeof port).toBe('number');
|
|
161
|
+
expect(port).toBeGreaterThan(0);
|
|
162
|
+
});
|
|
163
|
+
// 10. getPort() returns undefined before listen
|
|
164
|
+
it('getPort() returns undefined before listen', () => {
|
|
165
|
+
expect(server.getPort()).toBeUndefined();
|
|
166
|
+
});
|
|
167
|
+
// 11. close() resolves when server not started
|
|
168
|
+
it('close() resolves when server not started', async () => {
|
|
169
|
+
await expect(server.close()).resolves.toBeUndefined();
|
|
170
|
+
});
|
|
171
|
+
// 12. close() stops the server
|
|
172
|
+
it('close() stops the server', async () => {
|
|
173
|
+
baseUrl = await startServer();
|
|
174
|
+
// Verify server is running
|
|
175
|
+
const res = await jsonrpcRequest(baseUrl, {
|
|
176
|
+
jsonrpc: '2.0',
|
|
177
|
+
method: 'ping',
|
|
178
|
+
id: 1,
|
|
179
|
+
});
|
|
180
|
+
expect(res.status).toBe(200);
|
|
181
|
+
await server.close();
|
|
182
|
+
// Prevent afterEach from double-closing (which would throw "Server is not running")
|
|
183
|
+
server = new NegotiationServer();
|
|
184
|
+
// Verify server is stopped — fetch should reject
|
|
185
|
+
try {
|
|
186
|
+
await jsonrpcRequest(baseUrl, {
|
|
187
|
+
jsonrpc: '2.0',
|
|
188
|
+
method: 'ping',
|
|
189
|
+
id: 2,
|
|
190
|
+
});
|
|
191
|
+
expect.fail('Request should have failed after server close');
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Expected — connection refused or similar error
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
// 13. Returns null id when jsonrpc is wrong and no id provided
|
|
198
|
+
it('returns null id when jsonrpc is invalid and no id provided', async () => {
|
|
199
|
+
baseUrl = await startServer();
|
|
200
|
+
const res = await jsonrpcRequest(baseUrl, {
|
|
201
|
+
jsonrpc: '1.0',
|
|
202
|
+
method: 'test',
|
|
203
|
+
});
|
|
204
|
+
const json = await res.json();
|
|
205
|
+
expect(json).toEqual({
|
|
206
|
+
jsonrpc: '2.0',
|
|
207
|
+
id: null,
|
|
208
|
+
error: { code: -32600, message: 'Invalid Request: jsonrpc must be "2.0"' },
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
// 14. Multiple handlers can be registered
|
|
212
|
+
it('multiple handlers can be registered and dispatched independently', async () => {
|
|
213
|
+
server.handle('greet', async (params) => {
|
|
214
|
+
const { name } = params;
|
|
215
|
+
return `Hello, ${name}`;
|
|
216
|
+
});
|
|
217
|
+
server.handle('multiply', async (params) => {
|
|
218
|
+
const { x, y } = params;
|
|
219
|
+
return x * y;
|
|
220
|
+
});
|
|
221
|
+
baseUrl = await startServer();
|
|
222
|
+
const [greetRes, multiplyRes] = await Promise.all([
|
|
223
|
+
jsonrpcRequest(baseUrl, {
|
|
224
|
+
jsonrpc: '2.0',
|
|
225
|
+
method: 'greet',
|
|
226
|
+
params: { name: 'Ophir' },
|
|
227
|
+
id: 1,
|
|
228
|
+
}).then((r) => r.json()),
|
|
229
|
+
jsonrpcRequest(baseUrl, {
|
|
230
|
+
jsonrpc: '2.0',
|
|
231
|
+
method: 'multiply',
|
|
232
|
+
params: { x: 4, y: 5 },
|
|
233
|
+
id: 2,
|
|
234
|
+
}).then((r) => r.json()),
|
|
235
|
+
]);
|
|
236
|
+
expect(greetRes.result).toBe('Hello, Ophir');
|
|
237
|
+
expect(multiplyRes.result).toBe(20);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|