@cosmicdrift/kumiko-bundled-features 0.15.0 → 0.18.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.
Files changed (111) hide show
  1. package/package.json +1 -1
  2. package/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
  3. package/src/cap-counter/__tests__/{cap-counter.integration.ts → cap-counter.integration.test.ts} +14 -3
  4. package/src/cap-counter/__tests__/enforce-cap.test.ts +8 -4
  5. package/src/cap-counter/__tests__/{with-cap-enforcement.integration.ts → with-cap-enforcement.integration.test.ts} +14 -3
  6. package/src/cap-counter/enforce-cap.ts +2 -4
  7. package/src/cap-counter/handlers/get-counter.query.ts +1 -3
  8. package/src/cap-counter/handlers/increment.write.ts +1 -2
  9. package/src/cap-counter/handlers/mark-soft-warned.write.ts +1 -2
  10. package/src/channel-in-app/in-app-channel.ts +1 -3
  11. package/src/compliance-profiles/_internal/parse-override.ts +19 -0
  12. package/src/compliance-profiles/handlers/for-tenant.query.ts +6 -25
  13. package/src/compliance-profiles/resolve-for-tenant.ts +6 -20
  14. package/src/custom-fields/__tests__/cross-tenant-field-delete.integration.test.ts +177 -0
  15. package/src/custom-fields/__tests__/{custom-fields.integration.ts → custom-fields.integration.test.ts} +105 -0
  16. package/src/custom-fields/db/queries/projection.ts +33 -4
  17. package/src/custom-fields/db/queries/retention.ts +2 -2
  18. package/src/custom-fields/db/queries/user-data-rights.ts +6 -3
  19. package/src/custom-fields/feature.ts +10 -4
  20. package/src/custom-fields/handlers/delete-system-field.write.ts +5 -1
  21. package/src/custom-fields/handlers/delete-tenant-field.write.ts +1 -1
  22. package/src/custom-fields/handlers/set-custom-field.write.ts +33 -17
  23. package/src/custom-fields/lib/field-access.ts +39 -14
  24. package/src/custom-fields/lib/value-schema.ts +45 -0
  25. package/src/custom-fields/run-retention.ts +1 -1
  26. package/src/custom-fields/wire-for-entity.ts +22 -4
  27. package/src/custom-fields/wire-user-data-rights.ts +3 -2
  28. package/src/delivery/delivery-service.ts +1 -1
  29. package/src/delivery/feature.ts +8 -1
  30. package/src/delivery/types.ts +2 -2
  31. package/src/feature-toggles/__tests__/{feature-toggles.integration.ts → feature-toggles.integration.test.ts} +6 -6
  32. package/src/feature-toggles/handlers/set.write.ts +10 -8
  33. package/src/jobs/feature.ts +4 -1
  34. package/src/subscription-stripe/__tests__/{stripe-foundation.integration.ts → stripe-foundation.integration.test.ts} +7 -10
  35. package/src/tier-engine/__tests__/{resolver.integration.ts → resolver.integration.test.ts} +4 -3
  36. package/src/user-data-rights/__tests__/{audit-log.integration.ts → audit-log.integration.test.ts} +12 -5
  37. package/src/user-data-rights/__tests__/{cross-data-matrix.integration.ts → cross-data-matrix.integration.test.ts} +29 -12
  38. package/src/user-data-rights/__tests__/{download.integration.ts → download.integration.test.ts} +15 -7
  39. package/src/user-data-rights/__tests__/{export-job-idempotency.integration.ts → export-job-idempotency.integration.test.ts} +13 -11
  40. package/src/user-data-rights/__tests__/{request-cancel-deletion.integration.ts → request-cancel-deletion.integration.test.ts} +8 -7
  41. package/src/user-data-rights/__tests__/{request-deletion-callback.integration.ts → request-deletion-callback.integration.test.ts} +8 -5
  42. package/src/user-data-rights/__tests__/{request-export.integration.ts → request-export.integration.test.ts} +6 -3
  43. package/src/user-data-rights/__tests__/{restriction-flow.integration.ts → restriction-flow.integration.test.ts} +11 -8
  44. package/src/user-data-rights/__tests__/{run-export-jobs.integration.ts → run-export-jobs.integration.test.ts} +25 -13
  45. package/src/user-data-rights/__tests__/{run-forget-cleanup.integration.ts → run-forget-cleanup.integration.test.ts} +6 -3
  46. package/src/user-data-rights/__tests__/{run-user-export.integration.ts → run-user-export.integration.test.ts} +6 -3
  47. package/src/user-data-rights/__tests__/{user-data-rights.integration.ts → user-data-rights.integration.test.ts} +3 -1
  48. package/src/user-data-rights/db/queries/export-jobs.ts +6 -5
  49. package/src/user-data-rights/db/queries/forget-cleanup.ts +11 -6
  50. package/src/user-data-rights/handlers/cancel-deletion.write.ts +5 -10
  51. package/src/user-data-rights/handlers/export-status.query.ts +12 -12
  52. package/src/user-data-rights/run-export-jobs.ts +2 -5
  53. package/src/user-data-rights/run-forget-cleanup.ts +0 -1
  54. package/src/user-data-rights-defaults/__tests__/{user-data-rights-defaults.integration.ts → user-data-rights-defaults.integration.test.ts} +2 -0
  55. /package/src/__tests__/{es-ops-e2e.integration.ts → es-ops-e2e.integration.test.ts} +0 -0
  56. /package/src/audit/__tests__/{audit.integration.ts → audit.integration.test.ts} +0 -0
  57. /package/src/auth-email-password/__tests__/{account-lockout-no-redis.integration.ts → account-lockout-no-redis.integration.test.ts} +0 -0
  58. /package/src/auth-email-password/__tests__/{account-lockout.integration.ts → account-lockout.integration.test.ts} +0 -0
  59. /package/src/auth-email-password/__tests__/{auth-claims.integration.ts → auth-claims.integration.test.ts} +0 -0
  60. /package/src/auth-email-password/__tests__/{auth.integration.ts → auth.integration.test.ts} +0 -0
  61. /package/src/auth-email-password/__tests__/{email-verification.integration.ts → email-verification.integration.test.ts} +0 -0
  62. /package/src/auth-email-password/__tests__/{identity-v3-login.integration.ts → identity-v3-login.integration.test.ts} +0 -0
  63. /package/src/auth-email-password/__tests__/{invite-flow.integration.ts → invite-flow.integration.test.ts} +0 -0
  64. /package/src/auth-email-password/__tests__/{multi-roles.integration.ts → multi-roles.integration.test.ts} +0 -0
  65. /package/src/auth-email-password/__tests__/{password-reset.integration.ts → password-reset.integration.test.ts} +0 -0
  66. /package/src/auth-email-password/__tests__/{public-routes-rate-limit.integration.ts → public-routes-rate-limit.integration.test.ts} +0 -0
  67. /package/src/auth-email-password/__tests__/{seed-admin.integration.ts → seed-admin.integration.test.ts} +0 -0
  68. /package/src/auth-email-password/__tests__/{session-callbacks.integration.ts → session-callbacks.integration.test.ts} +0 -0
  69. /package/src/auth-email-password/__tests__/{session-strict-mode.integration.ts → session-strict-mode.integration.test.ts} +0 -0
  70. /package/src/auth-email-password/__tests__/{signup-flow.integration.ts → signup-flow.integration.test.ts} +0 -0
  71. /package/src/billing-foundation/__tests__/{billing-foundation.integration.ts → billing-foundation.integration.test.ts} +0 -0
  72. /package/src/compliance-profiles/__tests__/{compliance-profiles.integration.ts → compliance-profiles.integration.test.ts} +0 -0
  73. /package/src/compliance-profiles/__tests__/{seeding.integration.ts → seeding.integration.test.ts} +0 -0
  74. /package/src/config/__tests__/{cascade.integration.ts → cascade.integration.test.ts} +0 -0
  75. /package/src/config/__tests__/{config.integration.ts → config.integration.test.ts} +0 -0
  76. /package/src/custom-fields/__tests__/{audit-integration.integration.ts → audit-integration.integration.test.ts} +0 -0
  77. /package/src/custom-fields/__tests__/{field-access.integration.ts → field-access.integration.test.ts} +0 -0
  78. /package/src/custom-fields/__tests__/{quota.integration.ts → quota.integration.test.ts} +0 -0
  79. /package/src/custom-fields/__tests__/{retention.integration.ts → retention.integration.test.ts} +0 -0
  80. /package/src/custom-fields/__tests__/{user-data-rights.integration.ts → user-data-rights.integration.test.ts} +0 -0
  81. /package/src/data-retention/__tests__/{data-retention.integration.ts → data-retention.integration.test.ts} +0 -0
  82. /package/src/data-retention/__tests__/{policy-for.integration.ts → policy-for.integration.test.ts} +0 -0
  83. /package/src/delivery/__tests__/{delivery-events.integration.ts → delivery-events.integration.test.ts} +0 -0
  84. /package/src/delivery/__tests__/{delivery.integration.ts → delivery.integration.test.ts} +0 -0
  85. /package/src/file-foundation/__tests__/{file-foundation.integration.ts → file-foundation.integration.test.ts} +0 -0
  86. /package/src/files/__tests__/{files.integration.ts → files.integration.test.ts} +0 -0
  87. /package/src/files-provider-s3/__tests__/{s3-provider.integration.ts → s3-provider.integration.test.ts} +0 -0
  88. /package/src/jobs/__tests__/{job-system-user.integration.ts → job-system-user.integration.test.ts} +0 -0
  89. /package/src/jobs/__tests__/{jobs-events.integration.ts → jobs-events.integration.test.ts} +0 -0
  90. /package/src/jobs/__tests__/{jobs-feature.integration.ts → jobs-feature.integration.test.ts} +0 -0
  91. /package/src/legal-pages/__tests__/{legal-pages.integration.ts → legal-pages.integration.test.ts} +0 -0
  92. /package/src/mail-foundation/__tests__/{mail-foundation.integration.ts → mail-foundation.integration.test.ts} +0 -0
  93. /package/src/rate-limiting/__tests__/{rate-limiting.integration.ts → rate-limiting.integration.test.ts} +0 -0
  94. /package/src/renderer-foundation/__tests__/{collect-plugins.integration.ts → collect-plugins.integration.test.ts} +0 -0
  95. /package/src/secrets/__tests__/{rotate.integration.ts → rotate.integration.test.ts} +0 -0
  96. /package/src/secrets/__tests__/{secrets-events.integration.ts → secrets-events.integration.test.ts} +0 -0
  97. /package/src/secrets/__tests__/{secrets.integration.ts → secrets.integration.test.ts} +0 -0
  98. /package/src/sessions/__tests__/{cleanup.integration.ts → cleanup.integration.test.ts} +0 -0
  99. /package/src/sessions/__tests__/{password-auto-revoke.integration.ts → password-auto-revoke.integration.test.ts} +0 -0
  100. /package/src/sessions/__tests__/{sessions.integration.ts → sessions.integration.test.ts} +0 -0
  101. /package/src/subscription-mollie/__tests__/{mollie-foundation.integration.ts → mollie-foundation.integration.test.ts} +0 -0
  102. /package/src/template-resolver/__tests__/{handlers.integration.ts → handlers.integration.test.ts} +0 -0
  103. /package/src/template-resolver/__tests__/{template-resolver.integration.ts → template-resolver.integration.test.ts} +0 -0
  104. /package/src/tenant/__tests__/{multi-tenant.integration.ts → multi-tenant.integration.test.ts} +0 -0
  105. /package/src/tenant/__tests__/{seed-testing.integration.ts → seed-testing.integration.test.ts} +0 -0
  106. /package/src/tenant/__tests__/{tenant.integration.ts → tenant.integration.test.ts} +0 -0
  107. /package/src/text-content/__tests__/{text-content.integration.ts → text-content.integration.test.ts} +0 -0
  108. /package/src/tier-engine/__tests__/{auto-default-tier.integration.ts → auto-default-tier.integration.test.ts} +0 -0
  109. /package/src/tier-engine/__tests__/{tier-engine.integration.ts → tier-engine.integration.test.ts} +0 -0
  110. /package/src/user/__tests__/{seed-testing.integration.ts → seed-testing.integration.test.ts} +0 -0
  111. /package/src/user/__tests__/{user.integration.ts → user.integration.test.ts} +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
4
4
  import { asRawClient, insertMany, insertOne } from "@cosmicdrift/kumiko-framework/bun-db";
5
- import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
5
+ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
6
6
  import {
7
7
  createTestUser,
8
8
  setupTestStack,
@@ -10,11 +10,14 @@ import {
10
10
  testTenantId,
11
11
  unsafeCreateEntityTable,
12
12
  } from "@cosmicdrift/kumiko-framework/stack";
13
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
13
14
  import {
14
15
  createComplianceProfilesFeature,
15
16
  tenantComplianceProfileEntity,
17
+ tenantComplianceProfileTable,
16
18
  } from "../../compliance-profiles";
17
19
  import { createDataRetentionFeature } from "../../data-retention";
20
+ import { createSessionsFeature } from "../../sessions";
18
21
  import { USER_STATUS, userEntity, userTable } from "../../user";
19
22
  import { createUserFeature } from "../../user/feature";
20
23
  import { createUserDataRightsFeature } from "../feature";
@@ -36,6 +39,8 @@ beforeAll(async () => {
36
39
  createUserFeature(),
37
40
  createDataRetentionFeature(),
38
41
  createComplianceProfilesFeature(),
42
+ createSessionsFeature(),
43
+
39
44
  createUserDataRightsFeature(),
40
45
  ],
41
46
  });
@@ -50,10 +55,12 @@ afterAll(async () => {
50
55
  });
51
56
 
52
57
  beforeEach(async () => {
53
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
54
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_compliance_profiles`);
55
- await asRawClient(stack.db).unsafe(`DELETE FROM read_download_attempts`);
56
- await asRawClient(stack.db).unsafe(`DELETE FROM kumiko_events`);
58
+ await resetTestTables(stack.db, [
59
+ userTable,
60
+ tenantComplianceProfileTable,
61
+ downloadAttemptsTable,
62
+ eventsTable,
63
+ ]);
57
64
  });
58
65
 
59
66
  async function seedUser(u: typeof alice, email: string): Promise<void> {
@@ -13,18 +13,20 @@
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, insertOne } from "@cosmicdrift/kumiko-framework/bun-db";
16
+ import { asRawClient, deleteMany, insertOne } from "@cosmicdrift/kumiko-framework/bun-db";
17
17
  import {
18
18
  defineFeature,
19
19
  EXT_USER_DATA,
20
20
  type UserDataDeleteHook,
21
21
  type UserDataExportHook,
22
22
  } from "@cosmicdrift/kumiko-framework/engine";
23
+ import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
23
24
  import {
24
25
  setupTestStack,
25
26
  type TestStack,
26
27
  unsafeCreateEntityTable,
27
28
  } from "@cosmicdrift/kumiko-framework/stack";
29
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
28
30
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
29
31
  import {
30
32
  createComplianceProfilesFeature,
@@ -32,6 +34,8 @@ import {
32
34
  } from "../../compliance-profiles";
33
35
  import { createDataRetentionFeature, tenantRetentionOverrideEntity } from "../../data-retention";
34
36
  import { createFilesFeature } from "../../files";
37
+ import { createSessionsFeature } from "../../sessions";
38
+ import { tenantMembershipsTable } from "../../tenant";
35
39
  import { createUserFeature, USER_STATUS, userEntity, userTable } from "../../user";
36
40
  import { createUserDataRightsDefaultsFeature } from "../../user-data-rights-defaults";
37
41
  import { createUserDataRightsFeature } from "../feature";
@@ -55,6 +59,18 @@ type Instant = InstanceType<ReturnType<typeof getTemporal>["Instant"]>;
55
59
  const NOW = (): Instant => getTemporal().Now.instant();
56
60
  const PAST = (): Instant => getTemporal().Instant.fromEpochMilliseconds(Date.now() - 60_000);
57
61
 
62
+ const KUMIKO_NAME = Symbol.for("kumiko:schema:Name");
63
+ const KUMIKO_COLUMNS = Symbol.for("kumiko:schema:Columns");
64
+
65
+ /** Minimal bun-db table descriptor for the synthetic test_notes table. */
66
+ const testNotesTable = {
67
+ [KUMIKO_NAME]: "test_notes",
68
+ [KUMIKO_COLUMNS]: {
69
+ tenantId: { name: "tenant_id", getSQLType: () => "uuid" },
70
+ authorId: { name: "author_id", getSQLType: () => "text" },
71
+ },
72
+ };
73
+
58
74
  // Synthetic third-party Domain-Feature: "note" mit export- + delete-Hook.
59
75
  // Stellvertretend fuer App-spezifische Entities (Chat-Message, Blog-Post
60
76
  // etc.), die ueber EXT_USER_DATA sauber in die Pipeline integrieren.
@@ -81,13 +97,10 @@ const exportNotes: UserDataExportHook = async (ctx) => {
81
97
  };
82
98
 
83
99
  const deleteNotes: UserDataDeleteHook = async (ctx, _strategy) => {
84
- await asRawClient(ctx.db).unsafe(
85
- `
86
- DELETE FROM test_notes
87
- WHERE tenant_id = $1 AND author_id = $2
88
- `,
89
- [ctx.tenantId, ctx.userId],
90
- );
100
+ await deleteMany(ctx.db, testNotesTable, {
101
+ tenantId: ctx.tenantId,
102
+ authorId: ctx.userId,
103
+ });
91
104
  };
92
105
 
93
106
  const testNotesFeature = defineFeature("test-notes", (r) => {
@@ -104,6 +117,8 @@ beforeAll(async () => {
104
117
  createFilesFeature(),
105
118
  createDataRetentionFeature(),
106
119
  createComplianceProfilesFeature(),
120
+ createSessionsFeature(),
121
+
107
122
  createUserDataRightsFeature(),
108
123
  createUserDataRightsDefaultsFeature(),
109
124
  testNotesFeature,
@@ -161,10 +176,12 @@ afterAll(async () => {
161
176
  });
162
177
 
163
178
  beforeEach(async () => {
164
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
165
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_memberships`);
166
- await asRawClient(stack.db).unsafe(`DELETE FROM file_refs`);
167
- await asRawClient(stack.db).unsafe(`DELETE FROM test_notes`);
179
+ await resetTestTables(stack.db, [
180
+ userTable,
181
+ tenantMembershipsTable,
182
+ fileRefsTable,
183
+ testNotesTable,
184
+ ]);
168
185
  });
169
186
 
170
187
  async function seedUser(
@@ -12,7 +12,7 @@ import { randomBytes } from "node:crypto";
12
12
  import { asRawClient, selectMany, updateMany } 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
- import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
15
+ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
16
16
  import {
17
17
  createInMemoryFileProvider,
18
18
  type FileStorageProvider,
@@ -25,10 +25,12 @@ import {
25
25
  unsafeCreateEntityTable,
26
26
  unsafePushTables,
27
27
  } from "@cosmicdrift/kumiko-framework/stack";
28
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
28
29
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
29
30
  import {
30
31
  createComplianceProfilesFeature,
31
32
  tenantComplianceProfileEntity,
33
+ tenantComplianceProfileTable,
32
34
  } from "../../compliance-profiles";
33
35
  import { createConfigFeature } from "../../config";
34
36
  import { ConfigHandlers } from "../../config/constants";
@@ -38,6 +40,8 @@ import { configValuesTable } from "../../config/table";
38
40
  import { createDataRetentionFeature } from "../../data-retention";
39
41
  import { fileFoundationFeature } from "../../file-foundation";
40
42
  import { fileProviderInMemoryFeature } from "../../file-provider-inmemory";
43
+ import { createSessionsFeature } from "../../sessions";
44
+ import { tenantMembershipsTable } from "../../tenant";
41
45
  import { createUserFeature } from "../../user";
42
46
  import { createUserDataRightsFeature } from "../feature";
43
47
  import { runExportJobs } from "../run-export-jobs";
@@ -108,6 +112,8 @@ beforeAll(async () => {
108
112
  fileFoundationFeature,
109
113
  fileProviderInMemoryFeature,
110
114
  noSignedUrlProviderFeature,
115
+ createSessionsFeature(),
116
+
111
117
  createUserDataRightsFeature(),
112
118
  ],
113
119
  extraContext: ({ registry }) => ({
@@ -151,12 +157,14 @@ afterAll(async () => {
151
157
  });
152
158
 
153
159
  beforeEach(async () => {
154
- await asRawClient(stack.db).unsafe(`DELETE FROM "${exportDownloadTokensTable.tableName}"`);
155
- await asRawClient(stack.db).unsafe(`DELETE FROM "${exportJobsTable.tableName}"`);
156
- await asRawClient(stack.db).unsafe(`DELETE FROM kumiko_events`);
157
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_compliance_profiles`);
158
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_memberships`);
159
- await asRawClient(stack.db).unsafe(`DELETE FROM $1`, [configValuesTable]);
160
+ await resetTestTables(stack.db, [
161
+ exportDownloadTokensTable,
162
+ exportJobsTable,
163
+ eventsTable,
164
+ tenantComplianceProfileTable,
165
+ tenantMembershipsTable,
166
+ configValuesTable,
167
+ ]);
160
168
  providerPerTenant = new Map();
161
169
 
162
170
  // Setup file-foundation provider="inmemory" pro Tenant.
@@ -16,14 +16,17 @@ import {
16
16
  selectMany,
17
17
  updateMany,
18
18
  } from "@cosmicdrift/kumiko-framework/bun-db";
19
+ import { extractPgError } from "@cosmicdrift/kumiko-framework/db";
19
20
  import {
20
21
  setupTestStack,
21
22
  type TestStack,
22
23
  unsafeCreateEntityTable,
23
24
  } from "@cosmicdrift/kumiko-framework/stack";
25
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
24
26
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
25
27
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
26
28
  import { createDataRetentionFeature } from "../../data-retention";
29
+ import { createSessionsFeature } from "../../sessions";
27
30
  import { createUserFeature } from "../../user";
28
31
  import { createUserDataRightsFeature } from "../feature";
29
32
  import {
@@ -45,6 +48,8 @@ beforeAll(async () => {
45
48
  createUserFeature(),
46
49
  createDataRetentionFeature(),
47
50
  createComplianceProfilesFeature(),
51
+ createSessionsFeature(),
52
+
48
53
  createUserDataRightsFeature(),
49
54
  ],
50
55
  });
@@ -57,18 +62,15 @@ afterAll(async () => {
57
62
  });
58
63
 
59
64
  beforeEach(async () => {
60
- await asRawClient(stack.db).unsafe(`DELETE FROM "${exportJobsTable.tableName}"`);
65
+ await resetTestTables(stack.db, [exportJobsTable]);
61
66
  });
62
67
 
63
68
  const NOW = () => getTemporal().Now.instant();
64
69
 
65
- // Drizzle wraps PostgresError mit "Failed query: ..."; die original
66
- // PG-Message ist im `cause`. Unique-Violation hat sqlstate 23505.
67
- //
68
- // Constraint-Name pinnen damit der Test nur unsere Idempotency-
69
- // Constraint pinst, nicht zufaellig eine andere unique-violation
70
- // (z.B. UUID-Kollision auf id-PK durch wiederholtes Test-Setup) —
71
- // silent-Pass durch fremden Constraint waere falsche Bestaetigung.
70
+ // postgres-js wirft PostgresError direkt; Drizzle wrappt in .cause.
71
+ // extractPgError normalisiert beide Shapes. Constraint-Name pinnen damit
72
+ // der Test nur unsere Idempotency-Constraint pinst, nicht zufaellig eine
73
+ // andere unique-violation (z.B. UUID-Kollision auf id-PK).
72
74
  async function expectUniqueViolation(
73
75
  promise: Promise<unknown>,
74
76
  expectedConstraint: string,
@@ -80,9 +82,9 @@ async function expectUniqueViolation(
80
82
  caught = e;
81
83
  }
82
84
  expect(caught).toBeDefined();
83
- const cause = (caught as { cause?: { code?: string; constraint_name?: string } }).cause;
84
- expect(cause?.code).toBe("23505");
85
- expect(cause?.constraint_name).toBe(expectedConstraint);
85
+ const pg = extractPgError(caught);
86
+ expect(pg?.code).toBe("23505");
87
+ expect(pg?.constraint_name).toBe(expectedConstraint);
86
88
  }
87
89
 
88
90
  // Re-Export aus dem Schema — Single source of truth, Rename hier
@@ -10,8 +10,8 @@
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 { asRawClient, insertOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
14
- import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
13
+ import { insertOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
14
+ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
15
15
  import {
16
16
  createTestUser,
17
17
  setupTestStack,
@@ -20,12 +20,15 @@ import {
20
20
  testUserId,
21
21
  unsafeCreateEntityTable,
22
22
  } from "@cosmicdrift/kumiko-framework/stack";
23
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
23
24
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
24
25
  import {
25
26
  createComplianceProfilesFeature,
26
27
  tenantComplianceProfileEntity,
28
+ tenantComplianceProfileTable,
27
29
  } from "../../compliance-profiles";
28
30
  import { createDataRetentionFeature } from "../../data-retention";
31
+ import { createSessionsFeature } from "../../sessions";
29
32
  import { USER_STATUS, userEntity, userTable } from "../../user";
30
33
  import { createUserFeature } from "../../user/feature";
31
34
  import { createUserDataRightsFeature } from "../feature";
@@ -54,6 +57,8 @@ const features = [
54
57
  createUserFeature(),
55
58
  createDataRetentionFeature(),
56
59
  createComplianceProfilesFeature(),
60
+ createSessionsFeature(),
61
+
57
62
  createUserDataRightsFeature(),
58
63
  ];
59
64
 
@@ -69,11 +74,7 @@ afterAll(async () => {
69
74
  });
70
75
 
71
76
  beforeEach(async () => {
72
- // Hard-clean User-Rows fuer einen sauberen Start je Test. softDelete
73
- // wuerde sonst row-state aus voherigen Tests einschleppen.
74
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
75
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_compliance_profiles`);
76
- await asRawClient(stack.db).unsafe(`DELETE FROM kumiko_events`);
77
+ await resetTestTables(stack.db, [userTable, tenantComplianceProfileTable, eventsTable]);
77
78
  });
78
79
 
79
80
  // gracePeriodEnd ist `instant()` (Temporal.Instant in JS). Nicht JS-Date —
@@ -8,8 +8,8 @@
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 { asRawClient, insertOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
12
- import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
11
+ import { insertOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
12
+ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
13
13
  import {
14
14
  createTestUser,
15
15
  setupTestStack,
@@ -17,11 +17,14 @@ import {
17
17
  testTenantId,
18
18
  unsafeCreateEntityTable,
19
19
  } from "@cosmicdrift/kumiko-framework/stack";
20
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
20
21
  import {
21
22
  createComplianceProfilesFeature,
22
23
  tenantComplianceProfileEntity,
24
+ tenantComplianceProfileTable,
23
25
  } from "../../compliance-profiles";
24
26
  import { createDataRetentionFeature } from "../../data-retention";
27
+ import { createSessionsFeature } from "../../sessions";
25
28
  import { USER_STATUS, userEntity, userTable } from "../../user";
26
29
  import { createUserFeature } from "../../user/feature";
27
30
  import { createUserDataRightsFeature } from "../feature";
@@ -61,6 +64,8 @@ beforeAll(async () => {
61
64
  createUserFeature(),
62
65
  createDataRetentionFeature(),
63
66
  createComplianceProfilesFeature(),
67
+ createSessionsFeature(),
68
+
64
69
  createUserDataRightsFeature({ sendDeletionRequestedEmail }),
65
70
  ],
66
71
  });
@@ -76,9 +81,7 @@ afterAll(async () => {
76
81
  beforeEach(async () => {
77
82
  state.calls = [];
78
83
  state.shouldThrow = false;
79
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
80
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_compliance_profiles`);
81
- await asRawClient(stack.db).unsafe(`DELETE FROM kumiko_events`);
84
+ await resetTestTables(stack.db, [userTable, tenantComplianceProfileTable, eventsTable]);
82
85
  });
83
86
 
84
87
  async function seedAlice(email: string = "alice@example.com"): Promise<void> {
@@ -24,7 +24,7 @@ import {
24
24
  selectMany,
25
25
  updateMany,
26
26
  } from "@cosmicdrift/kumiko-framework/bun-db";
27
- import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
27
+ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
28
28
  import {
29
29
  createTestUser,
30
30
  setupTestStack,
@@ -32,9 +32,11 @@ import {
32
32
  testTenantId,
33
33
  unsafeCreateEntityTable,
34
34
  } from "@cosmicdrift/kumiko-framework/stack";
35
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
35
36
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
36
37
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
37
38
  import { createDataRetentionFeature } from "../../data-retention";
39
+ import { createSessionsFeature } from "../../sessions";
38
40
  import { createUserFeature } from "../../user";
39
41
  import { createUserDataRightsFeature } from "../feature";
40
42
  import { EXPORT_JOB_STATUS, exportJobEntity, exportJobsTable } from "../schema/export-job";
@@ -56,6 +58,8 @@ beforeAll(async () => {
56
58
  createUserFeature(),
57
59
  createDataRetentionFeature(),
58
60
  createComplianceProfilesFeature(),
61
+ createSessionsFeature(),
62
+
59
63
  createUserDataRightsFeature(),
60
64
  ],
61
65
  });
@@ -68,8 +72,7 @@ afterAll(async () => {
68
72
  });
69
73
 
70
74
  beforeEach(async () => {
71
- await asRawClient(stack.db).unsafe(`DELETE FROM "${exportJobsTable.tableName}"`);
72
- await asRawClient(stack.db).unsafe(`DELETE FROM kumiko_events`);
75
+ await resetTestTables(stack.db, [exportJobsTable, eventsTable]);
73
76
  });
74
77
 
75
78
  type RequestExportResponse = {
@@ -12,10 +12,10 @@
12
12
 
13
13
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
14
14
  import { randomBytes } from "node:crypto";
15
- import { asRawClient, selectMany, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
15
+ import { selectMany, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
16
16
  import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
17
17
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
18
- import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
18
+ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
19
19
  import {
20
20
  setupTestStack,
21
21
  type TestStack,
@@ -24,13 +24,14 @@ import {
24
24
  unsafeCreateEntityTable,
25
25
  unsafePushTables,
26
26
  } from "@cosmicdrift/kumiko-framework/stack";
27
- import { createLateBoundHolder } from "@cosmicdrift/kumiko-framework/testing";
27
+ import { createLateBoundHolder, resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
28
28
  import { AuthErrors, AuthHandlers } from "../../auth-email-password/constants";
29
29
  import { createAuthEmailPasswordFeature } from "../../auth-email-password/feature";
30
30
  import { hashPassword } from "../../auth-email-password/password-hashing";
31
31
  import {
32
32
  createComplianceProfilesFeature,
33
33
  tenantComplianceProfileEntity,
34
+ tenantComplianceProfileTable,
34
35
  } from "../../compliance-profiles";
35
36
  import { createConfigFeature } from "../../config";
36
37
  import { createConfigResolver } from "../../config/resolver";
@@ -97,11 +98,13 @@ afterAll(async () => {
97
98
  });
98
99
 
99
100
  beforeEach(async () => {
100
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userSessionTable.tableName}"`);
101
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
102
- await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
103
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_compliance_profiles`);
104
- await asRawClient(stack.db).unsafe(`DELETE FROM kumiko_events`);
101
+ await resetTestTables(stack.db, [
102
+ userSessionTable,
103
+ userTable,
104
+ tenantMembershipsTable,
105
+ tenantComplianceProfileTable,
106
+ eventsTable,
107
+ ]);
105
108
  });
106
109
 
107
110
  async function seedAliceWithMembership(
@@ -23,7 +23,7 @@ import {
23
23
  selectMany,
24
24
  updateMany,
25
25
  } from "@cosmicdrift/kumiko-framework/bun-db";
26
- import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
26
+ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
27
27
  import {
28
28
  createInMemoryFileProvider,
29
29
  type FileStorageProvider,
@@ -35,12 +35,16 @@ import {
35
35
  testTenantId,
36
36
  unsafeCreateEntityTable,
37
37
  } from "@cosmicdrift/kumiko-framework/stack";
38
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
38
39
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
39
40
  import {
40
41
  createComplianceProfilesFeature,
41
42
  tenantComplianceProfileEntity,
43
+ tenantComplianceProfileTable,
42
44
  } from "../../compliance-profiles";
43
45
  import { createDataRetentionFeature } from "../../data-retention";
46
+ import { createSessionsFeature } from "../../sessions";
47
+ import { tenantMembershipsTable } from "../../tenant";
44
48
  import { createUserFeature, USER_STATUS, userEntity, userTable } from "../../user";
45
49
  import { createUserDataRightsFeature } from "../feature";
46
50
  import { runExportJobs } from "../run-export-jobs";
@@ -60,6 +64,8 @@ beforeAll(async () => {
60
64
  createUserFeature(),
61
65
  createDataRetentionFeature(),
62
66
  createComplianceProfilesFeature(),
67
+ createSessionsFeature(),
68
+
63
69
  createUserDataRightsFeature(),
64
70
  ],
65
71
  });
@@ -94,12 +100,14 @@ afterAll(async () => {
94
100
  });
95
101
 
96
102
  beforeEach(async () => {
97
- await asRawClient(stack.db).unsafe(`DELETE FROM "${exportDownloadTokensTable.tableName}"`);
98
- await asRawClient(stack.db).unsafe(`DELETE FROM "${exportJobsTable.tableName}"`);
99
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
100
- await asRawClient(stack.db).unsafe(`DELETE FROM kumiko_events`);
101
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_compliance_profiles`);
102
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_memberships`);
103
+ await resetTestTables(stack.db, [
104
+ exportDownloadTokensTable,
105
+ exportJobsTable,
106
+ userTable,
107
+ eventsTable,
108
+ tenantComplianceProfileTable,
109
+ tenantMembershipsTable,
110
+ ]);
103
111
  providerPerTenant = new Map();
104
112
 
105
113
  // Atom 5: aliceUser-Row mit email seeden — Worker-Notification-Callback
@@ -734,6 +742,8 @@ describe("runExportJobs :: Atom 3c file-binaries", () => {
734
742
  createUserFeature(),
735
743
  createDataRetentionFeature(),
736
744
  createComplianceProfilesFeature(),
745
+ createSessionsFeature(),
746
+
737
747
  createUserDataRightsFeature(),
738
748
  testFileExporter,
739
749
  ],
@@ -767,11 +777,13 @@ describe("runExportJobs :: Atom 3c file-binaries", () => {
767
777
 
768
778
  beforeEach(async () => {
769
779
  if (!localStack) return;
770
- await asRawClient(localStack.db).unsafe(`DELETE FROM "${exportDownloadTokensTable.tableName}"`);
771
- await asRawClient(localStack.db).unsafe(`DELETE FROM "${exportJobsTable.tableName}"`);
772
- await asRawClient(localStack.db).unsafe(`DELETE FROM kumiko_events`);
773
- await asRawClient(localStack.db).unsafe(`DELETE FROM read_tenant_compliance_profiles`);
774
- await asRawClient(localStack.db).unsafe(`DELETE FROM read_tenant_memberships`);
780
+ await resetTestTables(localStack.db, [
781
+ exportDownloadTokensTable,
782
+ exportJobsTable,
783
+ eventsTable,
784
+ tenantComplianceProfileTable,
785
+ tenantMembershipsTable,
786
+ ]);
775
787
  // Reset zu safe Default damit kein Test den State an den naechsten leakt.
776
788
  currentTestFileName = "report.pdf";
777
789
  });
@@ -1033,7 +1045,7 @@ describe("runExportJobs :: Atom 5 notification-callbacks", () => {
1033
1045
  test("user ohne email → Callback skipped + console.warn (kein Throw)", async () => {
1034
1046
  // User-Row mit email=null seeden (override). Worker logged warn,
1035
1047
  // Callback wird NICHT gerufen, Worker-Run bleibt successful.
1036
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
1048
+ await resetTestTables(stack.db, [userTable]);
1037
1049
  await insertOne(stack.db, userTable, {
1038
1050
  id: String(aliceUser.id),
1039
1051
  tenantId: tenantA,
@@ -17,15 +17,18 @@
17
17
 
18
18
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
19
19
  import { asRawClient, insertOne } from "@cosmicdrift/kumiko-framework/bun-db";
20
+ import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
20
21
  import {
21
22
  setupTestStack,
22
23
  type TestStack,
23
24
  unsafeCreateEntityTable,
24
25
  } from "@cosmicdrift/kumiko-framework/stack";
26
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
25
27
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
26
28
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
27
29
  import { createDataRetentionFeature, tenantRetentionOverrideEntity } from "../../data-retention";
28
30
  import { createFilesFeature } from "../../files";
31
+ import { createSessionsFeature } from "../../sessions";
29
32
  import {
30
33
  createUserFeature,
31
34
  USER_ANONYMIZED_DISPLAY_NAME,
@@ -61,6 +64,8 @@ beforeAll(async () => {
61
64
  createFilesFeature(),
62
65
  createDataRetentionFeature(),
63
66
  createComplianceProfilesFeature(),
67
+ createSessionsFeature(),
68
+
64
69
  createUserDataRightsFeature(),
65
70
  createUserDataRightsDefaultsFeature(),
66
71
  ],
@@ -109,9 +114,7 @@ afterAll(async () => {
109
114
  });
110
115
 
111
116
  beforeEach(async () => {
112
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
113
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_memberships`);
114
- await asRawClient(stack.db).unsafe(`DELETE FROM file_refs`);
117
+ await resetTestTables(stack.db, [userTable, "read_tenant_memberships", fileRefsTable]);
115
118
  });
116
119
 
117
120
  type Instant = InstanceType<ReturnType<typeof getTemporal>["Instant"]>;
@@ -16,15 +16,18 @@
16
16
 
17
17
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
18
18
  import { asRawClient, insertOne } from "@cosmicdrift/kumiko-framework/bun-db";
19
+ import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
19
20
  import {
20
21
  setupTestStack,
21
22
  type TestStack,
22
23
  unsafeCreateEntityTable,
23
24
  } from "@cosmicdrift/kumiko-framework/stack";
25
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
24
26
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
25
27
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
26
28
  import { createDataRetentionFeature } from "../../data-retention";
27
29
  import { createFilesFeature } from "../../files";
30
+ import { createSessionsFeature } from "../../sessions";
28
31
  import { createUserFeature, USER_STATUS, userEntity, userTable } from "../../user";
29
32
  import { createUserDataRightsDefaultsFeature } from "../../user-data-rights-defaults";
30
33
  import { createUserDataRightsFeature } from "../feature";
@@ -51,6 +54,8 @@ beforeAll(async () => {
51
54
  createFilesFeature(),
52
55
  createDataRetentionFeature(),
53
56
  createComplianceProfilesFeature(),
57
+ createSessionsFeature(),
58
+
54
59
  createUserDataRightsFeature(),
55
60
  createUserDataRightsDefaultsFeature(),
56
61
  ],
@@ -96,9 +101,7 @@ afterAll(async () => {
96
101
  });
97
102
 
98
103
  beforeEach(async () => {
99
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
100
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_memberships`);
101
- await asRawClient(stack.db).unsafe(`DELETE FROM file_refs`);
104
+ await resetTestTables(stack.db, [userTable, "read_tenant_memberships", fileRefsTable]);
102
105
  });
103
106
 
104
107
  const NOW = () => getTemporal().Now.instant();
@@ -10,6 +10,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
10
10
  import { setupTestStack, type TestStack } from "@cosmicdrift/kumiko-framework/stack";
11
11
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
12
12
  import { createDataRetentionFeature } from "../../data-retention";
13
+ import { createSessionsFeature } from "../../sessions";
13
14
  import { createUserFeature } from "../../user";
14
15
  import { createUserDataRightsFeature } from "../feature";
15
16
 
@@ -18,11 +19,12 @@ let stack: TestStack;
18
19
  const userFeature = createUserFeature();
19
20
  const dataRetention = createDataRetentionFeature();
20
21
  const complianceProfiles = createComplianceProfilesFeature();
22
+ const sessionsFeature = createSessionsFeature();
21
23
  const userDataRights = createUserDataRightsFeature();
22
24
 
23
25
  beforeAll(async () => {
24
26
  stack = await setupTestStack({
25
- features: [userFeature, dataRetention, complianceProfiles, userDataRights],
27
+ features: [userFeature, sessionsFeature, dataRetention, complianceProfiles, userDataRights],
26
28
  });
27
29
  });
28
30