@stardeck-customer-apps/testing 0.22.0 → 0.23.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/SKILL.md +33 -1
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +12 -0
- package/dist/index.mjs +12 -0
- package/dist/next/headers-shim.js +8 -5
- package/dist/next/headers-shim.mjs +8 -5
- package/dist/setup.js +1 -0
- package/dist/setup.mjs +1 -0
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -218,6 +218,13 @@ const integrations = createIntegrationsClient();
|
|
|
218
218
|
await integrations.line.push("U123", { type: "text", text: "Your order shipped" });
|
|
219
219
|
expect(app.messages.channel("line").to("U123")[0].body.text).toMatch(/shipped/i);
|
|
220
220
|
|
|
221
|
+
// Signed in through LINE (a shop's LINE MINI App): the session carries the
|
|
222
|
+
// person's shop-provider LINE user ID. Push to the ID on the SESSION.
|
|
223
|
+
app.asUser({ id: "buyer-1", lineUserId: "Ushop-provider-1" });
|
|
224
|
+
// route under test: const s = await getSession(); await integrations.line.push(s.user.lineUserId, ...)
|
|
225
|
+
await callRoute(POST);
|
|
226
|
+
expect(app.messages.channel("line").to("Ushop-provider-1")).toHaveLength(1);
|
|
227
|
+
|
|
221
228
|
// LINE Flex card — the simulator preserves the complete LINE-native container.
|
|
222
229
|
const card = {
|
|
223
230
|
type: "bubble" as const,
|
|
@@ -322,7 +329,18 @@ expect(app.edge.latestDisplay()?.action).toBe("show");
|
|
|
322
329
|
or the platform's test-run deadline.
|
|
323
330
|
|
|
324
331
|
- `app.asUser(partial?)` — sign in a test user; `getSession()` in app code
|
|
325
|
-
resolves to it. `app.signOut()` clears.
|
|
332
|
+
resolves to it. `app.signOut()` clears. Pass `lineUserId` to sign in as a
|
|
333
|
+
person who came through LINE sign-in inside the shop's LINE MINI App; it is
|
|
334
|
+
the same field production sets, so app code reads `session.user.lineUserId`.
|
|
335
|
+
`issueSession({ lineUserId })` carries it through cookie auth and the real
|
|
336
|
+
`/api/auth/me` route too.
|
|
337
|
+
- `app.setLiffId(id | null)` / `createTestApp({ liffId })` — the LIFF ID the
|
|
338
|
+
platform forwards to the app (`null` by default = no LINE MINI App). The real
|
|
339
|
+
project-auth `/api/auth/me` returns it as `liffId` on every response, signed
|
|
340
|
+
in or not, which is what makes `useLiff()` start LIFF. `reset()` restores the
|
|
341
|
+
`createTestApp` value. The simulator does not run LIFF sign-in itself
|
|
342
|
+
(`POST /api/auth/liff` needs a real LINE ID token), so seed the signed-in
|
|
343
|
+
state with `asUser`/`issueSession` instead.
|
|
326
344
|
- `app.issueSession(partial?)` — mint access/refresh tokens for cookie-based
|
|
327
345
|
auth flows (`__stardeck:sess`).
|
|
328
346
|
- `app.inbox` — emails sent through the email-sdk: `.latest(addr?)`,
|
|
@@ -619,6 +637,20 @@ per-field validation (unit-test the route), login/logout (platform-provided).
|
|
|
619
637
|
never `waitForTimeout` (web-first assertions auto-wait), every test
|
|
620
638
|
independent of other tests' data and ordering.
|
|
621
639
|
|
|
640
|
+
## Testing LINE flows
|
|
641
|
+
|
|
642
|
+
- Sign in as a LINE person with `app.asUser({ lineUserId: "U..." })` and call
|
|
643
|
+
the route with `callRoute`; assert the push with
|
|
644
|
+
`app.messages.channel("line").to("U...")`. Cover the person with no LINE
|
|
645
|
+
identity too (`asUser()` without `lineUserId`): the route must send nothing.
|
|
646
|
+
- The recipient of a LINE push comes from `session.user.lineUserId` on the
|
|
647
|
+
server, never from a request body, query string or client-side value. A
|
|
648
|
+
`lineUserId` a browser sends can be forged to message any friend of the shop's
|
|
649
|
+
LINE Official Account. Do not write a route that takes one, and do not write a
|
|
650
|
+
test that posts one to make a push happen; sign in with `lineUserId` instead.
|
|
651
|
+
- Whether the person is a friend of the shop's OA is a client concern
|
|
652
|
+
(`useLiff().isFriend`); a server route checks only that `lineUserId` exists.
|
|
653
|
+
|
|
622
654
|
## Rules
|
|
623
655
|
|
|
624
656
|
- Test **business logic and route handlers**, not framework plumbing. Focus on
|
package/dist/index.d.mts
CHANGED
|
@@ -38,6 +38,12 @@ interface TestUser {
|
|
|
38
38
|
projectId?: string | null;
|
|
39
39
|
/** Cross-app customer ownership key returned by the platform identity resolver. */
|
|
40
40
|
identityId?: string | null;
|
|
41
|
+
/**
|
|
42
|
+
* The person's LINE user ID under the shop's own LINE provider. Set it to sign in
|
|
43
|
+
* as someone who came through LINE sign-in inside the shop's LINE MINI App; app
|
|
44
|
+
* code reads it from `session.user.lineUserId` and pushes to it.
|
|
45
|
+
*/
|
|
46
|
+
lineUserId?: string | null;
|
|
41
47
|
}
|
|
42
48
|
interface CapturedEmail {
|
|
43
49
|
resendId: string;
|
|
@@ -615,6 +621,12 @@ interface TestStarlens {
|
|
|
615
621
|
get count(): number;
|
|
616
622
|
}
|
|
617
623
|
interface TestAppOptions {
|
|
624
|
+
/**
|
|
625
|
+
* The LIFF ID the platform forwards to the app for a LINE MINI App shop. It is
|
|
626
|
+
* what `GET /api/auth/me` returns as `liffId` and what makes `useLiff()` start
|
|
627
|
+
* LIFF. Defaults to `null` (the app has no LINE MINI App). Restored by `reset()`.
|
|
628
|
+
*/
|
|
629
|
+
liffId?: string | null;
|
|
618
630
|
/** Data stores exposed through the production-shaped STARDECK_DATA_STORES manifest. */
|
|
619
631
|
dataStores?: TestDataStoreInput[];
|
|
620
632
|
/**
|
|
@@ -669,6 +681,11 @@ interface TestApp {
|
|
|
669
681
|
asUser(user?: Partial<TestUser>): TestUser;
|
|
670
682
|
/** Clear the active test user. */
|
|
671
683
|
signOut(): void;
|
|
684
|
+
/**
|
|
685
|
+
* Set (or clear with `null`) the LIFF ID the platform forwards to the app, for
|
|
686
|
+
* the rest of the test. The real project-auth `me` route returns it as `liffId`.
|
|
687
|
+
*/
|
|
688
|
+
setLiffId(liffId: string | null): void;
|
|
672
689
|
/**
|
|
673
690
|
* Mint access/refresh tokens for cookie-based auth flows. The simulated
|
|
674
691
|
* control-plane verify endpoint resolves these tokens back to the user.
|
package/dist/index.d.ts
CHANGED
|
@@ -38,6 +38,12 @@ interface TestUser {
|
|
|
38
38
|
projectId?: string | null;
|
|
39
39
|
/** Cross-app customer ownership key returned by the platform identity resolver. */
|
|
40
40
|
identityId?: string | null;
|
|
41
|
+
/**
|
|
42
|
+
* The person's LINE user ID under the shop's own LINE provider. Set it to sign in
|
|
43
|
+
* as someone who came through LINE sign-in inside the shop's LINE MINI App; app
|
|
44
|
+
* code reads it from `session.user.lineUserId` and pushes to it.
|
|
45
|
+
*/
|
|
46
|
+
lineUserId?: string | null;
|
|
41
47
|
}
|
|
42
48
|
interface CapturedEmail {
|
|
43
49
|
resendId: string;
|
|
@@ -615,6 +621,12 @@ interface TestStarlens {
|
|
|
615
621
|
get count(): number;
|
|
616
622
|
}
|
|
617
623
|
interface TestAppOptions {
|
|
624
|
+
/**
|
|
625
|
+
* The LIFF ID the platform forwards to the app for a LINE MINI App shop. It is
|
|
626
|
+
* what `GET /api/auth/me` returns as `liffId` and what makes `useLiff()` start
|
|
627
|
+
* LIFF. Defaults to `null` (the app has no LINE MINI App). Restored by `reset()`.
|
|
628
|
+
*/
|
|
629
|
+
liffId?: string | null;
|
|
618
630
|
/** Data stores exposed through the production-shaped STARDECK_DATA_STORES manifest. */
|
|
619
631
|
dataStores?: TestDataStoreInput[];
|
|
620
632
|
/**
|
|
@@ -669,6 +681,11 @@ interface TestApp {
|
|
|
669
681
|
asUser(user?: Partial<TestUser>): TestUser;
|
|
670
682
|
/** Clear the active test user. */
|
|
671
683
|
signOut(): void;
|
|
684
|
+
/**
|
|
685
|
+
* Set (or clear with `null`) the LIFF ID the platform forwards to the app, for
|
|
686
|
+
* the rest of the test. The real project-auth `me` route returns it as `liffId`.
|
|
687
|
+
*/
|
|
688
|
+
setLiffId(liffId: string | null): void;
|
|
672
689
|
/**
|
|
673
690
|
* Mint access/refresh tokens for cookie-based auth flows. The simulated
|
|
674
691
|
* control-plane verify endpoint resolves these tokens back to the user.
|
package/dist/index.js
CHANGED
|
@@ -310,6 +310,7 @@ var TEST_ENV_DEFAULTS = {
|
|
|
310
310
|
DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c",
|
|
311
311
|
STORAGE_URL: STORAGE_TEST_URL
|
|
312
312
|
};
|
|
313
|
+
var LIFF_ID_HEADER = "x-stardeck-liff-id";
|
|
313
314
|
var DEFAULT_TEST_USER = {
|
|
314
315
|
id: "test-user-1",
|
|
315
316
|
email: "test-user@example.com",
|
|
@@ -324,6 +325,7 @@ var state = globalSingleton("state", () => ({
|
|
|
324
325
|
db: null,
|
|
325
326
|
lastHarnessDataStoreManifest: null,
|
|
326
327
|
currentUser: null,
|
|
328
|
+
liffId: null,
|
|
327
329
|
sessions: /* @__PURE__ */ new Map(),
|
|
328
330
|
refreshSessions: /* @__PURE__ */ new Map(),
|
|
329
331
|
emails: [],
|
|
@@ -2066,6 +2068,9 @@ async function callRoute(handler, options = {}) {
|
|
|
2066
2068
|
if (user && !headers.has("x-stardeck-user")) {
|
|
2067
2069
|
headers.set("x-stardeck-user", JSON.stringify(user));
|
|
2068
2070
|
}
|
|
2071
|
+
if (state.liffId && !headers.has(LIFF_ID_HEADER)) {
|
|
2072
|
+
headers.set(LIFF_ID_HEADER, state.liffId);
|
|
2073
|
+
}
|
|
2069
2074
|
if (options.body !== void 0 && !headers.has("Content-Type")) {
|
|
2070
2075
|
headers.set("Content-Type", "application/json");
|
|
2071
2076
|
}
|
|
@@ -3856,6 +3861,8 @@ async function createTestApp(options = {}) {
|
|
|
3856
3861
|
}
|
|
3857
3862
|
applyTestEnv(options);
|
|
3858
3863
|
state.allowNetwork = options.allowNetwork ?? false;
|
|
3864
|
+
const initialLiffId = options.liffId ?? null;
|
|
3865
|
+
state.liffId = initialLiffId;
|
|
3859
3866
|
installFetchRouter();
|
|
3860
3867
|
const schemaSql = loadSchemaSql(options) ?? "";
|
|
3861
3868
|
const db = await bootWithSchema(schemaSql);
|
|
@@ -3908,6 +3915,9 @@ async function createTestApp(options = {}) {
|
|
|
3908
3915
|
signOut() {
|
|
3909
3916
|
state.currentUser = null;
|
|
3910
3917
|
},
|
|
3918
|
+
setLiffId(liffId) {
|
|
3919
|
+
state.liffId = liffId;
|
|
3920
|
+
},
|
|
3911
3921
|
issueSession(user) {
|
|
3912
3922
|
const fullUser = buildUser(user);
|
|
3913
3923
|
const tokens = {
|
|
@@ -3930,6 +3940,7 @@ async function createTestApp(options = {}) {
|
|
|
3930
3940
|
}
|
|
3931
3941
|
}
|
|
3932
3942
|
state.currentUser = null;
|
|
3943
|
+
state.liffId = initialLiffId;
|
|
3933
3944
|
state.sessions.clear();
|
|
3934
3945
|
state.refreshSessions.clear();
|
|
3935
3946
|
state.emails = [];
|
|
@@ -3944,6 +3955,7 @@ async function createTestApp(options = {}) {
|
|
|
3944
3955
|
async close() {
|
|
3945
3956
|
state.db = null;
|
|
3946
3957
|
state.currentUser = null;
|
|
3958
|
+
state.liffId = null;
|
|
3947
3959
|
state.sessions.clear();
|
|
3948
3960
|
state.refreshSessions.clear();
|
|
3949
3961
|
state.emails = [];
|
package/dist/index.mjs
CHANGED
|
@@ -271,6 +271,7 @@ var TEST_ENV_DEFAULTS = {
|
|
|
271
271
|
DEPLOYMENT_ID: "00000000-0000-4000-8000-00000000000c",
|
|
272
272
|
STORAGE_URL: STORAGE_TEST_URL
|
|
273
273
|
};
|
|
274
|
+
var LIFF_ID_HEADER = "x-stardeck-liff-id";
|
|
274
275
|
var DEFAULT_TEST_USER = {
|
|
275
276
|
id: "test-user-1",
|
|
276
277
|
email: "test-user@example.com",
|
|
@@ -285,6 +286,7 @@ var state = globalSingleton("state", () => ({
|
|
|
285
286
|
db: null,
|
|
286
287
|
lastHarnessDataStoreManifest: null,
|
|
287
288
|
currentUser: null,
|
|
289
|
+
liffId: null,
|
|
288
290
|
sessions: /* @__PURE__ */ new Map(),
|
|
289
291
|
refreshSessions: /* @__PURE__ */ new Map(),
|
|
290
292
|
emails: [],
|
|
@@ -2027,6 +2029,9 @@ async function callRoute(handler, options = {}) {
|
|
|
2027
2029
|
if (user && !headers.has("x-stardeck-user")) {
|
|
2028
2030
|
headers.set("x-stardeck-user", JSON.stringify(user));
|
|
2029
2031
|
}
|
|
2032
|
+
if (state.liffId && !headers.has(LIFF_ID_HEADER)) {
|
|
2033
|
+
headers.set(LIFF_ID_HEADER, state.liffId);
|
|
2034
|
+
}
|
|
2030
2035
|
if (options.body !== void 0 && !headers.has("Content-Type")) {
|
|
2031
2036
|
headers.set("Content-Type", "application/json");
|
|
2032
2037
|
}
|
|
@@ -3817,6 +3822,8 @@ async function createTestApp(options = {}) {
|
|
|
3817
3822
|
}
|
|
3818
3823
|
applyTestEnv(options);
|
|
3819
3824
|
state.allowNetwork = options.allowNetwork ?? false;
|
|
3825
|
+
const initialLiffId = options.liffId ?? null;
|
|
3826
|
+
state.liffId = initialLiffId;
|
|
3820
3827
|
installFetchRouter();
|
|
3821
3828
|
const schemaSql = loadSchemaSql(options) ?? "";
|
|
3822
3829
|
const db = await bootWithSchema(schemaSql);
|
|
@@ -3869,6 +3876,9 @@ async function createTestApp(options = {}) {
|
|
|
3869
3876
|
signOut() {
|
|
3870
3877
|
state.currentUser = null;
|
|
3871
3878
|
},
|
|
3879
|
+
setLiffId(liffId) {
|
|
3880
|
+
state.liffId = liffId;
|
|
3881
|
+
},
|
|
3872
3882
|
issueSession(user) {
|
|
3873
3883
|
const fullUser = buildUser(user);
|
|
3874
3884
|
const tokens = {
|
|
@@ -3891,6 +3901,7 @@ async function createTestApp(options = {}) {
|
|
|
3891
3901
|
}
|
|
3892
3902
|
}
|
|
3893
3903
|
state.currentUser = null;
|
|
3904
|
+
state.liffId = initialLiffId;
|
|
3894
3905
|
state.sessions.clear();
|
|
3895
3906
|
state.refreshSessions.clear();
|
|
3896
3907
|
state.emails = [];
|
|
@@ -3905,6 +3916,7 @@ async function createTestApp(options = {}) {
|
|
|
3905
3916
|
async close() {
|
|
3906
3917
|
state.db = null;
|
|
3907
3918
|
state.currentUser = null;
|
|
3919
|
+
state.liffId = null;
|
|
3908
3920
|
state.sessions.clear();
|
|
3909
3921
|
state.refreshSessions.clear();
|
|
3910
3922
|
state.emails = [];
|
|
@@ -28,6 +28,12 @@ __export(headers_shim_exports, {
|
|
|
28
28
|
module.exports = __toCommonJS(headers_shim_exports);
|
|
29
29
|
var import_node_async_hooks = require("async_hooks");
|
|
30
30
|
|
|
31
|
+
// src/constants.ts
|
|
32
|
+
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
33
|
+
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
34
|
+
var APP_TEST_ORIGIN = "http://localhost:3333";
|
|
35
|
+
var LIFF_ID_HEADER = "x-stardeck-liff-id";
|
|
36
|
+
|
|
31
37
|
// src/global-singleton.ts
|
|
32
38
|
function globalSingleton(key, create) {
|
|
33
39
|
const symbol = /* @__PURE__ */ Symbol.for(`stardeck-testing.${key}`);
|
|
@@ -36,16 +42,12 @@ function globalSingleton(key, create) {
|
|
|
36
42
|
return holder[symbol];
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
// src/constants.ts
|
|
40
|
-
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
41
|
-
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
42
|
-
var APP_TEST_ORIGIN = "http://localhost:3333";
|
|
43
|
-
|
|
44
45
|
// src/state.ts
|
|
45
46
|
var state = globalSingleton("state", () => ({
|
|
46
47
|
db: null,
|
|
47
48
|
lastHarnessDataStoreManifest: null,
|
|
48
49
|
currentUser: null,
|
|
50
|
+
liffId: null,
|
|
49
51
|
sessions: /* @__PURE__ */ new Map(),
|
|
50
52
|
refreshSessions: /* @__PURE__ */ new Map(),
|
|
51
53
|
emails: [],
|
|
@@ -105,6 +107,7 @@ function ambientScope() {
|
|
|
105
107
|
if (state.currentUser) {
|
|
106
108
|
headers2.set("x-stardeck-user", JSON.stringify(state.currentUser));
|
|
107
109
|
}
|
|
110
|
+
if (state.liffId) headers2.set(LIFF_ID_HEADER, state.liffId);
|
|
108
111
|
return { headers: headers2, cookies: /* @__PURE__ */ new Map() };
|
|
109
112
|
}
|
|
110
113
|
function currentScope() {
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// src/next/headers-shim.ts
|
|
2
2
|
import { AsyncLocalStorage } from "async_hooks";
|
|
3
3
|
|
|
4
|
+
// src/constants.ts
|
|
5
|
+
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
6
|
+
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
7
|
+
var APP_TEST_ORIGIN = "http://localhost:3333";
|
|
8
|
+
var LIFF_ID_HEADER = "x-stardeck-liff-id";
|
|
9
|
+
|
|
4
10
|
// src/global-singleton.ts
|
|
5
11
|
function globalSingleton(key, create) {
|
|
6
12
|
const symbol = /* @__PURE__ */ Symbol.for(`stardeck-testing.${key}`);
|
|
@@ -9,16 +15,12 @@ function globalSingleton(key, create) {
|
|
|
9
15
|
return holder[symbol];
|
|
10
16
|
}
|
|
11
17
|
|
|
12
|
-
// src/constants.ts
|
|
13
|
-
var DATA_STORE_TEST_HOST = "db.stardeck.test";
|
|
14
|
-
var DATA_STORE_TEST_URL = `postgresql://test:test@${DATA_STORE_TEST_HOST}/main`;
|
|
15
|
-
var APP_TEST_ORIGIN = "http://localhost:3333";
|
|
16
|
-
|
|
17
18
|
// src/state.ts
|
|
18
19
|
var state = globalSingleton("state", () => ({
|
|
19
20
|
db: null,
|
|
20
21
|
lastHarnessDataStoreManifest: null,
|
|
21
22
|
currentUser: null,
|
|
23
|
+
liffId: null,
|
|
22
24
|
sessions: /* @__PURE__ */ new Map(),
|
|
23
25
|
refreshSessions: /* @__PURE__ */ new Map(),
|
|
24
26
|
emails: [],
|
|
@@ -78,6 +80,7 @@ function ambientScope() {
|
|
|
78
80
|
if (state.currentUser) {
|
|
79
81
|
headers2.set("x-stardeck-user", JSON.stringify(state.currentUser));
|
|
80
82
|
}
|
|
83
|
+
if (state.liffId) headers2.set(LIFF_ID_HEADER, state.liffId);
|
|
81
84
|
return { headers: headers2, cookies: /* @__PURE__ */ new Map() };
|
|
82
85
|
}
|
|
83
86
|
function currentScope() {
|
package/dist/setup.js
CHANGED
package/dist/setup.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
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",
|