@cosmicdrift/kumiko-framework 0.2.2 → 0.3.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/CHANGELOG.md +54 -0
- package/package.json +124 -38
- package/src/__tests__/full-stack.integration.ts +2 -2
- package/src/api/auth-routes.ts +5 -5
- package/src/api/jwt.ts +2 -2
- package/src/api/route-registrars.ts +1 -1
- package/src/api/routes.ts +3 -3
- package/src/api/server.ts +6 -7
- package/src/auth/__tests__/roles.test.ts +24 -0
- package/src/auth/index.ts +7 -0
- package/src/auth/roles.ts +42 -0
- package/src/compliance/__tests__/duration-spec.test.ts +72 -0
- package/src/compliance/__tests__/profiles.test.ts +308 -0
- package/src/compliance/__tests__/sub-processors.test.ts +139 -0
- package/src/compliance/duration-spec.ts +44 -0
- package/src/compliance/index.ts +31 -0
- package/src/compliance/override-schema.ts +136 -0
- package/src/compliance/profiles.ts +427 -0
- package/src/compliance/sub-processors.ts +152 -0
- package/src/db/__tests__/big-int-field.test.ts +131 -0
- package/src/db/assert-exists-in.ts +2 -2
- package/src/db/cursor.ts +3 -3
- package/src/db/event-store-executor.ts +19 -13
- package/src/db/located-timestamp.ts +1 -1
- package/src/db/money.ts +12 -2
- package/src/db/pg-error.ts +1 -1
- package/src/db/row-helpers.ts +1 -1
- package/src/db/table-builder.ts +20 -5
- package/src/db/tenant-db.ts +9 -9
- package/src/engine/__tests__/_pipeline-test-utils.ts +23 -0
- package/src/engine/__tests__/boot-validator-api-exposure.test.ts +142 -0
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +570 -0
- package/src/engine/__tests__/boot-validator-s0-integration.test.ts +160 -0
- package/src/engine/__tests__/build-target.test.ts +135 -0
- package/src/engine/__tests__/codemod-pipeline.test.ts +551 -0
- package/src/engine/__tests__/entity-handlers.test.ts +3 -3
- package/src/engine/__tests__/event-helpers.test.ts +4 -4
- package/src/engine/__tests__/pipeline-engine.test.ts +215 -0
- package/src/engine/__tests__/pipeline-handler.integration.ts +894 -0
- package/src/engine/__tests__/pipeline-observability.integration.ts +142 -0
- package/src/engine/__tests__/pipeline-performance.integration.ts +152 -0
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +288 -0
- package/src/engine/__tests__/raw-table.test.ts +2 -2
- package/src/engine/__tests__/steps-aggregate-append-event.test.ts +115 -0
- package/src/engine/__tests__/steps-aggregate-create.test.ts +92 -0
- package/src/engine/__tests__/steps-aggregate-update.test.ts +127 -0
- package/src/engine/__tests__/steps-call-feature.test.ts +123 -0
- package/src/engine/__tests__/steps-mail-send.test.ts +136 -0
- package/src/engine/__tests__/steps-read.test.ts +142 -0
- package/src/engine/__tests__/steps-resolver-utils.test.ts +50 -0
- package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +69 -0
- package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +117 -0
- package/src/engine/__tests__/steps-webhook-send.test.ts +135 -0
- package/src/engine/__tests__/steps-workflow.test.ts +198 -0
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +491 -0
- package/src/engine/__tests__/visual-tree-patterns.test.ts +251 -0
- package/src/engine/boot-validator/api-ext.ts +77 -0
- package/src/engine/boot-validator/config-deps.ts +163 -0
- package/src/engine/boot-validator/entity-handler.ts +466 -0
- package/src/engine/boot-validator/index.ts +159 -0
- package/src/engine/boot-validator/ownership.ts +198 -0
- package/src/engine/boot-validator/pii-retention.ts +155 -0
- package/src/engine/boot-validator/screens-nav.ts +624 -0
- package/src/engine/boot-validator.ts +1 -1528
- package/src/engine/build-app-schema.ts +1 -1
- package/src/engine/build-target.ts +99 -0
- package/src/engine/codemod/index.ts +15 -0
- package/src/engine/codemod/pipeline-codemod.ts +641 -0
- package/src/engine/config-helpers.ts +9 -19
- package/src/engine/constants.ts +1 -1
- package/src/engine/define-feature.ts +127 -9
- package/src/engine/define-handler.ts +89 -3
- package/src/engine/define-roles.ts +2 -2
- package/src/engine/define-step.ts +28 -0
- package/src/engine/define-workflow.ts +110 -0
- package/src/engine/entity-handlers.ts +10 -9
- package/src/engine/event-helpers.ts +4 -4
- package/src/engine/extension-names.ts +105 -0
- package/src/engine/extensions/user-data.ts +106 -0
- package/src/engine/factories.ts +26 -16
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +184 -0
- package/src/engine/feature-ast/extractors/index.ts +74 -0
- package/src/engine/feature-ast/extractors/round1.ts +110 -0
- package/src/engine/feature-ast/extractors/round2.ts +253 -0
- package/src/engine/feature-ast/extractors/round3.ts +471 -0
- package/src/engine/feature-ast/extractors/round4.ts +1365 -0
- package/src/engine/feature-ast/extractors/round5.ts +72 -0
- package/src/engine/feature-ast/extractors/round6.ts +66 -0
- package/src/engine/feature-ast/extractors/shared.ts +177 -0
- package/src/engine/feature-ast/parse.ts +13 -0
- package/src/engine/feature-ast/patch.ts +9 -1
- package/src/engine/feature-ast/patcher.ts +10 -3
- package/src/engine/feature-ast/patterns.ts +71 -1
- package/src/engine/feature-ast/render.ts +31 -1
- package/src/engine/index.ts +66 -2
- package/src/engine/pattern-library/__tests__/library.test.ts +11 -0
- package/src/engine/pattern-library/library.ts +78 -2
- package/src/engine/pipeline.ts +88 -0
- package/src/engine/projection-helpers.ts +1 -1
- package/src/engine/read-claim.ts +1 -1
- package/src/engine/registry.ts +30 -2
- package/src/engine/resolve-config-or-param.ts +4 -0
- package/src/engine/run-pipeline.ts +162 -0
- package/src/engine/schema-builder.ts +10 -4
- package/src/engine/state-machine.ts +1 -1
- package/src/engine/steps/_drizzle-boundary.ts +19 -0
- package/src/engine/steps/_duration-utils.ts +33 -0
- package/src/engine/steps/_no-return-guard.ts +21 -0
- package/src/engine/steps/_resolver-utils.ts +42 -0
- package/src/engine/steps/_step-dispatch-constants.ts +38 -0
- package/src/engine/steps/aggregate-append-event.ts +56 -0
- package/src/engine/steps/aggregate-create.ts +56 -0
- package/src/engine/steps/aggregate-update.ts +68 -0
- package/src/engine/steps/branch.ts +84 -0
- package/src/engine/steps/call-feature.ts +49 -0
- package/src/engine/steps/compute.ts +41 -0
- package/src/engine/steps/for-each.ts +111 -0
- package/src/engine/steps/mail-send.ts +44 -0
- package/src/engine/steps/read-find-many.ts +51 -0
- package/src/engine/steps/read-find-one.ts +58 -0
- package/src/engine/steps/retry.ts +87 -0
- package/src/engine/steps/return.ts +34 -0
- package/src/engine/steps/unsafe-projection-delete.ts +46 -0
- package/src/engine/steps/unsafe-projection-upsert.ts +69 -0
- package/src/engine/steps/wait-for-event.ts +71 -0
- package/src/engine/steps/wait.ts +69 -0
- package/src/engine/steps/webhook-send.ts +71 -0
- package/src/engine/system-user.ts +1 -1
- package/src/engine/types/feature.ts +143 -1
- package/src/engine/types/fields.ts +134 -10
- package/src/engine/types/handlers.ts +18 -10
- package/src/engine/types/identifiers.ts +1 -0
- package/src/engine/types/index.ts +15 -1
- package/src/engine/types/step.ts +334 -0
- package/src/engine/types/target-ref.ts +21 -0
- package/src/engine/types/tree-node.ts +130 -0
- package/src/engine/types/workspace.ts +7 -0
- package/src/engine/validate-projection-allowlist.ts +161 -0
- package/src/event-store/snapshot.ts +1 -1
- package/src/event-store/upcaster-dead-letter.ts +1 -1
- package/src/event-store/upcaster.ts +1 -1
- package/src/files/__tests__/read-stream.test.ts +105 -0
- package/src/files/__tests__/write-stream.test.ts +233 -0
- package/src/files/__tests__/zip-stream.test.ts +357 -0
- package/src/files/file-routes.ts +1 -1
- package/src/files/in-memory-provider.ts +38 -0
- package/src/files/index.ts +3 -0
- package/src/files/local-provider.ts +58 -1
- package/src/files/types.ts +36 -8
- package/src/files/zip-stream.ts +251 -0
- package/src/jobs/job-runner.ts +10 -10
- package/src/lifecycle/lifecycle.ts +0 -3
- package/src/logging/index.ts +1 -0
- package/src/logging/pino-logger.ts +11 -7
- package/src/logging/utils.ts +24 -0
- package/src/observability/prometheus-meter.ts +7 -5
- package/src/pipeline/__tests__/archive-stream.integration.ts +1 -1
- package/src/pipeline/__tests__/causation-chain.integration.ts +1 -1
- package/src/pipeline/__tests__/domain-events-projections.integration.ts +3 -3
- package/src/pipeline/__tests__/event-define-event-strict.integration.ts +4 -4
- package/src/pipeline/__tests__/load-aggregate-query.integration.ts +1 -1
- package/src/pipeline/__tests__/msp-multi-hop.integration.ts +3 -3
- package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
- package/src/pipeline/__tests__/multi-stream-projection.integration.ts +2 -2
- package/src/pipeline/__tests__/query-projection.integration.ts +5 -5
- package/src/pipeline/append-event-core.ts +22 -6
- package/src/pipeline/dispatcher-utils.ts +188 -0
- package/src/pipeline/dispatcher.ts +63 -283
- package/src/pipeline/distributed-lock.ts +1 -1
- package/src/pipeline/entity-cache.ts +2 -2
- package/src/pipeline/event-consumer-state.ts +0 -13
- package/src/pipeline/event-dispatcher.ts +4 -4
- package/src/pipeline/index.ts +0 -2
- package/src/pipeline/lifecycle-pipeline.ts +6 -12
- package/src/pipeline/msp-rebuild.ts +5 -5
- package/src/pipeline/multi-stream-apply-context.ts +6 -7
- package/src/pipeline/projection-rebuild.ts +2 -2
- package/src/pipeline/projection-state.ts +0 -12
- package/src/rate-limit/__tests__/resolver.integration.ts +8 -4
- package/src/rate-limit/resolver.ts +1 -1
- package/src/search/in-memory-adapter.ts +1 -1
- package/src/search/meilisearch-adapter.ts +3 -3
- package/src/search/types.ts +1 -1
- package/src/secrets/leak-guard.ts +2 -2
- package/src/stack/request-helper.ts +9 -5
- package/src/stack/test-stack.ts +1 -1
- package/src/testing/handler-context.ts +4 -4
- package/src/testing/http-cookies.ts +1 -1
- package/src/time/tz-context.ts +1 -2
- package/src/ui-types/index.ts +4 -0
- package/src/engine/feature-ast/extractors.ts +0 -2562
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Integration-Test: alle Sprint-0-Surfaces in einem Mini-Feature-Set.
|
|
2
|
+
//
|
|
3
|
+
// Beweist dass die einzelnen S0-Komponenten (PII-Annotations, retention,
|
|
4
|
+
// extension-names, exposesApi/usesApi, ROLES) zusammen funktionieren —
|
|
5
|
+
// keine still-konkurrierenden Validierungen, keine Race-Conditions
|
|
6
|
+
// zwischen Sub-Validatoren.
|
|
7
|
+
//
|
|
8
|
+
// Mini-Feature-Set:
|
|
9
|
+
// compliance-profiles exposesApi("compliance.forTenant")
|
|
10
|
+
// user-data-rights usesApi + extendsRegistrar(EXT_USER_DATA)
|
|
11
|
+
// tenant useExtension(EXT_USER_DATA, "user", ...)
|
|
12
|
+
// + entity mit pii / userOwned / tenantOwned-Fields
|
|
13
|
+
// + retention.blockDelete + anonymize-Funktion
|
|
14
|
+
// + handler-access mit ROLES.TenantAdmin
|
|
15
|
+
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { ROLES } from "../../auth";
|
|
19
|
+
import { validateBoot } from "../boot-validator";
|
|
20
|
+
import { defineFeature } from "../define-feature";
|
|
21
|
+
import {
|
|
22
|
+
createEntity,
|
|
23
|
+
createLongTextField,
|
|
24
|
+
createTextField,
|
|
25
|
+
createTimestampField,
|
|
26
|
+
EXT_USER_DATA,
|
|
27
|
+
} from "../index";
|
|
28
|
+
|
|
29
|
+
describe("S0 Integration — full surface stack", () => {
|
|
30
|
+
let warnSpy: ReturnType<typeof vi.spyOn>;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
warnSpy.mockRestore();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("alle S0-Surfaces zusammen passen Boot-Validation", () => {
|
|
41
|
+
const complianceProfiles = defineFeature("compliance-profiles", (r) => {
|
|
42
|
+
r.exposesApi("compliance.forTenant");
|
|
43
|
+
r.queryHandler({
|
|
44
|
+
name: "compliance:query:for-tenant",
|
|
45
|
+
schema: z.object({}),
|
|
46
|
+
handler: async () => ({ profile: "eu-dsgvo" }) as never,
|
|
47
|
+
access: { openToAll: true },
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const userDataRights = defineFeature("user-data-rights", (r) => {
|
|
52
|
+
r.requires("compliance-profiles");
|
|
53
|
+
r.usesApi("compliance.forTenant");
|
|
54
|
+
r.extendsRegistrar(EXT_USER_DATA, {
|
|
55
|
+
hooks: {},
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const tenantFeature = defineFeature("tenant-app", (r) => {
|
|
60
|
+
r.requires("user-data-rights", "compliance-profiles");
|
|
61
|
+
|
|
62
|
+
r.entity(
|
|
63
|
+
"user",
|
|
64
|
+
createEntity({
|
|
65
|
+
fields: {
|
|
66
|
+
email: createTextField({ pii: true }),
|
|
67
|
+
displayName: createTextField({ pii: true }),
|
|
68
|
+
lastLoginAt: createTimestampField({ pii: true }),
|
|
69
|
+
},
|
|
70
|
+
retention: {
|
|
71
|
+
keepFor: "10y",
|
|
72
|
+
strategy: "blockDelete",
|
|
73
|
+
reference: "createdAt",
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
r.entity(
|
|
79
|
+
"comment",
|
|
80
|
+
createEntity({
|
|
81
|
+
fields: {
|
|
82
|
+
body: createLongTextField({
|
|
83
|
+
userOwned: { ownerField: "authorId" },
|
|
84
|
+
anonymize: () => "[ANONYMIZED]",
|
|
85
|
+
}),
|
|
86
|
+
authorId: { type: "reference", entity: "user" },
|
|
87
|
+
},
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
r.queryHandler({
|
|
92
|
+
name: "user:list",
|
|
93
|
+
schema: z.object({}),
|
|
94
|
+
handler: async () => ({ rows: [], nextCursor: null }) as never,
|
|
95
|
+
access: { openToAll: true },
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
r.useExtension(EXT_USER_DATA, "user", {});
|
|
99
|
+
r.useExtension(EXT_USER_DATA, "comment", {});
|
|
100
|
+
|
|
101
|
+
r.writeHandler({
|
|
102
|
+
name: "user:rename",
|
|
103
|
+
schema: z.object({ id: z.string(), displayName: z.string() }),
|
|
104
|
+
handler: async () => undefined as never,
|
|
105
|
+
access: { roles: [ROLES.TenantAdmin] },
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(() => validateBoot([complianceProfiles, userDataRights, tenantFeature])).not.toThrow();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("missing requires() on usesApi-target throws even when other surfaces are clean", () => {
|
|
113
|
+
const complianceProfiles = defineFeature("compliance-profiles", (r) => {
|
|
114
|
+
r.exposesApi("compliance.forTenant");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const userDataRights = defineFeature("user-data-rights", (r) => {
|
|
118
|
+
// VERGESSEN: r.requires("compliance-profiles")
|
|
119
|
+
r.usesApi("compliance.forTenant");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(() => validateBoot([complianceProfiles, userDataRights])).toThrow(
|
|
123
|
+
/not in requires\/optionalRequires\. Add r\.requires\("compliance-profiles"\)/,
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("retention.reference pointing to non-existent field throws even with valid PII annotations", () => {
|
|
128
|
+
const feature = defineFeature("test", (r) => {
|
|
129
|
+
r.entity(
|
|
130
|
+
"user",
|
|
131
|
+
createEntity({
|
|
132
|
+
fields: {
|
|
133
|
+
email: createTextField({ pii: true }),
|
|
134
|
+
},
|
|
135
|
+
retention: {
|
|
136
|
+
keepFor: "30d",
|
|
137
|
+
strategy: "hardDelete",
|
|
138
|
+
reference: "notARealField",
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
expect(() => validateBoot([feature])).toThrow(
|
|
144
|
+
/retention\.reference "notARealField" does not exist/,
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("ROLES.TenantAdmin works as handler-access role string (no Admin/TenantAdmin drift)", () => {
|
|
149
|
+
const feature = defineFeature("test", (r) => {
|
|
150
|
+
r.entity("thing", createEntity({ fields: { ts: createTimestampField() } }));
|
|
151
|
+
r.writeHandler({
|
|
152
|
+
name: "thing:create",
|
|
153
|
+
schema: z.object({}),
|
|
154
|
+
handler: async () => undefined as never,
|
|
155
|
+
access: { roles: [ROLES.TenantAdmin] },
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
expect(() => validateBoot([feature])).not.toThrow();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { buildTarget, type TreeActionDef } from "../index";
|
|
3
|
+
|
|
4
|
+
// createTreeActionsStub — Test-Helper für Phase-0-Stub-Features. Das
|
|
5
|
+
// `const`-Generic-Modifier forciert Literal-Inference, sodass die
|
|
6
|
+
// Action-Namen als string-literal-Union ankommen (statt zu `string`
|
|
7
|
+
// widening). In V.1.1 wird der Helper überflüssig: echte defineFeature-
|
|
8
|
+
// Outputs haben dieselbe Shape und Tests konsumieren die direkt.
|
|
9
|
+
function createTreeActionsStub<const TActions extends Record<string, TreeActionDef>>(spec: {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly treeActions: TActions;
|
|
12
|
+
}): { readonly id: string; readonly treeActions: TActions } {
|
|
13
|
+
return Object.freeze({ id: spec.id, treeActions: spec.treeActions });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const textContentStub = createTreeActionsStub({
|
|
17
|
+
id: "text-content",
|
|
18
|
+
treeActions: {
|
|
19
|
+
edit: { args: { slug: "" as string } },
|
|
20
|
+
create: { args: { folder: "" as string } },
|
|
21
|
+
list: {},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("buildTarget — NoArgs-Action", () => {
|
|
26
|
+
test("erzeugt TargetRef ohne args-Feld", () => {
|
|
27
|
+
const ref = buildTarget({ target: textContentStub, action: "list" });
|
|
28
|
+
expect(ref).toEqual({ featureId: "text-content", action: "list" });
|
|
29
|
+
expect("args" in ref).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("output ist frozen (immutable)", () => {
|
|
33
|
+
const ref = buildTarget({ target: textContentStub, action: "list" });
|
|
34
|
+
expect(Object.isFrozen(ref)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("buildTarget — WithArgs-Action", () => {
|
|
39
|
+
test("erzeugt TargetRef mit args", () => {
|
|
40
|
+
const ref = buildTarget({
|
|
41
|
+
target: textContentStub,
|
|
42
|
+
action: "edit",
|
|
43
|
+
args: { slug: "imprint" },
|
|
44
|
+
});
|
|
45
|
+
expect(ref).toEqual({
|
|
46
|
+
featureId: "text-content",
|
|
47
|
+
action: "edit",
|
|
48
|
+
args: { slug: "imprint" },
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("args sind frozen — Mutation am Input-Objekt schlägt nicht durch", () => {
|
|
53
|
+
const inputArgs = { slug: "imprint" };
|
|
54
|
+
const ref = buildTarget({
|
|
55
|
+
target: textContentStub,
|
|
56
|
+
action: "edit",
|
|
57
|
+
args: inputArgs,
|
|
58
|
+
});
|
|
59
|
+
inputArgs.slug = "mutated";
|
|
60
|
+
expect(ref.args).toEqual({ slug: "imprint" });
|
|
61
|
+
expect(Object.isFrozen(ref.args)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("verschiedene Actions haben unterschiedliche Arg-Shapes", () => {
|
|
65
|
+
const editRef = buildTarget({
|
|
66
|
+
target: textContentStub,
|
|
67
|
+
action: "edit",
|
|
68
|
+
args: { slug: "imprint" },
|
|
69
|
+
});
|
|
70
|
+
const createRef = buildTarget({
|
|
71
|
+
target: textContentStub,
|
|
72
|
+
action: "create",
|
|
73
|
+
args: { folder: "/marketing" },
|
|
74
|
+
});
|
|
75
|
+
expect(editRef.args).toEqual({ slug: "imprint" });
|
|
76
|
+
expect(createRef.args).toEqual({ folder: "/marketing" });
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("buildTarget — Compile-Time-Safety (verified via @ts-expect-error)", () => {
|
|
81
|
+
// Jeder Test paart ein @ts-expect-error-Block (compile-time-validation
|
|
82
|
+
// via TypeScript) mit einem runtime-expect über den korrespondierenden
|
|
83
|
+
// Happy-Path. Doppelt-Coverage: Compiler prüft Rejection, vitest prüft
|
|
84
|
+
// dass die korrekte Form bei valid input das richtige Ergebnis liefert.
|
|
85
|
+
// Memory `[Keine Fake-Tests]` — Tests müssen runtime-Verhalten prüfen,
|
|
86
|
+
// nicht nur Compile-Time (Fake-Test-Guard).
|
|
87
|
+
|
|
88
|
+
test("unbekannte action wird vom Compiler abgelehnt", () => {
|
|
89
|
+
// @ts-expect-error — "delet" ist keine Action von textContentStub
|
|
90
|
+
buildTarget({ target: textContentStub, action: "delet" });
|
|
91
|
+
// Runtime: bekannte Action liefert valid TargetRef
|
|
92
|
+
const ref = buildTarget({ target: textContentStub, action: "list" });
|
|
93
|
+
expect(ref.action).toBe("list");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("falsche args-shape wird vom Compiler abgelehnt", () => {
|
|
97
|
+
// @ts-expect-error — slug muss string sein, nicht number
|
|
98
|
+
buildTarget({
|
|
99
|
+
target: textContentStub,
|
|
100
|
+
action: "edit",
|
|
101
|
+
args: { slug: 42 },
|
|
102
|
+
});
|
|
103
|
+
// Runtime: korrekt-typed args liefern valid TargetRef
|
|
104
|
+
const ref = buildTarget({
|
|
105
|
+
target: textContentStub,
|
|
106
|
+
action: "edit",
|
|
107
|
+
args: { slug: "imprint" },
|
|
108
|
+
});
|
|
109
|
+
expect(ref.args).toEqual({ slug: "imprint" });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("args bei NoArgs-Action wird vom Compiler abgelehnt", () => {
|
|
113
|
+
buildTarget({
|
|
114
|
+
target: textContentStub,
|
|
115
|
+
action: "list",
|
|
116
|
+
// @ts-expect-error — list hat keine args, args-Feld nicht erlaubt
|
|
117
|
+
args: { x: 1 },
|
|
118
|
+
});
|
|
119
|
+
// Runtime: NoArgs-Action ohne args-Feld liefert valid TargetRef
|
|
120
|
+
const ref = buildTarget({ target: textContentStub, action: "list" });
|
|
121
|
+
expect("args" in ref).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("fehlende args bei WithArgs-Action wird vom Compiler abgelehnt", () => {
|
|
125
|
+
// @ts-expect-error — edit braucht args, fehlt
|
|
126
|
+
buildTarget({ target: textContentStub, action: "edit" });
|
|
127
|
+
// Runtime: WithArgs-Action mit args liefert valid TargetRef
|
|
128
|
+
const ref = buildTarget({
|
|
129
|
+
target: textContentStub,
|
|
130
|
+
action: "edit",
|
|
131
|
+
args: { slug: "imprint" },
|
|
132
|
+
});
|
|
133
|
+
expect(ref.args).toBeDefined();
|
|
134
|
+
});
|
|
135
|
+
});
|