@ucptools/validator 1.0.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/CLAUDE.md +109 -0
- package/CONTRIBUTING.md +113 -0
- package/LICENSE +21 -0
- package/README.md +203 -0
- package/api/analyze-feed.js +140 -0
- package/api/badge.js +185 -0
- package/api/benchmark.js +177 -0
- package/api/directory-stats.ts +29 -0
- package/api/directory.ts +73 -0
- package/api/generate-compliance.js +143 -0
- package/api/generate-schema.js +457 -0
- package/api/generate.js +132 -0
- package/api/security-scan.js +133 -0
- package/api/simulate.js +187 -0
- package/api/tsconfig.json +10 -0
- package/api/validate.js +1351 -0
- package/apify-actor/.actor/actor.json +68 -0
- package/apify-actor/.actor/input_schema.json +32 -0
- package/apify-actor/APIFY-STORE-LISTING.md +412 -0
- package/apify-actor/Dockerfile +8 -0
- package/apify-actor/README.md +166 -0
- package/apify-actor/main.ts +111 -0
- package/apify-actor/package.json +17 -0
- package/apify-actor/src/main.js +199 -0
- package/docs/BRAND-IDENTITY.md +238 -0
- package/docs/BRAND-STYLE-GUIDE.md +356 -0
- package/drizzle/0000_black_king_cobra.sql +39 -0
- package/drizzle/meta/0000_snapshot.json +309 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +10 -0
- package/examples/full-profile.json +70 -0
- package/examples/minimal-profile.json +23 -0
- package/package.json +69 -0
- package/public/.well-known/ucp +25 -0
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/brand.css +321 -0
- package/public/directory.html +701 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/guides/bigcommerce.html +743 -0
- package/public/guides/fastucp.html +838 -0
- package/public/guides/magento.html +779 -0
- package/public/guides/shopify.html +726 -0
- package/public/guides/squarespace.html +749 -0
- package/public/guides/wix.html +747 -0
- package/public/guides/woocommerce.html +733 -0
- package/public/index.html +3835 -0
- package/public/learn.html +396 -0
- package/public/logo.jpeg +0 -0
- package/public/og-image-icon.png +0 -0
- package/public/og-image.png +0 -0
- package/public/robots.txt +6 -0
- package/public/site.webmanifest +31 -0
- package/public/sitemap.xml +69 -0
- package/public/social/linkedin-banner-1128x191.png +0 -0
- package/public/social/temp.PNG +0 -0
- package/public/social/x-header-1500x500.png +0 -0
- package/public/verify.html +410 -0
- package/scripts/generate-favicons.js +44 -0
- package/scripts/generate-ico.js +23 -0
- package/scripts/generate-og-image.js +45 -0
- package/scripts/reset-db.ts +77 -0
- package/scripts/seed-db.ts +71 -0
- package/scripts/setup-benchmark-db.js +70 -0
- package/src/api/server.ts +266 -0
- package/src/cli/index.ts +302 -0
- package/src/compliance/compliance-generator.ts +452 -0
- package/src/compliance/index.ts +28 -0
- package/src/compliance/templates.ts +338 -0
- package/src/compliance/types.ts +170 -0
- package/src/db/index.ts +28 -0
- package/src/db/schema.ts +84 -0
- package/src/feed-analyzer/feed-analyzer.ts +726 -0
- package/src/feed-analyzer/index.ts +34 -0
- package/src/feed-analyzer/types.ts +354 -0
- package/src/generator/index.ts +7 -0
- package/src/generator/key-generator.ts +124 -0
- package/src/generator/profile-builder.ts +402 -0
- package/src/hosting/artifacts-generator.ts +679 -0
- package/src/hosting/index.ts +6 -0
- package/src/index.ts +105 -0
- package/src/security/index.ts +15 -0
- package/src/security/security-scanner.ts +604 -0
- package/src/security/types.ts +55 -0
- package/src/services/directory.ts +434 -0
- package/src/simulator/agent-simulator.ts +941 -0
- package/src/simulator/index.ts +7 -0
- package/src/simulator/types.ts +170 -0
- package/src/types/generator.ts +140 -0
- package/src/types/index.ts +7 -0
- package/src/types/ucp-profile.ts +140 -0
- package/src/types/validation.ts +89 -0
- package/src/validator/index.ts +194 -0
- package/src/validator/network-validator.ts +417 -0
- package/src/validator/rules-validator.ts +297 -0
- package/src/validator/sdk-validator.ts +330 -0
- package/src/validator/structural-validator.ts +476 -0
- package/tests/fixtures/non-compliant-profile.json +25 -0
- package/tests/fixtures/official-sample-profile.json +75 -0
- package/tests/integration/benchmark.test.ts +207 -0
- package/tests/integration/database.test.ts +163 -0
- package/tests/integration/directory-api.test.ts +268 -0
- package/tests/integration/simulate-api.test.ts +230 -0
- package/tests/integration/validate-api.test.ts +269 -0
- package/tests/setup.ts +15 -0
- package/tests/unit/agent-simulator.test.ts +575 -0
- package/tests/unit/compliance-generator.test.ts +374 -0
- package/tests/unit/directory-service.test.ts +272 -0
- package/tests/unit/feed-analyzer.test.ts +517 -0
- package/tests/unit/lint-suggestions.test.ts +423 -0
- package/tests/unit/official-samples.test.ts +211 -0
- package/tests/unit/pdf-report.test.ts +390 -0
- package/tests/unit/sdk-validator.test.ts +531 -0
- package/tests/unit/security-scanner.test.ts +410 -0
- package/tests/unit/validation.test.ts +390 -0
- package/tsconfig.json +20 -0
- package/vercel.json +34 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for AI Agent Simulator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
simulateDiscoveryFlow,
|
|
8
|
+
inspectCapabilities,
|
|
9
|
+
inspectServices,
|
|
10
|
+
simulateRestApi,
|
|
11
|
+
simulateCheckoutFlow,
|
|
12
|
+
simulatePaymentReadiness,
|
|
13
|
+
simulateAgentInteraction,
|
|
14
|
+
calculateScore,
|
|
15
|
+
generateRecommendations,
|
|
16
|
+
} from '../../src/simulator/agent-simulator.js';
|
|
17
|
+
import type { UcpProfile } from '../../src/types/ucp-profile.js';
|
|
18
|
+
import type { DiscoveryFlowResult, CapabilityInspectionResult, ServiceInspectionResult } from '../../src/simulator/types.js';
|
|
19
|
+
|
|
20
|
+
// Mock fetch globally
|
|
21
|
+
const mockFetch = vi.fn();
|
|
22
|
+
global.fetch = mockFetch;
|
|
23
|
+
|
|
24
|
+
// Sample UCP profile for testing
|
|
25
|
+
const sampleProfile: UcpProfile = {
|
|
26
|
+
ucp: {
|
|
27
|
+
version: '2026-01-11',
|
|
28
|
+
services: {
|
|
29
|
+
'dev.ucp.shopping': {
|
|
30
|
+
version: '2026-01-11',
|
|
31
|
+
spec: 'https://ucp.dev/specs/shopping',
|
|
32
|
+
rest: {
|
|
33
|
+
schema: 'https://ucp.dev/services/shopping/openapi.json',
|
|
34
|
+
endpoint: 'https://example.com/api',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
capabilities: [
|
|
39
|
+
{
|
|
40
|
+
name: 'dev.ucp.shopping.checkout',
|
|
41
|
+
version: '2026-01-11',
|
|
42
|
+
spec: 'https://ucp.dev/specs/shopping/checkout',
|
|
43
|
+
schema: 'https://ucp.dev/schemas/shopping/checkout.json',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'dev.ucp.shopping.order',
|
|
47
|
+
version: '2026-01-11',
|
|
48
|
+
spec: 'https://ucp.dev/specs/shopping/order',
|
|
49
|
+
schema: 'https://ucp.dev/schemas/shopping/order.json',
|
|
50
|
+
extends: 'dev.ucp.shopping.checkout',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
signing_keys: [
|
|
55
|
+
{
|
|
56
|
+
kty: 'EC',
|
|
57
|
+
crv: 'P-256',
|
|
58
|
+
x: 'test-x',
|
|
59
|
+
y: 'test-y',
|
|
60
|
+
kid: 'key-1',
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
payment: {
|
|
64
|
+
handlers: [
|
|
65
|
+
{
|
|
66
|
+
id: 'stripe',
|
|
67
|
+
name: 'com.stripe.checkout',
|
|
68
|
+
version: '2026-01-11',
|
|
69
|
+
spec: 'https://stripe.com/docs/ucp',
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Minimal profile without optional features
|
|
76
|
+
const minimalProfile: UcpProfile = {
|
|
77
|
+
ucp: {
|
|
78
|
+
version: '2026-01-11',
|
|
79
|
+
services: {
|
|
80
|
+
'dev.ucp.shopping': {
|
|
81
|
+
version: '2026-01-11',
|
|
82
|
+
spec: 'https://ucp.dev/specs/shopping',
|
|
83
|
+
rest: {
|
|
84
|
+
schema: 'https://example.com/api/schema.json',
|
|
85
|
+
endpoint: 'https://example.com/api',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
capabilities: [],
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
describe('Agent Simulator', () => {
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
mockFetch.mockReset();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
vi.restoreAllMocks();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('simulateDiscoveryFlow', () => {
|
|
103
|
+
it('should successfully discover a UCP profile', async () => {
|
|
104
|
+
mockFetch.mockResolvedValueOnce({
|
|
105
|
+
ok: true,
|
|
106
|
+
status: 200,
|
|
107
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
108
|
+
json: async () => sampleProfile,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const result = await simulateDiscoveryFlow('example.com', 5000);
|
|
112
|
+
|
|
113
|
+
expect(result.success).toBe(true);
|
|
114
|
+
expect(result.profileUrl).toBe('https://example.com/.well-known/ucp');
|
|
115
|
+
expect(result.services).toContain('dev.ucp.shopping');
|
|
116
|
+
expect(result.capabilities).toContain('dev.ucp.shopping.checkout');
|
|
117
|
+
expect(result.transports).toContain('rest');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should try .well-known/ucp.json if .well-known/ucp fails', async () => {
|
|
121
|
+
// First call fails
|
|
122
|
+
mockFetch.mockResolvedValueOnce({
|
|
123
|
+
ok: false,
|
|
124
|
+
status: 404,
|
|
125
|
+
});
|
|
126
|
+
// Second call succeeds
|
|
127
|
+
mockFetch.mockResolvedValueOnce({
|
|
128
|
+
ok: true,
|
|
129
|
+
status: 200,
|
|
130
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
131
|
+
json: async () => sampleProfile,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const result = await simulateDiscoveryFlow('example.com', 5000);
|
|
135
|
+
|
|
136
|
+
expect(result.success).toBe(true);
|
|
137
|
+
expect(result.profileUrl).toBe('https://example.com/.well-known/ucp.json');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should fail if no UCP profile found', async () => {
|
|
141
|
+
mockFetch.mockResolvedValue({
|
|
142
|
+
ok: false,
|
|
143
|
+
status: 404,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const result = await simulateDiscoveryFlow('example.com', 5000);
|
|
147
|
+
|
|
148
|
+
expect(result.success).toBe(false);
|
|
149
|
+
expect(result.steps.some(s => s.status === 'failed')).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should detect available transports', async () => {
|
|
153
|
+
const profileWithMultipleTransports = {
|
|
154
|
+
...sampleProfile,
|
|
155
|
+
ucp: {
|
|
156
|
+
...sampleProfile.ucp,
|
|
157
|
+
services: {
|
|
158
|
+
'dev.ucp.shopping': {
|
|
159
|
+
version: '2026-01-11',
|
|
160
|
+
spec: 'https://ucp.dev/specs/shopping',
|
|
161
|
+
rest: {
|
|
162
|
+
schema: 'https://example.com/openapi.json',
|
|
163
|
+
endpoint: 'https://example.com/api',
|
|
164
|
+
},
|
|
165
|
+
mcp: {
|
|
166
|
+
schema: 'https://example.com/mcp.json',
|
|
167
|
+
endpoint: 'https://example.com/mcp',
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
mockFetch.mockResolvedValueOnce({
|
|
175
|
+
ok: true,
|
|
176
|
+
status: 200,
|
|
177
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
178
|
+
json: async () => profileWithMultipleTransports,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const result = await simulateDiscoveryFlow('example.com', 5000);
|
|
182
|
+
|
|
183
|
+
expect(result.transports).toContain('rest');
|
|
184
|
+
expect(result.transports).toContain('mcp');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should handle network timeout gracefully', async () => {
|
|
188
|
+
mockFetch.mockImplementation(() => {
|
|
189
|
+
return new Promise((_, reject) => {
|
|
190
|
+
setTimeout(() => reject(new Error('timeout')), 100);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const result = await simulateDiscoveryFlow('example.com', 50);
|
|
195
|
+
|
|
196
|
+
expect(result.success).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('inspectCapabilities', () => {
|
|
201
|
+
it('should inspect capabilities and check schema accessibility', async () => {
|
|
202
|
+
// Schema fetch succeeds
|
|
203
|
+
mockFetch.mockResolvedValueOnce({
|
|
204
|
+
ok: true,
|
|
205
|
+
status: 200,
|
|
206
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
207
|
+
json: async () => ({ type: 'object' }),
|
|
208
|
+
});
|
|
209
|
+
// Spec fetch succeeds
|
|
210
|
+
mockFetch.mockResolvedValueOnce({
|
|
211
|
+
ok: true,
|
|
212
|
+
status: 200,
|
|
213
|
+
});
|
|
214
|
+
// Second capability schema
|
|
215
|
+
mockFetch.mockResolvedValueOnce({
|
|
216
|
+
ok: true,
|
|
217
|
+
status: 200,
|
|
218
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
219
|
+
json: async () => ({ type: 'object' }),
|
|
220
|
+
});
|
|
221
|
+
// Second capability spec
|
|
222
|
+
mockFetch.mockResolvedValueOnce({
|
|
223
|
+
ok: true,
|
|
224
|
+
status: 200,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const result = await inspectCapabilities(sampleProfile, 5000);
|
|
228
|
+
|
|
229
|
+
expect(result.length).toBe(2);
|
|
230
|
+
expect(result[0].name).toBe('dev.ucp.shopping.checkout');
|
|
231
|
+
expect(result[0].schemaAccessible).toBe(true);
|
|
232
|
+
expect(result[1].isExtension).toBe(true);
|
|
233
|
+
expect(result[1].parentCapability).toBe('dev.ucp.shopping.checkout');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should handle inaccessible schemas', async () => {
|
|
237
|
+
mockFetch.mockResolvedValue({
|
|
238
|
+
ok: false,
|
|
239
|
+
status: 404,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const result = await inspectCapabilities(sampleProfile, 5000);
|
|
243
|
+
|
|
244
|
+
expect(result[0].schemaAccessible).toBe(false);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('inspectServices', () => {
|
|
249
|
+
it('should inspect REST service transport', async () => {
|
|
250
|
+
// Schema check
|
|
251
|
+
mockFetch.mockResolvedValueOnce({
|
|
252
|
+
ok: true,
|
|
253
|
+
status: 200,
|
|
254
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
255
|
+
json: async () => ({ openapi: '3.0.0' }),
|
|
256
|
+
});
|
|
257
|
+
// Endpoint check
|
|
258
|
+
mockFetch.mockResolvedValueOnce({
|
|
259
|
+
ok: true,
|
|
260
|
+
status: 200,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const result = await inspectServices(sampleProfile, 5000);
|
|
264
|
+
|
|
265
|
+
expect(result.length).toBe(1);
|
|
266
|
+
expect(result[0].name).toBe('dev.ucp.shopping');
|
|
267
|
+
expect(result[0].transports.rest?.schemaAccessible).toBe(true);
|
|
268
|
+
expect(result[0].transports.rest?.endpointResponsive).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should detect unresponsive endpoints', async () => {
|
|
272
|
+
mockFetch.mockResolvedValueOnce({
|
|
273
|
+
ok: true,
|
|
274
|
+
status: 200,
|
|
275
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
276
|
+
json: async () => ({}),
|
|
277
|
+
});
|
|
278
|
+
mockFetch.mockResolvedValueOnce({
|
|
279
|
+
ok: false,
|
|
280
|
+
status: 500,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const result = await inspectServices(sampleProfile, 5000);
|
|
284
|
+
|
|
285
|
+
expect(result[0].transports.rest?.endpointResponsive).toBe(false);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('simulateRestApi', () => {
|
|
290
|
+
it('should load and analyze OpenAPI schema', async () => {
|
|
291
|
+
const openApiSchema = {
|
|
292
|
+
openapi: '3.0.0',
|
|
293
|
+
paths: {
|
|
294
|
+
'/checkout': { post: {} },
|
|
295
|
+
'/orders': { get: {} },
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// Schema fetch
|
|
300
|
+
mockFetch.mockResolvedValueOnce({
|
|
301
|
+
ok: true,
|
|
302
|
+
status: 200,
|
|
303
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
304
|
+
json: async () => openApiSchema,
|
|
305
|
+
});
|
|
306
|
+
// Endpoint check
|
|
307
|
+
mockFetch.mockResolvedValueOnce({
|
|
308
|
+
ok: true,
|
|
309
|
+
status: 200,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const result = await simulateRestApi(sampleProfile, 5000);
|
|
313
|
+
|
|
314
|
+
expect(result.success).toBe(true);
|
|
315
|
+
expect(result.schemaLoaded).toBe(true);
|
|
316
|
+
expect(result.endpointAccessible).toBe(true);
|
|
317
|
+
expect(result.steps.some(s => s.message.includes('2 operation path'))).toBe(true);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should skip if no REST service configured', async () => {
|
|
321
|
+
const noRestProfile: UcpProfile = {
|
|
322
|
+
ucp: {
|
|
323
|
+
version: '2026-01-11',
|
|
324
|
+
services: {
|
|
325
|
+
'dev.ucp.shopping': {
|
|
326
|
+
version: '2026-01-11',
|
|
327
|
+
spec: 'https://ucp.dev/specs/shopping',
|
|
328
|
+
mcp: {
|
|
329
|
+
schema: 'https://example.com/mcp.json',
|
|
330
|
+
endpoint: 'https://example.com/mcp',
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
capabilities: [],
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const result = await simulateRestApi(noRestProfile, 5000);
|
|
339
|
+
|
|
340
|
+
expect(result.success).toBe(false);
|
|
341
|
+
expect(result.steps[0].status).toBe('skipped');
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
describe('simulateCheckoutFlow', () => {
|
|
346
|
+
it('should detect checkout capability', async () => {
|
|
347
|
+
mockFetch.mockResolvedValueOnce({
|
|
348
|
+
ok: true,
|
|
349
|
+
status: 200,
|
|
350
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
351
|
+
json: async () => ({ properties: { checkout_id: {} } }),
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const result = await simulateCheckoutFlow(sampleProfile, 5000);
|
|
355
|
+
|
|
356
|
+
expect(result.canCreateCheckout).toBe(true);
|
|
357
|
+
expect(result.checkoutSchemaValid).toBe(true);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('should detect order and fulfillment capabilities', async () => {
|
|
361
|
+
const fullProfile: UcpProfile = {
|
|
362
|
+
ucp: {
|
|
363
|
+
version: '2026-01-11',
|
|
364
|
+
services: {},
|
|
365
|
+
capabilities: [
|
|
366
|
+
{ name: 'dev.ucp.shopping.checkout', version: '2026-01-11', spec: '', schema: '' },
|
|
367
|
+
{ name: 'dev.ucp.shopping.order', version: '2026-01-11', spec: '', schema: '' },
|
|
368
|
+
{ name: 'dev.ucp.shopping.fulfillment', version: '2026-01-11', spec: '', schema: '' },
|
|
369
|
+
],
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// Skip schema checks
|
|
374
|
+
mockFetch.mockResolvedValue({ ok: false });
|
|
375
|
+
|
|
376
|
+
const result = await simulateCheckoutFlow(fullProfile, 5000);
|
|
377
|
+
|
|
378
|
+
expect(result.canCreateCheckout).toBe(true);
|
|
379
|
+
expect(result.orderFlowSupported).toBe(true);
|
|
380
|
+
expect(result.fulfillmentSupported).toBe(true);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('should fail if no checkout capability', async () => {
|
|
384
|
+
const result = await simulateCheckoutFlow(minimalProfile, 5000);
|
|
385
|
+
|
|
386
|
+
expect(result.canCreateCheckout).toBe(false);
|
|
387
|
+
expect(result.steps.some(s => s.status === 'failed')).toBe(true);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe('simulatePaymentReadiness', () => {
|
|
392
|
+
it('should detect payment handlers', async () => {
|
|
393
|
+
const result = await simulatePaymentReadiness(sampleProfile, 5000);
|
|
394
|
+
|
|
395
|
+
expect(result.handlersFound).toBe(1);
|
|
396
|
+
expect(result.steps.some(s => s.message.includes('payment handler'))).toBe(true);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should validate signing keys', async () => {
|
|
400
|
+
const result = await simulatePaymentReadiness(sampleProfile, 5000);
|
|
401
|
+
|
|
402
|
+
expect(result.webhookVerifiable).toBe(true);
|
|
403
|
+
expect(result.signingKeyValid).toBe(true);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('should detect missing payment config', async () => {
|
|
407
|
+
const result = await simulatePaymentReadiness(minimalProfile, 5000);
|
|
408
|
+
|
|
409
|
+
expect(result.handlersFound).toBe(0);
|
|
410
|
+
expect(result.webhookVerifiable).toBe(false);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe('calculateScore', () => {
|
|
415
|
+
it('should give high score for complete profile', () => {
|
|
416
|
+
const discovery: DiscoveryFlowResult = {
|
|
417
|
+
success: true,
|
|
418
|
+
steps: [],
|
|
419
|
+
capabilities: ['dev.ucp.shopping.checkout'],
|
|
420
|
+
services: ['dev.ucp.shopping'],
|
|
421
|
+
transports: ['rest'],
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const capabilities: CapabilityInspectionResult[] = [
|
|
425
|
+
{ name: 'dev.ucp.shopping.checkout', version: '2026-01-11', schemaAccessible: true, specAccessible: true, isExtension: false },
|
|
426
|
+
];
|
|
427
|
+
|
|
428
|
+
const services: ServiceInspectionResult[] = [
|
|
429
|
+
{ name: 'dev.ucp.shopping', version: '2026-01-11', transports: { rest: { endpoint: '', schemaAccessible: true, endpointResponsive: true } } },
|
|
430
|
+
];
|
|
431
|
+
|
|
432
|
+
const checkout = { success: true, steps: [], canCreateCheckout: true, checkoutSchemaValid: true, orderFlowSupported: true, fulfillmentSupported: false };
|
|
433
|
+
const payment = { success: true, steps: [], handlersFound: 1, webhookVerifiable: true, signingKeyValid: true };
|
|
434
|
+
|
|
435
|
+
const score = calculateScore(discovery, capabilities, services, undefined, checkout, payment);
|
|
436
|
+
|
|
437
|
+
expect(score).toBeGreaterThanOrEqual(70);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('should give low score for failed discovery', () => {
|
|
441
|
+
const discovery: DiscoveryFlowResult = {
|
|
442
|
+
success: false,
|
|
443
|
+
steps: [],
|
|
444
|
+
capabilities: [],
|
|
445
|
+
services: [],
|
|
446
|
+
transports: [],
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const score = calculateScore(discovery, [], [], undefined, undefined, undefined);
|
|
450
|
+
|
|
451
|
+
expect(score).toBeLessThan(30);
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
describe('generateRecommendations', () => {
|
|
456
|
+
it('should recommend fixing discovery issues', () => {
|
|
457
|
+
const discovery: DiscoveryFlowResult = {
|
|
458
|
+
success: false,
|
|
459
|
+
steps: [],
|
|
460
|
+
capabilities: [],
|
|
461
|
+
services: [],
|
|
462
|
+
transports: [],
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const recommendations = generateRecommendations(discovery, [], []);
|
|
466
|
+
|
|
467
|
+
expect(recommendations.some(r => r.includes('/.well-known/ucp'))).toBe(true);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('should recommend adding checkout capability', () => {
|
|
471
|
+
const discovery: DiscoveryFlowResult = {
|
|
472
|
+
success: true,
|
|
473
|
+
steps: [],
|
|
474
|
+
capabilities: [],
|
|
475
|
+
services: ['dev.ucp.shopping'],
|
|
476
|
+
transports: ['rest'],
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const checkout = { success: false, steps: [], canCreateCheckout: false, checkoutSchemaValid: false, orderFlowSupported: false, fulfillmentSupported: false };
|
|
480
|
+
|
|
481
|
+
const recommendations = generateRecommendations(discovery, [], [], undefined, checkout);
|
|
482
|
+
|
|
483
|
+
expect(recommendations.some(r => r.includes('checkout capability'))).toBe(true);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('should give positive message when all looks good', () => {
|
|
487
|
+
const discovery: DiscoveryFlowResult = {
|
|
488
|
+
success: true,
|
|
489
|
+
steps: [],
|
|
490
|
+
capabilities: ['dev.ucp.shopping.checkout'],
|
|
491
|
+
services: ['dev.ucp.shopping'],
|
|
492
|
+
transports: ['rest'],
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
const capabilities: CapabilityInspectionResult[] = [
|
|
496
|
+
{ name: 'dev.ucp.shopping.checkout', version: '2026-01-11', schemaAccessible: true, specAccessible: true, isExtension: false },
|
|
497
|
+
];
|
|
498
|
+
|
|
499
|
+
const restApi = { success: true, steps: [], schemaLoaded: true, endpointAccessible: true, sampleOperations: [] };
|
|
500
|
+
const checkout = { success: true, steps: [], canCreateCheckout: true, checkoutSchemaValid: true, orderFlowSupported: true, fulfillmentSupported: true };
|
|
501
|
+
const payment = { success: true, steps: [], handlersFound: 1, webhookVerifiable: true, signingKeyValid: true };
|
|
502
|
+
|
|
503
|
+
const recommendations = generateRecommendations(discovery, capabilities, [], restApi, checkout, payment);
|
|
504
|
+
|
|
505
|
+
expect(recommendations.some(r => r.includes('well-configured'))).toBe(true);
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
describe('simulateAgentInteraction (integration)', () => {
|
|
510
|
+
it('should run full simulation successfully', async () => {
|
|
511
|
+
// Profile fetch (discovery)
|
|
512
|
+
mockFetch.mockResolvedValueOnce({
|
|
513
|
+
ok: true,
|
|
514
|
+
status: 200,
|
|
515
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
516
|
+
json: async () => sampleProfile,
|
|
517
|
+
});
|
|
518
|
+
// Profile fetch again for detailed inspection
|
|
519
|
+
mockFetch.mockResolvedValueOnce({
|
|
520
|
+
ok: true,
|
|
521
|
+
status: 200,
|
|
522
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
523
|
+
json: async () => sampleProfile,
|
|
524
|
+
});
|
|
525
|
+
// All subsequent fetches succeed
|
|
526
|
+
mockFetch.mockResolvedValue({
|
|
527
|
+
ok: true,
|
|
528
|
+
status: 200,
|
|
529
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
530
|
+
json: async () => ({ type: 'object', properties: { checkout_id: {} } }),
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const result = await simulateAgentInteraction('example.com', { timeoutMs: 10000 });
|
|
534
|
+
|
|
535
|
+
expect(result.ok).toBe(true);
|
|
536
|
+
expect(result.domain).toBe('example.com');
|
|
537
|
+
expect(result.overallScore).toBeGreaterThan(0);
|
|
538
|
+
expect(result.discovery.success).toBe(true);
|
|
539
|
+
expect(result.summary.totalSteps).toBeGreaterThan(0);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('should handle complete failure gracefully', async () => {
|
|
543
|
+
mockFetch.mockResolvedValue({
|
|
544
|
+
ok: false,
|
|
545
|
+
status: 404,
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
const result = await simulateAgentInteraction('nonexistent.com', { timeoutMs: 5000 });
|
|
549
|
+
|
|
550
|
+
expect(result.ok).toBe(false);
|
|
551
|
+
expect(result.overallScore).toBe(0);
|
|
552
|
+
expect(result.recommendations.length).toBeGreaterThan(0);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
it('should include timing information', async () => {
|
|
556
|
+
mockFetch.mockResolvedValueOnce({
|
|
557
|
+
ok: true,
|
|
558
|
+
status: 200,
|
|
559
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
560
|
+
json: async () => sampleProfile,
|
|
561
|
+
});
|
|
562
|
+
mockFetch.mockResolvedValue({
|
|
563
|
+
ok: true,
|
|
564
|
+
status: 200,
|
|
565
|
+
headers: new Map([['content-type', 'application/json']]),
|
|
566
|
+
json: async () => ({}),
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
const result = await simulateAgentInteraction('example.com', { timeoutMs: 10000 });
|
|
570
|
+
|
|
571
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
572
|
+
expect(result.simulatedAt).toBeTruthy();
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
});
|