@uniforge/testing 0.1.0-alpha.2

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.
Files changed (85) hide show
  1. package/dist/auth/index.d.cts +177 -0
  2. package/dist/auth/index.d.ts +177 -0
  3. package/dist/auth/index.js +459 -0
  4. package/dist/auth/index.js.map +1 -0
  5. package/dist/auth/index.mjs +418 -0
  6. package/dist/auth/index.mjs.map +1 -0
  7. package/dist/billing/index.d.cts +27 -0
  8. package/dist/billing/index.d.ts +27 -0
  9. package/dist/billing/index.js +208 -0
  10. package/dist/billing/index.js.map +1 -0
  11. package/dist/billing/index.mjs +178 -0
  12. package/dist/billing/index.mjs.map +1 -0
  13. package/dist/database/index.d.cts +399 -0
  14. package/dist/database/index.d.ts +399 -0
  15. package/dist/database/index.js +19054 -0
  16. package/dist/database/index.js.map +1 -0
  17. package/dist/database/index.mjs +19046 -0
  18. package/dist/database/index.mjs.map +1 -0
  19. package/dist/graphql/index.d.cts +23 -0
  20. package/dist/graphql/index.d.ts +23 -0
  21. package/dist/graphql/index.js +18511 -0
  22. package/dist/graphql/index.js.map +1 -0
  23. package/dist/graphql/index.mjs +18505 -0
  24. package/dist/graphql/index.mjs.map +1 -0
  25. package/dist/index.d.cts +10 -0
  26. package/dist/index.d.ts +10 -0
  27. package/dist/index.js +31 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/index.mjs +6 -0
  30. package/dist/index.mjs.map +1 -0
  31. package/dist/multi-store/index.d.cts +66 -0
  32. package/dist/multi-store/index.d.ts +66 -0
  33. package/dist/multi-store/index.js +319 -0
  34. package/dist/multi-store/index.js.map +1 -0
  35. package/dist/multi-store/index.mjs +287 -0
  36. package/dist/multi-store/index.mjs.map +1 -0
  37. package/dist/multi-tenant/index.d.cts +15 -0
  38. package/dist/multi-tenant/index.d.ts +15 -0
  39. package/dist/multi-tenant/index.js +87 -0
  40. package/dist/multi-tenant/index.js.map +1 -0
  41. package/dist/multi-tenant/index.mjs +57 -0
  42. package/dist/multi-tenant/index.mjs.map +1 -0
  43. package/dist/performance/index.d.cts +60 -0
  44. package/dist/performance/index.d.ts +60 -0
  45. package/dist/performance/index.js +280 -0
  46. package/dist/performance/index.js.map +1 -0
  47. package/dist/performance/index.mjs +246 -0
  48. package/dist/performance/index.mjs.map +1 -0
  49. package/dist/platform/index.d.cts +71 -0
  50. package/dist/platform/index.d.ts +71 -0
  51. package/dist/platform/index.js +435 -0
  52. package/dist/platform/index.js.map +1 -0
  53. package/dist/platform/index.mjs +396 -0
  54. package/dist/platform/index.mjs.map +1 -0
  55. package/dist/rbac/index.d.cts +21 -0
  56. package/dist/rbac/index.d.ts +21 -0
  57. package/dist/rbac/index.js +178 -0
  58. package/dist/rbac/index.js.map +1 -0
  59. package/dist/rbac/index.mjs +150 -0
  60. package/dist/rbac/index.mjs.map +1 -0
  61. package/dist/security/index.d.cts +73 -0
  62. package/dist/security/index.d.ts +73 -0
  63. package/dist/security/index.js +246 -0
  64. package/dist/security/index.js.map +1 -0
  65. package/dist/security/index.mjs +211 -0
  66. package/dist/security/index.mjs.map +1 -0
  67. package/dist/shopify-api/index.d.cts +139 -0
  68. package/dist/shopify-api/index.d.ts +139 -0
  69. package/dist/shopify-api/index.js +469 -0
  70. package/dist/shopify-api/index.js.map +1 -0
  71. package/dist/shopify-api/index.mjs +439 -0
  72. package/dist/shopify-api/index.mjs.map +1 -0
  73. package/dist/shopify-compliance/index.d.cts +85 -0
  74. package/dist/shopify-compliance/index.d.ts +85 -0
  75. package/dist/shopify-compliance/index.js +287 -0
  76. package/dist/shopify-compliance/index.js.map +1 -0
  77. package/dist/shopify-compliance/index.mjs +259 -0
  78. package/dist/shopify-compliance/index.mjs.map +1 -0
  79. package/dist/webhooks/index.d.cts +127 -0
  80. package/dist/webhooks/index.d.ts +127 -0
  81. package/dist/webhooks/index.js +18934 -0
  82. package/dist/webhooks/index.js.map +1 -0
  83. package/dist/webhooks/index.mjs +18916 -0
  84. package/dist/webhooks/index.mjs.map +1 -0
  85. package/package.json +112 -0
@@ -0,0 +1,439 @@
1
+ // src/shopify-api/mock-server.ts
2
+ import { createServer } from "http";
3
+
4
+ // src/shopify-api/graphql-handler.ts
5
+ var GraphQLHandler = class {
6
+ operations = /* @__PURE__ */ new Map();
7
+ sequences = /* @__PURE__ */ new Map();
8
+ queryPatterns = [];
9
+ defaultResponse = null;
10
+ rateLimitConfig = null;
11
+ onOperation(operationName, response) {
12
+ this.operations.set(operationName, { response });
13
+ }
14
+ onQuery(queryPattern, response) {
15
+ this.queryPatterns.push({ pattern: queryPattern, response });
16
+ }
17
+ onOperationSequence(operationName, responses) {
18
+ this.sequences.set(operationName, { responses, index: 0 });
19
+ }
20
+ enableRateLimiting(config) {
21
+ const maxCost = config?.maxCost ?? 1e3;
22
+ const restoreRate = config?.restoreRate ?? 50;
23
+ this.rateLimitConfig = {
24
+ maxCost,
25
+ restoreRate,
26
+ currentBudget: maxCost,
27
+ lastRestoreTime: Date.now()
28
+ };
29
+ }
30
+ disableRateLimiting() {
31
+ this.rateLimitConfig = null;
32
+ }
33
+ setDefaultResponse(response) {
34
+ this.defaultResponse = response;
35
+ }
36
+ handle(body) {
37
+ if (this.rateLimitConfig) {
38
+ this.restoreBudget();
39
+ }
40
+ const operationName = body.operationName ?? this.extractOperationName(body.query);
41
+ if (operationName) {
42
+ const sequence = this.sequences.get(operationName);
43
+ if (sequence) {
44
+ const response = sequence.responses[sequence.index] ?? sequence.responses[sequence.responses.length - 1];
45
+ if (sequence.index < sequence.responses.length) {
46
+ sequence.index++;
47
+ }
48
+ return this.applyRateLimiting(response);
49
+ }
50
+ }
51
+ if (operationName) {
52
+ const registration = this.operations.get(operationName);
53
+ if (registration) {
54
+ return this.applyRateLimiting(registration.response);
55
+ }
56
+ }
57
+ for (const { pattern, response } of this.queryPatterns) {
58
+ if (typeof pattern === "string") {
59
+ if (body.query.includes(pattern)) {
60
+ return this.applyRateLimiting(response);
61
+ }
62
+ } else {
63
+ if (pattern.test(body.query)) {
64
+ return this.applyRateLimiting(response);
65
+ }
66
+ }
67
+ }
68
+ if (this.defaultResponse) {
69
+ return this.applyRateLimiting(this.defaultResponse);
70
+ }
71
+ return {
72
+ errors: [{ message: `No mock registered for operation: ${operationName ?? "unknown"}` }]
73
+ };
74
+ }
75
+ reset() {
76
+ this.operations.clear();
77
+ this.sequences.clear();
78
+ this.queryPatterns = [];
79
+ this.defaultResponse = null;
80
+ this.rateLimitConfig = null;
81
+ }
82
+ extractOperationName(query) {
83
+ const match = query.match(/(?:query|mutation|subscription)\s+(\w+)/);
84
+ return match?.[1];
85
+ }
86
+ restoreBudget() {
87
+ if (!this.rateLimitConfig) return;
88
+ const now = Date.now();
89
+ const elapsed = (now - this.rateLimitConfig.lastRestoreTime) / 1e3;
90
+ const restored = elapsed * this.rateLimitConfig.restoreRate;
91
+ this.rateLimitConfig.currentBudget = Math.min(
92
+ this.rateLimitConfig.maxCost,
93
+ this.rateLimitConfig.currentBudget + restored
94
+ );
95
+ this.rateLimitConfig.lastRestoreTime = now;
96
+ }
97
+ applyRateLimiting(response) {
98
+ if (!this.rateLimitConfig) return response;
99
+ const cost = response.extensions?.cost?.actualQueryCost ?? 10;
100
+ if (this.rateLimitConfig.currentBudget < cost) {
101
+ return {
102
+ errors: [{ message: "Throttled" }],
103
+ extensions: {
104
+ cost: {
105
+ requestedQueryCost: cost,
106
+ actualQueryCost: cost,
107
+ throttleStatus: {
108
+ maximumAvailable: this.rateLimitConfig.maxCost,
109
+ currentlyAvailable: this.rateLimitConfig.currentBudget,
110
+ restoreRate: this.rateLimitConfig.restoreRate
111
+ }
112
+ }
113
+ }
114
+ };
115
+ }
116
+ this.rateLimitConfig.currentBudget -= cost;
117
+ const extensions = response.extensions ?? {
118
+ cost: {
119
+ requestedQueryCost: cost,
120
+ actualQueryCost: cost,
121
+ throttleStatus: {
122
+ maximumAvailable: this.rateLimitConfig.maxCost,
123
+ currentlyAvailable: this.rateLimitConfig.currentBudget,
124
+ restoreRate: this.rateLimitConfig.restoreRate
125
+ }
126
+ }
127
+ };
128
+ return { ...response, extensions };
129
+ }
130
+ };
131
+
132
+ // src/shopify-api/rest-handler.ts
133
+ var RESTHandler = class {
134
+ routes = [];
135
+ onRequest(method, pathPattern, response) {
136
+ this.routes.push({ method: method.toUpperCase(), pattern: pathPattern, response });
137
+ }
138
+ onGet(pathPattern, response) {
139
+ this.onRequest("GET", pathPattern, response);
140
+ }
141
+ onPost(pathPattern, response) {
142
+ this.onRequest("POST", pathPattern, response);
143
+ }
144
+ onPut(pathPattern, response) {
145
+ this.onRequest("PUT", pathPattern, response);
146
+ }
147
+ onDelete(pathPattern, response) {
148
+ this.onRequest("DELETE", pathPattern, response);
149
+ }
150
+ handle(method, path, _body) {
151
+ const upperMethod = method.toUpperCase();
152
+ for (const route of this.routes) {
153
+ if (route.method !== upperMethod) continue;
154
+ if (typeof route.pattern === "string") {
155
+ if (path === route.pattern || path.startsWith(route.pattern)) {
156
+ return route.response;
157
+ }
158
+ } else {
159
+ if (route.pattern.test(path)) {
160
+ return route.response;
161
+ }
162
+ }
163
+ }
164
+ return { status: 404, body: { errors: "Not Found" } };
165
+ }
166
+ reset() {
167
+ this.routes = [];
168
+ }
169
+ };
170
+
171
+ // src/shopify-api/oauth-handler.ts
172
+ import { randomBytes } from "crypto";
173
+ var OAuthHandler = class {
174
+ config;
175
+ generatedCodes = /* @__PURE__ */ new Map();
176
+ constructor(config) {
177
+ this.config = {
178
+ accessToken: "shpat_test_token_000000000000000000000000",
179
+ scope: "read_products,write_products",
180
+ expiresIn: 86400,
181
+ ...config
182
+ };
183
+ }
184
+ configure(config) {
185
+ this.config = { ...this.config, ...config };
186
+ }
187
+ handleAuthorize(query) {
188
+ const code = randomBytes(16).toString("hex");
189
+ const redirectUri = query["redirect_uri"] ?? "";
190
+ const state = query["state"] ?? "";
191
+ const shop = query["shop"] ?? "test-shop.myshopify.com";
192
+ this.generatedCodes.set(code, { nonce: state, shop });
193
+ const url = new URL(redirectUri || "https://localhost/callback");
194
+ url.searchParams.set("code", code);
195
+ url.searchParams.set("shop", shop);
196
+ url.searchParams.set("state", state);
197
+ url.searchParams.set("hmac", "mock_hmac_value");
198
+ url.searchParams.set("timestamp", String(Math.floor(Date.now() / 1e3)));
199
+ return { redirectUrl: url.toString(), code };
200
+ }
201
+ handleAccessToken(_body) {
202
+ const response = {
203
+ access_token: this.config.accessToken,
204
+ scope: this.config.scope
205
+ };
206
+ if (this.config.expiresIn !== void 0) {
207
+ response["expires_in"] = this.config.expiresIn;
208
+ }
209
+ if (this.config.associatedUser !== void 0) {
210
+ response["associated_user"] = {
211
+ id: this.config.associatedUser.id,
212
+ first_name: this.config.associatedUser.firstName,
213
+ last_name: this.config.associatedUser.lastName,
214
+ email: this.config.associatedUser.email,
215
+ email_verified: this.config.associatedUser.emailVerified,
216
+ account_owner: this.config.associatedUser.accountOwner,
217
+ locale: this.config.associatedUser.locale,
218
+ collaborator: this.config.associatedUser.collaborator
219
+ };
220
+ response["associated_user_scope"] = this.config.scope;
221
+ }
222
+ return { status: 200, body: response };
223
+ }
224
+ handleTokenExchange(_body) {
225
+ const response = {
226
+ access_token: this.config.accessToken,
227
+ scope: this.config.scope,
228
+ token_type: "bearer",
229
+ expires_in: this.config.expiresIn ?? 86400
230
+ };
231
+ if (this.config.associatedUser !== void 0) {
232
+ response["associated_user"] = {
233
+ id: this.config.associatedUser.id,
234
+ first_name: this.config.associatedUser.firstName,
235
+ last_name: this.config.associatedUser.lastName,
236
+ email: this.config.associatedUser.email,
237
+ email_verified: this.config.associatedUser.emailVerified,
238
+ account_owner: this.config.associatedUser.accountOwner,
239
+ locale: this.config.associatedUser.locale,
240
+ collaborator: this.config.associatedUser.collaborator
241
+ };
242
+ response["associated_user_scope"] = this.config.scope;
243
+ }
244
+ return { status: 200, body: response };
245
+ }
246
+ reset() {
247
+ this.generatedCodes.clear();
248
+ this.config = {
249
+ accessToken: "shpat_test_token_000000000000000000000000",
250
+ scope: "read_products,write_products",
251
+ expiresIn: 86400
252
+ };
253
+ }
254
+ };
255
+
256
+ // src/shopify-api/mock-server.ts
257
+ function readBody(req) {
258
+ return new Promise((resolve, reject) => {
259
+ const chunks = [];
260
+ req.on("data", (chunk) => chunks.push(chunk));
261
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
262
+ req.on("error", reject);
263
+ });
264
+ }
265
+ function parseQuery(url) {
266
+ const query = {};
267
+ url.searchParams.forEach((value, key) => {
268
+ query[key] = value;
269
+ });
270
+ return query;
271
+ }
272
+ function delay(ms) {
273
+ return new Promise((resolve) => setTimeout(resolve, ms));
274
+ }
275
+ var MockShopifyServer = class {
276
+ graphql;
277
+ rest;
278
+ oauth;
279
+ server = null;
280
+ serverPort = 0;
281
+ config;
282
+ requests = [];
283
+ constructor(config) {
284
+ this.config = {
285
+ port: config?.port ?? 0,
286
+ apiVersion: config?.apiVersion ?? "2024-01",
287
+ defaultShop: config?.defaultShop ?? "test-shop.myshopify.com",
288
+ latencyMs: config?.latencyMs ?? 0
289
+ };
290
+ this.graphql = new GraphQLHandler();
291
+ this.rest = new RESTHandler();
292
+ this.oauth = new OAuthHandler();
293
+ }
294
+ async start() {
295
+ return new Promise((resolve, reject) => {
296
+ this.server = createServer((req, res) => {
297
+ this.handleRequest(req, res).catch((err) => {
298
+ res.writeHead(500, { "Content-Type": "application/json" });
299
+ res.end(JSON.stringify({ error: String(err) }));
300
+ });
301
+ });
302
+ this.server.on("error", reject);
303
+ this.server.listen(this.config.port, "127.0.0.1", () => {
304
+ const addr = this.server.address();
305
+ if (addr && typeof addr === "object") {
306
+ this.serverPort = addr.port;
307
+ }
308
+ resolve({ port: this.serverPort, url: this.url });
309
+ });
310
+ });
311
+ }
312
+ async stop() {
313
+ return new Promise((resolve, reject) => {
314
+ if (!this.server) {
315
+ resolve();
316
+ return;
317
+ }
318
+ this.server.close((err) => {
319
+ this.server = null;
320
+ if (err) reject(err);
321
+ else resolve();
322
+ });
323
+ });
324
+ }
325
+ get url() {
326
+ return `http://127.0.0.1:${this.serverPort}`;
327
+ }
328
+ get port() {
329
+ return this.serverPort;
330
+ }
331
+ getRequests() {
332
+ return [...this.requests];
333
+ }
334
+ getLastRequest() {
335
+ return this.requests[this.requests.length - 1];
336
+ }
337
+ getRequestsByPath(pathPattern) {
338
+ return this.requests.filter((req) => {
339
+ if (typeof pathPattern === "string") {
340
+ return req.path.includes(pathPattern);
341
+ }
342
+ return pathPattern.test(req.path);
343
+ });
344
+ }
345
+ clearRequests() {
346
+ this.requests = [];
347
+ }
348
+ reset() {
349
+ this.graphql.reset();
350
+ this.rest.reset();
351
+ this.oauth.reset();
352
+ this.requests = [];
353
+ }
354
+ async handleRequest(req, res) {
355
+ if (this.config.latencyMs > 0) {
356
+ await delay(this.config.latencyMs);
357
+ }
358
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "127.0.0.1"}`);
359
+ const method = (req.method ?? "GET").toUpperCase();
360
+ const path = url.pathname;
361
+ const query = parseQuery(url);
362
+ const rawBody = await readBody(req);
363
+ let body = null;
364
+ if (rawBody) {
365
+ try {
366
+ body = JSON.parse(rawBody);
367
+ } catch {
368
+ body = rawBody;
369
+ }
370
+ }
371
+ const headers = {};
372
+ for (const [key, value] of Object.entries(req.headers)) {
373
+ if (typeof value === "string") {
374
+ headers[key] = value;
375
+ } else if (Array.isArray(value)) {
376
+ headers[key] = value.join(", ");
377
+ }
378
+ }
379
+ this.requests.push({
380
+ method,
381
+ path,
382
+ headers,
383
+ body,
384
+ query,
385
+ timestamp: /* @__PURE__ */ new Date()
386
+ });
387
+ if (path === "/admin/oauth/authorize") {
388
+ const result = this.oauth.handleAuthorize(query);
389
+ res.writeHead(302, { Location: result.redirectUrl });
390
+ res.end();
391
+ return;
392
+ }
393
+ if (path === `/admin/oauth/access_token` && method === "POST" && typeof body === "object" && body !== null && "grant_type" in body) {
394
+ const tokenBody = body;
395
+ if (tokenBody["grant_type"] === "urn:ietf:params:oauth:grant-type:token-exchange") {
396
+ const result = this.oauth.handleTokenExchange(tokenBody);
397
+ res.writeHead(result.status, { "Content-Type": "application/json" });
398
+ res.end(JSON.stringify(result.body));
399
+ return;
400
+ }
401
+ }
402
+ if (path === "/admin/oauth/access_token" && method === "POST") {
403
+ const tokenBody = typeof body === "object" && body !== null ? body : {};
404
+ const result = this.oauth.handleAccessToken(tokenBody);
405
+ res.writeHead(result.status, { "Content-Type": "application/json" });
406
+ res.end(JSON.stringify(result.body));
407
+ return;
408
+ }
409
+ const graphqlPath = `/admin/api/${this.config.apiVersion}/graphql.json`;
410
+ if (path === graphqlPath && method === "POST") {
411
+ const graphqlBody = body;
412
+ const result = this.graphql.handle(graphqlBody);
413
+ const statusCode = result.errors?.some((e) => e.message === "Throttled") ? 429 : 200;
414
+ res.writeHead(statusCode, { "Content-Type": "application/json" });
415
+ res.end(JSON.stringify(result));
416
+ return;
417
+ }
418
+ const restPathPrefix = `/admin/api/${this.config.apiVersion}/`;
419
+ if (path.startsWith(restPathPrefix)) {
420
+ const result = this.rest.handle(method, path, body);
421
+ const responseHeaders = {
422
+ "Content-Type": "application/json",
423
+ ...result.headers
424
+ };
425
+ res.writeHead(result.status ?? 200, responseHeaders);
426
+ res.end(JSON.stringify(result.body ?? {}));
427
+ return;
428
+ }
429
+ res.writeHead(404, { "Content-Type": "application/json" });
430
+ res.end(JSON.stringify({ errors: "Not Found" }));
431
+ }
432
+ };
433
+ export {
434
+ GraphQLHandler,
435
+ MockShopifyServer,
436
+ OAuthHandler,
437
+ RESTHandler
438
+ };
439
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shopify-api/mock-server.ts","../../src/shopify-api/graphql-handler.ts","../../src/shopify-api/rest-handler.ts","../../src/shopify-api/oauth-handler.ts"],"sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse, type Server } from 'node:http';\nimport { GraphQLHandler } from './graphql-handler';\nimport { RESTHandler } from './rest-handler';\nimport { OAuthHandler } from './oauth-handler';\nimport type { MockShopifyServerConfig, MockServerRequest } from './types';\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));\n req.on('error', reject);\n });\n}\n\nfunction parseQuery(url: URL): Record<string, string> {\n const query: Record<string, string> = {};\n url.searchParams.forEach((value, key) => {\n query[key] = value;\n });\n return query;\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport class MockShopifyServer {\n readonly graphql: GraphQLHandler;\n readonly rest: RESTHandler;\n readonly oauth: OAuthHandler;\n\n private server: Server | null = null;\n private serverPort = 0;\n private readonly config: Required<MockShopifyServerConfig>;\n private requests: MockServerRequest[] = [];\n\n constructor(config?: MockShopifyServerConfig) {\n this.config = {\n port: config?.port ?? 0,\n apiVersion: config?.apiVersion ?? '2024-01',\n defaultShop: config?.defaultShop ?? 'test-shop.myshopify.com',\n latencyMs: config?.latencyMs ?? 0,\n };\n this.graphql = new GraphQLHandler();\n this.rest = new RESTHandler();\n this.oauth = new OAuthHandler();\n }\n\n async start(): Promise<{ port: number; url: string }> {\n return new Promise((resolve, reject) => {\n this.server = createServer((req, res) => {\n this.handleRequest(req, res).catch((err) => {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: String(err) }));\n });\n });\n\n this.server.on('error', reject);\n\n this.server.listen(this.config.port, '127.0.0.1', () => {\n const addr = this.server!.address();\n if (addr && typeof addr === 'object') {\n this.serverPort = addr.port;\n }\n resolve({ port: this.serverPort, url: this.url });\n });\n });\n }\n\n async stop(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (!this.server) {\n resolve();\n return;\n }\n this.server.close((err) => {\n this.server = null;\n if (err) reject(err);\n else resolve();\n });\n });\n }\n\n get url(): string {\n return `http://127.0.0.1:${this.serverPort}`;\n }\n\n get port(): number {\n return this.serverPort;\n }\n\n getRequests(): MockServerRequest[] {\n return [...this.requests];\n }\n\n getLastRequest(): MockServerRequest | undefined {\n return this.requests[this.requests.length - 1];\n }\n\n getRequestsByPath(pathPattern: string | RegExp): MockServerRequest[] {\n return this.requests.filter((req) => {\n if (typeof pathPattern === 'string') {\n return req.path.includes(pathPattern);\n }\n return pathPattern.test(req.path);\n });\n }\n\n clearRequests(): void {\n this.requests = [];\n }\n\n reset(): void {\n this.graphql.reset();\n this.rest.reset();\n this.oauth.reset();\n this.requests = [];\n }\n\n private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n if (this.config.latencyMs > 0) {\n await delay(this.config.latencyMs);\n }\n\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? '127.0.0.1'}`);\n const method = (req.method ?? 'GET').toUpperCase();\n const path = url.pathname;\n const query = parseQuery(url);\n const rawBody = await readBody(req);\n let body: unknown = null;\n\n if (rawBody) {\n try {\n body = JSON.parse(rawBody);\n } catch {\n body = rawBody;\n }\n }\n\n const headers: Record<string, string> = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (typeof value === 'string') {\n headers[key] = value;\n } else if (Array.isArray(value)) {\n headers[key] = value.join(', ');\n }\n }\n\n this.requests.push({\n method,\n path,\n headers,\n body,\n query,\n timestamp: new Date(),\n });\n\n // OAuth authorize\n if (path === '/admin/oauth/authorize') {\n const result = this.oauth.handleAuthorize(query);\n res.writeHead(302, { Location: result.redirectUrl });\n res.end();\n return;\n }\n\n // Token exchange (must be checked before generic OAuth access token)\n if (path === `/admin/oauth/access_token` && method === 'POST' && typeof body === 'object' && body !== null && 'grant_type' in body) {\n const tokenBody = body as Record<string, string>;\n if (tokenBody['grant_type'] === 'urn:ietf:params:oauth:grant-type:token-exchange') {\n const result = this.oauth.handleTokenExchange(tokenBody);\n res.writeHead(result.status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(result.body));\n return;\n }\n }\n\n // OAuth access token\n if (path === '/admin/oauth/access_token' && method === 'POST') {\n const tokenBody = typeof body === 'object' && body !== null ? body as Record<string, string> : {};\n const result = this.oauth.handleAccessToken(tokenBody);\n res.writeHead(result.status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(result.body));\n return;\n }\n\n // GraphQL endpoint\n const graphqlPath = `/admin/api/${this.config.apiVersion}/graphql.json`;\n if (path === graphqlPath && method === 'POST') {\n const graphqlBody = body as { query: string; variables?: Record<string, unknown>; operationName?: string };\n const result = this.graphql.handle(graphqlBody);\n\n const statusCode = result.errors?.some((e) => e.message === 'Throttled') ? 429 : 200;\n res.writeHead(statusCode, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(result));\n return;\n }\n\n // REST endpoints\n const restPathPrefix = `/admin/api/${this.config.apiVersion}/`;\n if (path.startsWith(restPathPrefix)) {\n const result = this.rest.handle(method, path, body);\n const responseHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...result.headers,\n };\n res.writeHead(result.status ?? 200, responseHeaders);\n res.end(JSON.stringify(result.body ?? {}));\n return;\n }\n\n // Unmatched\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ errors: 'Not Found' }));\n }\n}\n","import type { GraphQLResponseConfig } from './types';\n\ninterface OperationRegistration {\n response: GraphQLResponseConfig;\n}\n\ninterface SequenceRegistration {\n responses: GraphQLResponseConfig[];\n index: number;\n}\n\ninterface QueryPatternRegistration {\n pattern: string | RegExp;\n response: GraphQLResponseConfig;\n}\n\ninterface RateLimitConfig {\n maxCost: number;\n restoreRate: number;\n currentBudget: number;\n lastRestoreTime: number;\n}\n\nexport class GraphQLHandler {\n private operations = new Map<string, OperationRegistration>();\n private sequences = new Map<string, SequenceRegistration>();\n private queryPatterns: QueryPatternRegistration[] = [];\n private defaultResponse: GraphQLResponseConfig | null = null;\n private rateLimitConfig: RateLimitConfig | null = null;\n\n onOperation(operationName: string, response: GraphQLResponseConfig): void {\n this.operations.set(operationName, { response });\n }\n\n onQuery(queryPattern: string | RegExp, response: GraphQLResponseConfig): void {\n this.queryPatterns.push({ pattern: queryPattern, response });\n }\n\n onOperationSequence(operationName: string, responses: GraphQLResponseConfig[]): void {\n this.sequences.set(operationName, { responses, index: 0 });\n }\n\n enableRateLimiting(config?: { maxCost?: number; restoreRate?: number }): void {\n const maxCost = config?.maxCost ?? 1000;\n const restoreRate = config?.restoreRate ?? 50;\n this.rateLimitConfig = {\n maxCost,\n restoreRate,\n currentBudget: maxCost,\n lastRestoreTime: Date.now(),\n };\n }\n\n disableRateLimiting(): void {\n this.rateLimitConfig = null;\n }\n\n setDefaultResponse(response: GraphQLResponseConfig): void {\n this.defaultResponse = response;\n }\n\n handle(body: {\n query: string;\n variables?: Record<string, unknown>;\n operationName?: string;\n }): GraphQLResponseConfig {\n if (this.rateLimitConfig) {\n this.restoreBudget();\n }\n\n const operationName = body.operationName ?? this.extractOperationName(body.query);\n\n // Check sequences first\n if (operationName) {\n const sequence = this.sequences.get(operationName);\n if (sequence) {\n const response = sequence.responses[sequence.index] ?? sequence.responses[sequence.responses.length - 1]!;\n if (sequence.index < sequence.responses.length) {\n sequence.index++;\n }\n return this.applyRateLimiting(response!);\n }\n }\n\n // Check exact operation name match\n if (operationName) {\n const registration = this.operations.get(operationName);\n if (registration) {\n return this.applyRateLimiting(registration.response);\n }\n }\n\n // Check query patterns\n for (const { pattern, response } of this.queryPatterns) {\n if (typeof pattern === 'string') {\n if (body.query.includes(pattern)) {\n return this.applyRateLimiting(response);\n }\n } else {\n if (pattern.test(body.query)) {\n return this.applyRateLimiting(response);\n }\n }\n }\n\n // Fall back to default\n if (this.defaultResponse) {\n return this.applyRateLimiting(this.defaultResponse);\n }\n\n return {\n errors: [{ message: `No mock registered for operation: ${operationName ?? 'unknown'}` }],\n };\n }\n\n reset(): void {\n this.operations.clear();\n this.sequences.clear();\n this.queryPatterns = [];\n this.defaultResponse = null;\n this.rateLimitConfig = null;\n }\n\n private extractOperationName(query: string): string | undefined {\n const match = query.match(/(?:query|mutation|subscription)\\s+(\\w+)/);\n return match?.[1];\n }\n\n private restoreBudget(): void {\n if (!this.rateLimitConfig) return;\n const now = Date.now();\n const elapsed = (now - this.rateLimitConfig.lastRestoreTime) / 1000;\n const restored = elapsed * this.rateLimitConfig.restoreRate;\n this.rateLimitConfig.currentBudget = Math.min(\n this.rateLimitConfig.maxCost,\n this.rateLimitConfig.currentBudget + restored,\n );\n this.rateLimitConfig.lastRestoreTime = now;\n }\n\n private applyRateLimiting(response: GraphQLResponseConfig): GraphQLResponseConfig {\n if (!this.rateLimitConfig) return response;\n\n const cost = response.extensions?.cost?.actualQueryCost ?? 10;\n\n if (this.rateLimitConfig.currentBudget < cost) {\n return {\n errors: [{ message: 'Throttled' }],\n extensions: {\n cost: {\n requestedQueryCost: cost,\n actualQueryCost: cost,\n throttleStatus: {\n maximumAvailable: this.rateLimitConfig.maxCost,\n currentlyAvailable: this.rateLimitConfig.currentBudget,\n restoreRate: this.rateLimitConfig.restoreRate,\n },\n },\n },\n };\n }\n\n this.rateLimitConfig.currentBudget -= cost;\n\n const extensions = response.extensions ?? {\n cost: {\n requestedQueryCost: cost,\n actualQueryCost: cost,\n throttleStatus: {\n maximumAvailable: this.rateLimitConfig.maxCost,\n currentlyAvailable: this.rateLimitConfig.currentBudget,\n restoreRate: this.rateLimitConfig.restoreRate,\n },\n },\n };\n\n return { ...response, extensions };\n }\n}\n","import type { RESTResponseConfig } from './types';\n\ninterface RouteRegistration {\n method: string;\n pattern: string | RegExp;\n response: RESTResponseConfig;\n}\n\nexport class RESTHandler {\n private routes: RouteRegistration[] = [];\n\n onRequest(method: string, pathPattern: string | RegExp, response: RESTResponseConfig): void {\n this.routes.push({ method: method.toUpperCase(), pattern: pathPattern, response });\n }\n\n onGet(pathPattern: string | RegExp, response: RESTResponseConfig): void {\n this.onRequest('GET', pathPattern, response);\n }\n\n onPost(pathPattern: string | RegExp, response: RESTResponseConfig): void {\n this.onRequest('POST', pathPattern, response);\n }\n\n onPut(pathPattern: string | RegExp, response: RESTResponseConfig): void {\n this.onRequest('PUT', pathPattern, response);\n }\n\n onDelete(pathPattern: string | RegExp, response: RESTResponseConfig): void {\n this.onRequest('DELETE', pathPattern, response);\n }\n\n handle(method: string, path: string, _body?: unknown): RESTResponseConfig {\n const upperMethod = method.toUpperCase();\n\n for (const route of this.routes) {\n if (route.method !== upperMethod) continue;\n\n if (typeof route.pattern === 'string') {\n if (path === route.pattern || path.startsWith(route.pattern)) {\n return route.response;\n }\n } else {\n if (route.pattern.test(path)) {\n return route.response;\n }\n }\n }\n\n return { status: 404, body: { errors: 'Not Found' } };\n }\n\n reset(): void {\n this.routes = [];\n }\n}\n","import { randomBytes } from 'node:crypto';\nimport type { OAuthConfig } from './types';\n\nexport class OAuthHandler {\n private config: OAuthConfig;\n private generatedCodes = new Map<string, { nonce: string; shop: string }>();\n\n constructor(config?: OAuthConfig) {\n this.config = {\n accessToken: 'shpat_test_token_000000000000000000000000',\n scope: 'read_products,write_products',\n expiresIn: 86400,\n ...config,\n };\n }\n\n configure(config: OAuthConfig): void {\n this.config = { ...this.config, ...config };\n }\n\n handleAuthorize(query: Record<string, string>): { redirectUrl: string; code: string } {\n const code = randomBytes(16).toString('hex');\n const redirectUri = query['redirect_uri'] ?? '';\n const state = query['state'] ?? '';\n const shop = query['shop'] ?? 'test-shop.myshopify.com';\n\n this.generatedCodes.set(code, { nonce: state, shop });\n\n const url = new URL(redirectUri || 'https://localhost/callback');\n url.searchParams.set('code', code);\n url.searchParams.set('shop', shop);\n url.searchParams.set('state', state);\n url.searchParams.set('hmac', 'mock_hmac_value');\n url.searchParams.set('timestamp', String(Math.floor(Date.now() / 1000)));\n\n return { redirectUrl: url.toString(), code };\n }\n\n handleAccessToken(_body: Record<string, string>): { status: number; body: unknown } {\n const response: Record<string, unknown> = {\n access_token: this.config.accessToken,\n scope: this.config.scope,\n };\n\n if (this.config.expiresIn !== undefined) {\n response['expires_in'] = this.config.expiresIn;\n }\n\n if (this.config.associatedUser !== undefined) {\n response['associated_user'] = {\n id: this.config.associatedUser.id,\n first_name: this.config.associatedUser.firstName,\n last_name: this.config.associatedUser.lastName,\n email: this.config.associatedUser.email,\n email_verified: this.config.associatedUser.emailVerified,\n account_owner: this.config.associatedUser.accountOwner,\n locale: this.config.associatedUser.locale,\n collaborator: this.config.associatedUser.collaborator,\n };\n response['associated_user_scope'] = this.config.scope;\n }\n\n return { status: 200, body: response };\n }\n\n handleTokenExchange(_body: Record<string, string>): { status: number; body: unknown } {\n const response: Record<string, unknown> = {\n access_token: this.config.accessToken,\n scope: this.config.scope,\n token_type: 'bearer',\n expires_in: this.config.expiresIn ?? 86400,\n };\n\n if (this.config.associatedUser !== undefined) {\n response['associated_user'] = {\n id: this.config.associatedUser.id,\n first_name: this.config.associatedUser.firstName,\n last_name: this.config.associatedUser.lastName,\n email: this.config.associatedUser.email,\n email_verified: this.config.associatedUser.emailVerified,\n account_owner: this.config.associatedUser.accountOwner,\n locale: this.config.associatedUser.locale,\n collaborator: this.config.associatedUser.collaborator,\n };\n response['associated_user_scope'] = this.config.scope;\n }\n\n return { status: 200, body: response };\n }\n\n reset(): void {\n this.generatedCodes.clear();\n this.config = {\n accessToken: 'shpat_test_token_000000000000000000000000',\n scope: 'read_products,write_products',\n expiresIn: 86400,\n };\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Types for Built for Shopify compliance checking.
3
+ */
4
+ type ComplianceCategory = 'authentication' | 'billing' | 'webhooks' | 'performance' | 'security' | 'ux' | 'api_usage' | 'gdpr';
5
+ type ComplianceSeverity = 'required' | 'recommended' | 'optional';
6
+ interface ComplianceCheckResult {
7
+ id: string;
8
+ name: string;
9
+ category: ComplianceCategory;
10
+ severity: ComplianceSeverity;
11
+ passed: boolean;
12
+ message: string;
13
+ details?: string;
14
+ }
15
+ interface ComplianceSummary {
16
+ requiredPassed: number;
17
+ requiredTotal: number;
18
+ recommendedPassed: number;
19
+ recommendedTotal: number;
20
+ optionalPassed: number;
21
+ optionalTotal: number;
22
+ isEligible: boolean;
23
+ }
24
+ interface ComplianceReport {
25
+ appName: string;
26
+ checkedAt: Date;
27
+ totalChecks: number;
28
+ passed: number;
29
+ failed: number;
30
+ warnings: number;
31
+ score: number;
32
+ checks: ComplianceCheckResult[];
33
+ summary: ComplianceSummary;
34
+ }
35
+ interface ComplianceCheckConfig {
36
+ appName: string;
37
+ hasOfflineTokens?: boolean;
38
+ hasWebhookHandlers?: boolean;
39
+ hasGDPREndpoints?: boolean;
40
+ hasBillingIntegration?: boolean;
41
+ hasRateLimiting?: boolean;
42
+ hasSecurityHeaders?: boolean;
43
+ hasInputValidation?: boolean;
44
+ hasErrorHandling?: boolean;
45
+ hasSessionManagement?: boolean;
46
+ hasAppUninstallHandler?: boolean;
47
+ hasRetryLogic?: boolean;
48
+ supportsMultipleLanguages?: boolean;
49
+ hasAccessibilityCompliance?: boolean;
50
+ hasPerformanceOptimization?: boolean;
51
+ avgResponseTimeMs?: number;
52
+ hasContentSecurityPolicy?: boolean;
53
+ }
54
+
55
+ /**
56
+ * Built for Shopify compliance checks.
57
+ */
58
+
59
+ interface ComplianceCheck {
60
+ id: string;
61
+ name: string;
62
+ category: ComplianceCategory;
63
+ severity: ComplianceSeverity;
64
+ check: (config: ComplianceCheckConfig) => ComplianceCheckResult;
65
+ }
66
+ declare const BUILT_FOR_SHOPIFY_CHECKS: ComplianceCheck[];
67
+
68
+ /**
69
+ * Compliance checker for Built for Shopify requirements.
70
+ */
71
+
72
+ declare class ComplianceChecker {
73
+ private readonly checks;
74
+ constructor(checks?: ComplianceCheck[]);
75
+ runAll(config: ComplianceCheckConfig): ComplianceReport;
76
+ runCategory(config: ComplianceCheckConfig, category: ComplianceCategory): ComplianceCheckResult[];
77
+ getEligibility(config: ComplianceCheckConfig): {
78
+ eligible: boolean;
79
+ blockers: ComplianceCheckResult[];
80
+ };
81
+ generateScore(results: ComplianceCheckResult[]): number;
82
+ private buildSummary;
83
+ }
84
+
85
+ export { BUILT_FOR_SHOPIFY_CHECKS, type ComplianceCategory, type ComplianceCheck, type ComplianceCheckConfig, type ComplianceCheckResult, ComplianceChecker, type ComplianceReport, type ComplianceSeverity, type ComplianceSummary };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Types for Built for Shopify compliance checking.
3
+ */
4
+ type ComplianceCategory = 'authentication' | 'billing' | 'webhooks' | 'performance' | 'security' | 'ux' | 'api_usage' | 'gdpr';
5
+ type ComplianceSeverity = 'required' | 'recommended' | 'optional';
6
+ interface ComplianceCheckResult {
7
+ id: string;
8
+ name: string;
9
+ category: ComplianceCategory;
10
+ severity: ComplianceSeverity;
11
+ passed: boolean;
12
+ message: string;
13
+ details?: string;
14
+ }
15
+ interface ComplianceSummary {
16
+ requiredPassed: number;
17
+ requiredTotal: number;
18
+ recommendedPassed: number;
19
+ recommendedTotal: number;
20
+ optionalPassed: number;
21
+ optionalTotal: number;
22
+ isEligible: boolean;
23
+ }
24
+ interface ComplianceReport {
25
+ appName: string;
26
+ checkedAt: Date;
27
+ totalChecks: number;
28
+ passed: number;
29
+ failed: number;
30
+ warnings: number;
31
+ score: number;
32
+ checks: ComplianceCheckResult[];
33
+ summary: ComplianceSummary;
34
+ }
35
+ interface ComplianceCheckConfig {
36
+ appName: string;
37
+ hasOfflineTokens?: boolean;
38
+ hasWebhookHandlers?: boolean;
39
+ hasGDPREndpoints?: boolean;
40
+ hasBillingIntegration?: boolean;
41
+ hasRateLimiting?: boolean;
42
+ hasSecurityHeaders?: boolean;
43
+ hasInputValidation?: boolean;
44
+ hasErrorHandling?: boolean;
45
+ hasSessionManagement?: boolean;
46
+ hasAppUninstallHandler?: boolean;
47
+ hasRetryLogic?: boolean;
48
+ supportsMultipleLanguages?: boolean;
49
+ hasAccessibilityCompliance?: boolean;
50
+ hasPerformanceOptimization?: boolean;
51
+ avgResponseTimeMs?: number;
52
+ hasContentSecurityPolicy?: boolean;
53
+ }
54
+
55
+ /**
56
+ * Built for Shopify compliance checks.
57
+ */
58
+
59
+ interface ComplianceCheck {
60
+ id: string;
61
+ name: string;
62
+ category: ComplianceCategory;
63
+ severity: ComplianceSeverity;
64
+ check: (config: ComplianceCheckConfig) => ComplianceCheckResult;
65
+ }
66
+ declare const BUILT_FOR_SHOPIFY_CHECKS: ComplianceCheck[];
67
+
68
+ /**
69
+ * Compliance checker for Built for Shopify requirements.
70
+ */
71
+
72
+ declare class ComplianceChecker {
73
+ private readonly checks;
74
+ constructor(checks?: ComplianceCheck[]);
75
+ runAll(config: ComplianceCheckConfig): ComplianceReport;
76
+ runCategory(config: ComplianceCheckConfig, category: ComplianceCategory): ComplianceCheckResult[];
77
+ getEligibility(config: ComplianceCheckConfig): {
78
+ eligible: boolean;
79
+ blockers: ComplianceCheckResult[];
80
+ };
81
+ generateScore(results: ComplianceCheckResult[]): number;
82
+ private buildSummary;
83
+ }
84
+
85
+ export { BUILT_FOR_SHOPIFY_CHECKS, type ComplianceCategory, type ComplianceCheck, type ComplianceCheckConfig, type ComplianceCheckResult, ComplianceChecker, type ComplianceReport, type ComplianceSeverity, type ComplianceSummary };