@cosmicdrift/kumiko-bundled-features 0.13.0 → 0.15.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 +6 -6
- package/src/__tests__/env-schemas.test.ts +1 -1
- package/src/__tests__/es-ops-e2e.integration.ts +10 -9
- package/src/audit/__tests__/audit.integration.ts +3 -3
- package/src/audit/handlers/list.query.ts +39 -51
- package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.ts +4 -3
- package/src/auth-email-password/__tests__/account-lockout.integration.ts +4 -3
- package/src/auth-email-password/__tests__/auth-claims.integration.ts +5 -4
- package/src/auth-email-password/__tests__/auth.integration.ts +4 -3
- package/src/auth-email-password/__tests__/confirm-token-flow.test.ts +1 -1
- package/src/auth-email-password/__tests__/email-templates.test.ts +1 -1
- package/src/auth-email-password/__tests__/email-verification.integration.ts +7 -10
- package/src/auth-email-password/__tests__/identity-v3-hash.test.ts +1 -1
- package/src/auth-email-password/__tests__/identity-v3-login.integration.ts +4 -3
- package/src/auth-email-password/__tests__/invite-flow.integration.ts +16 -43
- package/src/auth-email-password/__tests__/multi-roles.integration.ts +6 -9
- package/src/auth-email-password/__tests__/password-reset.integration.ts +8 -7
- package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.ts +4 -3
- package/src/auth-email-password/__tests__/seed-admin.integration.ts +19 -32
- package/src/auth-email-password/__tests__/session-callbacks.integration.ts +6 -5
- package/src/auth-email-password/__tests__/session-strict-mode.integration.ts +1 -1
- package/src/auth-email-password/__tests__/signed-token.test.ts +1 -1
- package/src/auth-email-password/__tests__/signup-flow.integration.ts +11 -15
- package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +26 -26
- package/src/auth-email-password/handlers/invite-accept.write.ts +24 -21
- package/src/auth-email-password/handlers/invite-create.write.ts +3 -8
- package/src/auth-email-password/handlers/invite-signup-complete.write.ts +20 -17
- package/src/auth-email-password/handlers/signup-confirm.write.ts +3 -7
- package/src/auth-email-password/seeding.ts +1 -1
- package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +1 -2
- package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +10 -19
- package/src/auth-email-password/web/__tests__/login-screen.test.tsx +12 -18
- package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +12 -17
- package/src/auth-email-password/web/__tests__/session-roles.test.ts +1 -1
- package/src/auth-email-password/web/__tests__/tenant-switcher.test.tsx +1 -8
- package/src/auth-email-password/web/__tests__/test-utils.tsx +4 -8
- package/src/auth-email-password/web/__tests__/user-menu.test.tsx +2 -8
- package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +10 -15
- package/src/billing-foundation/__tests__/billing-foundation.integration.ts +1 -1
- package/src/billing-foundation/__tests__/feature.test.ts +1 -1
- package/src/billing-foundation/__tests__/webhook-handler.test.ts +6 -5
- package/src/billing-foundation/db/queries/subscription-projection.ts +15 -0
- package/src/billing-foundation/get-subscription-for-tenant.ts +2 -6
- package/src/billing-foundation/handlers/create-portal-session.write.ts +2 -2
- package/src/billing-foundation/handlers/list-subscriptions.query.ts +4 -1
- package/src/billing-foundation/projection.ts +32 -13
- package/src/cap-counter/__tests__/cap-counter.integration.ts +1 -1
- package/src/cap-counter/__tests__/enforce-cap.test.ts +37 -32
- package/src/cap-counter/__tests__/with-cap-enforcement.integration.ts +1 -1
- package/src/cap-counter/enforce-cap.ts +14 -20
- package/src/cap-counter/handlers/get-counter.query.ts +7 -13
- package/src/cap-counter/handlers/increment.write.ts +2 -2
- package/src/cap-counter/handlers/mark-soft-warned.write.ts +2 -2
- package/src/channel-in-app/handlers/inbox.query.ts +7 -13
- package/src/channel-in-app/handlers/mark-all-read.write.ts +7 -9
- package/src/channel-in-app/handlers/mark-read.write.ts +8 -14
- package/src/channel-in-app/handlers/unread-count.query.ts +10 -9
- package/src/channel-in-app/in-app-channel.ts +10 -12
- package/src/channel-in-app/tables.ts +1 -1
- package/src/compliance-profiles/__tests__/compliance-profiles.integration.ts +1 -1
- package/src/compliance-profiles/__tests__/seeding.integration.ts +1 -1
- package/src/compliance-profiles/handlers/for-tenant.query.ts +4 -7
- package/src/compliance-profiles/handlers/needs-profile.query.ts +4 -7
- package/src/compliance-profiles/handlers/set-profile.write.ts +5 -7
- package/src/compliance-profiles/resolve-for-tenant.ts +5 -7
- package/src/compliance-profiles/schema/profile-selection.ts +2 -2
- package/src/compliance-profiles/seeding.ts +4 -7
- package/src/config/__tests__/app-overrides.test.ts +1 -1
- package/src/config/__tests__/cascade.integration.ts +1 -1
- package/src/config/__tests__/config.integration.ts +8 -27
- package/src/config/db/queries/resolver.ts +47 -0
- package/src/config/handlers/__tests__/prepare-config-write.test.ts +1 -1
- package/src/config/resolver.ts +14 -62
- package/src/config/table.ts +4 -4
- package/src/config/write-helpers.ts +7 -11
- package/src/custom-fields/__tests__/audit-integration.integration.ts +6 -6
- package/src/custom-fields/__tests__/custom-fields.integration.ts +7 -7
- package/src/custom-fields/__tests__/feature.test.ts +1 -1
- package/src/custom-fields/__tests__/field-access.integration.ts +6 -6
- package/src/custom-fields/__tests__/quota.integration.ts +6 -6
- package/src/custom-fields/__tests__/retention.integration.ts +12 -10
- package/src/custom-fields/__tests__/user-data-rights.integration.ts +27 -17
- package/src/custom-fields/__tests__/wire-for-entity.test.ts +5 -5
- package/src/custom-fields/db/queries/field-access.ts +16 -0
- package/src/custom-fields/db/queries/projection.ts +43 -0
- package/src/custom-fields/db/queries/quota.ts +14 -0
- package/src/custom-fields/db/queries/retention.ts +39 -0
- package/src/custom-fields/db/queries/user-data-rights.ts +54 -0
- package/src/custom-fields/lib/field-access.ts +2 -41
- package/src/custom-fields/lib/quota.ts +2 -25
- package/src/custom-fields/run-retention.ts +19 -21
- package/src/custom-fields/wire-for-entity.ts +30 -23
- package/src/custom-fields/wire-user-data-rights.ts +33 -85
- package/src/data-retention/__tests__/data-retention.integration.ts +1 -1
- package/src/data-retention/__tests__/keep-for.test.ts +1 -1
- package/src/data-retention/__tests__/override-schema.test.ts +1 -1
- package/src/data-retention/__tests__/policy-for.integration.ts +1 -1
- package/src/data-retention/__tests__/resolver.test.ts +1 -1
- package/src/data-retention/handlers/policy-for.query.ts +5 -8
- package/src/data-retention/resolve-for-tenant.ts +6 -8
- package/src/data-retention/schema/tenant-retention-override.ts +2 -2
- package/src/delivery/__tests__/delivery-events.integration.ts +8 -21
- package/src/delivery/__tests__/delivery.integration.ts +100 -190
- package/src/delivery/db/queries/preferences.ts +30 -0
- package/src/delivery/delivery-service.ts +8 -36
- package/src/delivery/feature.ts +2 -1
- package/src/delivery/handlers/log.query.ts +5 -7
- package/src/delivery/handlers/preferences.query.ts +2 -5
- package/src/delivery/tables.ts +26 -1
- package/src/delivery/upsert-preference.ts +8 -14
- package/src/feature-toggles/__tests__/feature-toggles.integration.ts +30 -30
- package/src/feature-toggles/__tests__/registered-system-tenant.test.ts +7 -6
- package/src/feature-toggles/db/queries/toggle-state.ts +25 -0
- package/src/feature-toggles/feature.ts +16 -2
- package/src/feature-toggles/global-feature-state-table.ts +1 -1
- package/src/feature-toggles/handlers/list.query.ts +9 -2
- package/src/feature-toggles/handlers/registered.query.ts +3 -7
- package/src/feature-toggles/handlers/set.write.ts +37 -25
- package/src/feature-toggles/toggle-runtime.ts +3 -6
- package/src/file-foundation/__tests__/feature.test.ts +1 -1
- package/src/file-foundation/__tests__/file-foundation.integration.ts +1 -1
- package/src/file-provider-inmemory/__tests__/feature.test.ts +1 -1
- package/src/file-provider-s3/__tests__/feature.test.ts +1 -1
- package/src/files/__tests__/files.integration.ts +18 -7
- package/src/files/schema/file-ref.ts +1 -1
- package/src/files-provider-s3/__tests__/env-helper.test.ts +1 -1
- package/src/files-provider-s3/__tests__/s3-provider.integration.ts +1 -1
- package/src/files-provider-s3/__tests__/s3-provider.test.ts +1 -1
- package/src/jobs/__tests__/job-system-user.integration.ts +1 -1
- package/src/jobs/__tests__/jobs-events.integration.ts +8 -21
- package/src/jobs/__tests__/jobs-feature.integration.ts +1 -1
- package/src/jobs/feature.ts +22 -14
- package/src/jobs/handlers/detail.query.ts +10 -8
- package/src/jobs/handlers/list.query.ts +9 -21
- package/src/jobs/handlers/retry.write.ts +2 -7
- package/src/jobs/job-run-logger.ts +3 -9
- package/src/jobs/job-run-table.ts +49 -17
- package/src/legal-pages/__tests__/legal-pages.integration.ts +1 -1
- package/src/mail-foundation/__tests__/feature.test.ts +1 -1
- package/src/mail-foundation/__tests__/mail-foundation.integration.ts +1 -1
- package/src/mail-transport-inmemory/__tests__/feature.test.ts +1 -1
- package/src/mail-transport-smtp/__tests__/feature.test.ts +1 -1
- package/src/rate-limiting/__tests__/rate-limiting.integration.ts +1 -1
- package/src/renderer-foundation/__tests__/api.test.ts +2 -2
- package/src/renderer-foundation/__tests__/collect-plugins.integration.ts +1 -1
- package/src/renderer-simple/__tests__/adapter.test.ts +2 -2
- package/src/renderer-simple/__tests__/simple-renderer.test.ts +1 -1
- package/src/secrets/__tests__/require-secrets-context.test.ts +6 -5
- package/src/secrets/__tests__/rotate.integration.ts +6 -9
- package/src/secrets/__tests__/secrets-events.integration.ts +6 -12
- package/src/secrets/__tests__/secrets.integration.ts +6 -11
- package/src/secrets/db/queries/read.ts +16 -0
- package/src/secrets/handlers/list.query.ts +16 -17
- package/src/secrets/handlers/rotate.job.ts +8 -12
- package/src/secrets/secrets-context.ts +9 -21
- package/src/secrets/table.ts +1 -1
- package/src/sessions/__tests__/cleanup.integration.ts +8 -6
- package/src/sessions/__tests__/password-auto-revoke.integration.ts +7 -6
- package/src/sessions/__tests__/sessions.integration.ts +23 -38
- package/src/sessions/__tests__/test-helpers.ts +1 -1
- package/src/sessions/db/queries/cleanup.ts +21 -0
- package/src/sessions/handlers/cleanup.job.ts +6 -29
- package/src/sessions/handlers/list.query.ts +24 -24
- package/src/sessions/handlers/mine.query.ts +24 -23
- package/src/sessions/handlers/revoke-all-for-user.write.ts +7 -11
- package/src/sessions/handlers/revoke-all-others.write.ts +7 -12
- package/src/sessions/handlers/revoke.write.ts +11 -18
- package/src/sessions/schema/user-session.ts +2 -2
- package/src/sessions/session-callbacks.ts +19 -21
- package/src/subscription-mollie/__tests__/feature.test.ts +1 -1
- package/src/subscription-mollie/__tests__/mollie-foundation.integration.ts +1 -1
- package/src/subscription-mollie/__tests__/verify-webhook.test.ts +8 -7
- package/src/subscription-stripe/__tests__/feature.test.ts +1 -1
- package/src/subscription-stripe/__tests__/plugin-methods.test.ts +14 -15
- package/src/subscription-stripe/__tests__/stripe-foundation.integration.ts +1 -1
- package/src/subscription-stripe/__tests__/verify-webhook.test.ts +14 -14
- package/src/subscription-stripe/verify-webhook.ts +1 -1
- package/src/template-resolver/__tests__/handlers.integration.ts +1 -1
- package/src/template-resolver/__tests__/template-resolver.integration.ts +3 -2
- package/src/template-resolver/api.ts +7 -13
- package/src/template-resolver/handlers/archive.write.ts +4 -7
- package/src/template-resolver/handlers/find-by-id.query.ts +4 -7
- package/src/template-resolver/handlers/list.query.ts +13 -21
- package/src/template-resolver/handlers/publish.write.ts +4 -7
- package/src/template-resolver/handlers/upsert-system.write.ts +7 -10
- package/src/template-resolver/handlers/upsert-tenant.write.ts +7 -10
- package/src/template-resolver/table.ts +2 -5
- package/src/tenant/__tests__/multi-tenant.integration.ts +1 -1
- package/src/tenant/__tests__/seed-testing.integration.ts +19 -45
- package/src/tenant/__tests__/tenant.integration.ts +1 -1
- package/src/tenant/handlers/active-tenant-ids.query.ts +3 -8
- package/src/tenant/handlers/add-member.write.ts +6 -8
- package/src/tenant/handlers/cancel-invitation.write.ts +5 -7
- package/src/tenant/handlers/invitations.query.ts +5 -10
- package/src/tenant/handlers/me.query.ts +2 -3
- package/src/tenant/handlers/members.query.ts +4 -5
- package/src/tenant/handlers/memberships.query.ts +2 -5
- package/src/tenant/handlers/remove-member.write.ts +6 -8
- package/src/tenant/handlers/resolve-user-ids.query.ts +6 -16
- package/src/tenant/handlers/update-member-roles.write.ts +6 -8
- package/src/tenant/invitation-table.ts +2 -5
- package/src/tenant/membership-table.ts +3 -6
- package/src/tenant/schema/tenant.ts +2 -2
- package/src/tenant/seeding.ts +12 -18
- package/src/text-content/README.md +1 -1
- package/src/text-content/__tests__/text-content.integration.ts +2 -2
- package/src/text-content/api.ts +2 -9
- package/src/text-content/handlers/by-slug.query.ts +6 -9
- package/src/text-content/handlers/by-tenant.query.ts +2 -2
- package/src/text-content/handlers/set.write.ts +7 -9
- package/src/text-content/seeding.ts +6 -9
- package/src/text-content/table.ts +2 -2
- package/src/text-content/web/__tests__/editor-read-only.test.tsx +31 -45
- package/src/text-content/web/__tests__/group-blocks.test.ts +1 -18
- package/src/text-content/web/client-plugin.tsx +11 -23
- package/src/tier-engine/__tests__/auto-default-tier.integration.ts +10 -16
- package/src/tier-engine/__tests__/compose-app.test.ts +1 -1
- package/src/tier-engine/__tests__/drift.test.ts +1 -1
- package/src/tier-engine/__tests__/resolver.integration.ts +6 -6
- package/src/tier-engine/__tests__/tier-engine.integration.ts +1 -1
- package/src/tier-engine/feature.ts +9 -16
- package/src/user/__tests__/seed-testing.integration.ts +10 -22
- package/src/user/__tests__/user-status.test.ts +1 -1
- package/src/user/__tests__/user.integration.ts +6 -5
- package/src/user/handlers/create.write.ts +5 -7
- package/src/user/handlers/find-for-auth.query.ts +5 -7
- package/src/user/schema/user.ts +2 -2
- package/src/user/seeding.ts +2 -3
- package/src/user-data-rights/__tests__/audit-log.integration.ts +24 -12
- package/src/user-data-rights/__tests__/cross-data-matrix.integration.ts +64 -37
- package/src/user-data-rights/__tests__/download.integration.ts +29 -46
- package/src/user-data-rights/__tests__/export-job-idempotency.integration.ts +35 -28
- package/src/user-data-rights/__tests__/export-job-schema.test.ts +2 -2
- package/src/user-data-rights/__tests__/policy-to-strategy.test.ts +1 -1
- package/src/user-data-rights/__tests__/request-cancel-deletion.integration.ts +11 -15
- package/src/user-data-rights/__tests__/request-deletion-callback.integration.ts +10 -12
- package/src/user-data-rights/__tests__/request-export.integration.ts +23 -16
- package/src/user-data-rights/__tests__/restriction-flow.integration.ts +24 -32
- package/src/user-data-rights/__tests__/run-export-jobs.integration.ts +142 -137
- package/src/user-data-rights/__tests__/run-forget-cleanup.integration.ts +46 -28
- package/src/user-data-rights/__tests__/run-user-export.integration.ts +20 -14
- package/src/user-data-rights/__tests__/token-helpers.test.ts +1 -1
- package/src/user-data-rights/__tests__/user-data-rights.integration.ts +1 -1
- package/src/user-data-rights/__tests__/zip-path.test.ts +1 -1
- package/src/user-data-rights/audit-download.ts +3 -3
- package/src/user-data-rights/db/queries/export-jobs.ts +23 -0
- package/src/user-data-rights/db/queries/forget-cleanup.ts +13 -0
- package/src/user-data-rights/handlers/cancel-deletion.write.ts +28 -22
- package/src/user-data-rights/handlers/download-by-job.query.ts +11 -21
- package/src/user-data-rights/handlers/download-by-token.query.ts +20 -35
- package/src/user-data-rights/handlers/export-status.query.ts +19 -33
- package/src/user-data-rights/handlers/lift-restriction.write.ts +7 -12
- package/src/user-data-rights/handlers/list-download-attempts.query.ts +14 -23
- package/src/user-data-rights/handlers/my-audit-log.query.ts +33 -23
- package/src/user-data-rights/handlers/request-deletion.write.ts +15 -15
- package/src/user-data-rights/handlers/request-export.write.ts +7 -11
- package/src/user-data-rights/handlers/restrict-account.write.ts +12 -12
- package/src/user-data-rights/run-export-jobs.ts +20 -60
- package/src/user-data-rights/run-forget-cleanup.ts +19 -33
- package/src/user-data-rights/run-user-export.ts +4 -6
- package/src/user-data-rights/schema/download-attempt.ts +2 -2
- package/src/user-data-rights/schema/download-token.ts +2 -2
- package/src/user-data-rights/schema/export-job.ts +2 -3
- package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.ts +37 -30
- package/src/user-data-rights-defaults/db/queries/user-hook.ts +17 -0
- package/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +12 -27
- package/src/user-data-rights-defaults/hooks/user.userdata-hook.ts +16 -18
- package/CHANGELOG.md +0 -680
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// S2.U7 — my-audit-log + invalid-attempt-audit + list-download-attempts.
|
|
2
2
|
|
|
3
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
4
|
+
import { asRawClient, insertMany, insertOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
3
5
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
4
6
|
import {
|
|
5
7
|
createTestUser,
|
|
@@ -8,8 +10,6 @@ import {
|
|
|
8
10
|
testTenantId,
|
|
9
11
|
unsafeCreateEntityTable,
|
|
10
12
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
11
|
-
import { sql } from "drizzle-orm";
|
|
12
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
13
13
|
import {
|
|
14
14
|
createComplianceProfilesFeature,
|
|
15
15
|
tenantComplianceProfileEntity,
|
|
@@ -50,14 +50,14 @@ afterAll(async () => {
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
beforeEach(async () => {
|
|
53
|
-
await stack.db.
|
|
54
|
-
await stack.db.
|
|
55
|
-
await stack.db.
|
|
56
|
-
await stack.db.
|
|
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`);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
async function seedUser(u: typeof alice, email: string): Promise<void> {
|
|
60
|
-
await stack.db
|
|
60
|
+
await insertOne(stack.db, userTable, {
|
|
61
61
|
id: u.id,
|
|
62
62
|
tenantId: u.tenantId,
|
|
63
63
|
email,
|
|
@@ -78,12 +78,24 @@ async function seedEvent(
|
|
|
78
78
|
payload: object,
|
|
79
79
|
): Promise<void> {
|
|
80
80
|
_eventVersion += 1;
|
|
81
|
-
await stack.db.
|
|
81
|
+
await asRawClient(stack.db).unsafe(
|
|
82
|
+
`
|
|
82
83
|
INSERT INTO kumiko_events
|
|
83
84
|
(tenant_id, aggregate_type, aggregate_id, version, type, payload, metadata, created_at, created_by)
|
|
84
|
-
VALUES ($
|
|
85
|
-
$
|
|
86
|
-
|
|
85
|
+
VALUES ($1, $2, $3,
|
|
86
|
+
$4, $5, $6, $7, now(), $8)
|
|
87
|
+
`,
|
|
88
|
+
[
|
|
89
|
+
tenantId,
|
|
90
|
+
"test-aggregate",
|
|
91
|
+
"00000000-0000-4000-8000-00000000aaaa",
|
|
92
|
+
_eventVersion,
|
|
93
|
+
type,
|
|
94
|
+
JSON.stringify(payload),
|
|
95
|
+
"{}",
|
|
96
|
+
createdBy,
|
|
97
|
+
],
|
|
98
|
+
);
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
describe("my-audit-log", () => {
|
|
@@ -161,7 +173,7 @@ describe("list-download-attempts (DPO operator-query)", () => {
|
|
|
161
173
|
// Direct-INSERT in attempts (simuliert was die download-handler schreiben).
|
|
162
174
|
const T = await import("@cosmicdrift/kumiko-framework/time");
|
|
163
175
|
const now = T.getTemporal().Now.instant();
|
|
164
|
-
await stack.db
|
|
176
|
+
await insertMany(stack.db, downloadAttemptsTable, [
|
|
165
177
|
{
|
|
166
178
|
id: "11111111-1111-4111-8111-111111111111",
|
|
167
179
|
tenantId: tenantA,
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
// - Other-User-Isolation: Bobs notes/files bleiben unangetastet bei
|
|
13
13
|
// Alices Forget; Bobs Daten landen NICHT in Alices Export-Bundle.
|
|
14
14
|
|
|
15
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
16
|
+
import { asRawClient, insertOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
15
17
|
import {
|
|
16
18
|
defineFeature,
|
|
17
19
|
EXT_USER_DATA,
|
|
@@ -24,8 +26,6 @@ import {
|
|
|
24
26
|
unsafeCreateEntityTable,
|
|
25
27
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
26
28
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
27
|
-
import { sql } from "drizzle-orm";
|
|
28
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
29
29
|
import {
|
|
30
30
|
createComplianceProfilesFeature,
|
|
31
31
|
tenantComplianceProfileEntity,
|
|
@@ -59,11 +59,14 @@ const PAST = (): Instant => getTemporal().Instant.fromEpochMilliseconds(Date.now
|
|
|
59
59
|
// Stellvertretend fuer App-spezifische Entities (Chat-Message, Blog-Post
|
|
60
60
|
// etc.), die ueber EXT_USER_DATA sauber in die Pipeline integrieren.
|
|
61
61
|
const exportNotes: UserDataExportHook = async (ctx) => {
|
|
62
|
-
const result = await ctx.db.
|
|
62
|
+
const result = await asRawClient(ctx.db).unsafe(
|
|
63
|
+
`
|
|
63
64
|
SELECT id, title, body
|
|
64
65
|
FROM test_notes
|
|
65
|
-
WHERE tenant_id = $
|
|
66
|
-
|
|
66
|
+
WHERE tenant_id = $1 AND author_id = $2
|
|
67
|
+
`,
|
|
68
|
+
[ctx.tenantId, ctx.userId],
|
|
69
|
+
);
|
|
67
70
|
// biome-ignore lint/suspicious/noExplicitAny: drizzle execute typing
|
|
68
71
|
const rows = ((result as any).rows ?? result) as Array<{
|
|
69
72
|
id: string;
|
|
@@ -78,10 +81,13 @@ const exportNotes: UserDataExportHook = async (ctx) => {
|
|
|
78
81
|
};
|
|
79
82
|
|
|
80
83
|
const deleteNotes: UserDataDeleteHook = async (ctx, _strategy) => {
|
|
81
|
-
await ctx.db.
|
|
84
|
+
await asRawClient(ctx.db).unsafe(
|
|
85
|
+
`
|
|
82
86
|
DELETE FROM test_notes
|
|
83
|
-
WHERE tenant_id = $
|
|
84
|
-
|
|
87
|
+
WHERE tenant_id = $1 AND author_id = $2
|
|
88
|
+
`,
|
|
89
|
+
[ctx.tenantId, ctx.userId],
|
|
90
|
+
);
|
|
85
91
|
};
|
|
86
92
|
|
|
87
93
|
const testNotesFeature = defineFeature("test-notes", (r) => {
|
|
@@ -107,7 +113,7 @@ beforeAll(async () => {
|
|
|
107
113
|
await unsafeCreateEntityTable(stack.db, userEntity);
|
|
108
114
|
await unsafeCreateEntityTable(stack.db, tenantRetentionOverrideEntity);
|
|
109
115
|
await unsafeCreateEntityTable(stack.db, tenantComplianceProfileEntity);
|
|
110
|
-
await stack.db.
|
|
116
|
+
await asRawClient(stack.db).unsafe(`
|
|
111
117
|
CREATE TABLE IF NOT EXISTS read_tenant_memberships (
|
|
112
118
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
113
119
|
tenant_id UUID NOT NULL,
|
|
@@ -124,7 +130,7 @@ beforeAll(async () => {
|
|
|
124
130
|
UNIQUE(user_id, tenant_id)
|
|
125
131
|
)
|
|
126
132
|
`);
|
|
127
|
-
await stack.db.
|
|
133
|
+
await asRawClient(stack.db).unsafe(`
|
|
128
134
|
CREATE TABLE IF NOT EXISTS file_refs (
|
|
129
135
|
id UUID PRIMARY KEY,
|
|
130
136
|
tenant_id UUID NOT NULL,
|
|
@@ -139,7 +145,7 @@ beforeAll(async () => {
|
|
|
139
145
|
inserted_by_id TEXT
|
|
140
146
|
)
|
|
141
147
|
`);
|
|
142
|
-
await stack.db.
|
|
148
|
+
await asRawClient(stack.db).unsafe(`
|
|
143
149
|
CREATE TABLE IF NOT EXISTS test_notes (
|
|
144
150
|
id UUID PRIMARY KEY,
|
|
145
151
|
tenant_id UUID NOT NULL,
|
|
@@ -155,10 +161,10 @@ afterAll(async () => {
|
|
|
155
161
|
});
|
|
156
162
|
|
|
157
163
|
beforeEach(async () => {
|
|
158
|
-
await stack.db.
|
|
159
|
-
await stack.db.
|
|
160
|
-
await stack.db.
|
|
161
|
-
await stack.db.
|
|
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`);
|
|
162
168
|
});
|
|
163
169
|
|
|
164
170
|
async function seedUser(
|
|
@@ -170,7 +176,7 @@ async function seedUser(
|
|
|
170
176
|
displayName?: string;
|
|
171
177
|
} = {},
|
|
172
178
|
): Promise<void> {
|
|
173
|
-
await stack.db
|
|
179
|
+
await insertOne(stack.db, userTable, {
|
|
174
180
|
id,
|
|
175
181
|
tenantId: TENANT_SYSTEM,
|
|
176
182
|
email: overrides.email ?? `user-${id}@example.com`,
|
|
@@ -185,11 +191,14 @@ async function seedUser(
|
|
|
185
191
|
}
|
|
186
192
|
|
|
187
193
|
async function seedMembership(userId: string, tenantId: string): Promise<void> {
|
|
188
|
-
await stack.db.
|
|
194
|
+
await asRawClient(stack.db).unsafe(
|
|
195
|
+
`
|
|
189
196
|
INSERT INTO read_tenant_memberships (tenant_id, user_id, roles)
|
|
190
|
-
VALUES ($
|
|
197
|
+
VALUES ($1, $2, '["Member"]')
|
|
191
198
|
ON CONFLICT (user_id, tenant_id) DO NOTHING
|
|
192
|
-
|
|
199
|
+
`,
|
|
200
|
+
[tenantId, userId],
|
|
201
|
+
);
|
|
193
202
|
}
|
|
194
203
|
|
|
195
204
|
async function seedFileRef(
|
|
@@ -198,10 +207,13 @@ async function seedFileRef(
|
|
|
198
207
|
userId: string,
|
|
199
208
|
name: string,
|
|
200
209
|
): Promise<void> {
|
|
201
|
-
await stack.db.
|
|
210
|
+
await asRawClient(stack.db).unsafe(
|
|
211
|
+
`
|
|
202
212
|
INSERT INTO file_refs (id, tenant_id, storage_key, file_name, mime_type, size, inserted_by_id)
|
|
203
|
-
VALUES ($
|
|
204
|
-
|
|
213
|
+
VALUES ($1, $2, $3, $4, 'application/pdf', 1024, $5)
|
|
214
|
+
`,
|
|
215
|
+
[id, tenantId, `storage/${id}`, name, userId],
|
|
216
|
+
);
|
|
205
217
|
}
|
|
206
218
|
|
|
207
219
|
async function seedNote(
|
|
@@ -210,16 +222,22 @@ async function seedNote(
|
|
|
210
222
|
userId: string,
|
|
211
223
|
title: string,
|
|
212
224
|
): Promise<void> {
|
|
213
|
-
await stack.db.
|
|
225
|
+
await asRawClient(stack.db).unsafe(
|
|
226
|
+
`
|
|
214
227
|
INSERT INTO test_notes (id, tenant_id, author_id, title, body)
|
|
215
|
-
VALUES ($
|
|
216
|
-
|
|
228
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
229
|
+
`,
|
|
230
|
+
[id, tenantId, userId, title, `body for ${title}`],
|
|
231
|
+
);
|
|
217
232
|
}
|
|
218
233
|
|
|
219
234
|
async function fetchNotes(tenantId: string, userId: string): Promise<unknown[]> {
|
|
220
|
-
const result = await stack.db.
|
|
221
|
-
|
|
222
|
-
|
|
235
|
+
const result = await asRawClient(stack.db).unsafe(
|
|
236
|
+
`
|
|
237
|
+
SELECT id, title FROM test_notes WHERE tenant_id = $1 AND author_id = $2
|
|
238
|
+
`,
|
|
239
|
+
[tenantId, userId],
|
|
240
|
+
);
|
|
223
241
|
// biome-ignore lint/suspicious/noExplicitAny: drizzle execute typing
|
|
224
242
|
return ((result as any).rows ?? result) as unknown[];
|
|
225
243
|
}
|
|
@@ -319,24 +337,33 @@ describe("Cross-Data-Matrix :: Forget cleant 3 Provider-Features cross-tenant",
|
|
|
319
337
|
expect(await fetchNotes(TENANT_B, ALICE_ID)).toHaveLength(0);
|
|
320
338
|
|
|
321
339
|
// Alices fileRef in Tenant A weg
|
|
322
|
-
const aliceFiles = await stack.db.
|
|
323
|
-
|
|
324
|
-
|
|
340
|
+
const aliceFiles = await asRawClient(stack.db).unsafe(
|
|
341
|
+
`
|
|
342
|
+
SELECT id FROM file_refs WHERE tenant_id = $1 AND inserted_by_id = $2
|
|
343
|
+
`,
|
|
344
|
+
[TENANT_A, ALICE_ID],
|
|
345
|
+
);
|
|
325
346
|
// biome-ignore lint/suspicious/noExplicitAny: drizzle execute typing
|
|
326
347
|
expect((((aliceFiles as any).rows ?? aliceFiles) as unknown[]).length).toBe(0);
|
|
327
348
|
|
|
328
349
|
// Bobs notes + files unangetastet
|
|
329
350
|
expect(await fetchNotes(TENANT_A, BOB_ID)).toHaveLength(1);
|
|
330
|
-
const bobFiles = await stack.db.
|
|
331
|
-
|
|
332
|
-
|
|
351
|
+
const bobFiles = await asRawClient(stack.db).unsafe(
|
|
352
|
+
`
|
|
353
|
+
SELECT id FROM file_refs WHERE tenant_id = $1 AND inserted_by_id = $2
|
|
354
|
+
`,
|
|
355
|
+
[TENANT_A, BOB_ID],
|
|
356
|
+
);
|
|
333
357
|
// biome-ignore lint/suspicious/noExplicitAny: drizzle execute typing
|
|
334
358
|
expect((((bobFiles as any).rows ?? bobFiles) as unknown[]).length).toBe(1);
|
|
335
359
|
|
|
336
360
|
// Alice-User-Row anonymisiert (DSGVO-Kern: PII raus, Sentinel-Email).
|
|
337
|
-
const aliceRow = await stack.db.
|
|
338
|
-
|
|
339
|
-
|
|
361
|
+
const aliceRow = await asRawClient(stack.db).unsafe(
|
|
362
|
+
`
|
|
363
|
+
SELECT email, status FROM read_users WHERE id = $1
|
|
364
|
+
`,
|
|
365
|
+
[ALICE_ID],
|
|
366
|
+
);
|
|
340
367
|
// biome-ignore lint/suspicious/noExplicitAny: drizzle execute typing
|
|
341
368
|
const aliceRows = ((aliceRow as any).rows ?? aliceRow) as Array<{
|
|
342
369
|
email: string;
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
// Plus Audit-Updates (useCount, lastUsedAt, IP, UA), TTL-checks,
|
|
8
8
|
// cross-user-isolation, cross-tenant-same-user.
|
|
9
9
|
|
|
10
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
10
11
|
import { randomBytes } from "node:crypto";
|
|
12
|
+
import { asRawClient, selectMany, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
11
13
|
import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
|
|
12
14
|
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
13
15
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
@@ -24,8 +26,6 @@ import {
|
|
|
24
26
|
unsafePushTables,
|
|
25
27
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
26
28
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
27
|
-
import { sql } from "drizzle-orm";
|
|
28
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
29
29
|
import {
|
|
30
30
|
createComplianceProfilesFeature,
|
|
31
31
|
tenantComplianceProfileEntity,
|
|
@@ -127,7 +127,7 @@ beforeAll(async () => {
|
|
|
127
127
|
await unsafeCreateEntityTable(stack.db, tenantComplianceProfileEntity);
|
|
128
128
|
await unsafePushTables(stack.db, { configValuesTable });
|
|
129
129
|
await createEventsTable(stack.db);
|
|
130
|
-
await stack.db.
|
|
130
|
+
await asRawClient(stack.db).unsafe(`
|
|
131
131
|
CREATE TABLE IF NOT EXISTS read_tenant_memberships (
|
|
132
132
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
133
133
|
tenant_id UUID NOT NULL,
|
|
@@ -151,12 +151,12 @@ afterAll(async () => {
|
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
beforeEach(async () => {
|
|
154
|
-
await stack.db.
|
|
155
|
-
await stack.db.
|
|
156
|
-
await stack.db.
|
|
157
|
-
await stack.db.
|
|
158
|
-
await stack.db.
|
|
159
|
-
await stack.db.
|
|
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
160
|
providerPerTenant = new Map();
|
|
161
161
|
|
|
162
162
|
// Setup file-foundation provider="inmemory" pro Tenant.
|
|
@@ -242,10 +242,7 @@ describe("download-by-token :: happy path", () => {
|
|
|
242
242
|
expect(result.expiresAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
|
|
243
243
|
|
|
244
244
|
// Audit-Update: useCount=1, lastUsedAt set, IP+UA persistiert
|
|
245
|
-
const tokenRows = (await stack.db
|
|
246
|
-
.select()
|
|
247
|
-
.from(exportDownloadTokensTable)
|
|
248
|
-
.where(sql`job_id = ${jobId}`)) as Array<{
|
|
245
|
+
const tokenRows = (await selectMany(stack.db, exportDownloadTokensTable, { jobId })) as Array<{
|
|
249
246
|
useCount: number;
|
|
250
247
|
lastUsedAt: { toString(): string } | null;
|
|
251
248
|
lastUsedFromIp: string | null;
|
|
@@ -271,10 +268,9 @@ describe("download-by-token :: happy path", () => {
|
|
|
271
268
|
aliceUser,
|
|
272
269
|
);
|
|
273
270
|
|
|
274
|
-
const [row] = (await stack.db
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
.where(sql`job_id = ${jobId}`)) as Array<{ useCount: number }>;
|
|
271
|
+
const [row] = (await selectMany(stack.db, exportDownloadTokensTable, { jobId })) as Array<{
|
|
272
|
+
useCount: number;
|
|
273
|
+
}>;
|
|
278
274
|
expect(row?.useCount).toBe(2);
|
|
279
275
|
});
|
|
280
276
|
});
|
|
@@ -298,10 +294,7 @@ describe("download-by-token :: error paths", () => {
|
|
|
298
294
|
const longAgo = getTemporal().Instant.fromEpochMilliseconds(
|
|
299
295
|
Date.now() - 365 * 24 * 60 * 60 * 1000,
|
|
300
296
|
);
|
|
301
|
-
await stack.db
|
|
302
|
-
.update(exportDownloadTokensTable)
|
|
303
|
-
.set({ expiresAt: longAgo })
|
|
304
|
-
.where(sql`job_id = ${jobId}`);
|
|
297
|
+
await updateMany(stack.db, exportDownloadTokensTable, { expiresAt: longAgo }, { jobId });
|
|
305
298
|
|
|
306
299
|
const res = await stack.http.query(
|
|
307
300
|
"user-data-rights:query:download-by-token",
|
|
@@ -315,7 +308,7 @@ describe("download-by-token :: error paths", () => {
|
|
|
315
308
|
|
|
316
309
|
test("failed Job → 404 download.unavailable", async () => {
|
|
317
310
|
const { jobId, plainToken } = await seedDoneJobWithToken();
|
|
318
|
-
await stack.db
|
|
311
|
+
await updateMany(stack.db, exportJobsTable, { status: "failed" }, { id: jobId });
|
|
319
312
|
|
|
320
313
|
const res = await stack.http.query(
|
|
321
314
|
"user-data-rights:query:download-by-token",
|
|
@@ -329,10 +322,7 @@ describe("download-by-token :: error paths", () => {
|
|
|
329
322
|
|
|
330
323
|
test("storage cleared → 404 download.expired", async () => {
|
|
331
324
|
const { jobId, plainToken } = await seedDoneJobWithToken();
|
|
332
|
-
await stack.db
|
|
333
|
-
.update(exportJobsTable)
|
|
334
|
-
.set({ downloadStorageKey: null })
|
|
335
|
-
.where(sql`id = ${jobId}`);
|
|
325
|
+
await updateMany(stack.db, exportJobsTable, { downloadStorageKey: null }, { id: jobId });
|
|
336
326
|
|
|
337
327
|
const res = await stack.http.query(
|
|
338
328
|
"user-data-rights:query:download-by-token",
|
|
@@ -368,10 +358,9 @@ describe("download-by-token :: error paths", () => {
|
|
|
368
358
|
expect(body.error.i18nKey).toBe("userDataRights.errors.download.signedUrlNotSupported");
|
|
369
359
|
|
|
370
360
|
// Sanity: Job-Row ist immer noch done — Operator-Bug aendert nicht den Job-State
|
|
371
|
-
const [row] = (await stack.db
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
.where(sql`id = ${jobId}`)) as Array<{ status: string }>;
|
|
361
|
+
const [row] = (await selectMany(stack.db, exportJobsTable, { id: jobId })) as Array<{
|
|
362
|
+
status: string;
|
|
363
|
+
}>;
|
|
375
364
|
expect(row?.status).toBe("done");
|
|
376
365
|
});
|
|
377
366
|
});
|
|
@@ -392,10 +381,7 @@ describe("download-by-job :: happy path", () => {
|
|
|
392
381
|
expect(result.url).toMatch(/^memory:\/\//);
|
|
393
382
|
|
|
394
383
|
// UI-Klick zaehlt auch als Use → audit-row updated
|
|
395
|
-
const [row] = (await stack.db
|
|
396
|
-
.select()
|
|
397
|
-
.from(exportDownloadTokensTable)
|
|
398
|
-
.where(sql`job_id = ${jobId}`)) as Array<{
|
|
384
|
+
const [row] = (await selectMany(stack.db, exportDownloadTokensTable, { jobId })) as Array<{
|
|
399
385
|
useCount: number;
|
|
400
386
|
lastUsedFromIp: string | null;
|
|
401
387
|
}>;
|
|
@@ -407,7 +393,7 @@ describe("download-by-job :: happy path", () => {
|
|
|
407
393
|
// Symmetrisch zum token-Test: gleicher Code-Pfad muss auch im job-
|
|
408
394
|
// handler 404 + unavailable raus, nicht 500.
|
|
409
395
|
const { jobId } = await seedDoneJobWithToken();
|
|
410
|
-
await stack.db
|
|
396
|
+
await updateMany(stack.db, exportJobsTable, { status: "failed" }, { id: jobId });
|
|
411
397
|
|
|
412
398
|
const res = await stack.http.query(
|
|
413
399
|
"user-data-rights:query:download-by-job",
|
|
@@ -421,10 +407,7 @@ describe("download-by-job :: happy path", () => {
|
|
|
421
407
|
|
|
422
408
|
test("storage cleared (downloadStorageKey null) → 404 download.expired (job-Pfad)", async () => {
|
|
423
409
|
const { jobId } = await seedDoneJobWithToken();
|
|
424
|
-
await stack.db
|
|
425
|
-
.update(exportJobsTable)
|
|
426
|
-
.set({ downloadStorageKey: null })
|
|
427
|
-
.where(sql`id = ${jobId}`);
|
|
410
|
+
await updateMany(stack.db, exportJobsTable, { downloadStorageKey: null }, { id: jobId });
|
|
428
411
|
|
|
429
412
|
const res = await stack.http.query(
|
|
430
413
|
"user-data-rights:query:download-by-job",
|
|
@@ -488,10 +471,7 @@ describe("r.httpRoute :: /user-export/by-token (Magic-Link e2e)", () => {
|
|
|
488
471
|
}),
|
|
489
472
|
);
|
|
490
473
|
|
|
491
|
-
const [row] = (await stack.db
|
|
492
|
-
.select()
|
|
493
|
-
.from(exportDownloadTokensTable)
|
|
494
|
-
.where(sql`job_id = ${jobId}`)) as Array<{
|
|
474
|
+
const [row] = (await selectMany(stack.db, exportDownloadTokensTable, { jobId })) as Array<{
|
|
495
475
|
useCount: number;
|
|
496
476
|
lastUsedFromIp: string | null;
|
|
497
477
|
lastUsedUserAgent: string | null;
|
|
@@ -549,11 +529,14 @@ describe("download-by-job :: cross-user + cross-tenant", () => {
|
|
|
549
529
|
roles: ["Member"],
|
|
550
530
|
});
|
|
551
531
|
// Membership in Tenant B persisten damit auth-stack User akzeptiert
|
|
552
|
-
await stack.db.
|
|
532
|
+
await asRawClient(stack.db).unsafe(
|
|
533
|
+
`
|
|
553
534
|
INSERT INTO read_tenant_memberships (tenant_id, user_id)
|
|
554
|
-
VALUES ($
|
|
535
|
+
VALUES ($1, $2)
|
|
555
536
|
ON CONFLICT (user_id, tenant_id) DO NOTHING
|
|
556
|
-
|
|
537
|
+
`,
|
|
538
|
+
[tenantB, String(aliceUser.id)],
|
|
539
|
+
);
|
|
557
540
|
|
|
558
541
|
const result = await stack.http.queryOk<{ url: string }>(
|
|
559
542
|
"user-data-rights:query:download-by-job",
|
|
@@ -9,14 +9,19 @@
|
|
|
9
9
|
// in Atom 1a's pure unit-Test absichtlich ausgelassen wurde weil er
|
|
10
10
|
// reale Postgres + Drizzle-customType-Codec-Path braucht.
|
|
11
11
|
|
|
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";
|
|
12
19
|
import {
|
|
13
20
|
setupTestStack,
|
|
14
21
|
type TestStack,
|
|
15
22
|
unsafeCreateEntityTable,
|
|
16
23
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
17
24
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
18
|
-
import { eq, sql } from "drizzle-orm";
|
|
19
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
20
25
|
import { createComplianceProfilesFeature } from "../../compliance-profiles";
|
|
21
26
|
import { createDataRetentionFeature } from "../../data-retention";
|
|
22
27
|
import { createUserFeature } from "../../user";
|
|
@@ -52,7 +57,7 @@ afterAll(async () => {
|
|
|
52
57
|
});
|
|
53
58
|
|
|
54
59
|
beforeEach(async () => {
|
|
55
|
-
await stack.db.
|
|
60
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${exportJobsTable.tableName}"`);
|
|
56
61
|
});
|
|
57
62
|
|
|
58
63
|
const NOW = () => getTemporal().Now.instant();
|
|
@@ -101,7 +106,7 @@ async function insertJob(
|
|
|
101
106
|
if (overrides.bytesWritten !== undefined) {
|
|
102
107
|
values["bytesWritten"] = overrides.bytesWritten;
|
|
103
108
|
}
|
|
104
|
-
await stack.db
|
|
109
|
+
await insertOne(stack.db, exportJobsTable, values);
|
|
105
110
|
return id;
|
|
106
111
|
}
|
|
107
112
|
|
|
@@ -114,7 +119,7 @@ describe("ExportJob :: Partial-UNIQUE-Index", () => {
|
|
|
114
119
|
IDEMPOTENCY_CONSTRAINT,
|
|
115
120
|
);
|
|
116
121
|
|
|
117
|
-
const rows = await stack.db
|
|
122
|
+
const rows = await selectMany(stack.db, exportJobsTable);
|
|
118
123
|
expect(rows).toHaveLength(1);
|
|
119
124
|
});
|
|
120
125
|
|
|
@@ -128,7 +133,7 @@ describe("ExportJob :: Partial-UNIQUE-Index", () => {
|
|
|
128
133
|
IDEMPOTENCY_CONSTRAINT,
|
|
129
134
|
);
|
|
130
135
|
|
|
131
|
-
const rows = await stack.db
|
|
136
|
+
const rows = await selectMany(stack.db, exportJobsTable);
|
|
132
137
|
expect(rows).toHaveLength(1);
|
|
133
138
|
});
|
|
134
139
|
|
|
@@ -136,7 +141,7 @@ describe("ExportJob :: Partial-UNIQUE-Index", () => {
|
|
|
136
141
|
await insertJob(ALICE_ID, EXPORT_JOB_STATUS.Pending);
|
|
137
142
|
await insertJob(BOB_ID, EXPORT_JOB_STATUS.Pending);
|
|
138
143
|
|
|
139
|
-
const rows = await stack.db
|
|
144
|
+
const rows = await selectMany(stack.db, exportJobsTable);
|
|
140
145
|
expect(rows).toHaveLength(2);
|
|
141
146
|
});
|
|
142
147
|
|
|
@@ -147,7 +152,7 @@ describe("ExportJob :: Partial-UNIQUE-Index", () => {
|
|
|
147
152
|
// erfolgreichem Download
|
|
148
153
|
await insertJob(ALICE_ID, EXPORT_JOB_STATUS.Pending);
|
|
149
154
|
|
|
150
|
-
const rows = await stack.db
|
|
155
|
+
const rows = await selectMany(stack.db, exportJobsTable);
|
|
151
156
|
expect(rows).toHaveLength(2);
|
|
152
157
|
});
|
|
153
158
|
|
|
@@ -155,7 +160,7 @@ describe("ExportJob :: Partial-UNIQUE-Index", () => {
|
|
|
155
160
|
await insertJob(ALICE_ID, EXPORT_JOB_STATUS.Failed);
|
|
156
161
|
await insertJob(ALICE_ID, EXPORT_JOB_STATUS.Pending);
|
|
157
162
|
|
|
158
|
-
const rows = await stack.db
|
|
163
|
+
const rows = await selectMany(stack.db, exportJobsTable);
|
|
159
164
|
expect(rows).toHaveLength(2);
|
|
160
165
|
});
|
|
161
166
|
|
|
@@ -163,7 +168,7 @@ describe("ExportJob :: Partial-UNIQUE-Index", () => {
|
|
|
163
168
|
await insertJob(ALICE_ID, EXPORT_JOB_STATUS.Done);
|
|
164
169
|
await insertJob(ALICE_ID, EXPORT_JOB_STATUS.Done);
|
|
165
170
|
|
|
166
|
-
const rows = await stack.db
|
|
171
|
+
const rows = await selectMany(stack.db, exportJobsTable);
|
|
167
172
|
expect(rows).toHaveLength(2);
|
|
168
173
|
});
|
|
169
174
|
|
|
@@ -171,10 +176,12 @@ describe("ExportJob :: Partial-UNIQUE-Index", () => {
|
|
|
171
176
|
// Alice pending insert
|
|
172
177
|
const aliceJobId = await insertJob(ALICE_ID, EXPORT_JOB_STATUS.Pending);
|
|
173
178
|
// Worker pickt auf — Status flip
|
|
174
|
-
await
|
|
175
|
-
.
|
|
176
|
-
|
|
177
|
-
.
|
|
179
|
+
await updateMany(
|
|
180
|
+
stack.db,
|
|
181
|
+
exportJobsTable,
|
|
182
|
+
{ status: EXPORT_JOB_STATUS.Running },
|
|
183
|
+
{ id: aliceJobId },
|
|
184
|
+
);
|
|
178
185
|
|
|
179
186
|
// Zweiter pending-Insert fuer Alice → faellt weiter, weil bestehender
|
|
180
187
|
// Job in running auch im Index-Filter ist.
|
|
@@ -184,13 +191,15 @@ describe("ExportJob :: Partial-UNIQUE-Index", () => {
|
|
|
184
191
|
);
|
|
185
192
|
|
|
186
193
|
// Aber wenn der running-Job fertig wird (done), darf User wieder pending starten
|
|
187
|
-
await
|
|
188
|
-
.
|
|
189
|
-
|
|
190
|
-
.
|
|
194
|
+
await updateMany(
|
|
195
|
+
stack.db,
|
|
196
|
+
exportJobsTable,
|
|
197
|
+
{ status: EXPORT_JOB_STATUS.Done },
|
|
198
|
+
{ id: aliceJobId },
|
|
199
|
+
);
|
|
191
200
|
|
|
192
201
|
await insertJob(ALICE_ID, EXPORT_JOB_STATUS.Pending);
|
|
193
|
-
const rows = await stack.db
|
|
202
|
+
const rows = await selectMany(stack.db, exportJobsTable);
|
|
194
203
|
expect(rows).toHaveLength(2);
|
|
195
204
|
});
|
|
196
205
|
});
|
|
@@ -202,10 +211,9 @@ describe("ExportJob :: bigInt bytesWritten DB-Roundtrip", () => {
|
|
|
202
211
|
bytesWritten: TWO_GB_PLUS,
|
|
203
212
|
});
|
|
204
213
|
|
|
205
|
-
const [row] = await stack.db
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
.where(eq(exportJobsTable["id"], id));
|
|
214
|
+
const [row] = (await selectMany(stack.db, exportJobsTable, { id })) as Array<{
|
|
215
|
+
bytesWritten: number;
|
|
216
|
+
}>;
|
|
209
217
|
|
|
210
218
|
expect(row?.bytesWritten).toBe(TWO_GB_PLUS);
|
|
211
219
|
});
|
|
@@ -216,10 +224,9 @@ describe("ExportJob :: bigInt bytesWritten DB-Roundtrip", () => {
|
|
|
216
224
|
bytesWritten: ONE_PB_PLUS,
|
|
217
225
|
});
|
|
218
226
|
|
|
219
|
-
const [row] = await stack.db
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
.where(eq(exportJobsTable["id"], id));
|
|
227
|
+
const [row] = (await selectMany(stack.db, exportJobsTable, { id })) as Array<{
|
|
228
|
+
bytesWritten: number;
|
|
229
|
+
}>;
|
|
223
230
|
|
|
224
231
|
expect(row?.bytesWritten).toBe(ONE_PB_PLUS);
|
|
225
232
|
});
|
|
@@ -232,7 +239,7 @@ describe("ExportJob :: bigInt bytesWritten DB-Roundtrip", () => {
|
|
|
232
239
|
// Driver-Mapping — wenn jemand `case "bigInt"` zu `integer` zurueck-
|
|
233
240
|
// refactored, faellt dieser Test um obwohl die JS-Werte sich
|
|
234
241
|
// numerisch verhalten.
|
|
235
|
-
const result = await stack.db.
|
|
242
|
+
const result = await asRawClient(stack.db).unsafe(`
|
|
236
243
|
SELECT data_type FROM information_schema.columns
|
|
237
244
|
WHERE table_name = 'read_export_jobs' AND column_name = 'bytes_written'
|
|
238
245
|
`);
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
// Schema-Snapshot, kein Behavior-Test. Behavior-Tests kommen mit Atom 2
|
|
14
14
|
// (request-export.write.ts) + Atom 3 (Worker).
|
|
15
15
|
|
|
16
|
+
import { describe, expect, test } from "bun:test";
|
|
16
17
|
import { COMPLIANCE_PROFILES } from "@cosmicdrift/kumiko-framework/compliance";
|
|
17
|
-
import { describe, expect, test } from "vitest";
|
|
18
18
|
import { EXPORT_JOB_STATUS, exportJobEntity } from "../schema/export-job";
|
|
19
19
|
|
|
20
20
|
describe("EXPORT_JOB_STATUS Drift-Guard", () => {
|
|
@@ -32,7 +32,7 @@ describe("EXPORT_JOB_STATUS Drift-Guard", () => {
|
|
|
32
32
|
// alphabetische Sortierung — die State-Machine kuemmert sich nicht
|
|
33
33
|
// um Sort-Order, ein Re-Order der Konstanten waere kein Bug.
|
|
34
34
|
for (const value of Object.values(EXPORT_JOB_STATUS)) {
|
|
35
|
-
expect(value).toBe(value.toLowerCase());
|
|
35
|
+
expect(value as string).toBe(value.toLowerCase());
|
|
36
36
|
expect(value).toMatch(/^[a-z]+$/);
|
|
37
37
|
}
|
|
38
38
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// werden. Memory `feedback_no_fake_tests`: das Mapping IST die Logik,
|
|
5
5
|
// nicht ein Detail.
|
|
6
6
|
|
|
7
|
-
import { describe, expect, test } from "
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
8
8
|
import { policyToStrategy } from "../run-forget-cleanup";
|
|
9
9
|
|
|
10
10
|
describe("policyToStrategy", () => {
|