@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/SKILL.md +33 -1
- package/dist/index.d.mts +107 -1
- package/dist/index.d.ts +107 -1
- package/dist/index.js +238 -4
- package/dist/index.mjs +238 -4
- package/dist/next/headers-shim.js +7 -0
- package/dist/next/headers-shim.mjs +7 -0
- package/dist/setup.js +188 -4
- package/dist/setup.mjs +188 -4
- package/package.json +6 -1
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
|
|
84
|
-
if (Math.abs(
|
|
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
|
-
|
|
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.
|
|
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": "*",
|