@cosmicdrift/kumiko-testing 0.369.0 → 0.371.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/package.json +5 -5
- package/src/bunfig.ts +4 -1
- package/src/changes.json +24 -0
- package/src/e2e/define-app-e2e-config.ts +1 -1
- package/src/e2e/index.ts +1 -1
- package/src/e2e/screenshots.ts +1 -1
- package/src/helpers/closed-connection-error.ts +1 -1
- package/src/helpers/index.ts +22 -21
- package/src/helpers/stored-event-fixture.ts +20 -0
- package/src/preload/real.ts +1 -1
- package/src/scaffold.ts +58 -22
- package/src/seed-tenant.ts +1 -1
- package/src/setup-app-test-stack.ts +1 -1
- package/src/stack/drain-event-consumers.ts +1 -1
- package/src/stack/index.ts +8 -8
- package/src/stack/redis.ts +1 -1
- package/src/stack/test-stack.ts +29 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.371.0",
|
|
4
4
|
"description": "Test template for Kumiko apps: per-flow seedTenant, app test stack, canonical test preloads, bunfig generator and integration runner.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -95,8 +95,8 @@
|
|
|
95
95
|
"kumiko-testing": "./bin/kumiko-testing.ts"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
99
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
98
|
+
"@cosmicdrift/kumiko-framework": "0.371.0",
|
|
99
|
+
"@cosmicdrift/kumiko-types": "0.371.0",
|
|
100
100
|
"zod": "^4.4.3"
|
|
101
101
|
},
|
|
102
102
|
"peerDependencies": {
|
|
@@ -128,8 +128,8 @@
|
|
|
128
128
|
}
|
|
129
129
|
},
|
|
130
130
|
"devDependencies": {
|
|
131
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
132
|
-
"@cosmicdrift/kumiko-dev-server": "0.
|
|
131
|
+
"@cosmicdrift/kumiko-bundled-features": "0.371.0",
|
|
132
|
+
"@cosmicdrift/kumiko-dev-server": "0.371.0",
|
|
133
133
|
"@happy-dom/global-registrator": "^20.11.2",
|
|
134
134
|
"@playwright/test": "^1.62.1",
|
|
135
135
|
"@testing-library/dom": "^10.4.1",
|
package/src/bunfig.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
// @runtime tooling
|
|
2
|
+
// Pure TOML rendering with no test-stack imports: dev-server's managed-files
|
|
3
|
+
// sync renders and merges app bunfigs with it outside of any test run.
|
|
1
4
|
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
2
|
-
import { E2E_TIMEOUT_MS } from "./e2e-timeouts";
|
|
5
|
+
import { E2E_TIMEOUT_MS } from "./e2e-timeouts.ts";
|
|
3
6
|
|
|
4
7
|
export const TEST_TIMEOUT_MS = {
|
|
5
8
|
unit: 5000,
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.371.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Scaffold test:dom passes without test files",
|
|
6
|
+
"detail": "The scaffold sets test:dom to pass without test files and adds the devDependencies @testing-library/react, @testing-library/dom and @happy-dom/global-registrator."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.370.0",
|
|
10
|
+
"type": "improvement",
|
|
11
|
+
"title": "setupTestStack binds the MFA token secrets",
|
|
12
|
+
"detail": "setupTestStack binds the auth-mfa token secrets from the stack's JWT secret, independent of the host env, so tests that mount auth-mfa without explicit secrets keep working."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.370.0",
|
|
16
|
+
"type": "improvement",
|
|
17
|
+
"title": "renderTestSetup writes the standard test scripts, bunfig.dom.toml and the helper homes",
|
|
18
|
+
"detail": "The scaffolded test setup now adds test:unit, test:integration:ci, test:dom and screenshots, writes bunfig.dom.toml (with @happy-dom/global-registrator as devDependency), e2e/_helpers/index.ts and src/__tests__/_setup.ts."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.370.0",
|
|
22
|
+
"type": "improvement",
|
|
23
|
+
"title": "createTestStoredEvent builds a StoredEvent fixture",
|
|
24
|
+
"detail": "createTestStoredEvent(overrides) from @cosmicdrift/kumiko-testing/helpers returns a complete StoredEvent with stable defaults, for hook tests that construct SaveContext or DeleteContext directly."
|
|
25
|
+
},
|
|
2
26
|
{
|
|
3
27
|
"version": "0.365.0",
|
|
4
28
|
"type": "fix",
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
type Project,
|
|
14
14
|
} from "@playwright/test";
|
|
15
15
|
import { E2E_TIMEOUT_MS } from "../e2e-timeouts";
|
|
16
|
-
import { isRealProviderRun } from "../helpers/real-providers.
|
|
16
|
+
import { isRealProviderRun } from "../helpers/real-providers.ts";
|
|
17
17
|
import { SERVICE_ENV_DEFAULTS } from "../preload/service-env-defaults-values";
|
|
18
18
|
import {
|
|
19
19
|
DESKTOP_VIEWPORT,
|
package/src/e2e/index.ts
CHANGED
package/src/e2e/screenshots.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdirSync, statSync } from "node:fs";
|
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { expect, type Page, errors as playwrightErrors, type Request } from "@playwright/test";
|
|
5
5
|
import { E2E_TIMEOUT_MS } from "../e2e-timeouts";
|
|
6
|
-
import { isRealProviderRun } from "../helpers/real-providers.
|
|
6
|
+
import { isRealProviderRun } from "../helpers/real-providers.ts";
|
|
7
7
|
import { DESKTOP_VIEWPORT } from "./constants";
|
|
8
8
|
import { pinEnglishLocale } from "./pin-english-locale";
|
|
9
9
|
import { requireScreenshotDir, SCREENSHOT_DIR_ENV } from "./screenshot-dir";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { isClosedConnectionError } from "@cosmicdrift/kumiko-framework/internal/stack-support";
|
|
5
5
|
import postgres from "postgres";
|
|
6
|
-
import { requireEnv } from "../stack/db.
|
|
6
|
+
import { requireEnv } from "../stack/db.ts";
|
|
7
7
|
|
|
8
8
|
export function testDatabaseUrl(): string {
|
|
9
9
|
return requireEnv("TEST_DATABASE_URL");
|
package/src/helpers/index.ts
CHANGED
|
@@ -14,45 +14,45 @@ export {
|
|
|
14
14
|
resetPiiSubjectKmsForTests,
|
|
15
15
|
} from "@cosmicdrift/kumiko-framework/internal/testing";
|
|
16
16
|
|
|
17
|
-
export { rolesOf } from "./access-assertions.
|
|
18
|
-
export { expectError, expectSuccess } from "./assertions.
|
|
19
|
-
export { withBootValidatorFixture } from "./boot-validator-fixture.
|
|
17
|
+
export { rolesOf } from "./access-assertions.ts";
|
|
18
|
+
export { expectError, expectSuccess } from "./assertions.ts";
|
|
19
|
+
export { withBootValidatorFixture } from "./boot-validator-fixture.ts";
|
|
20
20
|
export {
|
|
21
21
|
captureClosedConnectionError,
|
|
22
22
|
terminateBackendsByApplicationName,
|
|
23
23
|
testDatabaseUrl,
|
|
24
|
-
} from "./closed-connection-error.
|
|
25
|
-
export { type ClearableTable, clearTables, resetTestTables } from "./db-cleanup.
|
|
24
|
+
} from "./closed-connection-error.ts";
|
|
25
|
+
export { type ClearableTable, clearTables, resetTestTables } from "./db-cleanup.ts";
|
|
26
26
|
export {
|
|
27
27
|
type E2EGeneratorOptions,
|
|
28
28
|
type E2ETestSpec,
|
|
29
29
|
type EditFillOp,
|
|
30
30
|
generateE2ESpec,
|
|
31
31
|
generateZodFixture,
|
|
32
|
-
} from "./e2e-generator.
|
|
33
|
-
export { expectErrorIncludes } from "./expect-error.
|
|
34
|
-
export { describeFileProviderContract } from "./file-provider-contract.
|
|
32
|
+
} from "./e2e-generator.ts";
|
|
33
|
+
export { expectErrorIncludes } from "./expect-error.ts";
|
|
34
|
+
export { describeFileProviderContract } from "./file-provider-contract.ts";
|
|
35
35
|
export {
|
|
36
36
|
getSetCookieRaw,
|
|
37
37
|
getSetCookies,
|
|
38
38
|
getSetCookieValue,
|
|
39
39
|
type ParsedSetCookie,
|
|
40
|
-
} from "./http-cookies.
|
|
41
|
-
export { createLateBoundHolder, type LateBoundHolder } from "./late-bound.
|
|
42
|
-
export { createManualClock, type ManualClock } from "./manual-clock.
|
|
43
|
-
export { buildMultipartBody, patchFileInstanceofForBunTest } from "./multipart-helper.
|
|
40
|
+
} from "./http-cookies.ts";
|
|
41
|
+
export { createLateBoundHolder, type LateBoundHolder } from "./late-bound.ts";
|
|
42
|
+
export { createManualClock, type ManualClock } from "./manual-clock.ts";
|
|
43
|
+
export { buildMultipartBody, patchFileInstanceofForBunTest } from "./multipart-helper.ts";
|
|
44
44
|
export {
|
|
45
45
|
createMutableMasterKeyProvider,
|
|
46
46
|
createTestEnvelopeCipher,
|
|
47
47
|
createTestMasterKeyProvider,
|
|
48
48
|
type MutableMasterKeyProvider,
|
|
49
|
-
} from "./mutable-master-key-provider.
|
|
49
|
+
} from "./mutable-master-key-provider.ts";
|
|
50
50
|
export {
|
|
51
51
|
createRecordingProvider,
|
|
52
52
|
type RecordingProvider,
|
|
53
|
-
} from "./observability-recorder.
|
|
54
|
-
export { isRealProviderRun, REAL_PROVIDERS_ENV, requireRealProviders } from "./real-providers.
|
|
55
|
-
export { deleteRows, seedRow, seedRows, updateRows } from "./seed.
|
|
53
|
+
} from "./observability-recorder.ts";
|
|
54
|
+
export { isRealProviderRun, REAL_PROVIDERS_ENV, requireRealProviders } from "./real-providers.ts";
|
|
55
|
+
export { deleteRows, seedRow, seedRows, updateRows } from "./seed.ts";
|
|
56
56
|
export {
|
|
57
57
|
sharedItemEntity,
|
|
58
58
|
sharedItemTable,
|
|
@@ -60,8 +60,9 @@ export {
|
|
|
60
60
|
sharedUserTable,
|
|
61
61
|
sharedWidgetEntity,
|
|
62
62
|
sharedWidgetTable,
|
|
63
|
-
} from "./shared-entities.
|
|
64
|
-
export {
|
|
65
|
-
export {
|
|
66
|
-
export {
|
|
67
|
-
export {
|
|
63
|
+
} from "./shared-entities.ts";
|
|
64
|
+
export { createTestStoredEvent } from "./stored-event-fixture.ts";
|
|
65
|
+
export { sleep } from "./utils.ts";
|
|
66
|
+
export { waitFor } from "./wait-for.ts";
|
|
67
|
+
export { withoutAmbientTemporal } from "./without-ambient-temporal.ts";
|
|
68
|
+
export { zipWithEntries } from "./zip-fixture.ts";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { StoredEvent } from "@cosmicdrift/kumiko-types/event-store-types";
|
|
2
|
+
|
|
3
|
+
// SaveContext and DeleteContext always carry the event that produced them, so a
|
|
4
|
+
// hand-built context in a hook test needs one too.
|
|
5
|
+
export function createTestStoredEvent(overrides: Partial<StoredEvent> = {}): StoredEvent {
|
|
6
|
+
return {
|
|
7
|
+
id: "test-event-1",
|
|
8
|
+
aggregateId: "test-aggregate-1",
|
|
9
|
+
aggregateType: "test-aggregate",
|
|
10
|
+
tenantId: "test-tenant-1",
|
|
11
|
+
version: 1,
|
|
12
|
+
type: "test:event:saved",
|
|
13
|
+
eventVersion: 1,
|
|
14
|
+
payload: {},
|
|
15
|
+
metadata: { userId: "test-user-1" },
|
|
16
|
+
createdAt: Temporal.Instant.from("2026-01-01T00:00:00Z"),
|
|
17
|
+
createdBy: "test-user-1",
|
|
18
|
+
...overrides,
|
|
19
|
+
};
|
|
20
|
+
}
|
package/src/preload/real.ts
CHANGED
package/src/scaffold.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @runtime tooling
|
|
2
2
|
import { BUNFIG_FILES, renderBunfigFiles, TEST_TIMEOUT_MS } from "./bunfig";
|
|
3
|
-
import { REAL_PROVIDERS_ENV } from "./helpers/real-providers
|
|
3
|
+
import { REAL_PROVIDERS_ENV } from "./helpers/real-providers";
|
|
4
4
|
|
|
5
5
|
export type ScaffoldTestSetup = {
|
|
6
6
|
readonly files: Readonly<Record<string, string>>;
|
|
@@ -21,6 +21,10 @@ export const SCAFFOLD_E2E_PORT = 4174;
|
|
|
21
21
|
export const SCAFFOLD_INTEGRATION_PARALLEL = 4;
|
|
22
22
|
|
|
23
23
|
const PLAYWRIGHT_VERSION = "^1.62.1";
|
|
24
|
+
// bunfig.dom.toml preloads kumiko-testing's dom preload, which registers happy-dom.
|
|
25
|
+
const HAPPY_DOM_VERSION = "^20.11.2";
|
|
26
|
+
const TESTING_LIBRARY_REACT_VERSION = "^16.3.2";
|
|
27
|
+
const TESTING_LIBRARY_DOM_VERSION = "^10.4.1";
|
|
24
28
|
|
|
25
29
|
const TEST_RUNTIME_DIRECTIVE = "// @runtime test";
|
|
26
30
|
|
|
@@ -81,22 +85,8 @@ function renderE2eServer(appName: string): string {
|
|
|
81
85
|
function renderE2eSmokeSpec(): string {
|
|
82
86
|
return [
|
|
83
87
|
TEST_RUNTIME_DIRECTIVE,
|
|
84
|
-
'import type { SeedPart } from "@cosmicdrift/kumiko-testing";',
|
|
85
88
|
'import { expect, test } from "@cosmicdrift/kumiko-testing/e2e";',
|
|
86
|
-
'import
|
|
87
|
-
"",
|
|
88
|
-
`const TASK_CREATE = ${JSON.stringify(TASK_CREATE)};`,
|
|
89
|
-
"",
|
|
90
|
-
"function withTask(title: string): SeedPart {",
|
|
91
|
-
" return async ({ tenant }) => {",
|
|
92
|
-
' await tenant.api.writeOk(TASK_CREATE, { title, status: "todo", priority: 1 });',
|
|
93
|
-
" };",
|
|
94
|
-
"}",
|
|
95
|
-
"",
|
|
96
|
-
"async function openTasks(page: Page): Promise<void> {",
|
|
97
|
-
' await page.goto("/");',
|
|
98
|
-
' await page.getByRole("link", { name: "Tasks" }).first().click();',
|
|
99
|
-
"}",
|
|
89
|
+
'import { openTasks, withTask } from "./_helpers";',
|
|
100
90
|
"",
|
|
101
91
|
'test("a seeded task shows up on the tasks screen", async ({ seedTenant, page }) => {',
|
|
102
92
|
' const tenant = await seedTenant({ with: [withTask("Water the plants")] });',
|
|
@@ -117,6 +107,41 @@ function renderE2eSmokeSpec(): string {
|
|
|
117
107
|
].join("\n");
|
|
118
108
|
}
|
|
119
109
|
|
|
110
|
+
function renderE2eHelpers(): string {
|
|
111
|
+
return [
|
|
112
|
+
TEST_RUNTIME_DIRECTIVE,
|
|
113
|
+
'import type { SeedPart } from "@cosmicdrift/kumiko-testing";',
|
|
114
|
+
'import type { Page } from "@playwright/test";',
|
|
115
|
+
"",
|
|
116
|
+
`const TASK_CREATE = ${JSON.stringify(TASK_CREATE)};`,
|
|
117
|
+
"",
|
|
118
|
+
"export function withTask(title: string): SeedPart {",
|
|
119
|
+
" return async ({ tenant }) => {",
|
|
120
|
+
' await tenant.api.writeOk(TASK_CREATE, { title, status: "todo", priority: 1 });',
|
|
121
|
+
" };",
|
|
122
|
+
"}",
|
|
123
|
+
"",
|
|
124
|
+
"export async function openTasks(page: Page): Promise<void> {",
|
|
125
|
+
' await page.goto("/");',
|
|
126
|
+
' await page.getByRole("link", { name: "Tasks" }).first().click();',
|
|
127
|
+
"}",
|
|
128
|
+
"",
|
|
129
|
+
].join("\n");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function renderIntegrationSetup(): string {
|
|
133
|
+
return [
|
|
134
|
+
'import { setupAppTestStack } from "@cosmicdrift/kumiko-testing";',
|
|
135
|
+
'import type { TestStack } from "@cosmicdrift/kumiko-testing/stack";',
|
|
136
|
+
'import { tasksFeature } from "../features/tasks";',
|
|
137
|
+
"",
|
|
138
|
+
"export function setupTasksStack(): Promise<TestStack> {",
|
|
139
|
+
" return setupAppTestStack([tasksFeature]);",
|
|
140
|
+
"}",
|
|
141
|
+
"",
|
|
142
|
+
].join("\n");
|
|
143
|
+
}
|
|
144
|
+
|
|
120
145
|
function renderUnitTest(): string {
|
|
121
146
|
return [
|
|
122
147
|
'import { describe, expect, test } from "bun:test";',
|
|
@@ -140,9 +165,9 @@ function renderUnitTest(): string {
|
|
|
140
165
|
function renderIntegrationTest(): string {
|
|
141
166
|
return [
|
|
142
167
|
'import { afterAll, beforeAll, describe, expect, test } from "bun:test";',
|
|
143
|
-
'import { seedTenant
|
|
168
|
+
'import { seedTenant } from "@cosmicdrift/kumiko-testing";',
|
|
144
169
|
'import type { TestStack } from "@cosmicdrift/kumiko-testing/stack";',
|
|
145
|
-
'import {
|
|
170
|
+
'import { setupTasksStack } from "./_setup";',
|
|
146
171
|
"",
|
|
147
172
|
`const TASK_CREATE = ${JSON.stringify(TASK_CREATE)};`,
|
|
148
173
|
`const TASK_LIST = ${JSON.stringify(TASK_LIST)};`,
|
|
@@ -152,7 +177,7 @@ function renderIntegrationTest(): string {
|
|
|
152
177
|
"let stack: TestStack;",
|
|
153
178
|
"",
|
|
154
179
|
"beforeAll(async () => {",
|
|
155
|
-
" stack = await
|
|
180
|
+
" stack = await setupTasksStack();",
|
|
156
181
|
"});",
|
|
157
182
|
"",
|
|
158
183
|
"afterAll(async () => {",
|
|
@@ -178,18 +203,25 @@ function renderIntegrationTest(): string {
|
|
|
178
203
|
|
|
179
204
|
export function renderTestSetup(input: RenderTestSetupInput): ScaffoldTestSetup {
|
|
180
205
|
const { appName, frameworkVersion, port = SCAFFOLD_E2E_PORT } = input;
|
|
206
|
+
const unitTestScript = `bun --config=${BUNFIG_FILES.unit} test --timeout=${TEST_TIMEOUT_MS.unit} --dots`;
|
|
207
|
+
const integrationTestScript = `bun kumiko-testing integration --parallel ${SCAFFOLD_INTEGRATION_PARALLEL}`;
|
|
181
208
|
return {
|
|
182
209
|
files: {
|
|
183
|
-
...renderBunfigFiles({ install: { linker: "hoisted" } }),
|
|
210
|
+
...renderBunfigFiles({ install: { linker: "hoisted" }, dom: true }),
|
|
184
211
|
"playwright.config.ts": renderPlaywrightConfig(port),
|
|
212
|
+
"e2e/_helpers/index.ts": renderE2eHelpers(),
|
|
185
213
|
"e2e/server.ts": renderE2eServer(appName),
|
|
186
214
|
"e2e/smoke.spec.ts": renderE2eSmokeSpec(),
|
|
215
|
+
"src/__tests__/_setup.ts": renderIntegrationSetup(),
|
|
187
216
|
"src/__tests__/run-config.test.ts": renderUnitTest(),
|
|
188
217
|
"src/__tests__/tasks.integration.test.ts": renderIntegrationTest(),
|
|
189
218
|
},
|
|
190
219
|
scripts: {
|
|
191
|
-
test:
|
|
192
|
-
"test:
|
|
220
|
+
test: unitTestScript,
|
|
221
|
+
"test:unit": unitTestScript,
|
|
222
|
+
"test:integration": integrationTestScript,
|
|
223
|
+
"test:integration:ci": integrationTestScript,
|
|
224
|
+
"test:dom": `bun --config=${BUNFIG_FILES.dom} test --timeout=${TEST_TIMEOUT_MS.unit} --dots --pass-with-no-tests .test.tsx`,
|
|
193
225
|
// bunfig.real.toml's pathIgnorePatterns can only blacklist (no "!" negation,
|
|
194
226
|
// verified against bun 1.4), so it can't express "match only *.real.test.ts"
|
|
195
227
|
// on its own — every *.test.ts file matches bun's default test glob too,
|
|
@@ -201,10 +233,14 @@ export function renderTestSetup(input: RenderTestSetupInput): ScaffoldTestSetup
|
|
|
201
233
|
"test:bunfig": "bun kumiko-testing bunfig --hoisted",
|
|
202
234
|
e2e: "bunx --bun playwright test",
|
|
203
235
|
"e2e:real": `${REAL_PROVIDERS_ENV}=1 bunx --bun playwright test`,
|
|
236
|
+
screenshots: "SCREENSHOT_DIR=docs/screenshots bunx --bun playwright test",
|
|
204
237
|
},
|
|
205
238
|
devDependencies: {
|
|
206
239
|
"@cosmicdrift/kumiko-testing": frameworkVersion,
|
|
240
|
+
"@happy-dom/global-registrator": HAPPY_DOM_VERSION,
|
|
207
241
|
"@playwright/test": PLAYWRIGHT_VERSION,
|
|
242
|
+
"@testing-library/dom": TESTING_LIBRARY_DOM_VERSION,
|
|
243
|
+
"@testing-library/react": TESTING_LIBRARY_REACT_VERSION,
|
|
208
244
|
},
|
|
209
245
|
rulesMarkdown: RULES_MARKDOWN,
|
|
210
246
|
};
|
package/src/seed-tenant.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
setupTestStackFromFeatures,
|
|
4
4
|
} from "@cosmicdrift/kumiko-dev-server/setup-test-stack-from-features";
|
|
5
5
|
import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
6
|
-
import { pushEntityProjectionTables, type TestStack } from "./stack/index.
|
|
6
|
+
import { pushEntityProjectionTables, type TestStack } from "./stack/index.ts";
|
|
7
7
|
|
|
8
8
|
export type SetupAppTestStackOptions = SetupTestStackFromFeaturesOptions & {
|
|
9
9
|
readonly registryTables?: boolean;
|
package/src/stack/index.ts
CHANGED
|
@@ -18,11 +18,11 @@ export {
|
|
|
18
18
|
type CreateTestDbOptions,
|
|
19
19
|
createTestDb,
|
|
20
20
|
type TestDb,
|
|
21
|
-
} from "./db.
|
|
22
|
-
export { drainEventConsumers } from "./drain-event-consumers.
|
|
23
|
-
export { createEventCollector, type EventCollector } from "./event-collector.
|
|
24
|
-
export { pushEntityProjectionTables } from "./push-entity-projection-tables.
|
|
25
|
-
export { createTestRedis, type TestRedis } from "./redis.
|
|
26
|
-
export { createRequestHelper, type RequestHelper } from "./request-helper.
|
|
27
|
-
export { resetEventStore } from "./table-helpers.
|
|
28
|
-
export { setupTestStack, type TestStack, type TestStackOptions } from "./test-stack.
|
|
21
|
+
} from "./db.ts";
|
|
22
|
+
export { drainEventConsumers } from "./drain-event-consumers.ts";
|
|
23
|
+
export { createEventCollector, type EventCollector } from "./event-collector.ts";
|
|
24
|
+
export { pushEntityProjectionTables } from "./push-entity-projection-tables.ts";
|
|
25
|
+
export { createTestRedis, type TestRedis } from "./redis.ts";
|
|
26
|
+
export { createRequestHelper, type RequestHelper } from "./request-helper.ts";
|
|
27
|
+
export { resetEventStore } from "./table-helpers.ts";
|
|
28
|
+
export { setupTestStack, type TestStack, type TestStackOptions } from "./test-stack.ts";
|
package/src/stack/redis.ts
CHANGED
package/src/stack/test-stack.ts
CHANGED
|
@@ -50,11 +50,11 @@ import type { SearchAdapter } from "@cosmicdrift/kumiko-framework/search";
|
|
|
50
50
|
import { createInMemorySearchAdapter } from "@cosmicdrift/kumiko-framework/search";
|
|
51
51
|
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
52
52
|
import type { Hono } from "hono";
|
|
53
|
-
import { createTestDb } from "./db.
|
|
54
|
-
import { createJobFailureTracker, drainJobs } from "./drain-jobs.
|
|
55
|
-
import { createEventCollector, type EventCollector } from "./event-collector.
|
|
56
|
-
import { createTestRedis, queueNamePrefixForTestRedis, type TestRedis } from "./redis.
|
|
57
|
-
import { createRequestHelper, type RequestHelper } from "./request-helper.
|
|
53
|
+
import { createTestDb } from "./db.ts";
|
|
54
|
+
import { createJobFailureTracker, drainJobs } from "./drain-jobs.ts";
|
|
55
|
+
import { createEventCollector, type EventCollector } from "./event-collector.ts";
|
|
56
|
+
import { createTestRedis, queueNamePrefixForTestRedis, type TestRedis } from "./redis.ts";
|
|
57
|
+
import { createRequestHelper, type RequestHelper } from "./request-helper.ts";
|
|
58
58
|
|
|
59
59
|
export type TestStack = {
|
|
60
60
|
app: Hono;
|
|
@@ -279,6 +279,29 @@ export type TestStackOptions = {
|
|
|
279
279
|
|
|
280
280
|
const DEFAULT_JWT_SECRET = "test-stack-secret-minimum-32-characters!!";
|
|
281
281
|
|
|
282
|
+
type MfaTokenSecretBinder = (
|
|
283
|
+
masterSecret: string,
|
|
284
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
285
|
+
) => void;
|
|
286
|
+
|
|
287
|
+
// Mirrors bindMfaTokenSecretsFromFeature from bundled-features/auth-mfa,
|
|
288
|
+
// which this package cannot import: bundled-features is an optional peer.
|
|
289
|
+
function isMfaTokenSecretBinder(value: unknown): value is MfaTokenSecretBinder {
|
|
290
|
+
return typeof value === "function";
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// The env is empty on purpose: test secrets derive from jwtSecret,
|
|
294
|
+
// independent of the host env.
|
|
295
|
+
function bindMfaTokenSecrets(features: readonly FeatureDefinition[], jwtSecret: string): void {
|
|
296
|
+
for (const { exports } of features) {
|
|
297
|
+
if (typeof exports !== "object" || exports === null || !("bindTokenSecrets" in exports)) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const { bindTokenSecrets } = exports;
|
|
301
|
+
if (isMfaTokenSecretBinder(bindTokenSecrets)) bindTokenSecrets(jwtSecret, {});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
282
305
|
export async function setupTestStack(options: TestStackOptions): Promise<TestStack> {
|
|
283
306
|
const jwtSecret = options.jwtSecret ?? DEFAULT_JWT_SECRET;
|
|
284
307
|
const enabledHooks = options.systemHooks ?? ["sse", "search"];
|
|
@@ -298,6 +321,7 @@ export async function setupTestStack(options: TestStackOptions): Promise<TestSta
|
|
|
298
321
|
// Nav/workspace refs fail the prod boot too; the subset check tolerates refs into unmounted features.
|
|
299
322
|
validateNavBootSubset(options.features);
|
|
300
323
|
if (options.validateBoot === "full") validateBoot(options.features);
|
|
324
|
+
bindMfaTokenSecrets(options.features, jwtSecret);
|
|
301
325
|
|
|
302
326
|
// Forward db-name/persistent-flag through to createTestDb. The
|
|
303
327
|
// defaults (undefined dbName, persistent:false) keep the legacy
|