@cosmicdrift/kumiko-framework 0.2.3 → 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 +52 -0
- package/package.json +124 -39
- 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/compliance/profiles.ts +8 -8
- 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 +3 -5
- package/src/db/tenant-db.ts +9 -9
- package/src/engine/__tests__/_pipeline-test-utils.ts +23 -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 -1804
- 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 +88 -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/factories.ts +12 -12
- 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 +7 -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 +49 -1
- package/src/engine/feature-ast/render.ts +17 -1
- package/src/engine/index.ts +45 -2
- package/src/engine/pattern-library/__tests__/library.test.ts +6 -0
- package/src/engine/pattern-library/library.ts +42 -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 +2 -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 +92 -1
- package/src/engine/types/handlers.ts +18 -10
- package/src/engine/types/identifiers.ts +1 -0
- package/src/engine/types/index.ts +12 -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/file-routes.ts +1 -1
- package/src/files/types.ts +2 -2
- 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 -2602
package/src/db/money.ts
CHANGED
|
@@ -26,6 +26,11 @@ const FRAMEWORK_DEFAULT_CURRENCY = DEFAULT_CURRENCIES[0]; // "EUR"
|
|
|
26
26
|
*
|
|
27
27
|
* Pure — mutiert nicht.
|
|
28
28
|
*/
|
|
29
|
+
interface MoneyPair {
|
|
30
|
+
amount: number;
|
|
31
|
+
currency?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
export function flattenMoney(
|
|
30
35
|
payload: Record<string, unknown>,
|
|
31
36
|
entity: EntityDefinition,
|
|
@@ -42,8 +47,13 @@ export function flattenMoney(
|
|
|
42
47
|
let amount: number;
|
|
43
48
|
let currency: string;
|
|
44
49
|
|
|
45
|
-
if (
|
|
46
|
-
|
|
50
|
+
if (
|
|
51
|
+
typeof raw === "object" &&
|
|
52
|
+
raw !== null &&
|
|
53
|
+
"amount" in raw &&
|
|
54
|
+
typeof (raw as MoneyPair).amount === "number" // @cast-boundary schema-walk
|
|
55
|
+
) {
|
|
56
|
+
const pair = raw as MoneyPair; // @cast-boundary schema-walk
|
|
47
57
|
amount = pair.amount;
|
|
48
58
|
currency = pair.currency ?? fallbackCurrency;
|
|
49
59
|
} else if (typeof raw === "number") {
|
package/src/db/pg-error.ts
CHANGED
|
@@ -20,7 +20,7 @@ export function extractPgError(e: unknown): PgErrorInfo | null {
|
|
|
20
20
|
for (const layer of layers) {
|
|
21
21
|
// @cast-boundary error-details — postgres-js error shape (code, constraint_name)
|
|
22
22
|
const code = (layer as { code?: string }).code;
|
|
23
|
-
const constraintName = (layer as { constraint_name?: string }).constraint_name;
|
|
23
|
+
const constraintName = (layer as { constraint_name?: string }).constraint_name; // @cast-boundary error-details
|
|
24
24
|
if (code !== undefined || constraintName !== undefined) {
|
|
25
25
|
return { code, constraint_name: constraintName };
|
|
26
26
|
}
|
package/src/db/row-helpers.ts
CHANGED
|
@@ -49,5 +49,5 @@ export async function fetchOne<TRow = DbRow>(
|
|
|
49
49
|
): Promise<TRow | undefined> {
|
|
50
50
|
const where = conditions.length === 1 ? conditions[0] : and(...conditions);
|
|
51
51
|
const rows = await db.select().from(table).where(where).limit(1);
|
|
52
|
-
return rows[0] as TRow | undefined;
|
|
52
|
+
return rows[0] as TRow | undefined; // @cast-boundary db-row
|
|
53
53
|
}
|
package/src/db/table-builder.ts
CHANGED
|
@@ -133,10 +133,8 @@ function fieldToColumns(
|
|
|
133
133
|
// wrapper-feld mit boolean-flag oder discriminierte-union.
|
|
134
134
|
return { [name]: jsonb(snakeName).default({}).notNull() };
|
|
135
135
|
case "date": {
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
// instant() = TIMESTAMPTZ damit Caller die gleiche API nutzen wie für
|
|
139
|
-
// type:"timestamp". Echte PlainDate-Migration kommt nach Sprint F.
|
|
136
|
+
// `type:"date"` aliased auf instant() = TIMESTAMPTZ. Echte
|
|
137
|
+
// PlainDate-Migration (PG `date` Spalte, kein TZ) kommt später.
|
|
140
138
|
const col = instant(snakeName);
|
|
141
139
|
return { [name]: field.required ? col.notNull() : col };
|
|
142
140
|
}
|
|
@@ -478,7 +476,7 @@ export function buildDrizzleTable<E extends EntityDefinition>(
|
|
|
478
476
|
def.name ?? `${tableName}_${def.columns.map((c) => toSnakeCase(c)).join("_")}_${suffix}`;
|
|
479
477
|
const builder = def.unique === true ? uniqueIndex(indexName) : index(indexName);
|
|
480
478
|
// biome-ignore lint/suspicious/noExplicitAny: drizzle's .on(...cols) is variadic generic
|
|
481
|
-
let chain = (builder.on as any)(...cols);
|
|
479
|
+
let chain = (builder.on as any)(...cols); // @cast-boundary drizzle-bridge
|
|
482
480
|
if (def.where !== undefined) {
|
|
483
481
|
// Partial-Index: drizzle's IndexBuilder.where(SQL) emittiert das
|
|
484
482
|
// `WHERE <condition>` ans Ende der `CREATE [UNIQUE] INDEX`-DDL.
|
package/src/db/tenant-db.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SYSTEM_TENANT_ID, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
1
|
import { and, type Column, eq, getTableName, or, type SQL } from "drizzle-orm";
|
|
2
|
+
import { SYSTEM_TENANT_ID, type TenantId } from "../engine/types/identifiers";
|
|
3
3
|
import { emitDbQuery, type Meter, registerStandardMetrics, type Tracer } from "../observability";
|
|
4
4
|
import type { DbRunner } from "./connection";
|
|
5
5
|
import type { TableColumns } from "./dialect";
|
|
@@ -148,7 +148,7 @@ export function createTenantDb(
|
|
|
148
148
|
// types don't include PromiseLike. Cast via this helper so the double-
|
|
149
149
|
// cast is named and lives in exactly one place per scope.
|
|
150
150
|
function asDrizzleThenable<T>(builder: unknown): PromiseLike<T> {
|
|
151
|
-
return builder as PromiseLike<T>;
|
|
151
|
+
return builder as PromiseLike<T>; // @cast-boundary engine-bridge
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
// Wrap a DB query promise in a `db.query` span + emit the DB duration
|
|
@@ -229,7 +229,7 @@ export function createTenantDb(
|
|
|
229
229
|
const ownOrGlobal = or(
|
|
230
230
|
eq(table["tenantId"], tenantId),
|
|
231
231
|
eq(table["tenantId"], SYSTEM_TENANT_ID),
|
|
232
|
-
) as SQL;
|
|
232
|
+
) as SQL; // @cast-boundary db-operator
|
|
233
233
|
return extra.length > 0 ? and(ownOrGlobal, ...extra) : ownOrGlobal;
|
|
234
234
|
}
|
|
235
235
|
|
|
@@ -309,7 +309,7 @@ export function createTenantDb(
|
|
|
309
309
|
reject ?? undefined,
|
|
310
310
|
);
|
|
311
311
|
},
|
|
312
|
-
} as TenantSelectQuery;
|
|
312
|
+
} as TenantSelectQuery; // @cast-boundary drizzle-bridge
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
// --- Where helper for update/delete ---
|
|
@@ -342,7 +342,7 @@ export function createTenantDb(
|
|
|
342
342
|
return withDbSpan<Record<string, unknown>[]>(
|
|
343
343
|
"insert",
|
|
344
344
|
table,
|
|
345
|
-
() => q.returning() as PromiseLike<Record<string, unknown>[]>,
|
|
345
|
+
() => q.returning() as PromiseLike<Record<string, unknown>[]>, // @cast-boundary db-runner
|
|
346
346
|
);
|
|
347
347
|
},
|
|
348
348
|
onConflictDoUpdate(spec: ConflictUpdate) {
|
|
@@ -370,7 +370,7 @@ export function createTenantDb(
|
|
|
370
370
|
reject,
|
|
371
371
|
);
|
|
372
372
|
},
|
|
373
|
-
} as TenantInsertValues;
|
|
373
|
+
} as TenantInsertValues; // @cast-boundary drizzle-bridge
|
|
374
374
|
},
|
|
375
375
|
};
|
|
376
376
|
},
|
|
@@ -387,7 +387,7 @@ export function createTenantDb(
|
|
|
387
387
|
return withDbSpan<Record<string, unknown>[]>(
|
|
388
388
|
"update",
|
|
389
389
|
table,
|
|
390
|
-
() => wq.returning() as PromiseLike<Record<string, unknown>[]>,
|
|
390
|
+
() => wq.returning() as PromiseLike<Record<string, unknown>[]>, // @cast-boundary db-runner
|
|
391
391
|
);
|
|
392
392
|
},
|
|
393
393
|
// biome-ignore lint/suspicious/noThenProperty: thenable for await
|
|
@@ -397,7 +397,7 @@ export function createTenantDb(
|
|
|
397
397
|
reject,
|
|
398
398
|
);
|
|
399
399
|
},
|
|
400
|
-
} as TenantUpdateWhere;
|
|
400
|
+
} as TenantUpdateWhere; // @cast-boundary drizzle-bridge
|
|
401
401
|
},
|
|
402
402
|
returning(): PromiseLike<Record<string, unknown>[]> {
|
|
403
403
|
return Promise.reject(
|
|
@@ -416,7 +416,7 @@ export function createTenantDb(
|
|
|
416
416
|
),
|
|
417
417
|
).then(resolve, reject);
|
|
418
418
|
},
|
|
419
|
-
} as TenantUpdateSet;
|
|
419
|
+
} as TenantUpdateSet; // @cast-boundary drizzle-bridge
|
|
420
420
|
},
|
|
421
421
|
};
|
|
422
422
|
},
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Shared helpers for unit-tests of pipeline.ts / run-pipeline.ts and the
|
|
2
|
+
// step-builders. The minimal-ctx helper was 4 lines duplicated in every
|
|
3
|
+
// pipeline-* test file; centralised here for the M.1.6 cleanup-pass
|
|
4
|
+
// (Followup #7 splitting).
|
|
5
|
+
//
|
|
6
|
+
// Real-ctx integration lives in pipeline-handler.integration.ts —
|
|
7
|
+
// these helpers are deliberately for the no-DB tests where step-args
|
|
8
|
+
// + assembly + boot-time guards are what's exercised.
|
|
9
|
+
|
|
10
|
+
import type { HandlerContext } from "../types/handlers";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns an empty object cast as HandlerContext. Steps that only
|
|
14
|
+
* exercise their own arg-resolution + step-list-assembly don't read
|
|
15
|
+
* any ctx field; the runner needs an object-shaped ctx but no surface
|
|
16
|
+
* beyond that.
|
|
17
|
+
*
|
|
18
|
+
* Tests that actually use ctx fields (db, query, appendEvent, etc.)
|
|
19
|
+
* belong in pipeline-handler.integration.ts against the real stack.
|
|
20
|
+
*/
|
|
21
|
+
export function buildMinimalCtx(): HandlerContext {
|
|
22
|
+
return {} as HandlerContext;
|
|
23
|
+
}
|
|
@@ -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
|
+
});
|