@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,418 @@
|
|
|
1
|
+
// src/auth/mock-session-storage.ts
|
|
2
|
+
var MemorySessionStorage = class {
|
|
3
|
+
sessions = /* @__PURE__ */ new Map();
|
|
4
|
+
async storeSession(session) {
|
|
5
|
+
this.sessions.set(session.id, { ...session });
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
async loadSession(id) {
|
|
9
|
+
const session = this.sessions.get(id);
|
|
10
|
+
return session ? { ...session } : void 0;
|
|
11
|
+
}
|
|
12
|
+
async deleteSession(id) {
|
|
13
|
+
return this.sessions.delete(id);
|
|
14
|
+
}
|
|
15
|
+
async deleteSessions(ids) {
|
|
16
|
+
for (const id of ids) {
|
|
17
|
+
this.sessions.delete(id);
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
async findSessionsByShop(shop) {
|
|
22
|
+
const results = [];
|
|
23
|
+
for (const session of this.sessions.values()) {
|
|
24
|
+
if (session.shop === shop) {
|
|
25
|
+
results.push({ ...session });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return results;
|
|
29
|
+
}
|
|
30
|
+
clear() {
|
|
31
|
+
this.sessions.clear();
|
|
32
|
+
}
|
|
33
|
+
get size() {
|
|
34
|
+
return this.sessions.size;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/auth/factories.ts
|
|
39
|
+
function createMockSession(overrides = {}) {
|
|
40
|
+
const now = /* @__PURE__ */ new Date();
|
|
41
|
+
return {
|
|
42
|
+
id: "offline_test-shop.myshopify.com",
|
|
43
|
+
shop: "test-shop.myshopify.com",
|
|
44
|
+
state: "test-state-nonce",
|
|
45
|
+
isOnline: false,
|
|
46
|
+
scope: "read_products,write_orders",
|
|
47
|
+
expires: null,
|
|
48
|
+
accessToken: "shpat_test_token",
|
|
49
|
+
createdAt: now,
|
|
50
|
+
updatedAt: now,
|
|
51
|
+
...overrides
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function createMockShop(overrides = {}) {
|
|
55
|
+
const now = /* @__PURE__ */ new Date();
|
|
56
|
+
return {
|
|
57
|
+
shopDomain: "test-shop.myshopify.com",
|
|
58
|
+
isInstalled: true,
|
|
59
|
+
installedAt: now,
|
|
60
|
+
uninstalledAt: null,
|
|
61
|
+
scopes: "read_products,write_orders",
|
|
62
|
+
shopifyPlan: "basic",
|
|
63
|
+
createdAt: now,
|
|
64
|
+
updatedAt: now,
|
|
65
|
+
...overrides
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function createMockAuthEvent(overrides = {}) {
|
|
69
|
+
const now = /* @__PURE__ */ new Date();
|
|
70
|
+
return {
|
|
71
|
+
id: "00000000-0000-4000-a000-000000000001",
|
|
72
|
+
type: "session_created",
|
|
73
|
+
shopDomain: "test-shop.myshopify.com",
|
|
74
|
+
sessionId: "offline_test-shop.myshopify.com",
|
|
75
|
+
userId: null,
|
|
76
|
+
outcome: "success",
|
|
77
|
+
metadata: {},
|
|
78
|
+
timestamp: now,
|
|
79
|
+
...overrides
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/auth/mock-shopify-api.ts
|
|
84
|
+
function mockTokenExchangeResponse(overrides = {}) {
|
|
85
|
+
return {
|
|
86
|
+
access_token: "shpat_test_exchanged_token",
|
|
87
|
+
scope: "read_products,write_orders",
|
|
88
|
+
...overrides
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function mockOnlineTokenExchangeResponse(overrides = {}) {
|
|
92
|
+
return {
|
|
93
|
+
access_token: "shpat_test_online_token",
|
|
94
|
+
scope: "read_products,write_orders",
|
|
95
|
+
expires_in: 86400,
|
|
96
|
+
associated_user_scope: "read_products",
|
|
97
|
+
associated_user: {
|
|
98
|
+
id: 12345,
|
|
99
|
+
first_name: "Test",
|
|
100
|
+
last_name: "User",
|
|
101
|
+
email: "test@example.com",
|
|
102
|
+
email_verified: true,
|
|
103
|
+
account_owner: true,
|
|
104
|
+
locale: "en",
|
|
105
|
+
collaborator: false
|
|
106
|
+
},
|
|
107
|
+
...overrides
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function mockOAuthCallbackResponse(overrides = {}) {
|
|
111
|
+
return {
|
|
112
|
+
access_token: "shpat_test_oauth_token",
|
|
113
|
+
scope: "read_products,write_orders",
|
|
114
|
+
...overrides
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function mockRefreshTokenResponse(overrides = {}) {
|
|
118
|
+
return {
|
|
119
|
+
access_token: "shpat_test_refreshed_token",
|
|
120
|
+
refresh_token: "shprt_test_new_refresh_token",
|
|
121
|
+
expires_in: 3600,
|
|
122
|
+
scope: "read_products,write_orders",
|
|
123
|
+
...overrides
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/auth/session-fixture-builder.ts
|
|
128
|
+
var SessionFixtureBuilder = class _SessionFixtureBuilder {
|
|
129
|
+
data;
|
|
130
|
+
constructor(shop, isOnline, userId) {
|
|
131
|
+
const now = /* @__PURE__ */ new Date();
|
|
132
|
+
const id = isOnline ? `online_${shop}_${userId ?? 1}` : `offline_${shop}`;
|
|
133
|
+
this.data = {
|
|
134
|
+
id,
|
|
135
|
+
shop,
|
|
136
|
+
state: "test-state-nonce",
|
|
137
|
+
isOnline,
|
|
138
|
+
scope: "read_products,write_orders",
|
|
139
|
+
expires: null,
|
|
140
|
+
accessToken: "shpat_test_token",
|
|
141
|
+
createdAt: now,
|
|
142
|
+
updatedAt: now
|
|
143
|
+
};
|
|
144
|
+
if (isOnline && userId !== void 0) {
|
|
145
|
+
this.data.onlineAccessInfo = {
|
|
146
|
+
expiresIn: 86400,
|
|
147
|
+
associatedUserScope: "read_products",
|
|
148
|
+
associatedUser: {
|
|
149
|
+
id: userId,
|
|
150
|
+
firstName: "Test",
|
|
151
|
+
lastName: "User",
|
|
152
|
+
email: "test@example.com",
|
|
153
|
+
emailVerified: true,
|
|
154
|
+
accountOwner: true,
|
|
155
|
+
locale: "en",
|
|
156
|
+
collaborator: false
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
static offline(shop = "test-shop.myshopify.com") {
|
|
162
|
+
return new _SessionFixtureBuilder(shop, false);
|
|
163
|
+
}
|
|
164
|
+
static online(shop = "test-shop.myshopify.com", userId = 1) {
|
|
165
|
+
return new _SessionFixtureBuilder(shop, true, userId);
|
|
166
|
+
}
|
|
167
|
+
withAccessToken(token) {
|
|
168
|
+
this.data.accessToken = token;
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
withScopes(scopes) {
|
|
172
|
+
this.data.scope = Array.isArray(scopes) ? scopes.join(",") : scopes;
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
expired(minutesAgo = 60) {
|
|
176
|
+
const past = /* @__PURE__ */ new Date();
|
|
177
|
+
past.setMinutes(past.getMinutes() - minutesAgo);
|
|
178
|
+
this.data.expires = past;
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
expiringSoon(minutesUntil = 5) {
|
|
182
|
+
const soon = /* @__PURE__ */ new Date();
|
|
183
|
+
soon.setMinutes(soon.getMinutes() + minutesUntil);
|
|
184
|
+
this.data.expires = soon;
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
valid(hoursUntil = 24) {
|
|
188
|
+
const future = /* @__PURE__ */ new Date();
|
|
189
|
+
future.setHours(future.getHours() + hoursUntil);
|
|
190
|
+
this.data.expires = future;
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
193
|
+
withRefreshToken(token, expiresAt) {
|
|
194
|
+
this.data.refreshToken = token;
|
|
195
|
+
if (expiresAt !== void 0) {
|
|
196
|
+
this.data.refreshTokenExpiresAt = expiresAt;
|
|
197
|
+
}
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
withOnlineAccessInfo(info) {
|
|
201
|
+
const existing = this.data.onlineAccessInfo ?? {
|
|
202
|
+
expiresIn: 86400,
|
|
203
|
+
associatedUserScope: "read_products",
|
|
204
|
+
associatedUser: {
|
|
205
|
+
id: 1,
|
|
206
|
+
firstName: "Test",
|
|
207
|
+
lastName: "User",
|
|
208
|
+
email: "test@example.com",
|
|
209
|
+
emailVerified: true,
|
|
210
|
+
accountOwner: true,
|
|
211
|
+
locale: "en",
|
|
212
|
+
collaborator: false
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
this.data.onlineAccessInfo = { ...existing, ...info };
|
|
216
|
+
return this;
|
|
217
|
+
}
|
|
218
|
+
withState(state) {
|
|
219
|
+
this.data.state = state;
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
withId(id) {
|
|
223
|
+
this.data.id = id;
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
build() {
|
|
227
|
+
return { ...this.data };
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// src/auth/hmac-helper.ts
|
|
232
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
233
|
+
function computeShopifyHmac(params, secret) {
|
|
234
|
+
const sorted = Object.keys(params).filter((k) => k !== "hmac").sort().map((k) => `${k}=${params[k]}`).join("&");
|
|
235
|
+
return createHmac("sha256", secret).update(sorted).digest("hex");
|
|
236
|
+
}
|
|
237
|
+
function computeWebhookHmac(body, secret) {
|
|
238
|
+
return createHmac("sha256", secret).update(body).digest("base64");
|
|
239
|
+
}
|
|
240
|
+
function verifyHmac(computed, provided) {
|
|
241
|
+
const computedBuf = Buffer.from(computed, "utf-8");
|
|
242
|
+
const providedBuf = Buffer.from(provided, "utf-8");
|
|
243
|
+
if (computedBuf.length !== providedBuf.length) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
return timingSafeEqual(computedBuf, providedBuf);
|
|
247
|
+
}
|
|
248
|
+
function createSignedQueryString(params, secret) {
|
|
249
|
+
const hmac = computeShopifyHmac(params, secret);
|
|
250
|
+
const sorted = Object.keys(params).filter((k) => k !== "hmac").sort().map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`).join("&");
|
|
251
|
+
return `${sorted}&hmac=${hmac}`;
|
|
252
|
+
}
|
|
253
|
+
function createSignedUrl(baseUrl, params, secret) {
|
|
254
|
+
const queryString = createSignedQueryString(params, secret);
|
|
255
|
+
const separator = baseUrl.includes("?") ? "&" : "?";
|
|
256
|
+
return `${baseUrl}${separator}${queryString}`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/auth/auth-flow-simulator.ts
|
|
260
|
+
import { randomUUID } from "crypto";
|
|
261
|
+
var DEFAULT_CONFIG = {
|
|
262
|
+
apiKey: "test-api-key",
|
|
263
|
+
apiSecretKey: "test-api-secret",
|
|
264
|
+
scopes: ["read_products", "write_orders"],
|
|
265
|
+
hostName: "test-app.example.com"
|
|
266
|
+
};
|
|
267
|
+
var AuthFlowSimulator = class {
|
|
268
|
+
config;
|
|
269
|
+
constructor(config) {
|
|
270
|
+
this.config = { ...DEFAULT_CONFIG };
|
|
271
|
+
if (config) {
|
|
272
|
+
if (config.apiKey !== void 0) {
|
|
273
|
+
this.config.apiKey = config.apiKey;
|
|
274
|
+
}
|
|
275
|
+
if (config.apiSecretKey !== void 0) {
|
|
276
|
+
this.config.apiSecretKey = config.apiSecretKey;
|
|
277
|
+
}
|
|
278
|
+
if (config.scopes !== void 0) {
|
|
279
|
+
this.config.scopes = config.scopes;
|
|
280
|
+
}
|
|
281
|
+
if (config.hostName !== void 0) {
|
|
282
|
+
this.config.hostName = config.hostName;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
simulateTokenExchange(shopDomain, options) {
|
|
287
|
+
const isOnline = options?.isOnline ?? false;
|
|
288
|
+
const userId = options?.userId ?? 1;
|
|
289
|
+
const scopes = options?.scopes ?? this.config.scopes;
|
|
290
|
+
const accessToken = options?.accessToken ?? "shpat_test_exchanged_token";
|
|
291
|
+
const now = /* @__PURE__ */ new Date();
|
|
292
|
+
const expires = new Date(now.getTime() + 86400 * 1e3);
|
|
293
|
+
const id = isOnline ? `online_${shopDomain}_${userId}` : `offline_${shopDomain}`;
|
|
294
|
+
const session = {
|
|
295
|
+
id,
|
|
296
|
+
shop: shopDomain,
|
|
297
|
+
state: randomUUID(),
|
|
298
|
+
isOnline,
|
|
299
|
+
scope: scopes.join(","),
|
|
300
|
+
expires: isOnline ? expires : null,
|
|
301
|
+
accessToken,
|
|
302
|
+
createdAt: now,
|
|
303
|
+
updatedAt: now
|
|
304
|
+
};
|
|
305
|
+
if (isOnline) {
|
|
306
|
+
session.onlineAccessInfo = {
|
|
307
|
+
expiresIn: 86400,
|
|
308
|
+
associatedUserScope: scopes.join(","),
|
|
309
|
+
associatedUser: {
|
|
310
|
+
id: userId,
|
|
311
|
+
firstName: "Test",
|
|
312
|
+
lastName: "User",
|
|
313
|
+
email: "test@example.com",
|
|
314
|
+
emailVerified: true,
|
|
315
|
+
accountOwner: true,
|
|
316
|
+
locale: "en",
|
|
317
|
+
collaborator: false
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const shopContext = {
|
|
322
|
+
shopDomain,
|
|
323
|
+
accessToken,
|
|
324
|
+
scopes,
|
|
325
|
+
session
|
|
326
|
+
};
|
|
327
|
+
return { session, shopContext };
|
|
328
|
+
}
|
|
329
|
+
simulateOAuthFlow(shopDomain, options) {
|
|
330
|
+
const scopes = options?.scopes ?? this.config.scopes;
|
|
331
|
+
const state = randomUUID();
|
|
332
|
+
const authCode = `auth_code_${randomUUID().slice(0, 8)}`;
|
|
333
|
+
const authorizeUrl = `https://${shopDomain}/admin/oauth/authorize?client_id=${this.config.apiKey}&scope=${scopes.join(",")}&redirect_uri=https://${this.config.hostName}/auth/callback&state=${state}`;
|
|
334
|
+
const exchangeOptions = {
|
|
335
|
+
scopes,
|
|
336
|
+
accessToken: "shpat_test_oauth_token"
|
|
337
|
+
};
|
|
338
|
+
if (options?.isOnline !== void 0) {
|
|
339
|
+
exchangeOptions.isOnline = options.isOnline;
|
|
340
|
+
}
|
|
341
|
+
if (options?.userId !== void 0) {
|
|
342
|
+
exchangeOptions.userId = options.userId;
|
|
343
|
+
}
|
|
344
|
+
const { session: callbackSession, shopContext } = this.simulateTokenExchange(shopDomain, exchangeOptions);
|
|
345
|
+
return { authorizeUrl, authCode, callbackSession, shopContext };
|
|
346
|
+
}
|
|
347
|
+
simulateTokenRefresh(session, options) {
|
|
348
|
+
const now = /* @__PURE__ */ new Date();
|
|
349
|
+
const expires = new Date(now.getTime() + 86400 * 1e3);
|
|
350
|
+
const refreshed = {
|
|
351
|
+
...session,
|
|
352
|
+
accessToken: options?.newAccessToken ?? "shpat_test_refreshed_token",
|
|
353
|
+
expires,
|
|
354
|
+
updatedAt: now
|
|
355
|
+
};
|
|
356
|
+
if (options?.newRefreshToken !== void 0) {
|
|
357
|
+
refreshed.refreshToken = options.newRefreshToken;
|
|
358
|
+
refreshed.refreshTokenExpiresAt = new Date(
|
|
359
|
+
now.getTime() + 7 * 86400 * 1e3
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
if (options?.newScopes !== void 0) {
|
|
363
|
+
refreshed.scope = options.newScopes.join(",");
|
|
364
|
+
}
|
|
365
|
+
return refreshed;
|
|
366
|
+
}
|
|
367
|
+
createExpiredSession(shopDomain, options) {
|
|
368
|
+
const minutesAgo = options?.expiredMinutesAgo ?? 60;
|
|
369
|
+
const now = /* @__PURE__ */ new Date();
|
|
370
|
+
const expiredAt = new Date(now.getTime() - minutesAgo * 60 * 1e3);
|
|
371
|
+
const isOnline = options?.isOnline ?? false;
|
|
372
|
+
const id = isOnline ? `online_${shopDomain}_1` : `offline_${shopDomain}`;
|
|
373
|
+
return {
|
|
374
|
+
id,
|
|
375
|
+
shop: shopDomain,
|
|
376
|
+
state: "expired-state",
|
|
377
|
+
isOnline,
|
|
378
|
+
scope: this.config.scopes.join(","),
|
|
379
|
+
expires: expiredAt,
|
|
380
|
+
accessToken: "shpat_expired_token",
|
|
381
|
+
createdAt: new Date(expiredAt.getTime() - 86400 * 1e3),
|
|
382
|
+
updatedAt: expiredAt
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
generateAuthEvent(type, shopDomain, options) {
|
|
386
|
+
return {
|
|
387
|
+
id: randomUUID(),
|
|
388
|
+
type,
|
|
389
|
+
shopDomain,
|
|
390
|
+
sessionId: options?.sessionId ?? `offline_${shopDomain}`,
|
|
391
|
+
userId: options?.userId ?? null,
|
|
392
|
+
outcome: options?.outcome ?? "success",
|
|
393
|
+
metadata: options?.metadata ?? {},
|
|
394
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
get secret() {
|
|
398
|
+
return this.config.apiSecretKey;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
export {
|
|
402
|
+
AuthFlowSimulator,
|
|
403
|
+
MemorySessionStorage,
|
|
404
|
+
SessionFixtureBuilder,
|
|
405
|
+
computeShopifyHmac,
|
|
406
|
+
computeWebhookHmac,
|
|
407
|
+
createMockAuthEvent,
|
|
408
|
+
createMockSession,
|
|
409
|
+
createMockShop,
|
|
410
|
+
createSignedQueryString,
|
|
411
|
+
createSignedUrl,
|
|
412
|
+
mockOAuthCallbackResponse,
|
|
413
|
+
mockOnlineTokenExchangeResponse,
|
|
414
|
+
mockRefreshTokenResponse,
|
|
415
|
+
mockTokenExchangeResponse,
|
|
416
|
+
verifyHmac
|
|
417
|
+
};
|
|
418
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/auth/mock-session-storage.ts","../../src/auth/factories.ts","../../src/auth/mock-shopify-api.ts","../../src/auth/session-fixture-builder.ts","../../src/auth/hmac-helper.ts","../../src/auth/auth-flow-simulator.ts"],"sourcesContent":["/**\n * In-memory session storage for testing.\n */\n\nimport type {\n Session,\n SessionStorage,\n} from '@uniforge/platform-core/auth';\n\nexport class MemorySessionStorage implements SessionStorage {\n private sessions = new Map<string, Session>();\n\n async storeSession(session: Session): Promise<boolean> {\n this.sessions.set(session.id, { ...session });\n return true;\n }\n\n async loadSession(id: string): Promise<Session | undefined> {\n const session = this.sessions.get(id);\n return session ? { ...session } : undefined;\n }\n\n async deleteSession(id: string): Promise<boolean> {\n return this.sessions.delete(id);\n }\n\n async deleteSessions(ids: string[]): Promise<boolean> {\n for (const id of ids) {\n this.sessions.delete(id);\n }\n return true;\n }\n\n async findSessionsByShop(shop: string): Promise<Session[]> {\n const results: Session[] = [];\n for (const session of this.sessions.values()) {\n if (session.shop === shop) {\n results.push({ ...session });\n }\n }\n return results;\n }\n\n clear(): void {\n this.sessions.clear();\n }\n\n get size(): number {\n return this.sessions.size;\n }\n}\n","/**\n * Test data factories for authentication types.\n *\n * Each factory provides sensible defaults that can be overridden.\n */\n\nimport type {\n Session,\n Shop,\n AuthEvent,\n AuthEventType,\n} from '@uniforge/platform-core/auth';\n\nexport function createMockSession(overrides: Partial<Session> = {}): Session {\n const now = new Date();\n return {\n id: 'offline_test-shop.myshopify.com',\n shop: 'test-shop.myshopify.com',\n state: 'test-state-nonce',\n isOnline: false,\n scope: 'read_products,write_orders',\n expires: null,\n accessToken: 'shpat_test_token',\n createdAt: now,\n updatedAt: now,\n ...overrides,\n };\n}\n\nexport function createMockShop(overrides: Partial<Shop> = {}): Shop {\n const now = new Date();\n return {\n shopDomain: 'test-shop.myshopify.com',\n isInstalled: true,\n installedAt: now,\n uninstalledAt: null,\n scopes: 'read_products,write_orders',\n shopifyPlan: 'basic',\n createdAt: now,\n updatedAt: now,\n ...overrides,\n };\n}\n\nexport function createMockAuthEvent(\n overrides: Partial<AuthEvent> = {},\n): AuthEvent {\n const now = new Date();\n return {\n id: '00000000-0000-4000-a000-000000000001',\n type: 'session_created' as AuthEventType,\n shopDomain: 'test-shop.myshopify.com',\n sessionId: 'offline_test-shop.myshopify.com',\n userId: null,\n outcome: 'success',\n metadata: {},\n timestamp: now,\n ...overrides,\n };\n}\n","/**\n * Mock Shopify API response factories for testing.\n *\n * These mocks simulate Shopify API responses for token exchange,\n * OAuth callback, and token refresh flows.\n */\n\nexport interface MockTokenExchangeResponse {\n access_token: string;\n scope: string;\n expires_in?: number;\n associated_user_scope?: string;\n associated_user?: {\n id: number;\n first_name: string;\n last_name: string;\n email: string;\n email_verified: boolean;\n account_owner: boolean;\n locale: string;\n collaborator: boolean;\n };\n}\n\nexport interface MockOAuthCallbackResponse {\n access_token: string;\n scope: string;\n}\n\nexport interface MockRefreshTokenResponse {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n scope: string;\n}\n\nexport function mockTokenExchangeResponse(\n overrides: Partial<MockTokenExchangeResponse> = {},\n): MockTokenExchangeResponse {\n return {\n access_token: 'shpat_test_exchanged_token',\n scope: 'read_products,write_orders',\n ...overrides,\n };\n}\n\nexport function mockOnlineTokenExchangeResponse(\n overrides: Partial<MockTokenExchangeResponse> = {},\n): MockTokenExchangeResponse {\n return {\n access_token: 'shpat_test_online_token',\n scope: 'read_products,write_orders',\n expires_in: 86400,\n associated_user_scope: 'read_products',\n associated_user: {\n id: 12345,\n first_name: 'Test',\n last_name: 'User',\n email: 'test@example.com',\n email_verified: true,\n account_owner: true,\n locale: 'en',\n collaborator: false,\n },\n ...overrides,\n };\n}\n\nexport function mockOAuthCallbackResponse(\n overrides: Partial<MockOAuthCallbackResponse> = {},\n): MockOAuthCallbackResponse {\n return {\n access_token: 'shpat_test_oauth_token',\n scope: 'read_products,write_orders',\n ...overrides,\n };\n}\n\nexport function mockRefreshTokenResponse(\n overrides: Partial<MockRefreshTokenResponse> = {},\n): MockRefreshTokenResponse {\n return {\n access_token: 'shpat_test_refreshed_token',\n refresh_token: 'shprt_test_new_refresh_token',\n expires_in: 3600,\n scope: 'read_products,write_orders',\n ...overrides,\n };\n}\n","/**\n * Fluent builder for creating session test fixtures in various states.\n *\n * Provides a chainable API for constructing Session objects with\n * controlled expiry, tokens, and online access information.\n */\n\nimport type {\n Session,\n OnlineAccessInfo,\n AssociatedUser,\n} from '@uniforge/platform-core/auth';\n\nexport class SessionFixtureBuilder {\n private data: Record<string, unknown>;\n\n private constructor(shop: string, isOnline: boolean, userId?: number) {\n const now = new Date();\n const id = isOnline\n ? `online_${shop}_${userId ?? 1}`\n : `offline_${shop}`;\n\n this.data = {\n id,\n shop,\n state: 'test-state-nonce',\n isOnline,\n scope: 'read_products,write_orders',\n expires: null,\n accessToken: 'shpat_test_token',\n createdAt: now,\n updatedAt: now,\n };\n\n if (isOnline && userId !== undefined) {\n this.data.onlineAccessInfo = {\n expiresIn: 86400,\n associatedUserScope: 'read_products',\n associatedUser: {\n id: userId,\n firstName: 'Test',\n lastName: 'User',\n email: 'test@example.com',\n emailVerified: true,\n accountOwner: true,\n locale: 'en',\n collaborator: false,\n } satisfies AssociatedUser,\n } satisfies OnlineAccessInfo;\n }\n }\n\n static offline(shop = 'test-shop.myshopify.com'): SessionFixtureBuilder {\n return new SessionFixtureBuilder(shop, false);\n }\n\n static online(\n shop = 'test-shop.myshopify.com',\n userId = 1,\n ): SessionFixtureBuilder {\n return new SessionFixtureBuilder(shop, true, userId);\n }\n\n withAccessToken(token: string): this {\n this.data.accessToken = token;\n return this;\n }\n\n withScopes(scopes: string[] | string): this {\n this.data.scope = Array.isArray(scopes) ? scopes.join(',') : scopes;\n return this;\n }\n\n expired(minutesAgo = 60): this {\n const past = new Date();\n past.setMinutes(past.getMinutes() - minutesAgo);\n this.data.expires = past;\n return this;\n }\n\n expiringSoon(minutesUntil = 5): this {\n const soon = new Date();\n soon.setMinutes(soon.getMinutes() + minutesUntil);\n this.data.expires = soon;\n return this;\n }\n\n valid(hoursUntil = 24): this {\n const future = new Date();\n future.setHours(future.getHours() + hoursUntil);\n this.data.expires = future;\n return this;\n }\n\n withRefreshToken(token: string, expiresAt?: Date): this {\n this.data.refreshToken = token;\n if (expiresAt !== undefined) {\n this.data.refreshTokenExpiresAt = expiresAt;\n }\n return this;\n }\n\n withOnlineAccessInfo(info: Partial<OnlineAccessInfo>): this {\n const existing = (this.data.onlineAccessInfo as OnlineAccessInfo) ?? {\n expiresIn: 86400,\n associatedUserScope: 'read_products',\n associatedUser: {\n id: 1,\n firstName: 'Test',\n lastName: 'User',\n email: 'test@example.com',\n emailVerified: true,\n accountOwner: true,\n locale: 'en',\n collaborator: false,\n } satisfies AssociatedUser,\n };\n this.data.onlineAccessInfo = { ...existing, ...info };\n return this;\n }\n\n withState(state: string): this {\n this.data.state = state;\n return this;\n }\n\n withId(id: string): this {\n this.data.id = id;\n return this;\n }\n\n build(): Session {\n return { ...this.data } as unknown as Session;\n }\n}\n","/**\n * HMAC test utilities for Shopify authentication testing.\n *\n * Provides helpers for computing and verifying HMACs used in\n * OAuth callbacks and webhook validation.\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n/**\n * Compute Shopify HMAC for query params.\n * Sorts params alphabetically, joins as key=value pairs with &,\n * and returns a hex HMAC-SHA256 digest.\n */\nexport function computeShopifyHmac(\n params: Record<string, string>,\n secret: string,\n): string {\n const sorted = Object.keys(params)\n .filter((k) => k !== 'hmac')\n .sort()\n .map((k) => `${k}=${params[k]!}`)\n .join('&');\n\n return createHmac('sha256', secret).update(sorted).digest('hex');\n}\n\n/**\n * Compute webhook HMAC (base64 digest).\n * Used for validating Shopify webhook request bodies.\n */\nexport function computeWebhookHmac(\n body: string | Buffer,\n secret: string,\n): string {\n return createHmac('sha256', secret).update(body).digest('base64');\n}\n\n/**\n * Verify HMAC with timing-safe comparison.\n * Returns true if computed matches provided.\n */\nexport function verifyHmac(computed: string, provided: string): boolean {\n const computedBuf = Buffer.from(computed, 'utf-8');\n const providedBuf = Buffer.from(provided, 'utf-8');\n\n if (computedBuf.length !== providedBuf.length) {\n return false;\n }\n\n return timingSafeEqual(computedBuf, providedBuf);\n}\n\n/**\n * Generate a query string with HMAC appended.\n * Sorts params, computes HMAC, and returns the full query string.\n */\nexport function createSignedQueryString(\n params: Record<string, string>,\n secret: string,\n): string {\n const hmac = computeShopifyHmac(params, secret);\n const sorted = Object.keys(params)\n .filter((k) => k !== 'hmac')\n .sort()\n .map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(params[k]!)}`)\n .join('&');\n\n return `${sorted}&hmac=${hmac}`;\n}\n\n/**\n * Create a complete signed URL for OAuth callbacks.\n * Appends signed query params to the base URL.\n */\nexport function createSignedUrl(\n baseUrl: string,\n params: Record<string, string>,\n secret: string,\n): string {\n const queryString = createSignedQueryString(params, secret);\n const separator = baseUrl.includes('?') ? '&' : '?';\n return `${baseUrl}${separator}${queryString}`;\n}\n","/**\n * Auth flow simulator for testing authentication workflows.\n *\n * Simulates token exchange, OAuth flows, token refresh, and\n * generates auth events without requiring a real Shopify API.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type {\n Session,\n ShopContext,\n AuthEvent,\n AuthEventType,\n OnlineAccessInfo,\n AssociatedUser,\n} from '@uniforge/platform-core/auth';\nexport interface AuthFlowSimulatorConfig {\n apiKey?: string;\n apiSecretKey?: string;\n scopes?: string[];\n hostName?: string;\n}\n\nexport interface TokenExchangeResult {\n session: Session;\n shopContext: ShopContext;\n}\n\nexport interface OAuthFlowResult {\n authorizeUrl: string;\n authCode: string;\n callbackSession: Session;\n shopContext: ShopContext;\n}\n\nconst DEFAULT_CONFIG: Required<AuthFlowSimulatorConfig> = {\n apiKey: 'test-api-key',\n apiSecretKey: 'test-api-secret',\n scopes: ['read_products', 'write_orders'],\n hostName: 'test-app.example.com',\n};\n\nexport class AuthFlowSimulator {\n private readonly config: Required<AuthFlowSimulatorConfig>;\n\n constructor(config?: AuthFlowSimulatorConfig) {\n this.config = { ...DEFAULT_CONFIG };\n if (config) {\n if (config.apiKey !== undefined) {\n this.config.apiKey = config.apiKey;\n }\n if (config.apiSecretKey !== undefined) {\n this.config.apiSecretKey = config.apiSecretKey;\n }\n if (config.scopes !== undefined) {\n this.config.scopes = config.scopes;\n }\n if (config.hostName !== undefined) {\n this.config.hostName = config.hostName;\n }\n }\n }\n\n simulateTokenExchange(\n shopDomain: string,\n options?: {\n isOnline?: boolean;\n userId?: number;\n scopes?: string[];\n accessToken?: string;\n },\n ): TokenExchangeResult {\n const isOnline = options?.isOnline ?? false;\n const userId = options?.userId ?? 1;\n const scopes = options?.scopes ?? this.config.scopes;\n const accessToken = options?.accessToken ?? 'shpat_test_exchanged_token';\n const now = new Date();\n const expires = new Date(now.getTime() + 86400 * 1000);\n\n const id = isOnline\n ? `online_${shopDomain}_${userId}`\n : `offline_${shopDomain}`;\n\n const session: Session = {\n id,\n shop: shopDomain,\n state: randomUUID(),\n isOnline,\n scope: scopes.join(','),\n expires: isOnline ? expires : null,\n accessToken,\n createdAt: now,\n updatedAt: now,\n };\n\n if (isOnline) {\n session.onlineAccessInfo = {\n expiresIn: 86400,\n associatedUserScope: scopes.join(','),\n associatedUser: {\n id: userId,\n firstName: 'Test',\n lastName: 'User',\n email: 'test@example.com',\n emailVerified: true,\n accountOwner: true,\n locale: 'en',\n collaborator: false,\n } satisfies AssociatedUser,\n } satisfies OnlineAccessInfo;\n }\n\n const shopContext: ShopContext = {\n shopDomain,\n accessToken,\n scopes,\n session,\n };\n\n return { session, shopContext };\n }\n\n simulateOAuthFlow(\n shopDomain: string,\n options?: {\n isOnline?: boolean;\n userId?: number;\n scopes?: string[];\n },\n ): OAuthFlowResult {\n const scopes = options?.scopes ?? this.config.scopes;\n const state = randomUUID();\n const authCode = `auth_code_${randomUUID().slice(0, 8)}`;\n\n const authorizeUrl =\n `https://${shopDomain}/admin/oauth/authorize` +\n `?client_id=${this.config.apiKey}` +\n `&scope=${scopes.join(',')}` +\n `&redirect_uri=https://${this.config.hostName}/auth/callback` +\n `&state=${state}`;\n\n const exchangeOptions: {\n isOnline?: boolean;\n userId?: number;\n scopes?: string[];\n accessToken?: string;\n } = {\n scopes,\n accessToken: 'shpat_test_oauth_token',\n };\n if (options?.isOnline !== undefined) {\n exchangeOptions.isOnline = options.isOnline;\n }\n if (options?.userId !== undefined) {\n exchangeOptions.userId = options.userId;\n }\n const { session: callbackSession, shopContext } =\n this.simulateTokenExchange(shopDomain, exchangeOptions);\n\n return { authorizeUrl, authCode, callbackSession, shopContext };\n }\n\n simulateTokenRefresh(\n session: Session,\n options?: {\n newAccessToken?: string;\n newRefreshToken?: string;\n newScopes?: string[];\n },\n ): Session {\n const now = new Date();\n const expires = new Date(now.getTime() + 86400 * 1000);\n const refreshed: Session = {\n ...session,\n accessToken: options?.newAccessToken ?? 'shpat_test_refreshed_token',\n expires,\n updatedAt: now,\n };\n\n if (options?.newRefreshToken !== undefined) {\n refreshed.refreshToken = options.newRefreshToken;\n refreshed.refreshTokenExpiresAt = new Date(\n now.getTime() + 7 * 86400 * 1000,\n );\n }\n\n if (options?.newScopes !== undefined) {\n refreshed.scope = options.newScopes.join(',');\n }\n\n return refreshed;\n }\n\n createExpiredSession(\n shopDomain: string,\n options?: {\n isOnline?: boolean;\n expiredMinutesAgo?: number;\n },\n ): Session {\n const minutesAgo = options?.expiredMinutesAgo ?? 60;\n const now = new Date();\n const expiredAt = new Date(now.getTime() - minutesAgo * 60 * 1000);\n const isOnline = options?.isOnline ?? false;\n const id = isOnline\n ? `online_${shopDomain}_1`\n : `offline_${shopDomain}`;\n\n return {\n id,\n shop: shopDomain,\n state: 'expired-state',\n isOnline,\n scope: this.config.scopes.join(','),\n expires: expiredAt,\n accessToken: 'shpat_expired_token',\n createdAt: new Date(expiredAt.getTime() - 86400 * 1000),\n updatedAt: expiredAt,\n };\n }\n\n generateAuthEvent(\n type: AuthEventType,\n shopDomain: string,\n options?: {\n sessionId?: string;\n userId?: number;\n outcome?: 'success' | 'failure';\n metadata?: Record<string, string>;\n },\n ): AuthEvent {\n return {\n id: randomUUID(),\n type,\n shopDomain,\n sessionId: options?.sessionId ?? `offline_${shopDomain}`,\n userId: options?.userId ?? null,\n outcome: options?.outcome ?? 'success',\n metadata: options?.metadata ?? {},\n timestamp: new Date(),\n };\n }\n\n get secret(): string {\n return this.config.apiSecretKey;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BillingService, Subscription, UsageRecord, BillingPlan } from '@uniforge/platform-core/billing';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mock BillingService for unit testing.
|
|
5
|
+
*
|
|
6
|
+
* Provides an in-memory implementation of BillingService with
|
|
7
|
+
* internal maps for subscriptions and usage records.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
type MockBillingService = BillingService & {
|
|
11
|
+
_subscriptions: Map<string, Subscription>;
|
|
12
|
+
_usageRecords: Map<string, UsageRecord[]>;
|
|
13
|
+
_reset(): void;
|
|
14
|
+
};
|
|
15
|
+
declare function createMockBillingService(): MockBillingService;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Test data factories for billing types.
|
|
19
|
+
*
|
|
20
|
+
* Each factory provides sensible defaults that can be overridden.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
declare function createTestPlan(overrides?: Partial<BillingPlan>): BillingPlan;
|
|
24
|
+
declare function createTestSubscription(overrides?: Partial<Subscription>): Subscription;
|
|
25
|
+
declare function createTestUsageRecord(overrides?: Partial<UsageRecord>): UsageRecord;
|
|
26
|
+
|
|
27
|
+
export { type MockBillingService, createMockBillingService, createTestPlan, createTestSubscription, createTestUsageRecord };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BillingService, Subscription, UsageRecord, BillingPlan } from '@uniforge/platform-core/billing';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mock BillingService for unit testing.
|
|
5
|
+
*
|
|
6
|
+
* Provides an in-memory implementation of BillingService with
|
|
7
|
+
* internal maps for subscriptions and usage records.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
type MockBillingService = BillingService & {
|
|
11
|
+
_subscriptions: Map<string, Subscription>;
|
|
12
|
+
_usageRecords: Map<string, UsageRecord[]>;
|
|
13
|
+
_reset(): void;
|
|
14
|
+
};
|
|
15
|
+
declare function createMockBillingService(): MockBillingService;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Test data factories for billing types.
|
|
19
|
+
*
|
|
20
|
+
* Each factory provides sensible defaults that can be overridden.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
declare function createTestPlan(overrides?: Partial<BillingPlan>): BillingPlan;
|
|
24
|
+
declare function createTestSubscription(overrides?: Partial<Subscription>): Subscription;
|
|
25
|
+
declare function createTestUsageRecord(overrides?: Partial<UsageRecord>): UsageRecord;
|
|
26
|
+
|
|
27
|
+
export { type MockBillingService, createMockBillingService, createTestPlan, createTestSubscription, createTestUsageRecord };
|