@cosmicdrift/kumiko-framework 0.158.2 → 0.160.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 +7 -2
- package/src/__tests__/consumer-cli.integration.test.ts +110 -0
- package/src/api/__tests__/api.test.ts +65 -0
- package/src/api/__tests__/auth-routes-cookie.test.ts +17 -1
- package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +237 -0
- package/src/api/__tests__/auth-routes-mfa-verify.test.ts +1 -0
- package/src/api/__tests__/csrf-constants-sync.test.ts +20 -0
- package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
- package/src/api/__tests__/jwt.test.ts +150 -1
- package/src/api/__tests__/login-rate-limiter-sweep.test.ts +41 -0
- package/src/api/__tests__/server-boot-guards.test.ts +71 -0
- package/src/api/__tests__/server-jwt-ttl.test.ts +58 -0
- package/src/api/api-constants.ts +5 -0
- package/src/api/auth-middleware.ts +48 -59
- package/src/api/auth-routes.ts +51 -17
- package/src/api/index.ts +3 -3
- package/src/api/jwt.ts +148 -7
- package/src/api/pii-leak-guard.ts +5 -2
- package/src/api/routes.ts +57 -0
- package/src/api/server.ts +19 -5
- package/src/bun-db/__tests__/select-many-retry.test.ts +79 -0
- package/src/bun-db/query.ts +46 -27
- package/src/consumer-cli.ts +87 -0
- package/src/crypto/__tests__/pii-field-encryption.test.ts +69 -13
- package/src/crypto/blind-index.ts +8 -4
- package/src/crypto/event-pii.ts +1 -0
- package/src/crypto/kms-adapter.ts +2 -118
- package/src/crypto/pii-field-encryption.ts +49 -15
- package/src/db/__tests__/build-filter-where.test.ts +34 -0
- package/src/db/__tests__/event-store-executor-context.pii-roundtrip.test.ts +67 -0
- package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +396 -0
- package/src/db/__tests__/event-store-executor.integration.test.ts +5 -5
- package/src/db/blind-index-cleanup.ts +3 -1
- package/src/db/connection.ts +3 -11
- package/src/db/cursor.ts +1 -18
- package/src/db/dialect.ts +8 -19
- package/src/db/encryption.ts +2 -3
- package/src/db/entity-table-meta-types.ts +2 -0
- package/src/db/entity-table-meta.ts +16 -90
- package/src/db/event-store-executor.ts +4 -96
- package/src/db/queries/backfill-pii.ts +1 -0
- package/src/db/queries/event-consumer.ts +35 -2
- package/src/db/table-builder.ts +2 -19
- package/src/db/tenant-db.ts +6 -55
- package/src/engine/__tests__/boot-validator-boot-check.test.ts +99 -0
- package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +7 -233
- package/src/engine/__tests__/boot-validator.test.ts +46 -0
- package/src/engine/__tests__/codemod-pipeline.test.ts +139 -10
- package/src/engine/__tests__/define-roles.test.ts +21 -0
- package/src/engine/__tests__/engine.test.ts +28 -0
- package/src/engine/__tests__/event-type-map-augmentation.test.ts +24 -0
- package/src/engine/__tests__/registry-facade-sweep.test.ts +80 -0
- package/src/engine/__tests__/registry.test.ts +40 -0
- package/src/engine/__tests__/store-table.test.ts +12 -0
- package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
- package/src/engine/boot-validator/action-wiring.ts +1 -1
- package/src/engine/boot-validator/boot-check.ts +21 -0
- package/src/engine/boot-validator/entity-handler.ts +10 -1
- package/src/engine/boot-validator/entity-list-screens.ts +1 -1
- package/src/engine/boot-validator/gdpr-storage.ts +0 -112
- package/src/engine/boot-validator/index.ts +3 -9
- package/src/engine/boot-validator/screens.ts +1 -1
- package/src/engine/define-feature.ts +2 -0
- package/src/engine/define-handler.ts +11 -91
- package/src/engine/entity-handlers.ts +15 -27
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +11 -1
- package/src/engine/feature-ast/__tests__/parse.test.ts +983 -3
- package/src/engine/feature-ast/__tests__/patch.test.ts +168 -0
- package/src/engine/feature-ast/__tests__/patcher.test.ts +7 -0
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +9 -0
- package/src/engine/feature-ast/extractors/handlers.ts +19 -2
- package/src/engine/feature-ast/extractors/index.ts +1 -0
- package/src/engine/feature-ast/index.ts +2 -0
- package/src/engine/feature-ast/parse.ts +3 -0
- package/src/engine/feature-ast/patch.ts +2 -0
- package/src/engine/feature-ast/patcher.ts +21 -0
- package/src/engine/feature-ast/patterns.ts +16 -0
- package/src/engine/feature-ast/render.ts +15 -0
- package/src/engine/feature-builder-state.ts +6 -0
- package/src/engine/feature-config-events-jobs.ts +1 -1
- package/src/engine/feature-entity-handlers.ts +36 -2
- package/src/engine/feature-ui-extensions.ts +5 -1
- package/src/engine/field-helpers.ts +31 -0
- package/src/engine/handler-helpers.ts +26 -0
- package/src/engine/hook-helpers.ts +14 -0
- package/src/engine/index.ts +5 -2
- package/src/engine/ownership.ts +22 -76
- package/src/engine/pattern-library/__tests__/library.test.ts +9 -0
- package/src/engine/pattern-library/library.ts +2 -0
- package/src/engine/pattern-library/mixed-schemas.ts +37 -0
- package/src/engine/registry-facade.ts +9 -0
- package/src/engine/registry-ingest.ts +10 -0
- package/src/engine/registry-state.ts +3 -0
- package/src/engine/registry-validate.ts +1 -1
- package/src/engine/screen-helpers.ts +54 -0
- package/src/engine/tier-resolver-extension.ts +3 -2
- package/src/engine/types/config.ts +2 -497
- package/src/engine/types/define-handler.ts +2 -0
- package/src/engine/types/entity-handlers.ts +2 -0
- package/src/engine/types/event-type-map.ts +1 -37
- package/src/engine/types/feature.ts +2 -976
- package/src/engine/types/fields.ts +2 -697
- package/src/engine/types/handlers.ts +2 -839
- package/src/engine/types/hooks.ts +2 -184
- package/src/engine/types/http-route.ts +1 -72
- package/src/engine/types/identifiers.ts +1 -47
- package/src/engine/types/index.ts +66 -33
- package/src/engine/types/nav.ts +2 -67
- package/src/engine/types/ownership.ts +2 -0
- package/src/engine/types/projection.ts +2 -165
- package/src/engine/types/relations.ts +1 -51
- package/src/engine/types/screen.ts +2 -793
- package/src/engine/types/step.ts +2 -334
- package/src/engine/types/target-ref.ts +1 -21
- package/src/engine/types/tree-node.ts +1 -129
- package/src/engine/types/workspace.ts +2 -42
- package/src/entrypoint/index.ts +2 -2
- package/src/errors/write-error-info.ts +6 -22
- package/src/event-store/__tests__/event-store.integration.test.ts +31 -0
- package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +43 -0
- package/src/event-store/errors.ts +2 -35
- package/src/event-store/event-store.ts +28 -51
- package/src/event-store/events-schema.ts +1 -10
- package/src/event-store/index.ts +3 -2
- package/src/event-store/snapshot.ts +11 -35
- package/src/event-store/types.ts +2 -0
- package/src/files/__tests__/in-memory-provider.contract.test.ts +4 -0
- package/src/files/file-handle.ts +2 -19
- package/src/files/provider-resolver.ts +3 -5
- package/src/files/types.ts +5 -54
- package/src/i18n/required-surface-keys.ts +1 -1
- package/src/jobs/__tests__/jobs.integration.test.ts +102 -1
- package/src/logging/types.ts +1 -7
- package/src/observability/types/index.ts +1 -29
- package/src/observability/types/metric.ts +1 -56
- package/src/observability/types/provider.ts +1 -32
- package/src/observability/types/span.ts +1 -58
- package/src/pipeline/__tests__/dispatcher.test.ts +134 -1
- package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +126 -0
- package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +180 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +208 -0
- package/src/pipeline/dispatch-shared.ts +51 -3
- package/src/pipeline/dispatch-stream.ts +74 -0
- package/src/pipeline/dispatcher-utils.ts +1 -1
- package/src/pipeline/dispatcher.ts +7 -0
- package/src/pipeline/entity-cache.ts +2 -33
- package/src/pipeline/event-consumer-state.ts +28 -3
- package/src/pipeline/event-dispatcher-admin.ts +4 -0
- package/src/pipeline/event-dispatcher-delivery.ts +29 -3
- package/src/pipeline/event-dispatcher.ts +27 -1
- package/src/pipeline/multi-stream-apply-context.ts +4 -42
- package/src/pipeline/system-hooks.ts +7 -0
- package/src/rate-limit/resolver.ts +10 -30
- package/src/search/types.ts +1 -39
- package/src/secrets/__tests__/envelope-cipher.test.ts +2 -30
- package/src/secrets/__tests__/envelope.test.ts +1 -1
- package/src/secrets/envelope-cipher.ts +17 -45
- package/src/secrets/types.ts +2 -177
- package/src/stack/__tests__/event-collector.test.ts +42 -0
- package/src/testing/__tests__/late-bound.test.ts +25 -0
- package/src/testing/__tests__/wait-for.test.ts +53 -0
- package/src/testing/boot-validator-fixture.ts +1 -1
- package/src/testing/file-provider-contract.ts +84 -0
- package/src/testing/handler-context.ts +1 -1
- package/src/testing/index.ts +1 -0
- package/src/time/geo-tz.ts +1 -32
- package/src/time/tz-context.ts +9 -56
- package/src/ui-types/index.ts +7 -7
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
// V1 — GDPR storage-persistence boot guard. Catches the prod failure class:
|
|
2
2
|
// user-data-rights mounted but exports land in an ephemeral / missing store,
|
|
3
3
|
// and s3-env selected as the GDPR store without its env vars set.
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
//
|
|
5
|
+
// V2-V4 (export-without-erase / PII-entity-without-hook / tenantOwned-entity-
|
|
6
|
+
// without-hook) moved off this framework-internal validator onto
|
|
7
|
+
// `r.bootCheck()` calls declared by user-data-rights / user-data-rights-
|
|
8
|
+
// defaults (#1314) — see boot-checks.test.ts in the bundled-features package
|
|
9
|
+
// for their coverage.
|
|
9
10
|
|
|
10
11
|
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
validateGdprHookCompleteness,
|
|
14
|
-
validateGdprPiiHookCoverage,
|
|
15
|
-
validateGdprStoragePersistence,
|
|
16
|
-
validateTenantDataHookCoverage,
|
|
17
|
-
} from "../boot-validator/gdpr-storage";
|
|
12
|
+
import { validateGdprStoragePersistence } from "../boot-validator/gdpr-storage";
|
|
18
13
|
import { defineFeature } from "../define-feature";
|
|
19
|
-
import { EXT_TENANT_DATA, EXT_USER_DATA } from "../extension-names";
|
|
20
|
-
import { createEntity, createLongTextField, createTextField } from "../factories";
|
|
21
14
|
|
|
22
15
|
const udr = () => defineFeature("user-data-rights", () => {});
|
|
23
16
|
const fileProvider = (name: string) =>
|
|
@@ -25,26 +18,8 @@ const fileProvider = (name: string) =>
|
|
|
25
18
|
r.useExtension("fileProvider", name);
|
|
26
19
|
});
|
|
27
20
|
|
|
28
|
-
// Throwaway Drizzle table for r.projection() registrations — the projection
|
|
29
|
-
// runtime behaviour isn't under test here, only the guard's field scan.
|
|
30
|
-
const testProjectionTable = table("test_projection", {
|
|
31
|
-
id: uuid("id").primaryKey(),
|
|
32
|
-
count: integer("count").notNull().default(0),
|
|
33
|
-
}) as unknown as SchemaTable;
|
|
34
|
-
|
|
35
21
|
const S3_ENV = ["S3_BUCKET", "S3_REGION", "S3_ACCESS_KEY", "S3_SECRET_KEY"] as const;
|
|
36
22
|
|
|
37
|
-
// V2/V3 are hard boot gates now — capture the thrown message so a single case
|
|
38
|
-
// can assert several substrings (feature, entity, article).
|
|
39
|
-
function catchMessage(fn: () => void): string {
|
|
40
|
-
try {
|
|
41
|
-
fn();
|
|
42
|
-
} catch (e) {
|
|
43
|
-
return e instanceof Error ? e.message : String(e);
|
|
44
|
-
}
|
|
45
|
-
throw new Error("expected function to throw, but it did not");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
23
|
describe("validateGdprStoragePersistence (V1)", () => {
|
|
49
24
|
let warnSpy: ReturnType<typeof spyOn>;
|
|
50
25
|
let savedEnv: Array<readonly [string, string | undefined]>;
|
|
@@ -103,204 +78,3 @@ describe("validateGdprStoragePersistence (V1)", () => {
|
|
|
103
78
|
expect(warnSpy).not.toHaveBeenCalled();
|
|
104
79
|
});
|
|
105
80
|
});
|
|
106
|
-
|
|
107
|
-
describe("validateGdprHookCompleteness (V2)", () => {
|
|
108
|
-
const exportFn = async () => null;
|
|
109
|
-
const deleteFn = async () => {};
|
|
110
|
-
|
|
111
|
-
test("export + delete hooks → no throw", () => {
|
|
112
|
-
const f = defineFeature("my-feature", (r) => {
|
|
113
|
-
r.useExtension(EXT_USER_DATA, "myEntity", { export: exportFn, delete: deleteFn });
|
|
114
|
-
});
|
|
115
|
-
expect(() => validateGdprHookCompleteness([f])).not.toThrow();
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("export hook without delete hook → Art.17 throw", () => {
|
|
119
|
-
const f = defineFeature("my-feature", (r) => {
|
|
120
|
-
r.useExtension(EXT_USER_DATA, "myEntity", { export: exportFn });
|
|
121
|
-
});
|
|
122
|
-
const msg = catchMessage(() => validateGdprHookCompleteness([f]));
|
|
123
|
-
expect(msg).toContain("my-feature");
|
|
124
|
-
expect(msg).toContain("myEntity");
|
|
125
|
-
expect(msg).toContain("Art.17");
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test("delete hook only (no export) → no throw", () => {
|
|
129
|
-
const f = defineFeature("my-feature", (r) => {
|
|
130
|
-
r.useExtension(EXT_USER_DATA, "myEntity", { delete: deleteFn });
|
|
131
|
-
});
|
|
132
|
-
expect(() => validateGdprHookCompleteness([f])).not.toThrow();
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test("no EXT_USER_DATA hooks at all → no throw", () => {
|
|
136
|
-
const f = defineFeature("my-feature", (r) => {
|
|
137
|
-
r.useExtension("fileProvider", "s3");
|
|
138
|
-
});
|
|
139
|
-
expect(() => validateGdprHookCompleteness([f])).not.toThrow();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("multiple features, one missing delete → throws naming the offender", () => {
|
|
143
|
-
const good = defineFeature("good", (r) => {
|
|
144
|
-
r.useExtension(EXT_USER_DATA, "entityA", { export: exportFn, delete: deleteFn });
|
|
145
|
-
});
|
|
146
|
-
const bad = defineFeature("bad", (r) => {
|
|
147
|
-
r.useExtension(EXT_USER_DATA, "entityB", { export: exportFn });
|
|
148
|
-
});
|
|
149
|
-
const msg = catchMessage(() => validateGdprHookCompleteness([good, bad]));
|
|
150
|
-
expect(msg).toContain("entityB");
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe("validateGdprPiiHookCoverage (V3)", () => {
|
|
155
|
-
const exportFn = async () => null;
|
|
156
|
-
const deleteFn = async () => {};
|
|
157
|
-
|
|
158
|
-
const piiFeature = () =>
|
|
159
|
-
defineFeature("crm", (r) => {
|
|
160
|
-
r.entity(
|
|
161
|
-
"contact",
|
|
162
|
-
createEntity({
|
|
163
|
-
fields: {
|
|
164
|
-
email: createTextField({ pii: true }),
|
|
165
|
-
note: createLongTextField({ userOwned: { ownerField: "authorId" } }),
|
|
166
|
-
authorId: { type: "reference", entity: "user" },
|
|
167
|
-
},
|
|
168
|
-
}),
|
|
169
|
-
);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test("user-data-rights not mounted → no throw", () => {
|
|
173
|
-
expect(() => validateGdprPiiHookCoverage([piiFeature()])).not.toThrow();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
test("pii entity without any EXT_USER_DATA hook → throws naming entity and fields", () => {
|
|
177
|
-
const msg = catchMessage(() => validateGdprPiiHookCoverage([udr(), piiFeature()]));
|
|
178
|
-
expect(msg).toContain('"contact"');
|
|
179
|
-
expect(msg).toContain("email");
|
|
180
|
-
expect(msg).toContain("note");
|
|
181
|
-
expect(msg).toContain("Art.17");
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test("pii entity with hook registered by another feature → no throw", () => {
|
|
185
|
-
const hooks = defineFeature("crm-user-data", (r) => {
|
|
186
|
-
r.useExtension(EXT_USER_DATA, "contact", { export: exportFn, delete: deleteFn });
|
|
187
|
-
});
|
|
188
|
-
expect(() => validateGdprPiiHookCoverage([udr(), piiFeature(), hooks])).not.toThrow();
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
test("no-op hook is the intentional escape hatch → no throw", () => {
|
|
192
|
-
const hooks = defineFeature("crm-user-data", (r) => {
|
|
193
|
-
// Escape hatch: erasure handled elsewhere (crypto-shredding key-erase),
|
|
194
|
-
// so the pipeline hook is a deliberate no-op.
|
|
195
|
-
r.useExtension(EXT_USER_DATA, "contact", {
|
|
196
|
-
export: async () => null,
|
|
197
|
-
delete: async () => {},
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
expect(() => validateGdprPiiHookCoverage([udr(), piiFeature(), hooks])).not.toThrow();
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test("entity without subject annotations → no throw", () => {
|
|
204
|
-
const plain = defineFeature("catalog", (r) => {
|
|
205
|
-
r.entity(
|
|
206
|
-
"product",
|
|
207
|
-
createEntity({
|
|
208
|
-
fields: { sku: createTextField({ allowPlaintext: "is-business-data" }) },
|
|
209
|
-
}),
|
|
210
|
-
);
|
|
211
|
-
});
|
|
212
|
-
expect(() => validateGdprPiiHookCoverage([udr(), plain])).not.toThrow();
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("userOwned field alone counts as user-subject data → throws", () => {
|
|
216
|
-
const f = defineFeature("notes", (r) => {
|
|
217
|
-
r.entity(
|
|
218
|
-
"note",
|
|
219
|
-
createEntity({
|
|
220
|
-
fields: {
|
|
221
|
-
body: createLongTextField({ userOwned: { ownerField: "authorId" } }),
|
|
222
|
-
authorId: { type: "reference", entity: "user" },
|
|
223
|
-
},
|
|
224
|
-
}),
|
|
225
|
-
);
|
|
226
|
-
});
|
|
227
|
-
const msg = catchMessage(() => validateGdprPiiHookCoverage([udr(), f]));
|
|
228
|
-
expect(msg).toContain('"note"');
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
describe("validateTenantDataHookCoverage (V4)", () => {
|
|
233
|
-
const destroyFn = async () => {};
|
|
234
|
-
const tenantLifecycle = () => defineFeature("tenant-lifecycle", () => {});
|
|
235
|
-
|
|
236
|
-
const tenantEntityFeature = () =>
|
|
237
|
-
defineFeature("billing", (r) => {
|
|
238
|
-
r.entity(
|
|
239
|
-
"subscription",
|
|
240
|
-
createEntity({
|
|
241
|
-
fields: { providerCustomerId: createTextField({ tenantOwned: true }) },
|
|
242
|
-
}),
|
|
243
|
-
);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test("tenant-lifecycle not mounted → no throw", () => {
|
|
247
|
-
expect(() => validateTenantDataHookCoverage([tenantEntityFeature()])).not.toThrow();
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
test("tenantOwned entity without any EXT_TENANT_DATA hook → throws naming entity and field", () => {
|
|
251
|
-
const msg = catchMessage(() =>
|
|
252
|
-
validateTenantDataHookCoverage([tenantLifecycle(), tenantEntityFeature()]),
|
|
253
|
-
);
|
|
254
|
-
expect(msg).toContain('"subscription"');
|
|
255
|
-
expect(msg).toContain("providerCustomerId");
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("tenantOwned entity with hook registered → no throw", () => {
|
|
259
|
-
const hooks = defineFeature("billing-hooks", (r) => {
|
|
260
|
-
r.useExtension(EXT_TENANT_DATA, "subscription", { destroy: destroyFn });
|
|
261
|
-
});
|
|
262
|
-
expect(() =>
|
|
263
|
-
validateTenantDataHookCoverage([tenantLifecycle(), tenantEntityFeature(), hooks]),
|
|
264
|
-
).not.toThrow();
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// Regression: billing-foundation's real `subscription` shape — r.projection()
|
|
268
|
-
// (no executor, no r.entity) carrying an `entity` reference for its
|
|
269
|
-
// tenantOwned fields. Before this fix, feature.entities-only scanning made
|
|
270
|
-
// this shape invisible to the guard.
|
|
271
|
-
test("tenantOwned field on a projection-only entity (no r.entity) is still caught", () => {
|
|
272
|
-
const projectionFeature = defineFeature("billing", (r) => {
|
|
273
|
-
r.projection({
|
|
274
|
-
name: "subscription",
|
|
275
|
-
source: "subscription",
|
|
276
|
-
table: testProjectionTable,
|
|
277
|
-
entity: createEntity({
|
|
278
|
-
fields: { providerCustomerId: createTextField({ tenantOwned: true }) },
|
|
279
|
-
}),
|
|
280
|
-
apply: {},
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
const msg = catchMessage(() =>
|
|
284
|
-
validateTenantDataHookCoverage([tenantLifecycle(), projectionFeature]),
|
|
285
|
-
);
|
|
286
|
-
expect(msg).toContain("providerCustomerId");
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
test("tenantOwned field on a projection-only entity WITH a hook → no throw", () => {
|
|
290
|
-
const projectionFeature = defineFeature("billing", (r) => {
|
|
291
|
-
r.projection({
|
|
292
|
-
name: "subscription",
|
|
293
|
-
source: "subscription",
|
|
294
|
-
table: testProjectionTable,
|
|
295
|
-
entity: createEntity({
|
|
296
|
-
fields: { providerCustomerId: createTextField({ tenantOwned: true }) },
|
|
297
|
-
}),
|
|
298
|
-
apply: {},
|
|
299
|
-
});
|
|
300
|
-
r.useExtension(EXT_TENANT_DATA, "subscription", { destroy: destroyFn });
|
|
301
|
-
});
|
|
302
|
-
expect(() =>
|
|
303
|
-
validateTenantDataHookCoverage([tenantLifecycle(), projectionFeature]),
|
|
304
|
-
).not.toThrow();
|
|
305
|
-
});
|
|
306
|
-
});
|
|
@@ -590,6 +590,52 @@ describe("boot-validator", () => {
|
|
|
590
590
|
expect(() => validateBoot(features)).not.toThrow();
|
|
591
591
|
});
|
|
592
592
|
|
|
593
|
+
test("throws when a stream handler has no access rule", () => {
|
|
594
|
+
const features = [
|
|
595
|
+
defineFeature("a", (r) => {
|
|
596
|
+
r.streamHandler("chat:complete", z.object({}), async function* () {});
|
|
597
|
+
}),
|
|
598
|
+
];
|
|
599
|
+
expect(() => validateBoot(features)).toThrow(/a:stream:chat:complete.*missing an access rule/i);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test("accepts openToAll access rule on a stream handler", () => {
|
|
603
|
+
const features = [
|
|
604
|
+
defineFeature("a", (r) => {
|
|
605
|
+
r.streamHandler("chat:complete", z.object({}), async function* () {}, {
|
|
606
|
+
access: { openToAll: true },
|
|
607
|
+
});
|
|
608
|
+
}),
|
|
609
|
+
];
|
|
610
|
+
expect(() => validateBoot(features)).not.toThrow();
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
test("throws when an anonymous stream handler uses a user-bucketed rate limit", () => {
|
|
614
|
+
const features = [
|
|
615
|
+
defineFeature("a", (r) => {
|
|
616
|
+
r.streamHandler("chat:complete", z.object({}), async function* () {}, {
|
|
617
|
+
access: { roles: ["anonymous"] },
|
|
618
|
+
rateLimit: { per: "user", limit: 10, windowSeconds: 60 },
|
|
619
|
+
});
|
|
620
|
+
}),
|
|
621
|
+
];
|
|
622
|
+
expect(() => validateBoot(features)).toThrow(
|
|
623
|
+
/stream handler "a:stream:chat:complete" allows anonymous callers/i,
|
|
624
|
+
);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
test("accepts an anonymous stream handler with an ip-bucketed rate limit", () => {
|
|
628
|
+
const features = [
|
|
629
|
+
defineFeature("a", (r) => {
|
|
630
|
+
r.streamHandler("chat:complete", z.object({}), async function* () {}, {
|
|
631
|
+
access: { roles: ["anonymous"] },
|
|
632
|
+
rateLimit: { per: "ip", limit: 10, windowSeconds: 60 },
|
|
633
|
+
});
|
|
634
|
+
}),
|
|
635
|
+
];
|
|
636
|
+
expect(() => validateBoot(features)).not.toThrow();
|
|
637
|
+
});
|
|
638
|
+
|
|
593
639
|
describe("config key bounds consistency", () => {
|
|
594
640
|
test("accepts number key with consistent bounds + default", () => {
|
|
595
641
|
const features = [
|
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
import { afterAll, beforeAll, describe, expect, it, mock } from "bun:test";
|
|
2
|
-
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
|
-
// Bun.Glob is only used by scanForCandidates / runCodemod
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
// Bun.Glob is only used by scanForCandidates / runCodemod. Emulate
|
|
6
|
+
// **/*.write.ts via node:fs so scan/convert paths stay testable.
|
|
7
|
+
function collectWriteTsRelPaths(rootDir: string, dir = rootDir, prefix = ""): string[] {
|
|
8
|
+
const out: string[] = [];
|
|
9
|
+
for (const name of readdirSync(dir)) {
|
|
10
|
+
const rel = prefix ? `${prefix}/${name}` : name;
|
|
11
|
+
const full = join(dir, name);
|
|
12
|
+
if (statSync(full).isDirectory()) {
|
|
13
|
+
out.push(...collectWriteTsRelPaths(rootDir, full, rel));
|
|
14
|
+
} else if (name.endsWith(".write.ts")) {
|
|
15
|
+
out.push(rel);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
mock.module("bun", () => {
|
|
11
|
-
class
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
class FsGlob {
|
|
23
|
+
constructor(private readonly pattern: string) {}
|
|
24
|
+
scanSync(rootDir: string): string[] {
|
|
25
|
+
if (this.pattern !== "**/*.write.ts") return [];
|
|
26
|
+
return collectWriteTsRelPaths(rootDir).sort();
|
|
14
27
|
}
|
|
15
28
|
}
|
|
16
|
-
return { Glob:
|
|
29
|
+
return { Glob: FsGlob };
|
|
17
30
|
});
|
|
18
31
|
|
|
19
32
|
import {
|
|
@@ -21,6 +34,8 @@ import {
|
|
|
21
34
|
analyzeHandlerArrow,
|
|
22
35
|
convertFile,
|
|
23
36
|
generatePerformBlock,
|
|
37
|
+
runCodemod,
|
|
38
|
+
scanForCandidates,
|
|
24
39
|
} from "../codemod/index";
|
|
25
40
|
|
|
26
41
|
const tmpDir = join(__dirname, "__codemod_fixtures__");
|
|
@@ -31,6 +46,20 @@ function writeFixture(name: string, content: string): string {
|
|
|
31
46
|
return p;
|
|
32
47
|
}
|
|
33
48
|
|
|
49
|
+
function writeFixtureIn(dir: string, name: string, content: string): string {
|
|
50
|
+
const p = join(dir, name);
|
|
51
|
+
mkdirSync(join(p, ".."), { recursive: true });
|
|
52
|
+
writeFileSync(p, content, "utf8");
|
|
53
|
+
return p;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function freshScanRoot(name: string): string {
|
|
57
|
+
const dir = join(tmpDir, name);
|
|
58
|
+
rmSync(dir, { recursive: true, force: true });
|
|
59
|
+
mkdirSync(dir, { recursive: true });
|
|
60
|
+
return dir;
|
|
61
|
+
}
|
|
62
|
+
|
|
34
63
|
beforeAll(() => {
|
|
35
64
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
36
65
|
mkdirSync(tmpDir, { recursive: true });
|
|
@@ -399,6 +428,31 @@ export const h = defineWriteHandler({
|
|
|
399
428
|
// ts-morph should handle this gracefully (parse error → no call found)
|
|
400
429
|
expect(result.status).toBe("skipped");
|
|
401
430
|
});
|
|
431
|
+
|
|
432
|
+
it("returns error when the path is not a readable file", async () => {
|
|
433
|
+
const dirPath = join(tmpDir, "dir-not-file.write.ts");
|
|
434
|
+
rmSync(dirPath, { recursive: true, force: true });
|
|
435
|
+
mkdirSync(dirPath, { recursive: true });
|
|
436
|
+
const result = await convertFile(dirPath);
|
|
437
|
+
expect(result.status).toBe("error");
|
|
438
|
+
expect(result.reason).toBeDefined();
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("verbose logs when skipping a non-trivial handler", async () => {
|
|
442
|
+
const p = writeFixture("verbose-skip-file.write.ts", complexContent);
|
|
443
|
+
const logs: string[] = [];
|
|
444
|
+
const origLog = console.log;
|
|
445
|
+
console.log = (...args: unknown[]) => {
|
|
446
|
+
logs.push(args.map(String).join(" "));
|
|
447
|
+
};
|
|
448
|
+
try {
|
|
449
|
+
const result = await convertFile(p, undefined, { verbose: true });
|
|
450
|
+
expect(result.status).toBe("skipped");
|
|
451
|
+
expect(logs.some((l) => l.includes("non-trivial handler"))).toBe(true);
|
|
452
|
+
} finally {
|
|
453
|
+
console.log = origLog;
|
|
454
|
+
}
|
|
455
|
+
});
|
|
402
456
|
});
|
|
403
457
|
});
|
|
404
458
|
|
|
@@ -509,6 +563,81 @@ describe("analyzeHandlerArrow", () => {
|
|
|
509
563
|
expect(result.isGuardedCreate).toBe(true);
|
|
510
564
|
expect(result.isSimpleExecutorCreate).toBe(false);
|
|
511
565
|
});
|
|
566
|
+
|
|
567
|
+
it("returns non-convertible defaults for async ( without =>", () => {
|
|
568
|
+
const result = analyzeHandlerArrow("async (event, ctx) { return { isSuccess: true } }");
|
|
569
|
+
expect(result.isStaticReturn).toBe(false);
|
|
570
|
+
expect(result.isSimpleExecutorCreate).toBe(false);
|
|
571
|
+
expect(result.isSimpleExecutorUpdate).toBe(false);
|
|
572
|
+
expect(result.hasConditionalLogic).toBe(false);
|
|
573
|
+
expect(result.isExpressionBodyCreate).toBe(false);
|
|
574
|
+
expect(result.isGuardedCreate).toBe(false);
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
describe("scanForCandidates", () => {
|
|
579
|
+
it("returns empty list when no *.write.ts files exist", () => {
|
|
580
|
+
const root = freshScanRoot("scan-empty");
|
|
581
|
+
expect(scanForCandidates(root)).toEqual([]);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it("finds and classifies *.write.ts fixtures under rootDir", () => {
|
|
585
|
+
const root = freshScanRoot("scan-mixed");
|
|
586
|
+
writeFixtureIn(root, "freeform.write.ts", staticReturnContent);
|
|
587
|
+
writeFixtureIn(root, "already-pipeline.write.ts", alreadyPipelineContent);
|
|
588
|
+
writeFixtureIn(root, "nested/deep.write.ts", queryHandlerContent);
|
|
589
|
+
|
|
590
|
+
const results = scanForCandidates(root);
|
|
591
|
+
expect(results.map((r) => r.filePath).sort()).toEqual(
|
|
592
|
+
[
|
|
593
|
+
join(root, "already-pipeline.write.ts"),
|
|
594
|
+
join(root, "freeform.write.ts"),
|
|
595
|
+
join(root, "nested/deep.write.ts"),
|
|
596
|
+
].sort(),
|
|
597
|
+
);
|
|
598
|
+
expect(results.find((r) => r.pattern === "free-form-write")?.convertible).toBe(true);
|
|
599
|
+
expect(results.find((r) => r.pattern === "pipeline-write")?.convertible).toBe(false);
|
|
600
|
+
expect(results.find((r) => r.pattern === "query-handler")?.convertible).toBe(false);
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
describe("runCodemod", () => {
|
|
605
|
+
it("returns empty report when scan finds no convertible handlers", async () => {
|
|
606
|
+
const root = freshScanRoot("run-empty");
|
|
607
|
+
const report = await runCodemod(root);
|
|
608
|
+
expect(report).toEqual({ results: [], converted: 0, skipped: 0, errors: 0 });
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it("converts free-form write handlers discovered by scan", async () => {
|
|
612
|
+
const root = freshScanRoot("run-convert");
|
|
613
|
+
const p = writeFixtureIn(root, "to-convert.write.ts", staticReturnContent);
|
|
614
|
+
const report = await runCodemod(root);
|
|
615
|
+
expect(report.converted).toBe(1);
|
|
616
|
+
expect(report.skipped).toBe(0);
|
|
617
|
+
expect(report.errors).toBe(0);
|
|
618
|
+
expect(readFileSync(p, "utf8")).toContain("perform: stepsPipeline");
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it("verbose logs per-file outcomes", async () => {
|
|
622
|
+
const root = freshScanRoot("run-verbose");
|
|
623
|
+
writeFixtureIn(root, "verbose-convert.write.ts", staticReturnContent);
|
|
624
|
+
writeFixtureIn(root, "verbose-skip.write.ts", complexContent);
|
|
625
|
+
|
|
626
|
+
const logs: string[] = [];
|
|
627
|
+
const origLog = console.log;
|
|
628
|
+
console.log = (...args: unknown[]) => {
|
|
629
|
+
logs.push(args.map(String).join(" "));
|
|
630
|
+
};
|
|
631
|
+
try {
|
|
632
|
+
const report = await runCodemod(root, { verbose: true });
|
|
633
|
+
expect(report.converted).toBe(1);
|
|
634
|
+
expect(report.skipped).toBe(1);
|
|
635
|
+
expect(logs.some((l) => l.includes("verbose-convert.write.ts"))).toBe(true);
|
|
636
|
+
expect(logs.some((l) => l.includes("verbose-skip.write.ts"))).toBe(true);
|
|
637
|
+
} finally {
|
|
638
|
+
console.log = origLog;
|
|
639
|
+
}
|
|
640
|
+
});
|
|
512
641
|
});
|
|
513
642
|
|
|
514
643
|
describe("generatePerformBlock", () => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { defineRoles } from "../define-roles";
|
|
3
|
+
|
|
4
|
+
describe("defineRoles", () => {
|
|
5
|
+
test("maps each role name to itself", () => {
|
|
6
|
+
const roles = defineRoles(["Admin", "SystemAdmin", "Driver"] as const);
|
|
7
|
+
expect(roles.Admin).toBe("Admin");
|
|
8
|
+
expect(roles.SystemAdmin).toBe("SystemAdmin");
|
|
9
|
+
expect(roles.Driver).toBe("Driver");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("returns an object with exactly the given keys", () => {
|
|
13
|
+
const roles = defineRoles(["A", "B"] as const);
|
|
14
|
+
expect(Object.keys(roles).sort()).toEqual(["A", "B"]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("an empty role list returns an empty object", () => {
|
|
18
|
+
const roles = defineRoles([] as const);
|
|
19
|
+
expect(roles).toEqual({});
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
createTextField,
|
|
15
15
|
} from "../factories";
|
|
16
16
|
import { createApp, createRegistry, defineFeature } from "../index";
|
|
17
|
+
import { buildMinimalCtx } from "./_pipeline-test-utils";
|
|
17
18
|
|
|
18
19
|
// --- defineFeature ---
|
|
19
20
|
|
|
@@ -118,6 +119,33 @@ describe("defineFeature", () => {
|
|
|
118
119
|
expect(ref?.name).toBe("order:list");
|
|
119
120
|
});
|
|
120
121
|
|
|
122
|
+
test("streamHandler registers on feature.streamHandlers with schema/access/handler intact", async () => {
|
|
123
|
+
let ref: { name: string } | undefined;
|
|
124
|
+
const feature = defineFeature("test", (r) => {
|
|
125
|
+
ref = r.streamHandler(
|
|
126
|
+
"chat:complete",
|
|
127
|
+
z.object({ prompt: z.string() }),
|
|
128
|
+
async function* (query) {
|
|
129
|
+
yield `echo:${query.payload.prompt}`;
|
|
130
|
+
},
|
|
131
|
+
{ access: { openToAll: true } },
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
expect(ref?.name).toBe("chat:complete");
|
|
135
|
+
|
|
136
|
+
const stored = feature.streamHandlers["chat:complete"];
|
|
137
|
+
expect(stored?.access).toEqual({ openToAll: true });
|
|
138
|
+
|
|
139
|
+
const chunks: string[] = [];
|
|
140
|
+
for await (const chunk of stored!.handler(
|
|
141
|
+
{ type: "chat:complete", payload: { prompt: "hi" }, user: createTestUser() },
|
|
142
|
+
buildMinimalCtx(),
|
|
143
|
+
)) {
|
|
144
|
+
chunks.push(chunk as string);
|
|
145
|
+
}
|
|
146
|
+
expect(chunks).toEqual(["echo:hi"]);
|
|
147
|
+
});
|
|
148
|
+
|
|
121
149
|
test("r.defineEvent returns typed EventDef and registers on feature", () => {
|
|
122
150
|
let eventRef: { name: string } | undefined;
|
|
123
151
|
const feature = defineFeature("orders", (r) => {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Type-only fixture for the KumikoEventTypeMap declaration-merging channel
|
|
2
|
+
// (#1394). event-type-map.ts moved the marker interfaces behind a re-export
|
|
3
|
+
// chain (kumiko-types -> engine/types/event-type-map.ts shim -> engine
|
|
4
|
+
// barrel), while the codegen augmentation channel
|
|
5
|
+
// (dev-server/src/codegen/render.ts) still targets
|
|
6
|
+
// `declare module "@cosmicdrift/kumiko-framework/engine"`. If a future
|
|
7
|
+
// change breaks that re-export chain, augmentation silently stops merging
|
|
8
|
+
// and every event payload falls back to `unknown` with no compile error —
|
|
9
|
+
// this test only catches it because tsc checks the `declare module` below
|
|
10
|
+
// against the SAME specifier the codegen emits.
|
|
11
|
+
import { expectTypeOf, test } from "bun:test";
|
|
12
|
+
import type { KumikoEventTypeMap } from "@cosmicdrift/kumiko-framework/engine";
|
|
13
|
+
|
|
14
|
+
declare module "@cosmicdrift/kumiko-framework/engine" {
|
|
15
|
+
interface KumikoEventTypeMap {
|
|
16
|
+
"event-type-map-fixture:probe.created": { readonly probe: true };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
test("augmenting KumikoEventTypeMap via the engine specifier merges into the relocated interface", () => {
|
|
21
|
+
expectTypeOf<KumikoEventTypeMap["event-type-map-fixture:probe.created"]>().toEqualTypeOf<{
|
|
22
|
+
readonly probe: true;
|
|
23
|
+
}>();
|
|
24
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Dilution reclaim: integration lcov registers every facade getter as DA:N,0
|
|
2
|
+
// when the file loads, but unit only instruments getters that a test touches.
|
|
3
|
+
// Calling every getter once in unit raises LF+LH so the badge merge stops
|
|
4
|
+
// counting those one-liners as uncovered.
|
|
5
|
+
|
|
6
|
+
import { describe, expect, test } from "bun:test";
|
|
7
|
+
import { createRegistry } from "../registry";
|
|
8
|
+
|
|
9
|
+
describe("buildRegistryFacade — getter sweep", () => {
|
|
10
|
+
test("empty registry: every getter is callable without throw", () => {
|
|
11
|
+
const r = createRegistry([]);
|
|
12
|
+
|
|
13
|
+
expect(r.features.size).toBe(0);
|
|
14
|
+
expect(r.getFeature("x")).toBeUndefined();
|
|
15
|
+
expect(r.hasRateLimitedHandler()).toBe(false);
|
|
16
|
+
expect(r.getEntity("x")).toBeUndefined();
|
|
17
|
+
expect(r.getAllEntities().size).toBe(0);
|
|
18
|
+
expect(r.getWriteHandler("x")).toBeUndefined();
|
|
19
|
+
expect(r.getQueryHandler("x")).toBeUndefined();
|
|
20
|
+
expect(r.getAllQueryHandlers().size).toBe(0);
|
|
21
|
+
expect(r.getSearchableFields("x")).toEqual([]);
|
|
22
|
+
expect(r.getSortableFields("x")).toEqual([]);
|
|
23
|
+
expect(r.getRelations("x")).toEqual({});
|
|
24
|
+
expect(r.getSearchIncludes("x").size).toBe(0);
|
|
25
|
+
expect(r.getIncomingRelations("x")).toEqual([]);
|
|
26
|
+
expect(r.getPreSaveHooks("x")).toEqual([]);
|
|
27
|
+
expect(r.getPostSaveHooks("x")).toEqual([]);
|
|
28
|
+
expect(r.getPreDeleteHooks("x")).toEqual([]);
|
|
29
|
+
expect(r.getPostDeleteHooks("x")).toEqual([]);
|
|
30
|
+
expect(r.getPreQueryHooks("x")).toEqual([]);
|
|
31
|
+
expect(r.getPostQueryHooks("x")).toEqual([]);
|
|
32
|
+
expect(r.getEntityPostSaveHooks("x")).toEqual([]);
|
|
33
|
+
expect(r.getEntityPreDeleteHooks("x")).toEqual([]);
|
|
34
|
+
expect(r.getEntityPostDeleteHooks("x")).toEqual([]);
|
|
35
|
+
expect(r.getEntityPostQueryHooks("x")).toEqual([]);
|
|
36
|
+
expect(r.getSearchPayloadExtensions("x")).toEqual([]);
|
|
37
|
+
expect(r.getAllTranslations()).toBeDefined();
|
|
38
|
+
expect(r.getHandlerEntity("x")).toBeUndefined();
|
|
39
|
+
expect(r.isHandlerSystemScoped("x")).toBe(false);
|
|
40
|
+
expect(r.getHandlerFeature("x")).toBeUndefined();
|
|
41
|
+
expect(r.getAllMetrics().size).toBe(0);
|
|
42
|
+
expect(r.getAllSecretKeys().size).toBe(0);
|
|
43
|
+
expect(r.getSecretKey("x")).toBeUndefined();
|
|
44
|
+
expect(r.getConfigKey("x")).toBeUndefined();
|
|
45
|
+
expect(r.getAllConfigKeys().size).toBe(0);
|
|
46
|
+
expect(r.getJob("x")).toBeUndefined();
|
|
47
|
+
expect(r.getAllJobs().size).toBe(0);
|
|
48
|
+
expect(r.getEvent("x")).toBeUndefined();
|
|
49
|
+
expect(r.getEventUpcasters().size).toBe(0);
|
|
50
|
+
expect(r.getExtension("x")).toBeUndefined();
|
|
51
|
+
expect(r.getExtensionUsages("x")).toEqual([]);
|
|
52
|
+
expect(r.getAllExtensionSelectors().size).toBe(0);
|
|
53
|
+
expect(r.getAllNotifications().size).toBe(0);
|
|
54
|
+
expect(r.getAllReferenceData()).toEqual([]);
|
|
55
|
+
expect(r.getAllConfigSeeds()).toEqual([]);
|
|
56
|
+
expect(r.getProjectionsForSource("x")).toEqual([]);
|
|
57
|
+
expect(r.getAllProjections().size).toBe(0);
|
|
58
|
+
expect(r.getAllStoreTables().size).toBe(0);
|
|
59
|
+
expect(r.getAllMultiStreamProjections().size).toBe(0);
|
|
60
|
+
expect(r.getMultiStreamProjectionFeature("x")).toBeUndefined();
|
|
61
|
+
expect(r.getAuthClaimsHooks()).toEqual([]);
|
|
62
|
+
expect(r.getAllClaimKeys().size).toBe(0);
|
|
63
|
+
expect(r.getClaimKey("x")).toBeUndefined();
|
|
64
|
+
expect(r.getAllScreens().size).toBe(0);
|
|
65
|
+
expect(r.getScreen("x")).toBeUndefined();
|
|
66
|
+
expect(r.getScreenFeature("x")).toBeUndefined();
|
|
67
|
+
expect(r.getScreensByEntity("x")).toEqual([]);
|
|
68
|
+
expect(r.getAllNavs().size).toBe(0);
|
|
69
|
+
expect(r.getNav("x")).toBeUndefined();
|
|
70
|
+
expect(r.getNavFeature("x")).toBeUndefined();
|
|
71
|
+
expect(r.getNavsByParent("x")).toEqual([]);
|
|
72
|
+
expect(r.getTopLevelNavs()).toEqual([]);
|
|
73
|
+
expect(r.getAllWorkspaces().size).toBe(0);
|
|
74
|
+
expect(r.getWorkspace("x")).toBeUndefined();
|
|
75
|
+
expect(r.getWorkspaceFeature("x")).toBeUndefined();
|
|
76
|
+
expect(r.getWorkspaceNavs("x")).toEqual([]);
|
|
77
|
+
expect(r.getDefaultWorkspace()).toBeUndefined();
|
|
78
|
+
expect(r.getTreeActions("x")).toBeUndefined();
|
|
79
|
+
});
|
|
80
|
+
});
|