@stardeck-customer-apps/testing 0.8.0 → 0.10.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/LICENSE +21 -0
- package/SKILL.md +123 -5
- package/dist/config.d.mts +6 -2
- package/dist/config.d.ts +6 -2
- package/dist/config.js +75 -1
- package/dist/config.mjs +73 -1
- package/dist/data-store-manifest-Zwgcv0DJ.d.mts +25 -0
- package/dist/data-store-manifest-Zwgcv0DJ.d.ts +25 -0
- package/dist/index.d.mts +82 -2
- package/dist/index.d.ts +82 -2
- package/dist/index.js +453 -6
- package/dist/index.mjs +450 -6
- package/dist/next/headers-shim.js +8 -0
- package/dist/next/headers-shim.mjs +8 -0
- package/dist/setup.js +294 -4
- package/dist/setup.mjs +294 -4
- package/package.json +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stardeck Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/SKILL.md
CHANGED
|
@@ -29,6 +29,65 @@ This writes `src/generated/data-store-types.ts` and
|
|
|
29
29
|
`src/generated/data-store-schema.sql`. The harness applies the SQL snapshot to
|
|
30
30
|
PGlite at test boot. **Commit both files.**
|
|
31
31
|
|
|
32
|
+
### Data store bindings
|
|
33
|
+
|
|
34
|
+
Configure each app-local data store binding once in `vitest.config.ts`. The
|
|
35
|
+
harness injects the same `STARDECK_DATA_STORES` manifest shape as production, so
|
|
36
|
+
application code resolves the binding without test-only branches:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { defineStardeckTestConfig } from "@stardeck-customer-apps/testing/config";
|
|
40
|
+
|
|
41
|
+
export default defineStardeckTestConfig(
|
|
42
|
+
{},
|
|
43
|
+
{
|
|
44
|
+
dataStores: [{ bindingKey: "development" }],
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { createDataStore } from "@stardeck-customer-apps/data-store-sdk/server";
|
|
51
|
+
|
|
52
|
+
const db = await createDataStore({ storeName: "development" });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Do not branch on `DATA_STORE_URL?.includes(".stardeck.test")` or otherwise
|
|
56
|
+
sniff test hosts. Configure the binding and use `createDataStore({ storeName })`
|
|
57
|
+
the same way in tests and production.
|
|
58
|
+
|
|
59
|
+
For one test file that needs a different manifest, pass the same entries to the
|
|
60
|
+
app instead. This overrides the config-level manifest for that file:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
app = await createTestApp({
|
|
64
|
+
dataStores: [
|
|
65
|
+
{ bindingKey: "development" },
|
|
66
|
+
{ bindingKey: "uploads", name: "Customer Files", storeType: "storage" },
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
An app setup file may also set `STARDECK_DATA_STORES` directly. The harness
|
|
72
|
+
adopts a non-empty raw env value when `createTestApp()` has no `dataStores`
|
|
73
|
+
option; a per-app `dataStores` option still overrides it. An empty env value is
|
|
74
|
+
treated as unset, matching the SDK manifest parser.
|
|
75
|
+
|
|
76
|
+
Strictness follows adoption:
|
|
77
|
+
|
|
78
|
+
- With no manifest configuration, `createTestApp()` injects a `main` database
|
|
79
|
+
entry and keeps `DATA_STORE_URL` for older bare `createDataStore()` calls.
|
|
80
|
+
- Configuring `dataStores` through either channel removes `DATA_STORE_URL` by
|
|
81
|
+
default. A misspelled `storeName` then fails instead of silently connecting to
|
|
82
|
+
the default database.
|
|
83
|
+
- Passing `dataStores: []` explicitly creates a strict zero-store test world:
|
|
84
|
+
the manifest is `[]`, `DATA_STORE_URL` is absent, and every
|
|
85
|
+
`createDataStore(...)` call fails until a store is configured.
|
|
86
|
+
- Empty-string `bindingKey`, `slug`, or `name` values throw during test app
|
|
87
|
+
setup so typos cannot silently change manifest resolution.
|
|
88
|
+
- Set `legacyDataStoreUrl: true` per file only while migrating legacy code. Set
|
|
89
|
+
it to `false` to test strict manifest-only resolution even with zero config.
|
|
90
|
+
|
|
32
91
|
## Writing tests
|
|
33
92
|
|
|
34
93
|
```ts
|
|
@@ -93,6 +152,47 @@ await app.payments.deliverStripeEvent(POST, {
|
|
|
93
152
|
data: { object: { id: session.id } },
|
|
94
153
|
});
|
|
95
154
|
|
|
155
|
+
// Bolt+ terminal: create intent → approve → webhook (same deliverBeamEvent path)
|
|
156
|
+
// Simulator intents are real-shaped (bolti_<n>, isVirtual: false) — not bolti_emu_*.
|
|
157
|
+
// createBoltIntent validates like production: amount > 0, expiryDurationInSec 90–600,
|
|
158
|
+
// paymentMethod enum, deploymentId UUID, boltConnectionId required.
|
|
159
|
+
const connection = await payments.createBoltConnection({ pairingCode: "PAIR01" });
|
|
160
|
+
const intent = await payments.createBoltIntent({
|
|
161
|
+
amount: 10000,
|
|
162
|
+
boltConnectionId: connection.beamConnectionId,
|
|
163
|
+
paymentMethod: "CARD",
|
|
164
|
+
expiryDurationInSec: 300,
|
|
165
|
+
referenceId: "order-1",
|
|
166
|
+
});
|
|
167
|
+
expect(app.payments.getBoltIntent(intent.id)?.status).toBe("PENDING");
|
|
168
|
+
expect(app.payments.getBoltIntent(intent.id)?.isVirtual).toBe(false);
|
|
169
|
+
|
|
170
|
+
const charge = app.payments.approveBoltIntent(intent.id);
|
|
171
|
+
expect(app.payments.getBoltIntent(intent.id)?.status).toBe("PAID");
|
|
172
|
+
|
|
173
|
+
const { POST: beamPost } = paymentsHandler({
|
|
174
|
+
provider: "beam",
|
|
175
|
+
onBeamWebhook: async (event) => {
|
|
176
|
+
if (event.type === "charge.succeeded") {
|
|
177
|
+
await fulfillOrder(String(event.payload.referenceId ?? ""));
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
await app.payments.deliverBeamEvent(beamPost, {
|
|
182
|
+
type: "charge.succeeded",
|
|
183
|
+
payload: {
|
|
184
|
+
id: charge.id,
|
|
185
|
+
sourceId: intent.id,
|
|
186
|
+
referenceId: "order-1",
|
|
187
|
+
amount: 10000,
|
|
188
|
+
currency: "THB",
|
|
189
|
+
status: "SUCCEEDED",
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
// declineBoltIntent / expireBoltIntent deliver NO webhook — Beam has no
|
|
193
|
+
// charge-failure or expiry event for bolt payments.
|
|
194
|
+
// EXPIRED is derived from expiresAt on read, never stored.
|
|
195
|
+
|
|
96
196
|
// File upload
|
|
97
197
|
const storage = new StorageClient();
|
|
98
198
|
await storage.upload(new File(["hi"], "note.txt", { type: "text/plain" }));
|
|
@@ -145,7 +245,14 @@ expect(app.edge.latestDisplay()?.action).toBe("show");
|
|
|
145
245
|
|
|
146
246
|
- `createTestApp(options)` — boots PGlite + the control-plane simulator and
|
|
147
247
|
sets all Stardeck env vars (`CONTROL_PLANE_URL`, `DEPLOYMENT_SECRET`,
|
|
148
|
-
`
|
|
248
|
+
`STARDECK_DATA_STORES`, ids, and the legacy `DATA_STORE_URL` when enabled).
|
|
249
|
+
One per test file, in `beforeAll`.
|
|
250
|
+
- `dataStores` — per-file production-shaped data store manifest entries. The
|
|
251
|
+
first entry defaults to slug `main`; later entries need a `slug` or `name`.
|
|
252
|
+
Pass `[]` for strict zero-store mode. Empty `bindingKey`, `slug`, and `name`
|
|
253
|
+
values are rejected.
|
|
254
|
+
- `legacyDataStoreUrl` — force the legacy single-store URL on or off. When
|
|
255
|
+
omitted, it is on only for zero-config tests and off after manifest adoption.
|
|
149
256
|
- `schema` — path to the DDL snapshot (default
|
|
150
257
|
`./src/generated/data-store-schema.sql`); `null` for apps without a store.
|
|
151
258
|
- `schemaSql` — inline DDL instead of a file.
|
|
@@ -169,7 +276,16 @@ externalId })` helper models a trusted platform/channel link, and
|
|
|
169
276
|
- `app.payments` — checkouts created through payments-sdk:
|
|
170
277
|
`.checkouts`, `.latest()`, `.setProducts()`, `.markPaid(id)`,
|
|
171
278
|
`.deliverStripeEvent(handler, event)`, `.deliverBeamEvent(handler, event)`,
|
|
172
|
-
`.
|
|
279
|
+
Bolt+ controls: `.approveBoltIntent(id)`, `.declineBoltIntent(id)`,
|
|
280
|
+
`.expireBoltIntent(id)`, `.listBoltIntents()`, `.getBoltIntent(id)`,
|
|
281
|
+
`.count`, `.clear()`. Approve marks PAID and records a charge; deliver the
|
|
282
|
+
`charge.succeeded` webhook yourself via `.deliverBeamEvent` (same signing
|
|
283
|
+
path as payment links). Decline and expire deliver no webhook. `EXPIRED` is
|
|
284
|
+
derived from `expiresAt` whenever an intent is read — it is never stored.
|
|
285
|
+
Simulator-created intents use real Beam id shapes (`bolti_<n>`) with
|
|
286
|
+
`isVirtual: false`; create/list validation matches the control plane
|
|
287
|
+
(expiry 90–600s, amount positive int, payment-method enum, deploymentId UUID,
|
|
288
|
+
list `limit` 1–100, status filter values).
|
|
173
289
|
- `app.storage` — uploads through storage-sdk: `.uploads`, `.latest()`, `.count`, `.clear()`.
|
|
174
290
|
- `app.messages` — outbound Slack/LINE/Facebook sends:
|
|
175
291
|
`.all()`, `.latest()`, `.to(recipient)`, `.channel("line")`, `.count`, `.clear()`.
|
|
@@ -235,9 +351,11 @@ true })` remains source-compatible but the simulator (like the control plane)
|
|
|
235
351
|
- For merge-correct reads, request aliases in batches and include every id in
|
|
236
352
|
the returned canonical set. Page directory UIs through `search({ query,
|
|
237
353
|
cursor, limit })`; `list()` intentionally keeps its previous array shape.
|
|
238
|
-
- `PaymentsServerClient` (Stripe checkout + Beam payment links
|
|
239
|
-
captured in `app.payments`;
|
|
240
|
-
`
|
|
354
|
+
- `PaymentsServerClient` (Stripe checkout + Beam payment links + Bolt+
|
|
355
|
+
connections/intents/charges, product list) — captured in `app.payments`;
|
|
356
|
+
fulfill checkout via `markPaid` (poll) or `deliverStripeEvent` /
|
|
357
|
+
`deliverBeamEvent` (webhook push). Drive a terminal without hardware via
|
|
358
|
+
`approveBoltIntent` / `declineBoltIntent` / `expireBoltIntent`.
|
|
241
359
|
- `StorageClient` (upload/list/get/patch/delete, presigned upload) — captured
|
|
242
360
|
in `app.storage` against the simulated `STORAGE_URL` host.
|
|
243
361
|
- `client.slack` / `client.line` / `client.facebook` send endpoints — captured
|
package/dist/config.d.mts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ViteUserConfig } from 'vitest/config';
|
|
2
|
+
import { T as TestDataStoreInput } from './data-store-manifest-Zwgcv0DJ.mjs';
|
|
2
3
|
|
|
4
|
+
interface StardeckTestConfigOptions {
|
|
5
|
+
dataStores?: TestDataStoreInput[];
|
|
6
|
+
}
|
|
3
7
|
/**
|
|
4
8
|
* Vitest config for Stardeck customer apps. Aliases `next/headers` (and
|
|
5
9
|
* `server-only`) to harness shims so server code runs outside a Next request
|
|
@@ -11,6 +15,6 @@ import { ViteUserConfig } from 'vitest/config';
|
|
|
11
15
|
* export default defineStardeckTestConfig();
|
|
12
16
|
* ```
|
|
13
17
|
*/
|
|
14
|
-
declare function defineStardeckTestConfig(overrides?: ViteUserConfig): ViteUserConfig;
|
|
18
|
+
declare function defineStardeckTestConfig(overrides?: ViteUserConfig, stardeck?: StardeckTestConfigOptions): ViteUserConfig;
|
|
15
19
|
|
|
16
|
-
export { defineStardeckTestConfig };
|
|
20
|
+
export { type StardeckTestConfigOptions, defineStardeckTestConfig };
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ViteUserConfig } from 'vitest/config';
|
|
2
|
+
import { T as TestDataStoreInput } from './data-store-manifest-Zwgcv0DJ.js';
|
|
2
3
|
|
|
4
|
+
interface StardeckTestConfigOptions {
|
|
5
|
+
dataStores?: TestDataStoreInput[];
|
|
6
|
+
}
|
|
3
7
|
/**
|
|
4
8
|
* Vitest config for Stardeck customer apps. Aliases `next/headers` (and
|
|
5
9
|
* `server-only`) to harness shims so server code runs outside a Next request
|
|
@@ -11,6 +15,6 @@ import { ViteUserConfig } from 'vitest/config';
|
|
|
11
15
|
* export default defineStardeckTestConfig();
|
|
12
16
|
* ```
|
|
13
17
|
*/
|
|
14
|
-
declare function defineStardeckTestConfig(overrides?: ViteUserConfig): ViteUserConfig;
|
|
18
|
+
declare function defineStardeckTestConfig(overrides?: ViteUserConfig, stardeck?: StardeckTestConfigOptions): ViteUserConfig;
|
|
15
19
|
|
|
16
|
-
export { defineStardeckTestConfig };
|
|
20
|
+
export { type StardeckTestConfigOptions, defineStardeckTestConfig };
|
package/dist/config.js
CHANGED
|
@@ -23,7 +23,77 @@ __export(config_exports, {
|
|
|
23
23
|
defineStardeckTestConfig: () => defineStardeckTestConfig
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(config_exports);
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
// src/constants.ts
|
|
28
|
+
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
29
|
+
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
30
|
+
|
|
31
|
+
// src/data-store-manifest.ts
|
|
32
|
+
var STARDECK_DATA_STORES_ENV = "STARDECK_DATA_STORES";
|
|
33
|
+
function dataStoreSlug(name) {
|
|
34
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
35
|
+
return slug || "store";
|
|
36
|
+
}
|
|
37
|
+
function buildTestDataStoreManifest(inputs = [{}]) {
|
|
38
|
+
const takenSlugs = /* @__PURE__ */ new Set();
|
|
39
|
+
const takenBindingKeys = /* @__PURE__ */ new Set();
|
|
40
|
+
const takenIds = /* @__PURE__ */ new Set();
|
|
41
|
+
return inputs.map((input, index) => {
|
|
42
|
+
for (const field of ["bindingKey", "slug", "name"]) {
|
|
43
|
+
if (input[field] === "") {
|
|
44
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].${field} cannot be empty.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
let slug;
|
|
48
|
+
if (input.slug !== void 0) {
|
|
49
|
+
slug = input.slug;
|
|
50
|
+
if (takenSlugs.has(slug)) {
|
|
51
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].slug must be unique.`);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
const base = input.name !== void 0 ? dataStoreSlug(input.name) : index === 0 ? "main" : null;
|
|
55
|
+
if (base === null) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`[stardeck-testing] dataStores[${index}] requires a slug or name. Only the first entry defaults to the "main" slug.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
slug = base;
|
|
61
|
+
let suffix = 2;
|
|
62
|
+
while (takenSlugs.has(slug)) {
|
|
63
|
+
slug = `${base}_${suffix}`;
|
|
64
|
+
suffix++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const id = input.id ?? `00000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}`;
|
|
68
|
+
if (takenIds.has(id)) {
|
|
69
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].id must be unique.`);
|
|
70
|
+
}
|
|
71
|
+
if (input.bindingKey !== void 0 && takenBindingKeys.has(input.bindingKey)) {
|
|
72
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].bindingKey must be unique.`);
|
|
73
|
+
}
|
|
74
|
+
takenSlugs.add(slug);
|
|
75
|
+
takenIds.add(id);
|
|
76
|
+
if (input.bindingKey !== void 0) takenBindingKeys.add(input.bindingKey);
|
|
77
|
+
const storeType = input.storeType ?? "database";
|
|
78
|
+
return {
|
|
79
|
+
id,
|
|
80
|
+
name: input.name ?? slug,
|
|
81
|
+
slug,
|
|
82
|
+
storeType,
|
|
83
|
+
accessLevel: input.accessLevel ?? "admin",
|
|
84
|
+
...storeType === "database" ? { url: input.url ?? DATA_STORE_TEST_URL } : {},
|
|
85
|
+
...input.bindingKey ? { bindingKey: input.bindingKey } : {}
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/config.ts
|
|
91
|
+
function defineStardeckTestConfig(overrides = {}, stardeck = {}) {
|
|
92
|
+
const dataStoreEnv = stardeck.dataStores !== void 0 ? {
|
|
93
|
+
[STARDECK_DATA_STORES_ENV]: JSON.stringify(
|
|
94
|
+
buildTestDataStoreManifest(stardeck.dataStores)
|
|
95
|
+
)
|
|
96
|
+
} : {};
|
|
27
97
|
return {
|
|
28
98
|
...overrides,
|
|
29
99
|
resolve: {
|
|
@@ -44,6 +114,10 @@ function defineStardeckTestConfig(overrides = {}) {
|
|
|
44
114
|
pool: "forks",
|
|
45
115
|
isolate: true,
|
|
46
116
|
...overrides.test,
|
|
117
|
+
env: {
|
|
118
|
+
...dataStoreEnv,
|
|
119
|
+
...overrides.test?.env
|
|
120
|
+
},
|
|
47
121
|
setupFiles: [
|
|
48
122
|
"@stardeck-customer-apps/testing/setup",
|
|
49
123
|
...overrides.test?.setupFiles ? Array.isArray(overrides.test.setupFiles) ? overrides.test.setupFiles : [overrides.test.setupFiles] : []
|
package/dist/config.mjs
CHANGED
|
@@ -1,5 +1,73 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
3
|
+
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
4
|
+
|
|
5
|
+
// src/data-store-manifest.ts
|
|
6
|
+
var STARDECK_DATA_STORES_ENV = "STARDECK_DATA_STORES";
|
|
7
|
+
function dataStoreSlug(name) {
|
|
8
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
9
|
+
return slug || "store";
|
|
10
|
+
}
|
|
11
|
+
function buildTestDataStoreManifest(inputs = [{}]) {
|
|
12
|
+
const takenSlugs = /* @__PURE__ */ new Set();
|
|
13
|
+
const takenBindingKeys = /* @__PURE__ */ new Set();
|
|
14
|
+
const takenIds = /* @__PURE__ */ new Set();
|
|
15
|
+
return inputs.map((input, index) => {
|
|
16
|
+
for (const field of ["bindingKey", "slug", "name"]) {
|
|
17
|
+
if (input[field] === "") {
|
|
18
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].${field} cannot be empty.`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
let slug;
|
|
22
|
+
if (input.slug !== void 0) {
|
|
23
|
+
slug = input.slug;
|
|
24
|
+
if (takenSlugs.has(slug)) {
|
|
25
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].slug must be unique.`);
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
const base = input.name !== void 0 ? dataStoreSlug(input.name) : index === 0 ? "main" : null;
|
|
29
|
+
if (base === null) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`[stardeck-testing] dataStores[${index}] requires a slug or name. Only the first entry defaults to the "main" slug.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
slug = base;
|
|
35
|
+
let suffix = 2;
|
|
36
|
+
while (takenSlugs.has(slug)) {
|
|
37
|
+
slug = `${base}_${suffix}`;
|
|
38
|
+
suffix++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const id = input.id ?? `00000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}`;
|
|
42
|
+
if (takenIds.has(id)) {
|
|
43
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].id must be unique.`);
|
|
44
|
+
}
|
|
45
|
+
if (input.bindingKey !== void 0 && takenBindingKeys.has(input.bindingKey)) {
|
|
46
|
+
throw new Error(`[stardeck-testing] dataStores[${index}].bindingKey must be unique.`);
|
|
47
|
+
}
|
|
48
|
+
takenSlugs.add(slug);
|
|
49
|
+
takenIds.add(id);
|
|
50
|
+
if (input.bindingKey !== void 0) takenBindingKeys.add(input.bindingKey);
|
|
51
|
+
const storeType = input.storeType ?? "database";
|
|
52
|
+
return {
|
|
53
|
+
id,
|
|
54
|
+
name: input.name ?? slug,
|
|
55
|
+
slug,
|
|
56
|
+
storeType,
|
|
57
|
+
accessLevel: input.accessLevel ?? "admin",
|
|
58
|
+
...storeType === "database" ? { url: input.url ?? DATA_STORE_TEST_URL } : {},
|
|
59
|
+
...input.bindingKey ? { bindingKey: input.bindingKey } : {}
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
1
64
|
// src/config.ts
|
|
2
|
-
function defineStardeckTestConfig(overrides = {}) {
|
|
65
|
+
function defineStardeckTestConfig(overrides = {}, stardeck = {}) {
|
|
66
|
+
const dataStoreEnv = stardeck.dataStores !== void 0 ? {
|
|
67
|
+
[STARDECK_DATA_STORES_ENV]: JSON.stringify(
|
|
68
|
+
buildTestDataStoreManifest(stardeck.dataStores)
|
|
69
|
+
)
|
|
70
|
+
} : {};
|
|
3
71
|
return {
|
|
4
72
|
...overrides,
|
|
5
73
|
resolve: {
|
|
@@ -20,6 +88,10 @@ function defineStardeckTestConfig(overrides = {}) {
|
|
|
20
88
|
pool: "forks",
|
|
21
89
|
isolate: true,
|
|
22
90
|
...overrides.test,
|
|
91
|
+
env: {
|
|
92
|
+
...dataStoreEnv,
|
|
93
|
+
...overrides.test?.env
|
|
94
|
+
},
|
|
23
95
|
setupFiles: [
|
|
24
96
|
"@stardeck-customer-apps/testing/setup",
|
|
25
97
|
...overrides.test?.setupFiles ? Array.isArray(overrides.test.setupFiles) ? overrides.test.setupFiles : [overrides.test.setupFiles] : []
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
declare const STARDECK_DATA_STORES_ENV = "STARDECK_DATA_STORES";
|
|
2
|
+
interface TestDataStoreInput {
|
|
3
|
+
bindingKey?: string;
|
|
4
|
+
/** Stable snake_case handle for the store. */
|
|
5
|
+
slug?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
id?: string;
|
|
8
|
+
accessLevel?: "read" | "write" | "admin";
|
|
9
|
+
storeType?: "database" | "storage";
|
|
10
|
+
/** Defaults to the PGlite simulator URL for database stores. */
|
|
11
|
+
url?: string;
|
|
12
|
+
}
|
|
13
|
+
interface TestDataStoreManifestEntry {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
storeType: "database" | "storage";
|
|
18
|
+
accessLevel: "read" | "write" | "admin";
|
|
19
|
+
url?: string;
|
|
20
|
+
bindingKey?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Builds the production-shaped manifest injected into a test app. */
|
|
23
|
+
declare function buildTestDataStoreManifest(inputs?: TestDataStoreInput[]): TestDataStoreManifestEntry[];
|
|
24
|
+
|
|
25
|
+
export { STARDECK_DATA_STORES_ENV as S, type TestDataStoreInput as T, type TestDataStoreManifestEntry as a, buildTestDataStoreManifest as b };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
declare const STARDECK_DATA_STORES_ENV = "STARDECK_DATA_STORES";
|
|
2
|
+
interface TestDataStoreInput {
|
|
3
|
+
bindingKey?: string;
|
|
4
|
+
/** Stable snake_case handle for the store. */
|
|
5
|
+
slug?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
id?: string;
|
|
8
|
+
accessLevel?: "read" | "write" | "admin";
|
|
9
|
+
storeType?: "database" | "storage";
|
|
10
|
+
/** Defaults to the PGlite simulator URL for database stores. */
|
|
11
|
+
url?: string;
|
|
12
|
+
}
|
|
13
|
+
interface TestDataStoreManifestEntry {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
storeType: "database" | "storage";
|
|
18
|
+
accessLevel: "read" | "write" | "admin";
|
|
19
|
+
url?: string;
|
|
20
|
+
bindingKey?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Builds the production-shaped manifest injected into a test app. */
|
|
23
|
+
declare function buildTestDataStoreManifest(inputs?: TestDataStoreInput[]): TestDataStoreManifestEntry[];
|
|
24
|
+
|
|
25
|
+
export { STARDECK_DATA_STORES_ENV as S, type TestDataStoreInput as T, type TestDataStoreManifestEntry as a, buildTestDataStoreManifest as b };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as _stardeck_customer_apps_payments_sdk from '@stardeck-customer-apps/payments-sdk';
|
|
2
2
|
import * as next_server from 'next/server';
|
|
3
3
|
import { PGlite } from '@electric-sql/pglite';
|
|
4
|
+
import { T as TestDataStoreInput } from './data-store-manifest-Zwgcv0DJ.mjs';
|
|
5
|
+
export { S as STARDECK_DATA_STORES_ENV, a as TestDataStoreManifestEntry, b as buildTestDataStoreManifest } from './data-store-manifest-Zwgcv0DJ.mjs';
|
|
4
6
|
|
|
5
7
|
type RouteHandler<Req extends Request = Request, P = Record<string, string | string[]>> = (request: Req, context: {
|
|
6
8
|
params: Promise<P>;
|
|
@@ -131,6 +133,53 @@ interface CapturedCheckout {
|
|
|
131
133
|
metadata?: Record<string, string>;
|
|
132
134
|
createdAt: Date;
|
|
133
135
|
}
|
|
136
|
+
/** Simulated Bolt+ connection as returned by the control-plane store API. */
|
|
137
|
+
interface SimBoltConnection {
|
|
138
|
+
id: string;
|
|
139
|
+
projectId: string;
|
|
140
|
+
beamConnectionId: string;
|
|
141
|
+
displayName: string | null;
|
|
142
|
+
pairingCode: string;
|
|
143
|
+
status: string;
|
|
144
|
+
isSandbox: boolean;
|
|
145
|
+
environments: Array<"sandbox" | "preview" | "production">;
|
|
146
|
+
createdAt: string;
|
|
147
|
+
updatedAt: string;
|
|
148
|
+
}
|
|
149
|
+
type SimBoltIntentStoredStatus = "PENDING" | "PAID" | "FAILED" | "CANCELED";
|
|
150
|
+
type SimBoltIntentStatus = SimBoltIntentStoredStatus | "EXPIRED";
|
|
151
|
+
/**
|
|
152
|
+
* Stored Bolt+ intent. `EXPIRED` is never written — derive it from
|
|
153
|
+
* `expiresAt` on read, matching the real control plane.
|
|
154
|
+
*/
|
|
155
|
+
type SimBoltPaymentMethod = _stardeck_customer_apps_payments_sdk.BeamBoltPaymentMethod;
|
|
156
|
+
/** Bolt intent as returned by list/get helpers (timestamps ISO; nullables are null). */
|
|
157
|
+
interface SimBoltIntentRecord {
|
|
158
|
+
id: string;
|
|
159
|
+
beamIntentId: string;
|
|
160
|
+
boltConnectionId: string;
|
|
161
|
+
amount: number;
|
|
162
|
+
currency: string;
|
|
163
|
+
paymentMethodType: SimBoltPaymentMethod;
|
|
164
|
+
referenceId: string | null;
|
|
165
|
+
internalNote: string | null;
|
|
166
|
+
status: SimBoltIntentStatus;
|
|
167
|
+
isVirtual: boolean;
|
|
168
|
+
environment: "sandbox" | "preview" | "production";
|
|
169
|
+
expiresAt: string;
|
|
170
|
+
settledAt: string | null;
|
|
171
|
+
chargeId: string | null;
|
|
172
|
+
failureReason: string | null;
|
|
173
|
+
createdAt: string;
|
|
174
|
+
}
|
|
175
|
+
interface SimBoltCharge {
|
|
176
|
+
id: string;
|
|
177
|
+
status: "SUCCEEDED" | "FAILED" | "PENDING";
|
|
178
|
+
amount?: number;
|
|
179
|
+
currency?: string;
|
|
180
|
+
sourceId?: string;
|
|
181
|
+
createdAt?: string;
|
|
182
|
+
}
|
|
134
183
|
interface TestPayments {
|
|
135
184
|
get checkouts(): CapturedCheckout[];
|
|
136
185
|
latest(): CapturedCheckout | undefined;
|
|
@@ -153,6 +202,30 @@ interface TestPayments {
|
|
|
153
202
|
path?: string;
|
|
154
203
|
deploymentSecret?: string;
|
|
155
204
|
}): Promise<Response>;
|
|
205
|
+
/**
|
|
206
|
+
* Mark a PENDING bolt intent PAID and record a SUCCEEDED charge. Does not
|
|
207
|
+
* deliver a webhook — call `deliverBeamEvent` with `charge.succeeded` (reuse
|
|
208
|
+
* the existing signing path) after approving, the same way `markPaid` pairs
|
|
209
|
+
* with `deliverStripeEvent` / `deliverBeamEvent`.
|
|
210
|
+
*/
|
|
211
|
+
approveBoltIntent(intentId: string, options?: {
|
|
212
|
+
chargeId?: string;
|
|
213
|
+
}): SimBoltCharge;
|
|
214
|
+
/**
|
|
215
|
+
* Mark a PENDING bolt intent FAILED and record a FAILED charge.
|
|
216
|
+
* Delivers no webhook: Beam's catalog has no charge-failure event for bolt
|
|
217
|
+
* payments, and the real product notifies the app of nothing on decline.
|
|
218
|
+
*/
|
|
219
|
+
declineBoltIntent(intentId: string, options?: {
|
|
220
|
+
failureReason?: string;
|
|
221
|
+
}): SimBoltCharge;
|
|
222
|
+
/**
|
|
223
|
+
* Backdate `expiresAt` so the intent derives as EXPIRED on the next read.
|
|
224
|
+
* Delivers no webhook: Beam does not notify apps when a bolt intent expires.
|
|
225
|
+
*/
|
|
226
|
+
expireBoltIntent(intentId: string): void;
|
|
227
|
+
listBoltIntents(): SimBoltIntentRecord[];
|
|
228
|
+
getBoltIntent(intentId: string): SimBoltIntentRecord | undefined;
|
|
156
229
|
clear(): void;
|
|
157
230
|
get count(): number;
|
|
158
231
|
}
|
|
@@ -366,6 +439,13 @@ interface TestEdge {
|
|
|
366
439
|
get count(): number;
|
|
367
440
|
}
|
|
368
441
|
interface TestAppOptions {
|
|
442
|
+
/** Data stores exposed through the production-shaped STARDECK_DATA_STORES manifest. */
|
|
443
|
+
dataStores?: TestDataStoreInput[];
|
|
444
|
+
/**
|
|
445
|
+
* Force the legacy single-store DATA_STORE_URL on or off. By default it is
|
|
446
|
+
* present only for the zero-config manifest, and omitted once a manifest is configured.
|
|
447
|
+
*/
|
|
448
|
+
legacyDataStoreUrl?: boolean;
|
|
369
449
|
/**
|
|
370
450
|
* Path to the schema.sql snapshot generated by
|
|
371
451
|
* `npx stardeck-data-store generate-types` (relative to the project root).
|
|
@@ -446,6 +526,7 @@ declare function parseWorkflowName(describeTitle: string): string | null;
|
|
|
446
526
|
|
|
447
527
|
declare const CONTROL_PLANE_TEST_URL = "https://control-plane.stardeck.test";
|
|
448
528
|
declare const DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
529
|
+
declare const DATA_STORE_TEST_URL = "postgresql://test:test@db.stardeck.test/main";
|
|
449
530
|
declare const STORAGE_TEST_URL = "https://storage.stardeck.test";
|
|
450
531
|
declare const STORAGE_TEST_HOST = "storage.stardeck.test";
|
|
451
532
|
declare const TEST_ENV_DEFAULTS: {
|
|
@@ -454,10 +535,9 @@ declare const TEST_ENV_DEFAULTS: {
|
|
|
454
535
|
readonly ORGANIZATION_ID: "00000000-0000-4000-8000-00000000000a";
|
|
455
536
|
readonly PROJECT_ID: "00000000-0000-4000-8000-00000000000b";
|
|
456
537
|
readonly DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c";
|
|
457
|
-
readonly DATA_STORE_URL: "postgresql://test:test@db.stardeck.test/main";
|
|
458
538
|
readonly STORAGE_URL: "https://storage.stardeck.test";
|
|
459
539
|
};
|
|
460
540
|
/** Default location of the DDL snapshot written by `generate-types`. */
|
|
461
541
|
declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
|
|
462
542
|
|
|
463
|
-
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
|
543
|
+
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|