@stardeck-customer-apps/testing 0.4.0 → 0.5.1

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/setup.mjs CHANGED
@@ -26,6 +26,13 @@ var state = globalSingleton("state", () => ({
26
26
  storageFiles: /* @__PURE__ */ new Map(),
27
27
  presignPending: /* @__PURE__ */ new Map(),
28
28
  messages: [],
29
+ edgePrints: [],
30
+ edgeDisplays: [],
31
+ edgeTestPrints: [],
32
+ edgePrintCounter: 0,
33
+ edgeDevices: [],
34
+ edgePeripherals: [],
35
+ edgeBindings: /* @__PURE__ */ new Map(),
29
36
  allowNetwork: false
30
37
  }));
31
38
  function requireDb() {
@@ -80,8 +87,8 @@ function verifyDeploymentAuthHeader(secret, header) {
80
87
  return null;
81
88
  }
82
89
  if (payload.type !== "deployment-request") return null;
83
- const now2 = Math.floor(Date.now() / 1e3);
84
- if (Math.abs(now2 - payload.timestamp) > TIMESTAMP_TOLERANCE_SECONDS) return null;
90
+ const now3 = Math.floor(Date.now() / 1e3);
91
+ if (Math.abs(now3 - payload.timestamp) > TIMESTAMP_TOLERANCE_SECONDS) return null;
85
92
  return payload;
86
93
  }
87
94
 
@@ -1135,6 +1142,178 @@ async function handleMessagingRequest(request, channel, subPath) {
1135
1142
  return failure(`No messaging simulator for ${method} .../integrations/${channel}${subPath}`, 404);
1136
1143
  }
1137
1144
 
1145
+ // src/simulator/edge.ts
1146
+ function edgeFailure(error, status = 400, code) {
1147
+ return json({ success: false, error, ...code ? { code } : {} }, status);
1148
+ }
1149
+ function bindingNotFound(alias) {
1150
+ return edgeFailure(`No peripheral is paired to alias "${alias}"`, 404, "BINDING_NOT_FOUND");
1151
+ }
1152
+ function now2() {
1153
+ return (/* @__PURE__ */ new Date()).toISOString();
1154
+ }
1155
+ function nextJobId() {
1156
+ state.edgePrintCounter += 1;
1157
+ return `job_${state.edgePrintCounter}`;
1158
+ }
1159
+ function nextConfigVersion() {
1160
+ return state.edgeDisplays.length + 1;
1161
+ }
1162
+ function findPeripheral(id) {
1163
+ return state.edgePeripherals.find((p) => p.id === id);
1164
+ }
1165
+ function buildBinding(alias, peripheralId) {
1166
+ const peripheral = findPeripheral(peripheralId);
1167
+ return {
1168
+ alias,
1169
+ state: peripheral ? "ok" : "peripheral_missing",
1170
+ peripheral: peripheral ? {
1171
+ id: peripheral.id,
1172
+ displayName: peripheral.displayName,
1173
+ driver: peripheral.driver,
1174
+ connected: peripheral.connected
1175
+ } : null,
1176
+ device: peripheral ? {
1177
+ id: peripheral.device.id,
1178
+ displayName: peripheral.device.displayName,
1179
+ status: peripheral.device.status
1180
+ } : null,
1181
+ updatedAt: now2()
1182
+ };
1183
+ }
1184
+ function handleListDevices() {
1185
+ return success({ devices: [...state.edgeDevices] });
1186
+ }
1187
+ function handleListPeripherals(request) {
1188
+ const deviceId = new URL(request.url).searchParams.get("deviceId");
1189
+ const peripherals = deviceId ? state.edgePeripherals.filter((p) => p.device.id === deviceId) : [...state.edgePeripherals];
1190
+ return success({ peripherals });
1191
+ }
1192
+ function handleListBindings() {
1193
+ return success({ bindings: [...state.edgeBindings.values()] });
1194
+ }
1195
+ function handleGetBinding(alias) {
1196
+ const binding = state.edgeBindings.get(alias);
1197
+ if (!binding) return bindingNotFound(alias);
1198
+ return success({ binding });
1199
+ }
1200
+ async function handlePair(request) {
1201
+ const body = await readJsonBody(request);
1202
+ const alias = String(body.alias ?? "");
1203
+ const peripheralId = String(body.peripheralId ?? "");
1204
+ if (!alias) return edgeFailure("alias is required");
1205
+ if (!peripheralId) return edgeFailure("peripheralId is required");
1206
+ if (!findPeripheral(peripheralId)) {
1207
+ return edgeFailure("Peripheral not found or its device is not granted to this project", 404);
1208
+ }
1209
+ const binding = buildBinding(alias, peripheralId);
1210
+ state.edgeBindings.set(alias, binding);
1211
+ return success({ binding });
1212
+ }
1213
+ function handleUnpair(alias) {
1214
+ const existed = state.edgeBindings.delete(alias);
1215
+ if (!existed) return bindingNotFound(alias);
1216
+ return success({ deleted: true });
1217
+ }
1218
+ async function handlePrint(request) {
1219
+ const body = await readJsonBody(request);
1220
+ const jobId = nextJobId();
1221
+ const captured = {
1222
+ jobId,
1223
+ deploymentId: String(body.deploymentId ?? ""),
1224
+ alias: body.alias ? String(body.alias) : void 0,
1225
+ deviceId: body.deviceId ? String(body.deviceId) : void 0,
1226
+ peripheralId: body.peripheralId ? String(body.peripheralId) : void 0,
1227
+ receipt: body.receipt ?? {},
1228
+ openDrawer: body.openDrawer === true,
1229
+ logo: body.logo === true ? true : body.logo === false ? false : void 0,
1230
+ copies: typeof body.copies === "number" ? body.copies : 1
1231
+ };
1232
+ state.edgePrints.push(captured);
1233
+ return success({
1234
+ jobId,
1235
+ status: "completed"
1236
+ });
1237
+ }
1238
+ async function handleShowDisplay(request) {
1239
+ const body = await readJsonBody(request);
1240
+ const captured = {
1241
+ action: "show",
1242
+ alias: body.alias ? String(body.alias) : void 0,
1243
+ peripheralId: body.peripheralId ? String(body.peripheralId) : void 0,
1244
+ url: body.url ? String(body.url) : void 0
1245
+ };
1246
+ state.edgeDisplays.push(captured);
1247
+ return success({
1248
+ configVersion: nextConfigVersion(),
1249
+ pushed: true,
1250
+ state: "showing"
1251
+ });
1252
+ }
1253
+ function handleClearDisplay(request) {
1254
+ const url = new URL(request.url);
1255
+ const alias = url.searchParams.get("alias");
1256
+ const peripheralId = url.searchParams.get("peripheralId");
1257
+ const captured = {
1258
+ action: "clear",
1259
+ alias: alias ?? void 0,
1260
+ peripheralId: peripheralId ?? void 0
1261
+ };
1262
+ state.edgeDisplays.push(captured);
1263
+ return success({
1264
+ configVersion: nextConfigVersion(),
1265
+ pushed: true,
1266
+ state: "cleared"
1267
+ });
1268
+ }
1269
+ async function handleTestPrint(request) {
1270
+ const body = await readJsonBody(request);
1271
+ const captured = {
1272
+ alias: body.alias ? String(body.alias) : void 0,
1273
+ deviceId: body.deviceId ? String(body.deviceId) : void 0,
1274
+ peripheralId: body.peripheralId ? String(body.peripheralId) : void 0
1275
+ };
1276
+ state.edgeTestPrints.push(captured);
1277
+ return success({
1278
+ status: "ok",
1279
+ peripheralId: captured.peripheralId ?? captured.alias ?? "default"
1280
+ });
1281
+ }
1282
+ async function handleEdgeRequest(request, subPath) {
1283
+ const method = request.method;
1284
+ if (subPath === "/print" && method === "POST") {
1285
+ return handlePrint(request);
1286
+ }
1287
+ if (subPath === "/devices" && method === "GET") {
1288
+ return handleListDevices();
1289
+ }
1290
+ if (subPath === "/peripherals" && method === "GET") {
1291
+ return handleListPeripherals(request);
1292
+ }
1293
+ if (subPath === "/bindings" && method === "GET") {
1294
+ return handleListBindings();
1295
+ }
1296
+ if (subPath === "/bindings" && method === "PUT") {
1297
+ return handlePair(request);
1298
+ }
1299
+ const bindingMatch = subPath.match(/^\/bindings\/([^/]+)$/);
1300
+ if (bindingMatch) {
1301
+ const alias = decodeURIComponent(bindingMatch[1]);
1302
+ if (method === "GET") return handleGetBinding(alias);
1303
+ if (method === "DELETE") return handleUnpair(alias);
1304
+ }
1305
+ if (subPath === "/display" && method === "POST") {
1306
+ return handleShowDisplay(request);
1307
+ }
1308
+ if (subPath === "/display" && method === "DELETE") {
1309
+ return handleClearDisplay(request);
1310
+ }
1311
+ if (subPath === "/test-print" && method === "POST") {
1312
+ return handleTestPrint(request);
1313
+ }
1314
+ return edgeFailure(`No edge simulator for ${method} .../edge${subPath}`, 404);
1315
+ }
1316
+
1138
1317
  // src/simulator/router.ts
1139
1318
  var fetchHolder = globalSingleton("fetch-holder", () => ({
1140
1319
  originalFetch: null
@@ -1155,7 +1334,8 @@ function requiresDeploymentHmac(request, url) {
1155
1334
  const messagingMatch = url.pathname.match(
1156
1335
  /^\/api\/deployments\/[^/]+\/integrations\/(slack|line|facebook)(\/.*)?$/
1157
1336
  );
1158
- return !!(dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch);
1337
+ const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
1338
+ return !!(dataStoreMatch || isEmail || identitiesMatch || storeMatch || messagingMatch || edgeMatch);
1159
1339
  }
1160
1340
  async function handleSimulatedRequest(request, url) {
1161
1341
  if (url.pathname === "/sql") {
@@ -1172,6 +1352,7 @@ async function handleSimulatedRequest(request, url) {
1172
1352
  const messagingMatch = url.pathname.match(
1173
1353
  /^\/api\/deployments\/[^/]+\/integrations\/(slack|line|facebook)(\/.*)?$/
1174
1354
  );
1355
+ const edgeMatch = url.pathname.match(/^\/api\/deployments\/[^/]+\/edge(\/.*)?$/);
1175
1356
  const isStorageHost = url.hostname === STORAGE_TEST_HOST;
1176
1357
  if (requiresDeploymentHmac(request, url)) {
1177
1358
  const authHeader = request.headers.get("X-Stardeck-Auth");
@@ -1199,6 +1380,9 @@ async function handleSimulatedRequest(request, url) {
1199
1380
  const channel = messagingMatch[1];
1200
1381
  return handleMessagingRequest(request, channel, messagingMatch[2] ?? "");
1201
1382
  }
1383
+ if (edgeMatch) {
1384
+ return handleEdgeRequest(request, edgeMatch[1] ?? "");
1385
+ }
1202
1386
  if (dataStoreMatch) {
1203
1387
  const subPath = dataStoreMatch[1] ?? "";
1204
1388
  const db = requireDb();
@@ -1220,7 +1404,7 @@ async function handleSimulatedRequest(request, url) {
1220
1404
  }
1221
1405
  }
1222
1406
  return failure(
1223
- `[stardeck-testing] No simulator for ${request.method} ${url.pathname}. Supported: data-store query/mutate/schema, email send, identities CRUD, payments store/checkout, storage upload/files, messaging send, auth verify/refresh, Neon /sql.`,
1407
+ `[stardeck-testing] No simulator for ${request.method} ${url.pathname}. Supported: data-store query/mutate/schema, email send, identities CRUD, payments store/checkout, storage upload/files, messaging send, edge print/display/bindings, auth verify/refresh, Neon /sql.`,
1224
1408
  404
1225
1409
  );
1226
1410
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stardeck-customer-apps/testing",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Vitest test harness for Stardeck customer apps — in-process Postgres (PGlite) plus a control-plane simulator so the real Stardeck SDKs run unmodified in tests",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -66,6 +66,7 @@
66
66
  },
67
67
  "peerDependencies": {
68
68
  "@stardeck-customer-apps/integrations-sdk": ">=1.6.0",
69
+ "@stardeck-customer-apps/edge-sdk": ">=1.4.0",
69
70
  "@stardeck-customer-apps/payments-sdk": ">=0.1.0",
70
71
  "@stardeck-customer-apps/storage-sdk": ">=0.1.0",
71
72
  "next": "^14.0.0 || ^15.0.0 || ^16.0.0",
@@ -75,6 +76,9 @@
75
76
  "@stardeck-customer-apps/integrations-sdk": {
76
77
  "optional": true
77
78
  },
79
+ "@stardeck-customer-apps/edge-sdk": {
80
+ "optional": true
81
+ },
78
82
  "@stardeck-customer-apps/payments-sdk": {
79
83
  "optional": true
80
84
  },
@@ -90,6 +94,7 @@
90
94
  "@neondatabase/serverless": "^1.0.0",
91
95
  "@stardeck-customer-apps/data-store-sdk": "*",
92
96
  "@stardeck-customer-apps/email-sdk": "*",
97
+ "@stardeck-customer-apps/edge-sdk": "*",
93
98
  "@stardeck-customer-apps/integrations-sdk": "*",
94
99
  "@stardeck-customer-apps/payments-sdk": "*",
95
100
  "@stardeck-customer-apps/storage-sdk": "*",