@synap-core/sdk 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/testing.js CHANGED
@@ -1,3 +1,8 @@
1
+ import {
2
+ pkceChallenge,
3
+ randomToken
4
+ } from "./chunk-J7XHAAFJ.js";
5
+
1
6
  // src/testing/fixtures.ts
2
7
  var MOCK_FORM = {
3
8
  title: "Get in touch",
@@ -93,6 +98,378 @@ var MOCK_SITE = {
93
98
  }
94
99
  };
95
100
 
101
+ // src/testing/auth-server.ts
102
+ var MOCK_AUTH_ISSUER = "https://pod.mock-synap.test";
103
+ var MOCK_GRANT = [
104
+ { permission: "entity.post.read", workspaceId: "ws_mock_studio", workspaceName: "Studio" },
105
+ { permission: "entity.post.create", workspaceId: "ws_mock_studio", workspaceName: "Studio" }
106
+ ];
107
+ var MOCK_USER = { id: "user_mock_ada", name: "Ada Lovelace" };
108
+ var MOCK_APP = { id: "app_mock_studio", manageUrl: "https://pod-admin.mock-synap.test/apps/app_mock_studio" };
109
+ var MOCK_APP_AGENT_ID = "agent_mock_app";
110
+ function json(status, body, headers = {}) {
111
+ return new Response(JSON.stringify(body), {
112
+ status,
113
+ headers: { "Content-Type": "application/json", "Cache-Control": "no-store", ...headers }
114
+ });
115
+ }
116
+ function oauthError(error, description, status = 400) {
117
+ return json(status, { error, error_description: description });
118
+ }
119
+ function createMockAuthServer(options = {}) {
120
+ const issuer = options.issuer ?? MOCK_AUTH_ISSUER;
121
+ const now = options.now ?? Date.now;
122
+ const refresh = options.refresh ?? true;
123
+ const accessTtl = options.accessTtlSec ?? 3600;
124
+ const refreshIdle = options.refreshIdleSec ?? 30 * 24 * 3600;
125
+ const grant = options.grant ?? MOCK_GRANT;
126
+ const user = options.user ?? MOCK_USER;
127
+ const codes = /* @__PURE__ */ new Map();
128
+ const access = /* @__PURE__ */ new Map();
129
+ const refreshTokens = /* @__PURE__ */ new Map();
130
+ const requests = [];
131
+ const writes = [];
132
+ const proposals = [];
133
+ const entities = [...options.entities ?? []];
134
+ const graceMs = (options.refreshGraceSec ?? 30) * 1e3;
135
+ const app = options.app === void 0 ? MOCK_APP : options.app;
136
+ let proposalSeq = 0;
137
+ let graceAnswers = 0;
138
+ const metadata = () => {
139
+ const base = {
140
+ issuer,
141
+ authorization_endpoint: `${issuer}/authorize`,
142
+ token_endpoint: `${issuer}/token`,
143
+ registration_endpoint: `${issuer}/register`,
144
+ revocation_endpoint: `${issuer}/revoke`,
145
+ revocation_endpoint_auth_methods_supported: ["none"],
146
+ response_types_supported: ["code"],
147
+ grant_types_supported: refresh ? ["authorization_code", "refresh_token"] : ["authorization_code"],
148
+ code_challenge_methods_supported: ["S256"],
149
+ token_endpoint_auth_methods_supported: ["none"],
150
+ authorization_response_iss_parameter_supported: true,
151
+ client_id_metadata_document_supported: true
152
+ };
153
+ for (const [k, v] of Object.entries(options.metadata ?? {})) {
154
+ if (v === void 0) delete base[k];
155
+ else base[k] = v;
156
+ }
157
+ return base;
158
+ };
159
+ let loseNextRace = false;
160
+ let reuses = 0;
161
+ const revokeFamily = (family) => {
162
+ for (const r of refreshTokens.values()) if (r.family === family) r.revoked = true;
163
+ for (const a of access.values()) if (a.family === family) a.revoked = true;
164
+ };
165
+ const tokenBody = (scope, clientId, family, pair) => {
166
+ const accessToken = pair?.accessToken ?? `synap_hub_mock_${randomToken()}`;
167
+ if (!pair) access.set(accessToken, { expiresAt: now() + accessTtl * 1e3, revoked: false, family: refresh ? family : null, scope });
168
+ const row = access.get(accessToken);
169
+ const body = {
170
+ access_token: accessToken,
171
+ token_type: "Bearer",
172
+ // As the pod: what the key has left, never an assumed hour.
173
+ expires_in: Math.max(0, Math.floor((row.expiresAt - now()) / 1e3)),
174
+ scope: scope.join(" "),
175
+ synap_grant: grant.filter((g) => scope.includes(g.permission)),
176
+ synap_user: user,
177
+ ...app ? { synap_app: app } : {}
178
+ };
179
+ if (pair) {
180
+ body.refresh_token = pair.refreshToken;
181
+ if (options.advertiseRefreshExpiry) body.refresh_token_expires_in = Math.floor((refreshTokens.get(pair.refreshToken).idleUntil - now()) / 1e3);
182
+ } else if (refresh) {
183
+ const refreshToken = randomToken();
184
+ refreshTokens.set(refreshToken, { clientId, family, idleUntil: now() + refreshIdle * 1e3, used: false, revoked: false, scope });
185
+ body.refresh_token = refreshToken;
186
+ if (options.advertiseRefreshExpiry) body.refresh_token_expires_in = refreshIdle;
187
+ }
188
+ return body;
189
+ };
190
+ async function token(form) {
191
+ if (form.grant_type === "authorization_code") {
192
+ const row = form.code ? codes.get(form.code) : void 0;
193
+ if (form.code) codes.delete(form.code);
194
+ if (!row) return oauthError("invalid_grant", "Invalid, expired, or already-redeemed authorization code");
195
+ if (row.clientId !== form.client_id) return oauthError("invalid_grant", "client_id does not match the code");
196
+ if (row.redirectUri !== form.redirect_uri) return oauthError("invalid_grant", "redirect_uri mismatch");
197
+ if (!form.code_verifier || await pkceChallenge(form.code_verifier) !== row.challenge) {
198
+ return oauthError("invalid_grant", "PKCE verification failed");
199
+ }
200
+ if (form.resource !== void 0 && row.resource && form.resource !== row.resource) {
201
+ return oauthError("invalid_target", "resource does not match the authorization request");
202
+ }
203
+ for (const r of refreshTokens.values()) if (r.clientId === row.clientId) revokeFamily(r.family);
204
+ return json(200, tokenBody(row.scope, row.clientId, randomToken()));
205
+ }
206
+ if (form.grant_type === "refresh_token") {
207
+ if (!refresh) return oauthError("unsupported_grant_type", 'Only grant_type "authorization_code" is supported');
208
+ if (!form.refresh_token || !form.client_id) return oauthError("invalid_request", "refresh_token and client_id are required");
209
+ const row = refreshTokens.get(form.refresh_token);
210
+ if (!row || row.clientId !== form.client_id || row.revoked) {
211
+ return oauthError("invalid_grant", "Invalid, expired, revoked or already-used refresh token");
212
+ }
213
+ if (row.used) {
214
+ const successor = row.successor ? refreshTokens.get(row.successor) : void 0;
215
+ const key = row.pairAccess ? access.get(row.pairAccess) : void 0;
216
+ const inGrace = graceMs > 0 && row.usedAt !== void 0 && now() - row.usedAt < graceMs;
217
+ if (inGrace && successor && !successor.used && !successor.revoked && key && !key.revoked && key.expiresAt > now()) {
218
+ graceAnswers++;
219
+ return json(200, tokenBody(row.scope, row.clientId, row.family, { accessToken: row.pairAccess, refreshToken: row.successor }));
220
+ }
221
+ reuses++;
222
+ revokeFamily(row.family);
223
+ return oauthError("invalid_grant", "Invalid, expired, revoked or already-used refresh token");
224
+ }
225
+ if (row.idleUntil <= now()) return oauthError("invalid_grant", "Invalid, expired, revoked or already-used refresh token");
226
+ if (loseNextRace) {
227
+ loseNextRace = false;
228
+ return oauthError("invalid_grant", "Invalid, expired, revoked or already-used refresh token");
229
+ }
230
+ row.used = true;
231
+ row.usedAt = now();
232
+ for (const a of access.values()) if (a.family === row.family) a.revoked = true;
233
+ const body = tokenBody(row.scope, row.clientId, row.family);
234
+ row.successor = body.refresh_token;
235
+ row.pairAccess = body.access_token;
236
+ return json(200, body);
237
+ }
238
+ return oauthError("unsupported_grant_type", "Unsupported grant_type");
239
+ }
240
+ function hub(method, path, query, headers, body) {
241
+ const failure2 = options.failures?.[path];
242
+ if (failure2 === "unreachable") return "reject";
243
+ const bearer = /^Bearer (.+)$/.exec(headers.authorization ?? "")?.[1];
244
+ const row = bearer ? access.get(bearer) : void 0;
245
+ if (!row || row.revoked || row.expiresAt <= now()) return json(401, { error: "Unauthorized" });
246
+ switch (failure2) {
247
+ case "rate_limited":
248
+ return json(429, { error: "Too many requests" }, { "Retry-After": "30" });
249
+ case "forbidden":
250
+ return json(403, { error: "Forbidden" });
251
+ case "door_unsupported":
252
+ return json(403, { error: "This door is not available to a space-scoped key", code: "GRANT_DOOR_UNSUPPORTED" });
253
+ case "server":
254
+ return json(500, { error: "Internal error" });
255
+ case "unavailable":
256
+ return json(503, { error: "Unavailable" });
257
+ }
258
+ if (path.startsWith("/knowledge/") || path === "/entities/retrieve" || path === "/memory/search") {
259
+ return json(403, { error: "This door is not available to a space-scoped key", code: "GRANT_DOOR_UNSUPPORTED" });
260
+ }
261
+ if (method === "GET" && path === "/users/me") return json(200, user);
262
+ if (method === "GET" && path === "/entities") {
263
+ const ws = query.get("workspaceId");
264
+ const kind = query.get("profileSlug");
265
+ const offset = Number(query.get("offset") ?? 0) || 0;
266
+ const limit = Number(query.get("limit") ?? 50) || 50;
267
+ const rows = entities.filter((e) => (!ws || e.workspaceId === ws) && (!kind || e.profileSlug === kind));
268
+ return json(200, rows.slice(offset, offset + limit));
269
+ }
270
+ if (method === "GET" && path === "/proposals") {
271
+ const readable = new Set(grant.filter((g) => g.permission === "proposal.read" && row.scope.includes(g.permission)).map((g) => g.workspaceId));
272
+ const status = query.get("status") ?? "pending";
273
+ const ws = query.get("workspaceId");
274
+ const limit = Math.min(Math.max(Number(query.get("limit") ?? 50) || 50, 1), 200);
275
+ const offset = Number(query.get("offset") ?? 0) || 0;
276
+ const rows = proposals.filter((p) => p.workspaceId !== null && readable.has(p.workspaceId)).filter((p) => (status === "all" || p.status === status) && (!ws || p.workspaceId === ws)).reverse();
277
+ const page = rows.slice(offset, offset + limit);
278
+ return json(200, { proposals: page, limit, offset, total: rows.length, hasMore: offset + page.length < rows.length });
279
+ }
280
+ if (method !== "GET") {
281
+ writes.push({ method, path, body });
282
+ const fields = body && typeof body === "object" ? body : {};
283
+ const workspaceId = typeof fields.workspaceId === "string" ? fields.workspaceId : null;
284
+ if ((options.writeMode ?? "proposed") === "proposed") {
285
+ const proposalId = `prop_mock_${++proposalSeq}`;
286
+ proposals.push({
287
+ id: proposalId,
288
+ proposalType: method === "POST" ? "create" : method === "DELETE" ? "delete" : "update",
289
+ targetType: path.split("/")[1]?.replace(/s$/, "") ?? "entity",
290
+ targetId: null,
291
+ status: "pending",
292
+ workspaceId,
293
+ createdAt: new Date(now()).toISOString(),
294
+ correlationId: null,
295
+ sessionId: null,
296
+ agentUserId: MOCK_APP_AGENT_ID,
297
+ ...typeof fields.title === "string" ? { summary: `Create "${fields.title}"` } : {}
298
+ });
299
+ return json(200, {
300
+ status: "proposed",
301
+ message: "Sent for review",
302
+ proposalId,
303
+ reviewUrl: `${issuer}/open/${proposalId}`
304
+ });
305
+ }
306
+ const id = `ent_mock_${writes.length}`;
307
+ if (method === "POST" && path === "/entities") entities.push({ id, ...fields });
308
+ return json(200, { status: "created", id });
309
+ }
310
+ return json(404, { error: "Not found" });
311
+ }
312
+ const mockFetch = async (input, init) => {
313
+ const request = typeof input === "object" && "method" in input ? input : null;
314
+ const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
315
+ const method = (init?.method ?? request?.method ?? "GET").toUpperCase();
316
+ const headers = {};
317
+ new Headers(init?.headers ?? request?.headers).forEach((value, name) => {
318
+ headers[name.toLowerCase()] = value;
319
+ });
320
+ const rawBody = typeof init?.body === "string" ? init.body : request ? await request.clone().text() : "";
321
+ const isForm = (headers["content-type"] ?? "").includes("application/x-www-form-urlencoded");
322
+ const form = isForm ? Object.fromEntries(new URLSearchParams(rawBody)) : null;
323
+ requests.push({ method, url: url.href, headers, form });
324
+ if (url.origin !== new URL(issuer).origin) throw new TypeError(`Failed to fetch: ${url.origin} is not the mock pod`);
325
+ const path = url.pathname;
326
+ if (method === "GET" && path === "/.well-known/oauth-authorization-server") return json(200, metadata());
327
+ if (method === "POST" && path === "/token") return token(form ?? {});
328
+ if (method === "POST" && path === "/revoke") {
329
+ const t = form?.token ?? "";
330
+ const r = refreshTokens.get(t);
331
+ if (r && form?.client_id && form.client_id !== r.clientId) {
332
+ return oauthError("unauthorized_client", "The token was not issued to this client");
333
+ }
334
+ if (r) revokeFamily(r.family);
335
+ const a = access.get(t);
336
+ if (a) a.revoked = true;
337
+ return new Response(null, { status: 200 });
338
+ }
339
+ const hubPath = /^\/api\/hub(\/.*)$/.exec(path)?.[1];
340
+ if (hubPath) {
341
+ let body = void 0;
342
+ try {
343
+ body = rawBody ? JSON.parse(rawBody) : void 0;
344
+ } catch {
345
+ body = rawBody;
346
+ }
347
+ const res = hub(method, hubPath, url.searchParams, headers, body);
348
+ if (res === "reject") throw new TypeError("Failed to fetch: mock pod unreachable");
349
+ return res;
350
+ }
351
+ return json(404, { error: "Not found" });
352
+ };
353
+ return {
354
+ issuer,
355
+ fetch: mockFetch,
356
+ requests,
357
+ writes,
358
+ proposals,
359
+ settleProposal(id, status) {
360
+ const p = proposals.find((x) => x.id === id);
361
+ if (p) p.status = status;
362
+ },
363
+ async redeemElsewhere(t) {
364
+ const row = refreshTokens.get(t);
365
+ const res = await token({ grant_type: "refresh_token", refresh_token: t, client_id: row?.clientId ?? "" });
366
+ if (res.status !== 200) throw new Error(`redeemElsewhere: the pod answered ${res.status}`);
367
+ return await res.json();
368
+ },
369
+ get graceAnswers() {
370
+ return graceAnswers;
371
+ },
372
+ authorize(authorizeUrl, decision = {}) {
373
+ const url = new URL(authorizeUrl);
374
+ if (`${url.origin}${url.pathname}` !== `${issuer}/authorize`) throw new Error(`Not this pod's /authorize: ${authorizeUrl}`);
375
+ const q = url.searchParams;
376
+ const redirectUri = q.get("redirect_uri");
377
+ const clientId = q.get("client_id");
378
+ if (!clientId || !redirectUri) throw new Error("Authorization request rejected: client_id and redirect_uri are required");
379
+ const answer = new URL(redirectUri);
380
+ const state = decision.state ?? q.get("state");
381
+ const finish = (fields) => {
382
+ for (const [k, v] of Object.entries(fields)) answer.searchParams.set(k, v);
383
+ if (state !== null) answer.searchParams.set("state", state);
384
+ const iss = decision.iss === void 0 ? issuer : decision.iss;
385
+ if (iss !== null) answer.searchParams.set("iss", iss);
386
+ return answer.href;
387
+ };
388
+ if (q.get("response_type") !== "code") return finish({ error: "unsupported_response_type" });
389
+ if (q.get("code_challenge_method") !== "S256" || !q.get("code_challenge")) {
390
+ return finish({ error: "invalid_request", error_description: "PKCE S256 is required" });
391
+ }
392
+ const resource = q.get("resource");
393
+ if (resource !== null && resource !== `${issuer}/api/hub` && resource !== `${issuer}/mcp`) {
394
+ return finish({ error: "invalid_target" });
395
+ }
396
+ const scope = (q.get("scope") ?? "").split(/\s+/).filter(Boolean);
397
+ if (scope.length === 0) return finish({ error: "invalid_scope" });
398
+ if (decision.deny) return finish({ error: "access_denied" });
399
+ const code = randomToken();
400
+ codes.set(code, { clientId, redirectUri, challenge: q.get("code_challenge"), resource, scope });
401
+ return finish({ code });
402
+ },
403
+ consumeRefreshToken(t) {
404
+ const row = refreshTokens.get(t);
405
+ if (row) row.used = true;
406
+ },
407
+ loseNextRefreshRace() {
408
+ loseNextRace = true;
409
+ },
410
+ get reuses() {
411
+ return reuses;
412
+ },
413
+ revokeAll() {
414
+ for (const row of access.values()) row.revoked = true;
415
+ for (const row of refreshTokens.values()) row.revoked = true;
416
+ },
417
+ liveRefreshTokens() {
418
+ return [...refreshTokens].filter(([, r]) => !r.used && !r.revoked).map(([t]) => t);
419
+ },
420
+ reset() {
421
+ requests.length = 0;
422
+ writes.length = 0;
423
+ }
424
+ };
425
+ }
426
+
427
+ // src/testing/cloud.ts
428
+ var MOCK_CLOUD_URL = "https://cp.mock-synap.test";
429
+ var STATE_RE = /^[A-Za-z0-9._~-]{16,256}$/;
430
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
431
+ function createMockCloud(options = {}) {
432
+ const base = (options.url ?? MOCK_CLOUD_URL).replace(/\/+$/, "");
433
+ const requests = [];
434
+ return {
435
+ url: base,
436
+ requests,
437
+ choose(location, { pod, metadata }) {
438
+ const back = check(location, metadata);
439
+ back.searchParams.set("iss", pod);
440
+ back.searchParams.set("state", new URL(location).searchParams.get("state"));
441
+ return back.href;
442
+ },
443
+ cancel(location, { metadata }) {
444
+ const back = check(location, metadata);
445
+ back.searchParams.set("state", new URL(location).searchParams.get("state"));
446
+ back.searchParams.set("error", "access_denied");
447
+ return back.href;
448
+ }
449
+ };
450
+ function check(location, metadata) {
451
+ requests.push(location);
452
+ const url = new URL(location);
453
+ if (`${url.origin}${url.pathname}` !== `${base}/connect/pod`) throw new Error(`Not this CP's /connect/pod: ${location}`);
454
+ const clientId = url.searchParams.get("client_id");
455
+ const state = url.searchParams.get("state");
456
+ if (!clientId || !cimdUrlAccepted(clientId)) throw new Error("Chooser refused: client_id must be an https CIMD URL");
457
+ if (!state || !STATE_RE.test(state)) throw new Error("Chooser refused: state is missing or malformed");
458
+ if (clientId !== metadata.client_id) throw new Error("Chooser refused: the client metadata is not this client_id's");
459
+ return new URL(metadata.initiate_login_uri);
460
+ }
461
+ function cimdUrlAccepted(clientId) {
462
+ let url;
463
+ try {
464
+ url = new URL(clientId);
465
+ } catch {
466
+ return false;
467
+ }
468
+ if (url.protocol === "https:") return true;
469
+ return url.protocol === "http:" && options.allowInsecureLoopback === true && LOOPBACK_HOSTS.has(url.hostname);
470
+ }
471
+ }
472
+
96
473
  // src/testing/index.ts
97
474
  var MOCK_POD_URL = "https://mock.synap.invalid";
98
475
  var MOCK_TOKENS = { form: "demo-form", share: "demo", site: "demo-site" };
@@ -127,7 +504,7 @@ var MAX_OFFSET = 1e4;
127
504
  var MAX_LIMIT = 100;
128
505
  var MAX_SORT_KEYS = 3;
129
506
  var PUBLISHED_AT = "publishedAt";
130
- function json(status, body, headers = {}) {
507
+ function json2(status, body, headers = {}) {
131
508
  return new Response(JSON.stringify(body), {
132
509
  status,
133
510
  headers: { "Content-Type": "application/json", "Cache-Control": "no-cache", ...headers }
@@ -150,15 +527,15 @@ function etagMatches(header, etag) {
150
527
  function failure(token) {
151
528
  switch (token) {
152
529
  case MOCK_FAILURE_TOKENS.rateLimited:
153
- return json(429, { error: "Too many requests", retryAfter: "5 minutes" });
530
+ return json2(429, { error: "Too many requests", retryAfter: "5 minutes" });
154
531
  case MOCK_FAILURE_TOKENS.rateLimitedRetryAfter:
155
- return json(429, { error: "Too many requests" }, { "Retry-After": String(MOCK_RETRY_AFTER_SEC) });
532
+ return json2(429, { error: "Too many requests" }, { "Retry-After": String(MOCK_RETRY_AFTER_SEC) });
156
533
  case MOCK_FAILURE_TOKENS.doorsDisabled:
157
- return json(403, DOORS_DISABLED);
534
+ return json2(403, DOORS_DISABLED);
158
535
  case MOCK_FAILURE_TOKENS.serverError:
159
- return json(500, { error: "Internal error" });
536
+ return json2(500, { error: "Internal error" });
160
537
  case MOCK_FAILURE_TOKENS.unavailable:
161
- return json(503, { received: false });
538
+ return json2(503, { received: false });
162
539
  case MOCK_FAILURE_TOKENS.unreachable:
163
540
  return "reject";
164
541
  default:
@@ -233,17 +610,17 @@ function cached(view, ifNoneMatch) {
233
610
  if (etagMatches(ifNoneMatch, etag)) {
234
611
  return new Response(null, { status: 304, headers: { ETag: etag, "Cache-Control": "no-cache" } });
235
612
  }
236
- return json(200, view, { ETag: etag });
613
+ return json2(200, view, { ETag: etag });
237
614
  }
238
615
  function siteDoor(site, rest, params, ifNoneMatch) {
239
616
  if (rest.length === 0) return cached(manifest(site), ifNoneMatch);
240
617
  if (rest[0] !== "collections" || rest.length > 3 || !Object.hasOwn(site.collections, rest[1])) {
241
- return json(404, NOT_FOUND);
618
+ return json2(404, NOT_FOUND);
242
619
  }
243
620
  const c = site.collections[rest[1]];
244
621
  if (rest.length === 2) {
245
622
  const q = parseQuery(params, c);
246
- if (typeof q === "string") return json(400, { error: q });
623
+ if (typeof q === "string") return json2(400, { error: q });
247
624
  const sorted = [...c.records].sort(compareRecords(q.sort));
248
625
  const page = {
249
626
  items: sorted.slice(q.offset, q.offset + q.limit).map(summary),
@@ -255,10 +632,10 @@ function siteDoor(site, rest, params, ifNoneMatch) {
255
632
  }
256
633
  const address = rest[2];
257
634
  const byPublicId = PUBLIC_ID_RE.test(address);
258
- if (!byPublicId && !(address.length <= SLUG_MAX_LENGTH && SLUG_RE.test(address))) return json(404, NOT_FOUND);
635
+ if (!byPublicId && !(address.length <= SLUG_MAX_LENGTH && SLUG_RE.test(address))) return json2(404, NOT_FOUND);
259
636
  const record = c.records.find((r) => byPublicId ? r.publicId === address : r.slug === address);
260
- if (!record) return json(404, NOT_FOUND);
261
- if (params.has("embed")) return json(400, { error: "embed is not available" });
637
+ if (!record) return json2(404, NOT_FOUND);
638
+ if (params.has("embed")) return json2(400, { error: "embed is not available" });
262
639
  return cached(record, ifNoneMatch);
263
640
  }
264
641
  function invalidKeys(form, body) {
@@ -295,12 +672,12 @@ function createMockPod(options = {}) {
295
672
  if (preflighted.length > 0) throw new TypeError(`Failed to fetch: preflight refuses ${preflighted.join(", ")}`);
296
673
  }
297
674
  const m = url.pathname.match(/^\/api\/hub\/public\/(forms|shares|sites)\/(.+)$/);
298
- if (!m) return json(404, NOT_FOUND);
675
+ if (!m) return json2(404, NOT_FOUND);
299
676
  let segments;
300
677
  try {
301
678
  segments = m[2].split("/").map(decodeURIComponent);
302
679
  } catch {
303
- return json(404, NOT_FOUND);
680
+ return json2(404, NOT_FOUND);
304
681
  }
305
682
  const [token, ...rest] = segments;
306
683
  const failed = failure(token);
@@ -318,15 +695,15 @@ function createMockPod(options = {}) {
318
695
  const invalid = form ? invalidKeys(form, body) : [];
319
696
  const status = invalid.length > 0 ? 422 : 202;
320
697
  submissions.push({ token, body, status });
321
- return status === 422 ? json(422, { received: false, invalid }) : json(202, { received: true });
698
+ return status === 422 ? json2(422, { received: false, invalid }) : json2(202, { received: true });
322
699
  }
323
- if (method !== "GET") return json(404, NOT_FOUND);
324
- if (door === "forms" && rest.length === 0 && Object.hasOwn(forms, token)) return json(200, forms[token]);
325
- if (door === "shares" && rest.length === 0 && Object.hasOwn(shares, token)) return json(200, shares[token]);
700
+ if (method !== "GET") return json2(404, NOT_FOUND);
701
+ if (door === "forms" && rest.length === 0 && Object.hasOwn(forms, token)) return json2(200, forms[token]);
702
+ if (door === "shares" && rest.length === 0 && Object.hasOwn(shares, token)) return json2(200, shares[token]);
326
703
  if (door === "sites" && Object.hasOwn(sites, token)) {
327
704
  return siteDoor(sites[token], rest, url.searchParams, headers["if-none-match"] ?? null);
328
705
  }
329
- return json(404, NOT_FOUND);
706
+ return json2(404, NOT_FOUND);
330
707
  };
331
708
  return {
332
709
  podUrl: MOCK_POD_URL,
@@ -340,13 +717,21 @@ function createMockPod(options = {}) {
340
717
  };
341
718
  }
342
719
  export {
720
+ MOCK_APP,
721
+ MOCK_APP_AGENT_ID,
722
+ MOCK_AUTH_ISSUER,
723
+ MOCK_CLOUD_URL,
343
724
  MOCK_FAILURE_TOKENS,
344
725
  MOCK_FORM,
726
+ MOCK_GRANT,
345
727
  MOCK_POD_URL,
346
728
  MOCK_RETRY_AFTER_SEC,
347
729
  MOCK_SHARE,
348
730
  MOCK_SITE,
349
731
  MOCK_TOKENS,
732
+ MOCK_USER,
733
+ createMockAuthServer,
734
+ createMockCloud,
350
735
  createMockPod
351
736
  };
352
737
  //# sourceMappingURL=testing.js.map