@cosmicdrift/kumiko-bundled-features 0.105.2 → 0.108.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 +8 -6
- package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +3 -2
- package/src/auth-email-password/__tests__/email-verification.integration.test.ts +5 -9
- package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +3 -2
- package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +3 -2
- package/src/auth-email-password/__tests__/password-reset.integration.test.ts +4 -3
- package/src/auth-email-password/handlers/change-password.write.ts +11 -11
- package/src/auth-email-password/handlers/confirm-token-flow.ts +33 -104
- package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +44 -0
- package/src/auth-email-password/web/__tests__/user-menu.test.tsx +14 -1
- package/src/auth-email-password/web/auth-gate.tsx +3 -4
- package/src/auth-email-password/web/invite-accept-screen.tsx +7 -3
- package/src/auth-email-password/web/session.tsx +5 -1
- package/src/auth-email-password/web/user-menu.tsx +1 -1
- package/src/config/__tests__/deserialize-value.test.ts +3 -6
- package/src/custom-fields/__tests__/custom-fields.integration.test.ts +65 -0
- package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +4 -3
- package/src/custom-fields/constants.ts +1 -0
- package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
- package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
- package/src/custom-fields/wire-for-entity.ts +104 -77
- package/src/data-retention/__tests__/parse-override.test.ts +11 -6
- package/src/data-retention/resolve-for-tenant.ts +16 -4
- package/src/data-retention/run-retention-cleanup.ts +72 -10
- package/src/delivery/attempt-log.ts +22 -10
- package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +4 -4
- package/src/folders/__tests__/folders.integration.test.ts +26 -0
- package/src/folders/feature.ts +4 -2
- package/src/folders/handlers/delete-folder.write.ts +40 -0
- package/src/folders/web/__tests__/folder-manager.test.tsx +24 -0
- package/src/folders/web/folder-manager.tsx +18 -13
- package/src/folders/web/i18n.ts +0 -2
- package/src/folders-user-data/__tests__/hooks.integration.test.ts +69 -0
- package/src/folders-user-data/hooks.ts +31 -8
- package/src/folders-user-data/index.ts +4 -4
- package/src/jobs/feature.ts +23 -21
- package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
- package/src/ledger/__tests__/ledger.integration.test.ts +44 -0
- package/src/ledger/__tests__/reports.test.ts +20 -0
- package/src/ledger/entity.ts +1 -1
- package/src/ledger/handlers/confirm-schedule-period.write.ts +29 -13
- package/src/ledger/handlers/reverse-transaction.write.ts +44 -5
- package/src/ledger/recurring.ts +18 -9
- package/src/ledger/schemas.ts +4 -1
- package/src/ledger/web/index.ts +3 -2
- package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
- package/src/personal-access-tokens/__tests__/pat.integration.test.ts +204 -0
- package/src/personal-access-tokens/constants.ts +33 -0
- package/src/personal-access-tokens/feature.ts +78 -0
- package/src/personal-access-tokens/handlers/available-scopes.query.ts +15 -0
- package/src/personal-access-tokens/handlers/create.write.ts +41 -0
- package/src/personal-access-tokens/handlers/list.query.ts +47 -0
- package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
- package/src/personal-access-tokens/hash.ts +17 -0
- package/src/personal-access-tokens/index.ts +45 -0
- package/src/personal-access-tokens/resolver.ts +58 -0
- package/src/personal-access-tokens/roles.ts +29 -0
- package/src/personal-access-tokens/schema/api-token.ts +56 -0
- package/src/personal-access-tokens/scopes.ts +22 -0
- package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
- package/src/personal-access-tokens/web/i18n.ts +45 -0
- package/src/personal-access-tokens/web/index.ts +7 -0
- package/src/personal-access-tokens/web/pat-tokens-screen.tsx +150 -0
- package/src/renderer-simple/__tests__/template-resolver.integration.test.ts +2 -2
- package/src/sessions/__tests__/cleanup.integration.test.ts +3 -3
- package/src/sessions/__tests__/rebuild-survival.integration.test.ts +5 -9
- package/src/sessions/__tests__/sessions.integration.test.ts +14 -9
- package/src/sessions/schema/user-session.ts +8 -2
- package/src/sessions/session-callbacks.ts +9 -3
- package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
- package/src/tags/__tests__/tags.integration.test.ts +15 -0
- package/src/tags/handlers/assign-tag.write.ts +13 -3
- package/src/tags/schemas.ts +10 -4
- package/src/template-resolver/__tests__/template-resolver.integration.test.ts +2 -2
- package/src/template-resolver/testing.ts +4 -3
- package/src/tier-engine/feature.ts +22 -10
- package/src/tier-engine/handlers/set-tenant-tier.write.ts +11 -1
- package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
- package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
- package/src/user/index.ts +4 -0
- package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +4 -4
- package/src/user-data-rights/__tests__/audit-log.integration.test.ts +4 -4
- package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -4
- package/src/user-data-rights/__tests__/download.integration.test.ts +7 -7
- package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +5 -10
- package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +84 -0
- package/src/user-data-rights/__tests__/file-retention.integration.test.ts +3 -3
- package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +3 -2
- package/src/user-data-rights/__tests__/forget-test-helpers.ts +3 -2
- package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +3 -3
- package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +10 -6
- package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +4 -4
- package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -4
- package/src/user-data-rights/__tests__/request-export.integration.test.ts +4 -9
- package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +7 -3
- package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +8 -4
- package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +12 -17
- package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +9 -5
- package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +3 -3
- package/src/user-data-rights/handlers/download-attempt-list.query.ts +1 -1
- package/src/user-data-rights/handlers/download-by-job.query.ts +4 -6
- package/src/user-data-rights/handlers/export-job-detail.query.ts +1 -0
- package/src/user-data-rights/handlers/export-job-list.query.ts +1 -0
- package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
- package/src/user-data-rights/run-forget-cleanup.ts +8 -1
- package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
- package/src/user-data-rights/web/i18n.ts +0 -10
- package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +3 -2
- package/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +102 -54
- package/src/user-data-rights-defaults/hooks/user.userdata-hook.ts +53 -15
- package/src/user-profile/__tests__/profile-screen.test.tsx +9 -0
- package/src/user-profile/handlers/change-email.write.ts +11 -8
- package/src/user-profile/web/profile-screen.tsx +10 -8
- package/src/auth-email-password/stream-tenant.ts +0 -31
- package/src/user-data-rights-defaults/db/queries/user-hook.ts +0 -17
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// #762 — one-time migration tooling for pre-#497 user event streams.
|
|
2
|
+
//
|
|
3
|
+
// #497 made `user` a systemStream entity: every executor op addresses the
|
|
4
|
+
// stream on SYSTEM_TENANT_ID. Streams created before that live on whichever
|
|
5
|
+
// tenant created them ("scattered"), so post-#497 writes version-conflict
|
|
6
|
+
// against the empty SYSTEM stream. The raw UPDATE documented in the #497
|
|
7
|
+
// changeset breaks as soon as an aggregate has BOTH a legacy stream and
|
|
8
|
+
// post-#497 SYSTEM events (split stream — e.g. a skipOptimisticLock
|
|
9
|
+
// lifecycle write appended v1 on SYSTEM while v1..n live on the old
|
|
10
|
+
// tenant): retenanting then trips events_aggregate_version_uq.
|
|
11
|
+
//
|
|
12
|
+
// This tool merges per aggregate: all user events, ordered by global event
|
|
13
|
+
// id, are renumbered 1..n and retenanted to SYSTEM_TENANT_ID — two-phase
|
|
14
|
+
// version writes (negative interim values) dodge the unique index while the
|
|
15
|
+
// per-aggregate transaction is in flight. Snapshots of migrated aggregates
|
|
16
|
+
// are dropped (version numbering changed; the next snapshotting load
|
|
17
|
+
// recreates them); archived-stream markers move with the stream.
|
|
18
|
+
//
|
|
19
|
+
// Idempotent: the candidate query only matches aggregates that still have
|
|
20
|
+
// non-SYSTEM events. One failing aggregate does not abort the run — the
|
|
21
|
+
// rest of the estate migrates, failures are reported.
|
|
22
|
+
//
|
|
23
|
+
// After a run, rebuild the user projection so read_users.tenant_id reflects
|
|
24
|
+
// the stream move: rebuildProjection("user:projection:user-entity", ...) or
|
|
25
|
+
// the jobs:job:projection-rebuild job.
|
|
26
|
+
|
|
27
|
+
import { asRawClient, transaction } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
28
|
+
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
29
|
+
import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
30
|
+
|
|
31
|
+
export type UserStreamBackfillResult = {
|
|
32
|
+
readonly aggregatesMigrated: number;
|
|
33
|
+
readonly eventsMigrated: number;
|
|
34
|
+
readonly failed: ReadonlyArray<{ readonly aggregateId: string; readonly error: string }>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export async function backfillUserStreamTenants(
|
|
38
|
+
db: DbConnection,
|
|
39
|
+
): Promise<UserStreamBackfillResult> {
|
|
40
|
+
const candidates = (await asRawClient(db).unsafe(
|
|
41
|
+
`SELECT DISTINCT "aggregate_id" FROM "kumiko_events"
|
|
42
|
+
WHERE "aggregate_type" = 'user' AND "tenant_id" <> $1::uuid`,
|
|
43
|
+
[SYSTEM_TENANT_ID],
|
|
44
|
+
)) as ReadonlyArray<{ aggregate_id: string }>;
|
|
45
|
+
|
|
46
|
+
let aggregatesMigrated = 0;
|
|
47
|
+
let eventsMigrated = 0;
|
|
48
|
+
const failed: Array<{ aggregateId: string; error: string }> = [];
|
|
49
|
+
|
|
50
|
+
for (const { aggregate_id } of candidates) {
|
|
51
|
+
try {
|
|
52
|
+
eventsMigrated += await migrateAggregate(db, aggregate_id);
|
|
53
|
+
aggregatesMigrated++;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
failed.push({
|
|
56
|
+
aggregateId: aggregate_id,
|
|
57
|
+
error: e instanceof Error ? e.message : String(e),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { aggregatesMigrated, eventsMigrated, failed };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function migrateAggregate(db: DbConnection, aggregateId: string): Promise<number> {
|
|
65
|
+
return transaction(db, async (tx) => {
|
|
66
|
+
const raw = asRawClient(tx);
|
|
67
|
+
// Global id order = commit-adjacent replay order; merges a split stream
|
|
68
|
+
// (legacy tenant + SYSTEM) into one consistent version sequence.
|
|
69
|
+
const events = (await raw.unsafe(
|
|
70
|
+
`SELECT "id" FROM "kumiko_events"
|
|
71
|
+
WHERE "aggregate_type" = 'user' AND "aggregate_id" = $1::uuid
|
|
72
|
+
ORDER BY "id" ASC
|
|
73
|
+
FOR UPDATE`,
|
|
74
|
+
[aggregateId],
|
|
75
|
+
)) as ReadonlyArray<{ id: bigint | string }>;
|
|
76
|
+
|
|
77
|
+
// Phase 1: negative interim versions — real versions are always >= 1, so
|
|
78
|
+
// nothing can collide with events_aggregate_version_uq mid-migration.
|
|
79
|
+
for (const [i, ev] of events.entries()) {
|
|
80
|
+
await raw.unsafe(
|
|
81
|
+
`UPDATE "kumiko_events" SET "tenant_id" = $1::uuid, "version" = $2 WHERE "id" = $3`,
|
|
82
|
+
[SYSTEM_TENANT_ID, -(i + 1), ev.id],
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
// Phase 2: final contiguous 1..n.
|
|
86
|
+
for (const [i, ev] of events.entries()) {
|
|
87
|
+
await raw.unsafe(`UPDATE "kumiko_events" SET "version" = $1 WHERE "id" = $2`, [i + 1, ev.id]);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Version numbering changed → any snapshot of this aggregate is stale.
|
|
91
|
+
await raw.unsafe(`DELETE FROM "kumiko_snapshots" WHERE "aggregate_id" = $1::uuid`, [
|
|
92
|
+
aggregateId,
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
// Archived markers key on (tenant_id, aggregate_id) — move them with the
|
|
96
|
+
// stream, keeping an existing SYSTEM marker if both exist.
|
|
97
|
+
await raw.unsafe(
|
|
98
|
+
`INSERT INTO "kumiko_archived_streams" ("tenant_id", "aggregate_id", "aggregate_type", "archived_at", "archived_by", "reason")
|
|
99
|
+
SELECT $1::uuid, "aggregate_id", "aggregate_type", "archived_at", "archived_by", "reason"
|
|
100
|
+
FROM "kumiko_archived_streams"
|
|
101
|
+
WHERE "aggregate_id" = $2::uuid AND "tenant_id" <> $1::uuid
|
|
102
|
+
ON CONFLICT DO NOTHING`,
|
|
103
|
+
[SYSTEM_TENANT_ID, aggregateId],
|
|
104
|
+
);
|
|
105
|
+
await raw.unsafe(
|
|
106
|
+
`DELETE FROM "kumiko_archived_streams" WHERE "aggregate_id" = $1::uuid AND "tenant_id" <> $2::uuid`,
|
|
107
|
+
[aggregateId, SYSTEM_TENANT_ID],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return events.length;
|
|
111
|
+
});
|
|
112
|
+
}
|
package/src/user/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { UserCommandSchemas } from "./command-schemas";
|
|
2
2
|
export { USER_FEATURE, UserErrors, UserHandlers, UserQueries } from "./constants";
|
|
3
|
+
export {
|
|
4
|
+
backfillUserStreamTenants,
|
|
5
|
+
type UserStreamBackfillResult,
|
|
6
|
+
} from "./db/queries/stream-tenant-backfill";
|
|
3
7
|
export { createUserFeature } from "./feature";
|
|
4
8
|
export type { UserStatus } from "./schema/user";
|
|
5
9
|
export {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// Beweist end-to-end via echte /api/write-Calls OHNE Auth (anonymousAccess).
|
|
7
7
|
|
|
8
8
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
-
import {
|
|
9
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
10
10
|
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
11
11
|
import {
|
|
12
12
|
createTestUser,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
testTenantId,
|
|
16
16
|
unsafeCreateEntityTable,
|
|
17
17
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
18
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
18
|
+
import { resetTestTables, seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
19
19
|
import {
|
|
20
20
|
createComplianceProfilesFeature,
|
|
21
21
|
tenantComplianceProfileEntity,
|
|
@@ -77,7 +77,7 @@ beforeEach(async () => {
|
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
async function seedAlice(status: string = USER_STATUS.Active, email: string = ALICE_EMAIL) {
|
|
80
|
-
await
|
|
80
|
+
await seedRow(stack.db, userTable, {
|
|
81
81
|
id: aliceUser.id,
|
|
82
82
|
tenantId: tenantA,
|
|
83
83
|
email,
|
|
@@ -343,7 +343,7 @@ describe("anonymous deletion flow — not configured (kein Secret)", () => {
|
|
|
343
343
|
await unsafeCreateEntityTable(bareStack.db, userEntity);
|
|
344
344
|
await unsafeCreateEntityTable(bareStack.db, tenantComplianceProfileEntity);
|
|
345
345
|
await createEventsTable(bareStack.db);
|
|
346
|
-
await
|
|
346
|
+
await seedRow(bareStack.db, userTable, {
|
|
347
347
|
id: aliceUser.id,
|
|
348
348
|
tenantId: tenantA,
|
|
349
349
|
email: ALICE_EMAIL,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// S2.U7 — my-audit-log + invalid-attempt-audit + list-download-attempts.
|
|
2
2
|
|
|
3
3
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
4
|
-
import { asRawClient
|
|
4
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
5
5
|
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
6
6
|
import {
|
|
7
7
|
createTestUser,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
testTenantId,
|
|
11
11
|
unsafeCreateEntityTable,
|
|
12
12
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
13
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
13
|
+
import { resetTestTables, seedRow, seedRows } from "@cosmicdrift/kumiko-framework/testing";
|
|
14
14
|
import {
|
|
15
15
|
createComplianceProfilesFeature,
|
|
16
16
|
tenantComplianceProfileEntity,
|
|
@@ -64,7 +64,7 @@ beforeEach(async () => {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
async function seedUser(u: typeof alice, email: string): Promise<void> {
|
|
67
|
-
await
|
|
67
|
+
await seedRow(stack.db, userTable, {
|
|
68
68
|
id: u.id,
|
|
69
69
|
tenantId: u.tenantId,
|
|
70
70
|
email,
|
|
@@ -180,7 +180,7 @@ describe("list-download-attempts (DPO operator-query)", () => {
|
|
|
180
180
|
// Direct-INSERT in attempts (simuliert was die download-handler schreiben).
|
|
181
181
|
const T = await import("@cosmicdrift/kumiko-framework/time");
|
|
182
182
|
const now = T.getTemporal().Now.instant();
|
|
183
|
-
await
|
|
183
|
+
await seedRows(stack.db, downloadAttemptsTable, [
|
|
184
184
|
{
|
|
185
185
|
id: "11111111-1111-4111-8111-111111111111",
|
|
186
186
|
tenantId: tenantA,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// Alices Forget; Bobs Daten landen NICHT in Alices Export-Bundle.
|
|
14
14
|
|
|
15
15
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
16
|
-
import { asRawClient
|
|
16
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
17
17
|
import { defineUnmanagedTable } from "@cosmicdrift/kumiko-framework/db";
|
|
18
18
|
import {
|
|
19
19
|
defineFeature,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
unsafeCreateEntityTable,
|
|
29
29
|
unsafePushTables,
|
|
30
30
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
31
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
31
|
+
import { deleteRows, resetTestTables, seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
32
32
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
33
33
|
import {
|
|
34
34
|
createComplianceProfilesFeature,
|
|
@@ -98,7 +98,7 @@ const exportNotes: UserDataExportHook = async (ctx) => {
|
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
const deleteNotes: UserDataDeleteHook = async (ctx, _strategy) => {
|
|
101
|
-
await
|
|
101
|
+
await deleteRows(ctx.db, testNotesTable, {
|
|
102
102
|
tenantId: ctx.tenantId,
|
|
103
103
|
authorId: ctx.userId,
|
|
104
104
|
});
|
|
@@ -182,7 +182,7 @@ async function seedUser(
|
|
|
182
182
|
displayName?: string;
|
|
183
183
|
} = {},
|
|
184
184
|
): Promise<void> {
|
|
185
|
-
await
|
|
185
|
+
await seedRow(stack.db, userTable, {
|
|
186
186
|
id,
|
|
187
187
|
tenantId: TENANT_SYSTEM,
|
|
188
188
|
email: overrides.email ?? `user-${id}@example.com`,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
11
11
|
import { randomBytes } from "node:crypto";
|
|
12
|
-
import { asRawClient, selectMany
|
|
12
|
+
import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
13
13
|
import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
|
|
14
14
|
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
15
15
|
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
unsafeCreateEntityTable,
|
|
26
26
|
unsafePushTables,
|
|
27
27
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
28
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
28
|
+
import { resetTestTables, updateRows } from "@cosmicdrift/kumiko-framework/testing";
|
|
29
29
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
30
30
|
import {
|
|
31
31
|
createComplianceProfilesFeature,
|
|
@@ -302,7 +302,7 @@ describe("download-by-token :: error paths", () => {
|
|
|
302
302
|
const longAgo = getTemporal().Instant.fromEpochMilliseconds(
|
|
303
303
|
Date.now() - 365 * 24 * 60 * 60 * 1000,
|
|
304
304
|
);
|
|
305
|
-
await
|
|
305
|
+
await updateRows(stack.db, exportDownloadTokensTable, { expiresAt: longAgo }, { jobId });
|
|
306
306
|
|
|
307
307
|
const res = await stack.http.query(
|
|
308
308
|
"user-data-rights:query:download-by-token",
|
|
@@ -316,7 +316,7 @@ describe("download-by-token :: error paths", () => {
|
|
|
316
316
|
|
|
317
317
|
test("failed Job → 404 download.unavailable", async () => {
|
|
318
318
|
const { jobId, plainToken } = await seedDoneJobWithToken();
|
|
319
|
-
await
|
|
319
|
+
await updateRows(stack.db, exportJobsTable, { status: "failed" }, { id: jobId });
|
|
320
320
|
|
|
321
321
|
const res = await stack.http.query(
|
|
322
322
|
"user-data-rights:query:download-by-token",
|
|
@@ -330,7 +330,7 @@ describe("download-by-token :: error paths", () => {
|
|
|
330
330
|
|
|
331
331
|
test("storage cleared → 404 download.expired", async () => {
|
|
332
332
|
const { jobId, plainToken } = await seedDoneJobWithToken();
|
|
333
|
-
await
|
|
333
|
+
await updateRows(stack.db, exportJobsTable, { downloadStorageKey: null }, { id: jobId });
|
|
334
334
|
|
|
335
335
|
const res = await stack.http.query(
|
|
336
336
|
"user-data-rights:query:download-by-token",
|
|
@@ -404,7 +404,7 @@ describe("download-by-job :: happy path", () => {
|
|
|
404
404
|
// Symmetrisch zum token-Test: gleicher Code-Pfad muss auch im job-
|
|
405
405
|
// handler 404 + unavailable raus, nicht 500.
|
|
406
406
|
const { jobId } = await seedDoneJobWithToken();
|
|
407
|
-
await
|
|
407
|
+
await updateRows(stack.db, exportJobsTable, { status: "failed" }, { id: jobId });
|
|
408
408
|
|
|
409
409
|
const res = await stack.http.query(
|
|
410
410
|
"user-data-rights:query:download-by-job",
|
|
@@ -418,7 +418,7 @@ describe("download-by-job :: happy path", () => {
|
|
|
418
418
|
|
|
419
419
|
test("storage cleared (downloadStorageKey null) → 404 download.expired (job-Pfad)", async () => {
|
|
420
420
|
const { jobId } = await seedDoneJobWithToken();
|
|
421
|
-
await
|
|
421
|
+
await updateRows(stack.db, exportJobsTable, { downloadStorageKey: null }, { id: jobId });
|
|
422
422
|
|
|
423
423
|
const res = await stack.http.query(
|
|
424
424
|
"user-data-rights:query:download-by-job",
|
|
@@ -10,19 +10,14 @@
|
|
|
10
10
|
// reale Postgres + Drizzle-customType-Codec-Path braucht.
|
|
11
11
|
|
|
12
12
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
-
import {
|
|
14
|
-
asRawClient,
|
|
15
|
-
insertOne,
|
|
16
|
-
selectMany,
|
|
17
|
-
updateMany,
|
|
18
|
-
} from "@cosmicdrift/kumiko-framework/bun-db";
|
|
13
|
+
import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
19
14
|
import { extractPgError } from "@cosmicdrift/kumiko-framework/db";
|
|
20
15
|
import {
|
|
21
16
|
setupTestStack,
|
|
22
17
|
type TestStack,
|
|
23
18
|
unsafeCreateEntityTable,
|
|
24
19
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
25
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
20
|
+
import { resetTestTables, seedRow, updateRows } from "@cosmicdrift/kumiko-framework/testing";
|
|
26
21
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
27
22
|
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
28
23
|
import { createDataRetentionFeature } from "../../data-retention";
|
|
@@ -108,7 +103,7 @@ async function insertJob(
|
|
|
108
103
|
if (overrides.bytesWritten !== undefined) {
|
|
109
104
|
values["bytesWritten"] = overrides.bytesWritten;
|
|
110
105
|
}
|
|
111
|
-
await
|
|
106
|
+
await seedRow(stack.db, exportJobsTable, values);
|
|
112
107
|
return id;
|
|
113
108
|
}
|
|
114
109
|
|
|
@@ -178,7 +173,7 @@ describe("ExportJob :: Partial-UNIQUE-Index", () => {
|
|
|
178
173
|
// Alice pending insert
|
|
179
174
|
const aliceJobId = await insertJob(ALICE_ID, EXPORT_JOB_STATUS.Pending);
|
|
180
175
|
// Worker pickt auf — Status flip
|
|
181
|
-
await
|
|
176
|
+
await updateRows(
|
|
182
177
|
stack.db,
|
|
183
178
|
exportJobsTable,
|
|
184
179
|
{ status: EXPORT_JOB_STATUS.Running },
|
|
@@ -193,7 +188,7 @@ describe("ExportJob :: Partial-UNIQUE-Index", () => {
|
|
|
193
188
|
);
|
|
194
189
|
|
|
195
190
|
// Aber wenn der running-Job fertig wird (done), darf User wieder pending starten
|
|
196
|
-
await
|
|
191
|
+
await updateRows(
|
|
197
192
|
stack.db,
|
|
198
193
|
exportJobsTable,
|
|
199
194
|
{ status: EXPORT_JOB_STATUS.Done },
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// export-job-list/detail :: SystemAdmin cross-tenant visibility.
|
|
2
|
+
//
|
|
3
|
+
// Regression: the changeset describes these as "a platform-operator
|
|
4
|
+
// inspector over the GDPR read-models" (i.e. cross-tenant), but the handlers
|
|
5
|
+
// registered no crossTenant/systemScope override — a SystemAdmin acting in
|
|
6
|
+
// tenant A saw only tenant A's export jobs, never tenant B's.
|
|
7
|
+
|
|
8
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
+
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
10
|
+
import {
|
|
11
|
+
createTestUser,
|
|
12
|
+
setupTestStack,
|
|
13
|
+
type TestStack,
|
|
14
|
+
testTenantId,
|
|
15
|
+
unsafeCreateEntityTable,
|
|
16
|
+
} from "@cosmicdrift/kumiko-framework/stack";
|
|
17
|
+
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
18
|
+
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
19
|
+
import { createDataRetentionFeature } from "../../data-retention";
|
|
20
|
+
import { createSessionsFeature } from "../../sessions";
|
|
21
|
+
import { createUserFeature } from "../../user";
|
|
22
|
+
import { createUserDataRightsFeature } from "../feature";
|
|
23
|
+
import { exportJobEntity, exportJobsTable } from "../schema/export-job";
|
|
24
|
+
|
|
25
|
+
const REQUEST_EXPORT = "user-data-rights:write:request-export";
|
|
26
|
+
const EXPORT_JOB_LIST = "user-data-rights:query:export-job:list";
|
|
27
|
+
const EXPORT_JOB_DETAIL = "user-data-rights:query:export-job:detail";
|
|
28
|
+
|
|
29
|
+
let stack: TestStack;
|
|
30
|
+
|
|
31
|
+
const tenantA = testTenantId(1);
|
|
32
|
+
const tenantB = testTenantId(2);
|
|
33
|
+
const aliceInA = createTestUser({ id: 42, tenantId: tenantA, roles: ["Member"] });
|
|
34
|
+
const bobInB = createTestUser({ id: 43, tenantId: tenantB, roles: ["Member"] });
|
|
35
|
+
const sysadminInA = createTestUser({ id: 1, tenantId: tenantA, roles: ["SystemAdmin"] });
|
|
36
|
+
|
|
37
|
+
beforeAll(async () => {
|
|
38
|
+
stack = await setupTestStack({
|
|
39
|
+
features: [
|
|
40
|
+
createUserFeature(),
|
|
41
|
+
createDataRetentionFeature(),
|
|
42
|
+
createComplianceProfilesFeature(),
|
|
43
|
+
createSessionsFeature(),
|
|
44
|
+
createUserDataRightsFeature(),
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
await unsafeCreateEntityTable(stack.db, exportJobEntity);
|
|
48
|
+
await createEventsTable(stack.db);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterAll(async () => {
|
|
52
|
+
await stack.cleanup();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
beforeEach(async () => {
|
|
56
|
+
await resetTestTables(stack.db, [exportJobsTable, eventsTable]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("export-job list/detail :: SystemAdmin sees every tenant's jobs", () => {
|
|
60
|
+
test("list includes jobs from a tenant the SystemAdmin isn't acting in", async () => {
|
|
61
|
+
const aJob = await stack.http.writeOk<{ jobId: string }>(REQUEST_EXPORT, {}, aliceInA);
|
|
62
|
+
const bJob = await stack.http.writeOk<{ jobId: string }>(REQUEST_EXPORT, {}, bobInB);
|
|
63
|
+
|
|
64
|
+
const result = await stack.http.queryOk<{ rows: Array<{ id: string }> }>(
|
|
65
|
+
EXPORT_JOB_LIST,
|
|
66
|
+
{},
|
|
67
|
+
sysadminInA,
|
|
68
|
+
);
|
|
69
|
+
const ids = result.rows.map((r) => r.id);
|
|
70
|
+
expect(ids).toContain(aJob.jobId);
|
|
71
|
+
expect(ids).toContain(bJob.jobId);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("detail resolves a job that belongs to a different tenant than the caller", async () => {
|
|
75
|
+
const bJob = await stack.http.writeOk<{ jobId: string }>(REQUEST_EXPORT, {}, bobInB);
|
|
76
|
+
|
|
77
|
+
const detail = await stack.http.queryOk<{ id: string } | null>(
|
|
78
|
+
EXPORT_JOB_DETAIL,
|
|
79
|
+
{ id: bJob.jobId },
|
|
80
|
+
sysadminInA,
|
|
81
|
+
);
|
|
82
|
+
expect(detail?.id).toBe(bJob.jobId);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// die Policy entscheidet pro Tenant.
|
|
17
17
|
|
|
18
18
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
19
|
-
import { asRawClient
|
|
19
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
20
20
|
import {
|
|
21
21
|
createEventStoreExecutor,
|
|
22
22
|
createTenantDb,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
unsafeCreateEntityTable,
|
|
31
31
|
unsafePushTables,
|
|
32
32
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
33
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
33
|
+
import { resetTestTables, seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
34
34
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
35
35
|
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
36
36
|
import { createDataRetentionFeature, tenantRetentionOverrideEntity } from "../../data-retention";
|
|
@@ -117,7 +117,7 @@ beforeEach(async () => {
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
async function seedForgetUser(id: string): Promise<void> {
|
|
120
|
-
await
|
|
120
|
+
await seedRow(db, userTable, {
|
|
121
121
|
id,
|
|
122
122
|
tenantId: TENANT_SYSTEM,
|
|
123
123
|
email: `user-${id}@example.com`,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// (getExtensionUsages + direct call) and is structurally blind to ordering.
|
|
16
16
|
|
|
17
17
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
18
|
-
import { asRawClient
|
|
18
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
19
19
|
import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
|
|
20
20
|
import {
|
|
21
21
|
createEntity,
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
type TestStack,
|
|
33
33
|
unsafeCreateEntityTable,
|
|
34
34
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
35
|
+
import { seedRow as seedProjectionRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
35
36
|
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
36
37
|
import { fieldDefinitionEntity } from "../../custom-fields/entity";
|
|
37
38
|
import { createCustomFieldsFeature } from "../../custom-fields/feature";
|
|
@@ -181,7 +182,7 @@ async function defineSensitiveField(entityName: string, fieldKey: string, sensit
|
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
async function seedAnonymizeOverride(entityName: string) {
|
|
184
|
-
await
|
|
185
|
+
await seedProjectionRow(stack.db, tenantRetentionOverrideTable, {
|
|
185
186
|
entityName,
|
|
186
187
|
config: JSON.stringify({ keepFor: "0d", strategy: "anonymize" }),
|
|
187
188
|
reason: "test: force anonymize strategy",
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
// DDL and seed logic in one place so a file_refs/user-schema change updates
|
|
5
5
|
// both at once instead of drifting.
|
|
6
6
|
|
|
7
|
-
import { asRawClient
|
|
7
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
8
8
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
9
9
|
import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
10
10
|
import type { FileStorageProvider } from "@cosmicdrift/kumiko-framework/files";
|
|
11
|
+
import { seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
11
12
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
12
13
|
import { USER_STATUS, userTable } from "../../user";
|
|
13
14
|
|
|
@@ -66,7 +67,7 @@ export interface ForgetSeeders {
|
|
|
66
67
|
export function createForgetSeeders(db: DbConnection, writer: FileWriter): ForgetSeeders {
|
|
67
68
|
return {
|
|
68
69
|
async seedForgetUser(id) {
|
|
69
|
-
await
|
|
70
|
+
await seedRow(db, userTable, {
|
|
70
71
|
id,
|
|
71
72
|
tenantId: TENANT_SYSTEM,
|
|
72
73
|
email: `user-${id}@example.com`,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// #624: "App mountet mail-foundation+transport → GDPR-Mails ohne Callback-Code".
|
|
11
11
|
|
|
12
12
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
-
import { asRawClient
|
|
13
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
14
14
|
import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
|
|
15
15
|
import {
|
|
16
16
|
setupTestStack,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
unsafeCreateEntityTable,
|
|
19
19
|
unsafePushTables,
|
|
20
20
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
21
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
21
|
+
import { resetTestTables, seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
22
22
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
23
23
|
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
24
24
|
import { configValuesTable, createConfigFeature, createConfigResolver } from "../../config";
|
|
@@ -99,7 +99,7 @@ beforeEach(async () => {
|
|
|
99
99
|
|
|
100
100
|
describe("C6 default mail bridge :: forget cron sends deletion-executed without app callback", () => {
|
|
101
101
|
test("registered cron + configResolver(provider=inmemory) → user deleted + mail in inbox", async () => {
|
|
102
|
-
await
|
|
102
|
+
await seedRow(stack.db, userTable, {
|
|
103
103
|
id: USER_ID,
|
|
104
104
|
tenantId: TENANT_SYSTEM,
|
|
105
105
|
email: ORIGINAL_EMAIL,
|
package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
15
15
|
import { randomBytes } from "node:crypto";
|
|
16
|
-
import { selectMany
|
|
16
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
17
17
|
import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
|
|
18
18
|
import {
|
|
19
19
|
createRegistry,
|
|
@@ -38,7 +38,11 @@ import {
|
|
|
38
38
|
unsafeCreateEntityTable,
|
|
39
39
|
unsafePushTables,
|
|
40
40
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
41
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
createLateBoundHolder,
|
|
43
|
+
resetTestTables,
|
|
44
|
+
updateRows,
|
|
45
|
+
} from "@cosmicdrift/kumiko-framework/testing";
|
|
42
46
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
43
47
|
import { AuthHandlers } from "../../auth-email-password/constants";
|
|
44
48
|
import { createAuthEmailPasswordFeature } from "../../auth-email-password/feature";
|
|
@@ -218,7 +222,7 @@ describe("#494 :: read_users-Rebuild bewahrt Lifecycle-State", () => {
|
|
|
218
222
|
);
|
|
219
223
|
|
|
220
224
|
// Prae-Fix-Zustand simulieren: roher Write OHNE Event.
|
|
221
|
-
await
|
|
225
|
+
await updateRows(stack.db, userTable, { status: USER_STATUS.Restricted }, { id: created.id });
|
|
222
226
|
|
|
223
227
|
// Ohne Backfill replayt der Rebuild nur user.created -> Status weggewischt.
|
|
224
228
|
await rebuildProjection(USER_PROJECTION, { db: stack.db, registry });
|
|
@@ -229,7 +233,7 @@ describe("#494 :: read_users-Rebuild bewahrt Lifecycle-State", () => {
|
|
|
229
233
|
|
|
230
234
|
// Bestand wieder in den divergenten Live-State bringen (der Rebuild hat ihn
|
|
231
235
|
// auf Active gesetzt) und den Reconcile laufen lassen.
|
|
232
|
-
await
|
|
236
|
+
await updateRows(stack.db, userTable, { status: USER_STATUS.Restricted }, { id: created.id });
|
|
233
237
|
const { backfilled, failed } = await backfillUserLifecycleEvents(stack.db);
|
|
234
238
|
expect(backfilled).toBeGreaterThanOrEqual(1);
|
|
235
239
|
expect(failed).toEqual([]);
|
|
@@ -254,14 +258,14 @@ describe("#494 :: read_users-Rebuild bewahrt Lifecycle-State", () => {
|
|
|
254
258
|
{ email: "healthy.rebuild@example.com", passwordHash: hash, displayName: "Healthy" },
|
|
255
259
|
TestUsers.systemAdmin,
|
|
256
260
|
);
|
|
257
|
-
await
|
|
261
|
+
await updateRows(stack.db, userTable, { status: USER_STATUS.Restricted }, { id: goodRow.id });
|
|
258
262
|
|
|
259
263
|
const badRow = await stack.http.writeOk<{ id: string }>(
|
|
260
264
|
UserHandlers.create,
|
|
261
265
|
{ email: "corrupt.rebuild@example.com", passwordHash: hash, displayName: "Corrupt" },
|
|
262
266
|
TestUsers.systemAdmin,
|
|
263
267
|
);
|
|
264
|
-
await
|
|
268
|
+
await updateRows(stack.db, userTable, { status: USER_STATUS.Restricted }, { id: badRow.id });
|
|
265
269
|
// userEntity is systemStream:true (schema/user.ts) — its event stream
|
|
266
270
|
// lives on SYSTEM_TENANT_ID regardless of the creating tenant. Archiving
|
|
267
271
|
// under any other tenant would be a silent no-op against assertStreamWritable.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// hier; der Frist-Ablauf-Cleanup folgt mit S2.U5b.
|
|
11
11
|
|
|
12
12
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
-
import {
|
|
13
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
14
14
|
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
15
15
|
import {
|
|
16
16
|
createTestUser,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
testUserId,
|
|
21
21
|
unsafeCreateEntityTable,
|
|
22
22
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
23
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
23
|
+
import { resetTestTables, seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
24
24
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
25
25
|
import {
|
|
26
26
|
createComplianceProfilesFeature,
|
|
@@ -92,7 +92,7 @@ async function seedAlice(
|
|
|
92
92
|
gracePeriodEnd: Instant | null;
|
|
93
93
|
}> = {},
|
|
94
94
|
): Promise<void> {
|
|
95
|
-
await
|
|
95
|
+
await seedRow(stack.db, userTable, {
|
|
96
96
|
id: aliceUser.id,
|
|
97
97
|
tenantId: tenantA,
|
|
98
98
|
email: "alice@example.com",
|
|
@@ -345,7 +345,7 @@ describe("Cross-User-Isolation", () => {
|
|
|
345
345
|
tenantId: tenantA,
|
|
346
346
|
roles: ["Member"],
|
|
347
347
|
});
|
|
348
|
-
await
|
|
348
|
+
await seedRow(stack.db, userTable, {
|
|
349
349
|
id: testUserId(43),
|
|
350
350
|
tenantId: tenantA,
|
|
351
351
|
email: "bob@example.com",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// Properties — dieser Test verifiziert sie end-to-end.
|
|
9
9
|
|
|
10
10
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
11
|
-
import {
|
|
11
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
12
12
|
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
13
13
|
import {
|
|
14
14
|
createTestUser,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
testTenantId,
|
|
18
18
|
unsafeCreateEntityTable,
|
|
19
19
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
20
|
-
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
20
|
+
import { resetTestTables, seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
21
21
|
import {
|
|
22
22
|
createComplianceProfilesFeature,
|
|
23
23
|
tenantComplianceProfileEntity,
|
|
@@ -85,7 +85,7 @@ beforeEach(async () => {
|
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
async function seedAlice(email: string = "alice@example.com"): Promise<void> {
|
|
88
|
-
await
|
|
88
|
+
await seedRow(stack.db, userTable, {
|
|
89
89
|
id: aliceUser.id,
|
|
90
90
|
tenantId: tenantA,
|
|
91
91
|
email,
|
|
@@ -153,7 +153,7 @@ describe("request-deletion :: sendDeletionRequestedEmail callback", () => {
|
|
|
153
153
|
});
|
|
154
154
|
|
|
155
155
|
test("422 user_not_in_active_state → callback NICHT gefeuert", async () => {
|
|
156
|
-
await
|
|
156
|
+
await seedRow(stack.db, userTable, {
|
|
157
157
|
id: aliceUser.id,
|
|
158
158
|
tenantId: tenantA,
|
|
159
159
|
email: "alice@example.com",
|