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