@cosmicdrift/kumiko-bundled-features 0.14.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 +2 -2
- 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 -689
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-bundled-features",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -99,4 +99,4 @@
|
|
|
99
99
|
"README.md",
|
|
100
100
|
"LICENSE"
|
|
101
101
|
]
|
|
102
|
-
}
|
|
102
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
1
2
|
import { randomBytes } from "node:crypto";
|
|
2
3
|
import {
|
|
3
4
|
composeEnvSchema,
|
|
4
5
|
type KumikoBootError,
|
|
5
6
|
parseEnv,
|
|
6
7
|
} from "@cosmicdrift/kumiko-framework/env";
|
|
7
|
-
import { describe, expect, it } from "vitest";
|
|
8
8
|
import { authEmailPasswordEnvSchema, createAuthEmailPasswordFeature } from "../auth-email-password";
|
|
9
9
|
import { createSecretsFeature, secretsEnvSchema } from "../secrets";
|
|
10
10
|
import {
|
|
@@ -17,9 +17,11 @@
|
|
|
17
17
|
// updateMemberRoles auf — MUSS tenantIdOverride nutzen sonst
|
|
18
18
|
// version_conflict.
|
|
19
19
|
|
|
20
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
20
21
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
21
22
|
import { tmpdir } from "node:os";
|
|
22
23
|
import { join } from "node:path";
|
|
24
|
+
import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
23
25
|
import { createRegistry, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
24
26
|
import {
|
|
25
27
|
createEsOperationsTable,
|
|
@@ -36,8 +38,6 @@ import {
|
|
|
36
38
|
unsafeCreateEntityTable,
|
|
37
39
|
unsafePushTables,
|
|
38
40
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
39
|
-
import { sql } from "drizzle-orm";
|
|
40
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
41
41
|
import { createConfigFeature } from "../config/feature";
|
|
42
42
|
import { createConfigResolver } from "../config/resolver";
|
|
43
43
|
import { configValuesTable } from "../config/table";
|
|
@@ -79,7 +79,7 @@ afterAll(async () => {
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
beforeEach(async () => {
|
|
82
|
-
await testDb.db.
|
|
82
|
+
await asRawClient(testDb.db).unsafe(`
|
|
83
83
|
TRUNCATE kumiko_es_operations, kumiko_events, read_tenants, read_tenant_memberships
|
|
84
84
|
`);
|
|
85
85
|
});
|
|
@@ -144,21 +144,22 @@ describe("es-ops Phase 1.5 — E2E gegen real-Stack", () => {
|
|
|
144
144
|
expect(result.appliedIds).toEqual(["2026-05-21-add-tenant-admin"]);
|
|
145
145
|
|
|
146
146
|
// Verify (a) marker
|
|
147
|
-
const markers = await testDb.db
|
|
147
|
+
const markers = await selectMany(testDb.db, esOperationsTable);
|
|
148
148
|
expect(markers).toHaveLength(1);
|
|
149
149
|
expect(markers[0]?.id).toBe("2026-05-21-add-tenant-admin");
|
|
150
150
|
|
|
151
151
|
// Verify (b) event in store mit tenant-membership.updated
|
|
152
|
-
const events = (await testDb.db.
|
|
153
|
-
|
|
152
|
+
const events = (await asRawClient(testDb.db).unsafe(
|
|
153
|
+
`SELECT type, tenant_id::text AS tenant_id FROM kumiko_events ORDER BY id`,
|
|
154
154
|
)) as unknown as readonly { type: string; tenant_id: string }[];
|
|
155
155
|
const updateEvents = events.filter((e) => e.type === "tenant-membership.updated");
|
|
156
156
|
expect(updateEvents).toHaveLength(1);
|
|
157
157
|
expect(updateEvents[0]?.tenant_id).toBe(demoTenantId);
|
|
158
158
|
|
|
159
159
|
// Verify (c) read-model aktualisiert
|
|
160
|
-
const memberships = (await testDb.db.
|
|
161
|
-
|
|
160
|
+
const memberships = (await asRawClient(testDb.db).unsafe(
|
|
161
|
+
`SELECT roles FROM read_tenant_memberships WHERE user_id = $1`,
|
|
162
|
+
[adminUserId],
|
|
162
163
|
)) as unknown as readonly { roles: string }[];
|
|
163
164
|
expect(JSON.parse(memberships[0]?.roles ?? "[]")).toEqual(["Admin", "TenantAdmin"]);
|
|
164
165
|
} finally {
|
|
@@ -202,7 +203,7 @@ describe("es-ops Phase 1.5 — E2E gegen real-Stack", () => {
|
|
|
202
203
|
/dry-run found.*unknown handler-QN/,
|
|
203
204
|
);
|
|
204
205
|
|
|
205
|
-
const markers = await testDb.db
|
|
206
|
+
const markers = await selectMany(testDb.db, esOperationsTable);
|
|
206
207
|
expect(markers).toHaveLength(0);
|
|
207
208
|
} finally {
|
|
208
209
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// log IS the audit trail; this suite proves the query handler exposes the
|
|
3
3
|
// right slices of it (tenant-isolated, filtered, paginated, content-intact).
|
|
4
4
|
|
|
5
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
6
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
5
7
|
import {
|
|
6
8
|
createEntity,
|
|
7
9
|
createTextField,
|
|
@@ -18,8 +20,6 @@ import {
|
|
|
18
20
|
testTenantId,
|
|
19
21
|
unsafeCreateEntityTable,
|
|
20
22
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
21
|
-
import { sql } from "drizzle-orm";
|
|
22
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
23
23
|
import { AuditQueries } from "../constants";
|
|
24
24
|
import { createAuditFeature } from "../feature";
|
|
25
25
|
|
|
@@ -71,7 +71,7 @@ beforeEach(async () => {
|
|
|
71
71
|
// Fresh event log per test — the audit query reads the events table
|
|
72
72
|
// directly, so stale events from previous tests would leak into results.
|
|
73
73
|
await resetEventStore(stack);
|
|
74
|
-
await stack.db.
|
|
74
|
+
await asRawClient(stack.db).unsafe(`TRUNCATE audit_widgets`);
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
async function createWidget(user: SessionUser, name: string, color?: string): Promise<string> {
|
|
@@ -11,37 +11,23 @@
|
|
|
11
11
|
// append time (see event-store-executor → stripSensitive), so this query
|
|
12
12
|
// can't surface PII that the entity definition marked as sensitive.
|
|
13
13
|
|
|
14
|
+
import { selectMany, type WhereObject } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
14
15
|
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
15
16
|
import { eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
16
|
-
import { and, desc, eq, gte, lt, lte } from "drizzle-orm";
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
|
|
19
|
-
// Per-page cap. 100 keeps a single page payload bounded while being enough
|
|
20
|
-
// for a humans-browse UI — clients that need exports iterate by `before`.
|
|
21
19
|
const MAX_LIMIT = 100;
|
|
22
20
|
|
|
23
21
|
export const listQuery = defineQueryHandler({
|
|
24
22
|
name: "list",
|
|
25
23
|
schema: z
|
|
26
24
|
.object({
|
|
27
|
-
// Cursor-style pagination: pass the `id` from the last row of the
|
|
28
|
-
// previous page as `before`. bigserial ids are monotonic, so `< before`
|
|
29
|
-
// reliably returns "the next older page". Beats OFFSET on large tables.
|
|
30
|
-
// The regex pins the input to digits-only — otherwise an invalid value
|
|
31
|
-
// would surface as a raw PG `invalid_text_representation` instead of a
|
|
32
|
-
// clean 400 at the schema gate.
|
|
33
25
|
before: z.string().regex(/^\d+$/, "cursor must be a positive integer").optional(),
|
|
34
26
|
limit: z.number().int().min(1).max(MAX_LIMIT).default(50),
|
|
35
|
-
// Filters — all optional. Combined via AND.
|
|
36
27
|
aggregateType: z.string().optional(),
|
|
37
28
|
aggregateId: z.uuid().optional(),
|
|
38
29
|
eventType: z.string().optional(),
|
|
39
|
-
// createdBy is stored as text on the events table (it accepts both UUIDs
|
|
40
|
-
// and system actor strings like "SYSTEM"), so the filter is a plain
|
|
41
|
-
// equality check on the raw value.
|
|
42
30
|
userId: z.string().optional(),
|
|
43
|
-
// Inclusive bounds. Clients pass ISO-8601; we parse to Temporal.Instant
|
|
44
|
-
// and compare via the `instant()` column type.
|
|
45
31
|
from: z.iso.datetime().optional(),
|
|
46
32
|
to: z.iso.datetime().optional(),
|
|
47
33
|
})
|
|
@@ -52,47 +38,49 @@ export const listQuery = defineQueryHandler({
|
|
|
52
38
|
access: { roles: ["Admin", "SystemAdmin"] },
|
|
53
39
|
handler: async (query, ctx) => {
|
|
54
40
|
const p = query.payload;
|
|
55
|
-
const
|
|
41
|
+
const where: WhereObject = { tenantId: query.user.tenantId };
|
|
42
|
+
if (p.aggregateType) where["aggregateType"] = p.aggregateType;
|
|
43
|
+
if (p.aggregateId) where["aggregateId"] = p.aggregateId;
|
|
44
|
+
if (p.eventType) where["type"] = p.eventType;
|
|
45
|
+
if (p.userId) where["createdBy"] = p.userId;
|
|
46
|
+
if (p.from || p.to) {
|
|
47
|
+
const range: { gte?: unknown; lte?: unknown } = {};
|
|
48
|
+
if (p.from) range.gte = Temporal.Instant.from(p.from);
|
|
49
|
+
if (p.to) range.lte = Temporal.Instant.from(p.to);
|
|
50
|
+
where["createdAt"] = range;
|
|
51
|
+
}
|
|
52
|
+
if (p.before) where["id"] = { lt: BigInt(p.before) };
|
|
56
53
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
const rows = await selectMany<{
|
|
55
|
+
id: bigint;
|
|
56
|
+
aggregateId: string;
|
|
57
|
+
aggregateType: string;
|
|
58
|
+
version: number;
|
|
59
|
+
type: string;
|
|
60
|
+
payload: Record<string, unknown>;
|
|
61
|
+
metadata: Record<string, unknown>;
|
|
62
|
+
createdAt: unknown;
|
|
63
|
+
createdBy: string;
|
|
64
|
+
}>(ctx.db, eventsTable, where, {
|
|
65
|
+
orderBy: { col: "id", direction: "desc" },
|
|
66
|
+
limit: p.limit,
|
|
67
|
+
});
|
|
68
68
|
|
|
69
|
-
const
|
|
70
|
-
.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
})
|
|
81
|
-
.from(eventsTable)
|
|
82
|
-
.where(and(...conditions))
|
|
83
|
-
.orderBy(desc(eventsTable.id))
|
|
84
|
-
.limit(p.limit);
|
|
85
|
-
|
|
86
|
-
// bigint ids need serialisation — JSON can't carry a plain BigInt, and
|
|
87
|
-
// clients pass the cursor back as a string via `before`. Stringified once
|
|
88
|
-
// here so the response shape matches what the caller will re-submit.
|
|
89
|
-
const serialised = rows.map((r) => ({ ...r, id: String(r["id"]) }));
|
|
69
|
+
const serialised = rows.map((r) => ({
|
|
70
|
+
id: String(r.id),
|
|
71
|
+
aggregateId: r.aggregateId,
|
|
72
|
+
aggregateType: r.aggregateType,
|
|
73
|
+
version: r.version,
|
|
74
|
+
type: r.type,
|
|
75
|
+
payload: r.payload,
|
|
76
|
+
metadata: r.metadata,
|
|
77
|
+
createdAt: r.createdAt,
|
|
78
|
+
createdBy: r.createdBy,
|
|
79
|
+
}));
|
|
90
80
|
const last = serialised[serialised.length - 1];
|
|
91
81
|
return {
|
|
92
82
|
rows: serialised,
|
|
93
|
-
|
|
94
|
-
// the start of the log) so clients know to stop.
|
|
95
|
-
nextBefore: serialised.length === p.limit && last ? last["id"] : null,
|
|
83
|
+
nextBefore: serialised.length === p.limit && last ? last.id : null,
|
|
96
84
|
};
|
|
97
85
|
},
|
|
98
86
|
});
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
// the stack must be built without `context.redis` — shared `beforeAll`
|
|
11
11
|
// can't mix the two.
|
|
12
12
|
|
|
13
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
13
14
|
import { randomBytes } from "node:crypto";
|
|
15
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
14
16
|
import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
|
|
15
17
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
16
18
|
import {
|
|
@@ -20,7 +22,6 @@ import {
|
|
|
20
22
|
unsafeCreateEntityTable,
|
|
21
23
|
unsafePushTables,
|
|
22
24
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
23
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
24
25
|
import { createConfigFeature } from "../../config";
|
|
25
26
|
import { createConfigResolver } from "../../config/resolver";
|
|
26
27
|
import { configValuesTable } from "../../config/table";
|
|
@@ -84,8 +85,8 @@ afterAll(async () => {
|
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
beforeEach(async () => {
|
|
87
|
-
await stack.db.
|
|
88
|
-
await stack.db.
|
|
88
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
|
|
89
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
|
|
89
90
|
});
|
|
90
91
|
|
|
91
92
|
async function seedLoginUser(
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
// - Redis unset: handler still works, lockout is silently skipped (degrades
|
|
10
10
|
// gracefully to the IP-rate-limiter at the edge)
|
|
11
11
|
|
|
12
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
12
13
|
import { randomBytes } from "node:crypto";
|
|
14
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
13
15
|
import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
|
|
14
16
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
15
17
|
import {
|
|
@@ -19,7 +21,6 @@ import {
|
|
|
19
21
|
unsafeCreateEntityTable,
|
|
20
22
|
unsafePushTables,
|
|
21
23
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
22
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
23
24
|
import { createConfigFeature } from "../../config";
|
|
24
25
|
import { createConfigResolver } from "../../config/resolver";
|
|
25
26
|
import { configValuesTable } from "../../config/table";
|
|
@@ -84,8 +85,8 @@ afterAll(async () => {
|
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
beforeEach(async () => {
|
|
87
|
-
await stack.db.
|
|
88
|
-
await stack.db.
|
|
88
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
|
|
89
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
|
|
89
90
|
// Clear lockout state between tests — the key prefix is feature-owned, so
|
|
90
91
|
// a scan-and-del is the safe bet even if tests share a Redis namespace.
|
|
91
92
|
await stack.redis.flushNamespace();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
1
2
|
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { asRawClient, insertOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
4
|
import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
|
|
3
5
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
6
|
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
@@ -10,7 +12,6 @@ import {
|
|
|
10
12
|
unsafeCreateEntityTable,
|
|
11
13
|
unsafePushTables,
|
|
12
14
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
13
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
14
15
|
import { createConfigFeature } from "../../config";
|
|
15
16
|
import { createConfigResolver } from "../../config/resolver";
|
|
16
17
|
import { configValuesTable } from "../../config/table";
|
|
@@ -101,8 +102,8 @@ afterAll(async () => {
|
|
|
101
102
|
});
|
|
102
103
|
|
|
103
104
|
beforeEach(async () => {
|
|
104
|
-
await stack.db.
|
|
105
|
-
await stack.db.
|
|
105
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
|
|
106
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
|
|
106
107
|
segmentsByUserAndTenant.clear();
|
|
107
108
|
plansByTenant.clear();
|
|
108
109
|
});
|
|
@@ -118,7 +119,7 @@ async function seedUser(email: string, password: string): Promise<string> {
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
async function addMembership(userId: string, tenantId: TenantId, roles: string[]): Promise<void> {
|
|
121
|
-
await stack.db
|
|
122
|
+
await insertOne(stack.db, tenantMembershipsTable, {
|
|
122
123
|
userId,
|
|
123
124
|
tenantId,
|
|
124
125
|
roles: JSON.stringify(roles),
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
1
2
|
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
4
|
import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
|
|
3
5
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
6
|
import {
|
|
@@ -15,7 +17,6 @@ import {
|
|
|
15
17
|
getSetCookieRaw,
|
|
16
18
|
getSetCookieValue,
|
|
17
19
|
} from "@cosmicdrift/kumiko-framework/testing";
|
|
18
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
19
20
|
import { createConfigFeature } from "../../config";
|
|
20
21
|
import { createConfigResolver } from "../../config/resolver";
|
|
21
22
|
import { configValuesTable } from "../../config/table";
|
|
@@ -67,8 +68,8 @@ afterAll(async () => {
|
|
|
67
68
|
});
|
|
68
69
|
|
|
69
70
|
beforeEach(async () => {
|
|
70
|
-
await stack.db.
|
|
71
|
-
await stack.db.
|
|
71
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
|
|
72
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
|
|
72
73
|
});
|
|
73
74
|
|
|
74
75
|
// Helper: seed a full login-ready user (user row + membership).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
1
2
|
import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
3
|
import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
-
import { describe, expect, test } from "vitest";
|
|
4
4
|
import { runConfirmTokenFlow } from "../handlers/confirm-token-flow";
|
|
5
5
|
|
|
6
6
|
// Pins the "fail-loud when ctx.redis is missing" branch. Without this
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// expiresAt, HTML escaped XSS-Versuche, beide Locales unterscheiden
|
|
5
5
|
// sich erwartungsgemäß.
|
|
6
6
|
|
|
7
|
-
import { describe, expect, test } from "
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
8
8
|
import { renderResetPasswordEmail, renderVerifyEmail } from "../email-templates";
|
|
9
9
|
|
|
10
10
|
describe("renderResetPasswordEmail", () => {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
1
2
|
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { asRawClient, selectMany, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
4
|
import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
|
|
3
5
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
6
|
import {
|
|
@@ -8,9 +10,7 @@ import {
|
|
|
8
10
|
unsafeCreateEntityTable,
|
|
9
11
|
unsafePushTables,
|
|
10
12
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
11
|
-
import { eq } from "drizzle-orm";
|
|
12
13
|
import { Temporal } from "temporal-polyfill";
|
|
13
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
14
14
|
import { createConfigFeature } from "../../config";
|
|
15
15
|
import { createConfigResolver } from "../../config/resolver";
|
|
16
16
|
import { configValuesTable } from "../../config/table";
|
|
@@ -94,8 +94,8 @@ afterAll(async () => {
|
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
beforeEach(async () => {
|
|
97
|
-
await stack.db.
|
|
98
|
-
await stack.db.
|
|
97
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
|
|
98
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
|
|
99
99
|
capturedEmails.length = 0;
|
|
100
100
|
});
|
|
101
101
|
|
|
@@ -121,10 +121,7 @@ async function seedUser(opts: {
|
|
|
121
121
|
// it directly via SQL after create. Row.version is left at 1; no
|
|
122
122
|
// subsequent event-store writes happen on this row in these tests.
|
|
123
123
|
if (opts.emailVerified === true) {
|
|
124
|
-
await stack.db
|
|
125
|
-
.update(userTable)
|
|
126
|
-
.set({ emailVerified: true })
|
|
127
|
-
.where(eq(userTable["id"], created.id));
|
|
124
|
+
await updateMany(stack.db, userTable, { emailVerified: true }, { id: created.id });
|
|
128
125
|
}
|
|
129
126
|
const tenantId = opts.tenantId ?? "00000000-0000-4000-8000-000000000001";
|
|
130
127
|
await seedTenantMembership(stack.db, {
|
|
@@ -191,7 +188,7 @@ describe("POST /auth/verify-email", () => {
|
|
|
191
188
|
const res = await post("/api/auth/verify-email", { token });
|
|
192
189
|
expect(res.status).toBe(200);
|
|
193
190
|
|
|
194
|
-
const row = (await stack.db
|
|
191
|
+
const row = (await selectMany(stack.db, userTable)).find((r) => r["id"] === seed.id);
|
|
195
192
|
expect(row?.["emailVerified"]).toBe(true);
|
|
196
193
|
});
|
|
197
194
|
|
|
@@ -214,7 +211,7 @@ describe("POST /auth/verify-email", () => {
|
|
|
214
211
|
const seed = await seedUser({ email: "retry@example.com", password: "pw-retry-1234" });
|
|
215
212
|
const { token } = signVerificationToken(seed.id, 60, verifySecret);
|
|
216
213
|
|
|
217
|
-
await stack.db.
|
|
214
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
|
|
218
215
|
const firstAttempt = await post("/api/auth/verify-email", { token });
|
|
219
216
|
expect(firstAttempt.status).toBe(422);
|
|
220
217
|
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
// blobs, unsupported PRF — all the ways a verifier could leak a 500
|
|
14
14
|
// instead of returning false.
|
|
15
15
|
|
|
16
|
+
import { describe, expect, test } from "bun:test";
|
|
16
17
|
import { pbkdf2Sync } from "node:crypto";
|
|
17
|
-
import { describe, expect, test } from "vitest";
|
|
18
18
|
import { isIdentityV3Hash, verifyIdentityV3Hash } from "../identity-v3-hash";
|
|
19
19
|
import { verifyPassword } from "../password-hashing";
|
|
20
20
|
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
// Das ist der Kern-Use-Case der BMC-Migration — Legacy-Hashes 1:1
|
|
6
6
|
// übernommen, Login funktioniert weiter.
|
|
7
7
|
|
|
8
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
8
9
|
import { pbkdf2Sync, randomBytes } from "node:crypto";
|
|
10
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
9
11
|
import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
|
|
10
12
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
11
13
|
import {
|
|
@@ -15,7 +17,6 @@ import {
|
|
|
15
17
|
unsafeCreateEntityTable,
|
|
16
18
|
unsafePushTables,
|
|
17
19
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
18
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
19
20
|
import { createConfigFeature } from "../../config";
|
|
20
21
|
import { createConfigResolver } from "../../config/resolver";
|
|
21
22
|
import { configValuesTable } from "../../config/table";
|
|
@@ -79,8 +80,8 @@ afterAll(async () => {
|
|
|
79
80
|
});
|
|
80
81
|
|
|
81
82
|
beforeEach(async () => {
|
|
82
|
-
await stack.db.
|
|
83
|
-
await stack.db.
|
|
83
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
|
|
84
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
describe("Identity-V3 password-hash compatibility", () => {
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
// 4. Branch-spezifischer Accept-Endpoint
|
|
15
15
|
// 5. DB-State + Membership + Cookies/JWT verifizieren
|
|
16
16
|
|
|
17
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
18
|
+
import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
17
19
|
import {
|
|
18
20
|
createSystemUser,
|
|
19
21
|
type SessionUser,
|
|
@@ -26,8 +28,6 @@ import {
|
|
|
26
28
|
unsafeCreateEntityTable,
|
|
27
29
|
unsafePushTables,
|
|
28
30
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
29
|
-
import { eq } from "drizzle-orm";
|
|
30
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
31
31
|
import { createConfigFeature } from "../../config";
|
|
32
32
|
import { createConfigResolver } from "../../config/resolver";
|
|
33
33
|
import { configValuesTable } from "../../config/table";
|
|
@@ -114,10 +114,10 @@ afterAll(async () => {
|
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
beforeEach(async () => {
|
|
117
|
-
await stack.db.
|
|
118
|
-
await stack.db.
|
|
119
|
-
await stack.db.
|
|
120
|
-
await stack.db.
|
|
117
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
|
|
118
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
|
|
119
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantInvitationsTable.tableName}"`);
|
|
120
|
+
await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantTable.tableName}"`);
|
|
121
121
|
capturedInviteEmails.length = 0;
|
|
122
122
|
const allKeys = await stack.redis.redis.keys("invite:*");
|
|
123
123
|
if (allKeys.length > 0) await stack.redis.redis.del(...allKeys);
|
|
@@ -209,10 +209,7 @@ describe("invite-create", () => {
|
|
|
209
209
|
expect(result.token).toBeTruthy();
|
|
210
210
|
expect(result.token.length).toBeGreaterThanOrEqual(16);
|
|
211
211
|
|
|
212
|
-
const rows = await stack.db
|
|
213
|
-
.select()
|
|
214
|
-
.from(tenantInvitationsTable)
|
|
215
|
-
.where(eq(tenantInvitationsTable.email, BOB_EMAIL));
|
|
212
|
+
const rows = await selectMany(stack.db, tenantInvitationsTable, { email: BOB_EMAIL });
|
|
216
213
|
expect(rows).toHaveLength(1);
|
|
217
214
|
expect(rows[0]?.["status"]).toBe("pending");
|
|
218
215
|
expect(rows[0]?.["role"]).toBe("Admin");
|
|
@@ -226,10 +223,7 @@ describe("invite-create", () => {
|
|
|
226
223
|
expect(secondToken).toBe(firstToken);
|
|
227
224
|
|
|
228
225
|
// Eine Row, role updated
|
|
229
|
-
const rows = await stack.db
|
|
230
|
-
.select()
|
|
231
|
-
.from(tenantInvitationsTable)
|
|
232
|
-
.where(eq(tenantInvitationsTable.email, BOB_EMAIL));
|
|
226
|
+
const rows = await selectMany(stack.db, tenantInvitationsTable, { email: BOB_EMAIL });
|
|
233
227
|
expect(rows).toHaveLength(1);
|
|
234
228
|
expect(rows[0]?.["role"]).toBe("Editor");
|
|
235
229
|
});
|
|
@@ -250,19 +244,13 @@ describe("invite-accept (Branch 1: logged-in)", () => {
|
|
|
250
244
|
expect(result.alreadyMember).toBe(false);
|
|
251
245
|
|
|
252
246
|
// Bob hat jetzt 2 Memberships
|
|
253
|
-
const memberships = await stack.db
|
|
254
|
-
.select()
|
|
255
|
-
.from(tenantMembershipsTable)
|
|
256
|
-
.where(eq(tenantMembershipsTable.userId, bobId));
|
|
247
|
+
const memberships = await selectMany(stack.db, tenantMembershipsTable, { userId: bobId });
|
|
257
248
|
expect(memberships).toHaveLength(2);
|
|
258
249
|
const tenantIds = memberships.map((m) => m["tenantId"]).sort();
|
|
259
250
|
expect(tenantIds).toEqual([TENANT_A_ID, TENANT_B_ID].sort());
|
|
260
251
|
|
|
261
252
|
// Invitation status = accepted
|
|
262
|
-
const inv = await stack.db
|
|
263
|
-
.select()
|
|
264
|
-
.from(tenantInvitationsTable)
|
|
265
|
-
.where(eq(tenantInvitationsTable.email, BOB_EMAIL));
|
|
253
|
+
const inv = await selectMany(stack.db, tenantInvitationsTable, { email: BOB_EMAIL });
|
|
266
254
|
expect(inv[0]?.["status"]).toBe("accepted");
|
|
267
255
|
});
|
|
268
256
|
|
|
@@ -319,10 +307,7 @@ describe("invite-accept-with-login (Branch 2: anon + existing email)", () => {
|
|
|
319
307
|
expect(setCookies).toContain("kumiko_auth=");
|
|
320
308
|
|
|
321
309
|
// Membership added
|
|
322
|
-
const memberships = await stack.db
|
|
323
|
-
.select()
|
|
324
|
-
.from(tenantMembershipsTable)
|
|
325
|
-
.where(eq(tenantMembershipsTable.userId, bobId));
|
|
310
|
+
const memberships = await selectMany(stack.db, tenantMembershipsTable, { userId: bobId });
|
|
326
311
|
expect(memberships).toHaveLength(2);
|
|
327
312
|
});
|
|
328
313
|
|
|
@@ -359,10 +344,7 @@ describe("invite-signup-complete (Branch 3: anon + new email)", () => {
|
|
|
359
344
|
expect(body.role).toBe("Admin");
|
|
360
345
|
|
|
361
346
|
// Carol entstanden in users
|
|
362
|
-
const carolRows = await stack.db
|
|
363
|
-
.select()
|
|
364
|
-
.from(userTable)
|
|
365
|
-
.where(eq(userTable.email, CAROL_EMAIL));
|
|
347
|
+
const carolRows = await selectMany(stack.db, userTable, { email: CAROL_EMAIL });
|
|
366
348
|
expect(carolRows).toHaveLength(1);
|
|
367
349
|
expect(carolRows[0]?.["emailVerified"]).toBe(true);
|
|
368
350
|
expect(carolRows[0]?.["id"]).toBe(body.user.id);
|
|
@@ -387,10 +369,7 @@ describe("invite-signup-complete (Branch 3: anon + new email)", () => {
|
|
|
387
369
|
expect(body.error?.details?.reason).toBe(AuthErrors.invalidInviteToken);
|
|
388
370
|
|
|
389
371
|
// Bob hat keine zweite Membership erworben
|
|
390
|
-
const memberships = await stack.db
|
|
391
|
-
.select()
|
|
392
|
-
.from(tenantMembershipsTable)
|
|
393
|
-
.where(eq(tenantMembershipsTable.userId, bobId));
|
|
372
|
+
const memberships = await selectMany(stack.db, tenantMembershipsTable, { userId: bobId });
|
|
394
373
|
expect(memberships).toHaveLength(1);
|
|
395
374
|
void GUEST;
|
|
396
375
|
});
|
|
@@ -411,18 +390,12 @@ describe("cancel-invitation", () => {
|
|
|
411
390
|
const token = await inviteEmail(BOB_EMAIL, "Admin");
|
|
412
391
|
|
|
413
392
|
// Find invitationId
|
|
414
|
-
const rows = await stack.db
|
|
415
|
-
.select()
|
|
416
|
-
.from(tenantInvitationsTable)
|
|
417
|
-
.where(eq(tenantInvitationsTable.email, BOB_EMAIL));
|
|
393
|
+
const rows = await selectMany(stack.db, tenantInvitationsTable, { email: BOB_EMAIL });
|
|
418
394
|
const invitationId = rows[0]?.["id"] as string;
|
|
419
395
|
|
|
420
396
|
await stack.http.writeOk("tenant:write:cancel-invitation", { invitationId }, aliceSession());
|
|
421
397
|
|
|
422
|
-
const updated = await stack.db
|
|
423
|
-
.select()
|
|
424
|
-
.from(tenantInvitationsTable)
|
|
425
|
-
.where(eq(tenantInvitationsTable.id, invitationId));
|
|
398
|
+
const updated = await selectMany(stack.db, tenantInvitationsTable, { id: invitationId });
|
|
426
399
|
expect(updated[0]?.["status"]).toBe("cancelled");
|
|
427
400
|
|
|
428
401
|
// Accept mit dem gecancelten Token → invalid
|
|
@@ -437,7 +410,7 @@ describe("invitations-query (pending list)", () => {
|
|
|
437
410
|
await inviteEmail(CAROL_EMAIL, "Editor");
|
|
438
411
|
|
|
439
412
|
// Cancel das erste
|
|
440
|
-
const allRows = await stack.db
|
|
413
|
+
const allRows = await selectMany(stack.db, tenantInvitationsTable);
|
|
441
414
|
const bobInv = allRows.find((r) => r["email"] === BOB_EMAIL);
|
|
442
415
|
if (!bobInv) throw new Error("bob invitation missing");
|
|
443
416
|
await stack.http.writeOk(
|