@cosmicdrift/kumiko-bundled-features 0.1.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 +90 -0
- package/src/audit/__tests__/audit.integration.ts +328 -0
- package/src/audit/constants.ts +7 -0
- package/src/audit/feature.ts +23 -0
- package/src/audit/handlers/list.query.ts +98 -0
- package/src/audit/index.ts +2 -0
- package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.ts +149 -0
- package/src/auth-email-password/__tests__/account-lockout.integration.ts +308 -0
- package/src/auth-email-password/__tests__/auth-claims.integration.ts +512 -0
- package/src/auth-email-password/__tests__/auth.integration.ts +610 -0
- package/src/auth-email-password/__tests__/confirm-token-flow.test.ts +67 -0
- package/src/auth-email-password/__tests__/email-templates.test.ts +106 -0
- package/src/auth-email-password/__tests__/email-verification.integration.ts +327 -0
- package/src/auth-email-password/__tests__/identity-v3-hash.test.ts +174 -0
- package/src/auth-email-password/__tests__/identity-v3-login.integration.ts +150 -0
- package/src/auth-email-password/__tests__/invite-flow.integration.ts +458 -0
- package/src/auth-email-password/__tests__/multi-roles.integration.ts +256 -0
- package/src/auth-email-password/__tests__/password-reset.integration.ts +346 -0
- package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.ts +144 -0
- package/src/auth-email-password/__tests__/seed-admin.integration.ts +176 -0
- package/src/auth-email-password/__tests__/session-callbacks.integration.ts +310 -0
- package/src/auth-email-password/__tests__/session-strict-mode.integration.ts +101 -0
- package/src/auth-email-password/__tests__/signed-token.test.ts +78 -0
- package/src/auth-email-password/__tests__/signup-flow.integration.ts +259 -0
- package/src/auth-email-password/auth-user-row.ts +41 -0
- package/src/auth-email-password/constants.ts +101 -0
- package/src/auth-email-password/email-templates.ts +283 -0
- package/src/auth-email-password/feature.ts +140 -0
- package/src/auth-email-password/handlers/change-password.write.ts +58 -0
- package/src/auth-email-password/handlers/confirm-token-flow.ts +191 -0
- package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +203 -0
- package/src/auth-email-password/handlers/invite-accept.write.ts +189 -0
- package/src/auth-email-password/handlers/invite-create.write.ts +145 -0
- package/src/auth-email-password/handlers/invite-signup-complete.write.ts +192 -0
- package/src/auth-email-password/handlers/login.write.ts +208 -0
- package/src/auth-email-password/handlers/logout.write.ts +12 -0
- package/src/auth-email-password/handlers/request-email-verification.write.ts +29 -0
- package/src/auth-email-password/handlers/request-password-reset.write.ts +31 -0
- package/src/auth-email-password/handlers/reset-password.write.ts +61 -0
- package/src/auth-email-password/handlers/signup-confirm.write.ts +170 -0
- package/src/auth-email-password/handlers/signup-request.write.ts +104 -0
- package/src/auth-email-password/handlers/token-request-handler.ts +114 -0
- package/src/auth-email-password/handlers/verify-email.write.ts +62 -0
- package/src/auth-email-password/i18n.ts +211 -0
- package/src/auth-email-password/identity-v3-hash.ts +97 -0
- package/src/auth-email-password/index.ts +35 -0
- package/src/auth-email-password/invite-token-store.ts +92 -0
- package/src/auth-email-password/lockout-store.ts +118 -0
- package/src/auth-email-password/password-hashing.ts +43 -0
- package/src/auth-email-password/reset-token.ts +28 -0
- package/src/auth-email-password/seeding.ts +183 -0
- package/src/auth-email-password/signed-token.ts +85 -0
- package/src/auth-email-password/signup-token-store.ts +104 -0
- package/src/auth-email-password/stream-tenant.ts +31 -0
- package/src/auth-email-password/testing.ts +5 -0
- package/src/auth-email-password/token-burn-store.ts +57 -0
- package/src/auth-email-password/verification-token.ts +27 -0
- package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +51 -0
- package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +80 -0
- package/src/auth-email-password/web/__tests__/login-screen.test.tsx +94 -0
- package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +108 -0
- package/src/auth-email-password/web/__tests__/session-roles.test.ts +54 -0
- package/src/auth-email-password/web/__tests__/tenant-switcher.test.tsx +100 -0
- package/src/auth-email-password/web/__tests__/test-utils.tsx +73 -0
- package/src/auth-email-password/web/__tests__/user-menu.test.tsx +55 -0
- package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +59 -0
- package/src/auth-email-password/web/auth-client.ts +350 -0
- package/src/auth-email-password/web/auth-form-primitives.tsx +70 -0
- package/src/auth-email-password/web/auth-gate.tsx +33 -0
- package/src/auth-email-password/web/client-plugin.ts +48 -0
- package/src/auth-email-password/web/default-topbar-actions.tsx +47 -0
- package/src/auth-email-password/web/forgot-password-screen.tsx +110 -0
- package/src/auth-email-password/web/index.ts +56 -0
- package/src/auth-email-password/web/invite-accept-screen.tsx +220 -0
- package/src/auth-email-password/web/login-screen.tsx +150 -0
- package/src/auth-email-password/web/reset-password-screen.tsx +152 -0
- package/src/auth-email-password/web/session.tsx +171 -0
- package/src/auth-email-password/web/signup-complete-screen.tsx +150 -0
- package/src/auth-email-password/web/signup-screen.tsx +130 -0
- package/src/auth-email-password/web/tenant-switcher.tsx +116 -0
- package/src/auth-email-password/web/use-shell-user.ts +34 -0
- package/src/auth-email-password/web/user-menu.tsx +89 -0
- package/src/auth-email-password/web/verify-email-screen.tsx +102 -0
- package/src/billing-foundation/__tests__/billing-foundation.integration.ts +568 -0
- package/src/billing-foundation/__tests__/feature.test.ts +110 -0
- package/src/billing-foundation/__tests__/webhook-handler.test.ts +199 -0
- package/src/billing-foundation/aggregate-id.ts +21 -0
- package/src/billing-foundation/constants.ts +70 -0
- package/src/billing-foundation/entities.ts +50 -0
- package/src/billing-foundation/events.ts +71 -0
- package/src/billing-foundation/feature.ts +122 -0
- package/src/billing-foundation/get-subscription-for-tenant.ts +39 -0
- package/src/billing-foundation/handlers/create-checkout-session.write.ts +79 -0
- package/src/billing-foundation/handlers/create-portal-session.write.ts +73 -0
- package/src/billing-foundation/handlers/list-subscriptions.query.ts +20 -0
- package/src/billing-foundation/handlers/process-event.write.ts +160 -0
- package/src/billing-foundation/index.ts +42 -0
- package/src/billing-foundation/projection.ts +135 -0
- package/src/billing-foundation/types.ts +157 -0
- package/src/billing-foundation/webhook-handler.ts +184 -0
- package/src/cap-counter/__tests__/cap-counter.integration.ts +566 -0
- package/src/cap-counter/__tests__/enforce-cap.test.ts +422 -0
- package/src/cap-counter/__tests__/with-cap-enforcement.integration.ts +265 -0
- package/src/cap-counter/aggregate-id.ts +61 -0
- package/src/cap-counter/constants.ts +32 -0
- package/src/cap-counter/enforce-cap.ts +404 -0
- package/src/cap-counter/entity.ts +48 -0
- package/src/cap-counter/feature.ts +90 -0
- package/src/cap-counter/handlers/get-counter.query.ts +43 -0
- package/src/cap-counter/handlers/increment-rolling.write.ts +79 -0
- package/src/cap-counter/handlers/increment.write.ts +92 -0
- package/src/cap-counter/handlers/mark-soft-warned.write.ts +57 -0
- package/src/cap-counter/index.ts +34 -0
- package/src/cap-counter/with-cap-enforcement.ts +179 -0
- package/src/channel-email/email-channel.ts +48 -0
- package/src/channel-email/feature.ts +15 -0
- package/src/channel-email/index.ts +4 -0
- package/src/channel-email/smtp-transport.ts +65 -0
- package/src/channel-email/types.ts +34 -0
- package/src/channel-in-app/constants.ts +11 -0
- package/src/channel-in-app/feature.ts +30 -0
- package/src/channel-in-app/handlers/inbox.query.ts +28 -0
- package/src/channel-in-app/handlers/mark-all-read.write.ts +21 -0
- package/src/channel-in-app/handlers/mark-read.write.ts +32 -0
- package/src/channel-in-app/handlers/unread-count.query.ts +20 -0
- package/src/channel-in-app/in-app-channel.ts +44 -0
- package/src/channel-in-app/index.ts +4 -0
- package/src/channel-in-app/tables.ts +22 -0
- package/src/channel-push/feature.ts +15 -0
- package/src/channel-push/index.ts +3 -0
- package/src/channel-push/push-channel.ts +33 -0
- package/src/channel-push/types.ts +22 -0
- package/src/config/__tests__/app-overrides.test.ts +118 -0
- package/src/config/__tests__/config.integration.ts +1246 -0
- package/src/config/constants.ts +23 -0
- package/src/config/feature.ts +117 -0
- package/src/config/handlers/__tests__/prepare-config-write.test.ts +209 -0
- package/src/config/handlers/reset.write.ts +45 -0
- package/src/config/handlers/schema.query.ts +22 -0
- package/src/config/handlers/set.write.ts +93 -0
- package/src/config/handlers/values.query.ts +43 -0
- package/src/config/index.ts +15 -0
- package/src/config/resolver.ts +283 -0
- package/src/config/table.ts +35 -0
- package/src/config/write-helpers.ts +268 -0
- package/src/delivery/__tests__/delivery-events.integration.ts +166 -0
- package/src/delivery/__tests__/delivery.integration.ts +1405 -0
- package/src/delivery/constants.ts +33 -0
- package/src/delivery/delivery-service.ts +489 -0
- package/src/delivery/events.ts +18 -0
- package/src/delivery/feature.ts +70 -0
- package/src/delivery/handlers/log.query.ts +21 -0
- package/src/delivery/handlers/preferences.query.ts +18 -0
- package/src/delivery/handlers/set-preference.write.ts +28 -0
- package/src/delivery/index.ts +35 -0
- package/src/delivery/tables.ts +74 -0
- package/src/delivery/testing.ts +47 -0
- package/src/delivery/types.ts +71 -0
- package/src/delivery/unsubscribe.ts +99 -0
- package/src/delivery/upsert-preference.ts +145 -0
- package/src/feature-toggles/__tests__/feature-toggles.integration.ts +687 -0
- package/src/feature-toggles/constants.ts +20 -0
- package/src/feature-toggles/events.ts +18 -0
- package/src/feature-toggles/feature.ts +98 -0
- package/src/feature-toggles/global-feature-state-table.ts +28 -0
- package/src/feature-toggles/handlers/list.query.ts +26 -0
- package/src/feature-toggles/handlers/registered.query.ts +56 -0
- package/src/feature-toggles/handlers/set.write.ts +158 -0
- package/src/feature-toggles/index.ts +9 -0
- package/src/feature-toggles/toggle-runtime.ts +73 -0
- package/src/file-foundation/__tests__/feature.test.ts +35 -0
- package/src/file-foundation/__tests__/file-foundation.integration.ts +235 -0
- package/src/file-foundation/feature.ts +123 -0
- package/src/file-foundation/index.ts +7 -0
- package/src/file-provider-inmemory/__tests__/feature.test.ts +35 -0
- package/src/file-provider-inmemory/feature.ts +73 -0
- package/src/file-provider-inmemory/index.ts +3 -0
- package/src/file-provider-s3/__tests__/feature.test.ts +54 -0
- package/src/file-provider-s3/feature.ts +169 -0
- package/src/file-provider-s3/index.ts +3 -0
- package/src/files-provider-s3/__tests__/env-helper.test.ts +161 -0
- package/src/files-provider-s3/__tests__/s3-provider.integration.ts +134 -0
- package/src/files-provider-s3/__tests__/s3-provider.test.ts +36 -0
- package/src/files-provider-s3/env-helper.ts +49 -0
- package/src/files-provider-s3/index.ts +3 -0
- package/src/files-provider-s3/s3-provider.ts +114 -0
- package/src/foundation-shared/config-helpers.ts +67 -0
- package/src/foundation-shared/index.ts +4 -0
- package/src/jobs/__tests__/job-system-user.integration.ts +194 -0
- package/src/jobs/__tests__/jobs-events.integration.ts +143 -0
- package/src/jobs/__tests__/jobs-feature.integration.ts +342 -0
- package/src/jobs/constants.ts +21 -0
- package/src/jobs/events.ts +39 -0
- package/src/jobs/feature.ts +150 -0
- package/src/jobs/handlers/detail.query.ts +30 -0
- package/src/jobs/handlers/list.query.ts +36 -0
- package/src/jobs/handlers/retry.write.ts +69 -0
- package/src/jobs/handlers/trigger.write.ts +39 -0
- package/src/jobs/index.ts +5 -0
- package/src/jobs/job-run-logger.ts +213 -0
- package/src/jobs/job-run-table.ts +55 -0
- package/src/legal-pages/README.md +195 -0
- package/src/legal-pages/__tests__/legal-pages.integration.ts +361 -0
- package/src/legal-pages/constants.ts +36 -0
- package/src/legal-pages/feature.ts +187 -0
- package/src/legal-pages/index.ts +13 -0
- package/src/legal-pages/markdown.ts +69 -0
- package/src/mail-foundation/__tests__/feature.test.ts +46 -0
- package/src/mail-foundation/__tests__/mail-foundation.integration.ts +247 -0
- package/src/mail-foundation/feature.ts +160 -0
- package/src/mail-foundation/index.ts +14 -0
- package/src/mail-transport-inmemory/__tests__/feature.test.ts +37 -0
- package/src/mail-transport-inmemory/feature.ts +90 -0
- package/src/mail-transport-inmemory/index.ts +3 -0
- package/src/mail-transport-smtp/__tests__/feature.test.ts +61 -0
- package/src/mail-transport-smtp/feature.ts +182 -0
- package/src/mail-transport-smtp/index.ts +3 -0
- package/src/rate-limiting/__tests__/rate-limiting.integration.ts +84 -0
- package/src/rate-limiting/constants.ts +9 -0
- package/src/rate-limiting/feature.ts +16 -0
- package/src/rate-limiting/handlers/status.query.ts +52 -0
- package/src/rate-limiting/index.ts +2 -0
- package/src/renderer-simple/__tests__/simple-renderer.test.ts +97 -0
- package/src/renderer-simple/feature.ts +12 -0
- package/src/renderer-simple/index.ts +2 -0
- package/src/renderer-simple/simple-renderer.ts +72 -0
- package/src/secrets/__tests__/rotate.integration.ts +176 -0
- package/src/secrets/__tests__/secrets-events.integration.ts +125 -0
- package/src/secrets/__tests__/secrets.integration.ts +118 -0
- package/src/secrets/feature.ts +84 -0
- package/src/secrets/handlers/delete.write.ts +20 -0
- package/src/secrets/handlers/list.query.ts +38 -0
- package/src/secrets/handlers/rotate.job.ts +193 -0
- package/src/secrets/handlers/set.write.ts +50 -0
- package/src/secrets/index.ts +16 -0
- package/src/secrets/secrets-context.ts +296 -0
- package/src/secrets/table.ts +68 -0
- package/src/sessions/__tests__/cleanup.integration.ts +175 -0
- package/src/sessions/__tests__/password-auto-revoke.integration.ts +202 -0
- package/src/sessions/__tests__/sessions.integration.ts +472 -0
- package/src/sessions/__tests__/test-helpers.ts +66 -0
- package/src/sessions/constants.ts +43 -0
- package/src/sessions/feature.ts +84 -0
- package/src/sessions/handlers/cleanup.job.ts +109 -0
- package/src/sessions/handlers/list.query.ts +35 -0
- package/src/sessions/handlers/mine.query.ts +37 -0
- package/src/sessions/handlers/revoke-all-others.write.ts +42 -0
- package/src/sessions/handlers/revoke.write.ts +76 -0
- package/src/sessions/index.ts +17 -0
- package/src/sessions/schema/index.ts +5 -0
- package/src/sessions/schema/user-session.ts +67 -0
- package/src/sessions/session-callbacks.ts +110 -0
- package/src/sessions/testing.ts +42 -0
- package/src/subscription-mollie/__tests__/feature.test.ts +106 -0
- package/src/subscription-mollie/__tests__/mollie-foundation.integration.ts +421 -0
- package/src/subscription-mollie/__tests__/verify-webhook.test.ts +388 -0
- package/src/subscription-mollie/constants.ts +33 -0
- package/src/subscription-mollie/feature.ts +144 -0
- package/src/subscription-mollie/index.ts +13 -0
- package/src/subscription-mollie/plugin-methods.ts +79 -0
- package/src/subscription-mollie/verify-webhook.ts +244 -0
- package/src/subscription-stripe/__tests__/feature.test.ts +98 -0
- package/src/subscription-stripe/__tests__/plugin-methods.test.ts +161 -0
- package/src/subscription-stripe/__tests__/stripe-foundation.integration.ts +315 -0
- package/src/subscription-stripe/__tests__/verify-webhook.test.ts +306 -0
- package/src/subscription-stripe/constants.ts +20 -0
- package/src/subscription-stripe/feature.ts +120 -0
- package/src/subscription-stripe/index.ts +14 -0
- package/src/subscription-stripe/plugin-methods.ts +91 -0
- package/src/subscription-stripe/verify-webhook.ts +235 -0
- package/src/tenant/__tests__/multi-tenant.integration.ts +278 -0
- package/src/tenant/__tests__/seed-testing.integration.ts +229 -0
- package/src/tenant/__tests__/tenant.integration.ts +347 -0
- package/src/tenant/command-schemas.ts +37 -0
- package/src/tenant/constants.ts +37 -0
- package/src/tenant/feature.ts +109 -0
- package/src/tenant/handlers/active-tenant-ids.query.ts +19 -0
- package/src/tenant/handlers/add-member.write.ts +53 -0
- package/src/tenant/handlers/cancel-invitation.write.ts +87 -0
- package/src/tenant/handlers/create.write.ts +21 -0
- package/src/tenant/handlers/disable.write.ts +18 -0
- package/src/tenant/handlers/invitations.query.ts +31 -0
- package/src/tenant/handlers/list.query.ts +17 -0
- package/src/tenant/handlers/me.query.ts +17 -0
- package/src/tenant/handlers/members.query.ts +22 -0
- package/src/tenant/handlers/memberships.query.ts +24 -0
- package/src/tenant/handlers/remove-member.write.ts +40 -0
- package/src/tenant/handlers/resolve-user-ids.query.ts +43 -0
- package/src/tenant/handlers/update-member-roles.write.ts +54 -0
- package/src/tenant/handlers/update.write.ts +20 -0
- package/src/tenant/index.ts +12 -0
- package/src/tenant/invitation-table.ts +93 -0
- package/src/tenant/membership-table.ts +35 -0
- package/src/tenant/schema/index.ts +5 -0
- package/src/tenant/schema/tenant.ts +27 -0
- package/src/tenant/seeding.ts +155 -0
- package/src/tenant/testing.ts +8 -0
- package/src/text-content/README.md +190 -0
- package/src/text-content/__tests__/text-content.integration.ts +415 -0
- package/src/text-content/api.ts +92 -0
- package/src/text-content/constants.ts +19 -0
- package/src/text-content/feature.ts +29 -0
- package/src/text-content/handlers/by-slug.query.ts +55 -0
- package/src/text-content/handlers/set.write.ts +118 -0
- package/src/text-content/index.ts +14 -0
- package/src/text-content/seeding.ts +91 -0
- package/src/text-content/table.ts +45 -0
- package/src/tier-engine/__tests__/compose-app.test.ts +182 -0
- package/src/tier-engine/__tests__/drift.test.ts +42 -0
- package/src/tier-engine/__tests__/tier-engine.integration.ts +241 -0
- package/src/tier-engine/aggregate-id.ts +27 -0
- package/src/tier-engine/compose-app.ts +150 -0
- package/src/tier-engine/constants.ts +15 -0
- package/src/tier-engine/entity.ts +30 -0
- package/src/tier-engine/feature.ts +72 -0
- package/src/tier-engine/handlers/active-tier.query.ts +23 -0
- package/src/tier-engine/index.ts +22 -0
- package/src/user/__tests__/seed-testing.integration.ts +127 -0
- package/src/user/__tests__/user.integration.ts +198 -0
- package/src/user/command-schemas.ts +15 -0
- package/src/user/constants.ts +23 -0
- package/src/user/feature.ts +32 -0
- package/src/user/handlers/create.write.ts +54 -0
- package/src/user/handlers/detail.query.ts +9 -0
- package/src/user/handlers/find-for-auth.query.ts +38 -0
- package/src/user/handlers/list.query.ts +8 -0
- package/src/user/handlers/me.query.ts +15 -0
- package/src/user/handlers/update.write.ts +54 -0
- package/src/user/index.ts +4 -0
- package/src/user/schema/index.ts +5 -0
- package/src/user/schema/user.ts +69 -0
- package/src/user/seeding.ts +93 -0
- package/src/user/testing.ts +5 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// computeActiveRoles — client-side merge muss byte-identisch zum server-
|
|
2
|
+
// side merge in auth-routes.ts (switch-tenant) + login.write.ts sein.
|
|
3
|
+
// Sonst sieht client andere roles als server → role-gating divergiert
|
|
4
|
+
// (entweder UI zeigt was nicht erlaubt ist, oder umgekehrt).
|
|
5
|
+
|
|
6
|
+
import { describe, expect, test } from "vitest";
|
|
7
|
+
import type { CurrentUserProfile, TenantSummary } from "../auth-client";
|
|
8
|
+
import { computeActiveRoles } from "../session";
|
|
9
|
+
|
|
10
|
+
const user = (globalRoles: readonly string[]): CurrentUserProfile => ({
|
|
11
|
+
id: "u1",
|
|
12
|
+
email: "u@e.com",
|
|
13
|
+
displayName: "U",
|
|
14
|
+
globalRoles,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const t = (id: string, roles: readonly string[]): TenantSummary => ({ tenantId: id, roles });
|
|
18
|
+
|
|
19
|
+
describe("computeActiveRoles", () => {
|
|
20
|
+
test("user=null → []", () => {
|
|
21
|
+
expect(computeActiveRoles(null, "tenant-1", [t("tenant-1", ["Admin"])])).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("globalRoles + active tenant membership → merged + dedupe", () => {
|
|
25
|
+
const result = computeActiveRoles(user(["SystemAdmin"]), "tenant-1", [
|
|
26
|
+
t("tenant-1", ["Admin"]),
|
|
27
|
+
]);
|
|
28
|
+
expect([...result].sort()).toEqual(["Admin", "SystemAdmin"]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("dedupe: gleiche Rolle in global + membership → einmal", () => {
|
|
32
|
+
const result = computeActiveRoles(user(["Admin", "SystemAdmin"]), "tenant-1", [
|
|
33
|
+
t("tenant-1", ["Admin", "User"]),
|
|
34
|
+
]);
|
|
35
|
+
expect([...result].sort()).toEqual(["Admin", "SystemAdmin", "User"]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("kein activeTenantId → nur globalRoles", () => {
|
|
39
|
+
const result = computeActiveRoles(user(["SystemAdmin"]), null, [t("tenant-1", ["Admin"])]);
|
|
40
|
+
expect(result).toEqual(["SystemAdmin"]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("activeTenantId zeigt auf nicht-vorhandenen tenant → nur globalRoles", () => {
|
|
44
|
+
const result = computeActiveRoles(user(["SystemAdmin"]), "tenant-2", [
|
|
45
|
+
t("tenant-1", ["Admin"]),
|
|
46
|
+
]);
|
|
47
|
+
expect(result).toEqual(["SystemAdmin"]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("OHNE globalRoles + active membership → nur membership-roles", () => {
|
|
51
|
+
const result = computeActiveRoles(user([]), "tenant-1", [t("tenant-1", ["Admin"])]);
|
|
52
|
+
expect(result).toEqual(["Admin"]);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import { describe, expect, test } from "vitest";
|
|
5
|
+
import { TenantSwitcher } from "../tenant-switcher";
|
|
6
|
+
import { makeSessionApi, renderWithProviders } from "./test-utils";
|
|
7
|
+
|
|
8
|
+
// Radix-DropdownMenu reagiert auf pointerdown, nicht auf click — daher
|
|
9
|
+
// userEvent statt fireEvent.
|
|
10
|
+
|
|
11
|
+
describe("TenantSwitcher", () => {
|
|
12
|
+
test("renders nothing when user is null", () => {
|
|
13
|
+
const session = makeSessionApi({ status: "unauthenticated", user: null });
|
|
14
|
+
const { container } = renderWithProviders(<TenantSwitcher />, { session });
|
|
15
|
+
expect(container.firstChild).toBeNull();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("renders nothing when user has only one tenant", () => {
|
|
19
|
+
const session = makeSessionApi({
|
|
20
|
+
tenants: [{ tenantId: "t1", roles: ["Admin"] }],
|
|
21
|
+
});
|
|
22
|
+
const { container } = renderWithProviders(<TenantSwitcher />, { session });
|
|
23
|
+
expect(container.firstChild).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("renders trigger when user has multiple tenants", () => {
|
|
27
|
+
const session = makeSessionApi({
|
|
28
|
+
activeTenantId: "tenant-a",
|
|
29
|
+
tenants: [
|
|
30
|
+
{ tenantId: "tenant-a", roles: ["Admin"] },
|
|
31
|
+
{ tenantId: "tenant-b", roles: ["User"] },
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
renderWithProviders(<TenantSwitcher tenantName={(id) => `Tenant ${id}`} />, { session });
|
|
35
|
+
// tenantName-Resolver liefert "Tenant tenant-a" als Trigger-Label
|
|
36
|
+
expect(screen.getByText("Tenant tenant-a")).toBeTruthy();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("opens dropdown showing all memberships with roles", async () => {
|
|
40
|
+
const user = userEvent.setup();
|
|
41
|
+
const session = makeSessionApi({
|
|
42
|
+
activeTenantId: "tenant-a",
|
|
43
|
+
tenants: [
|
|
44
|
+
{ tenantId: "tenant-a", roles: ["Admin"] },
|
|
45
|
+
{ tenantId: "tenant-b", roles: ["User", "Billing"] },
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
renderWithProviders(<TenantSwitcher tenantName={(id) => `Tenant ${id}`} />, {
|
|
49
|
+
session,
|
|
50
|
+
});
|
|
51
|
+
await user.click(screen.getByRole("button", { name: /Tenant tenant-a/ }));
|
|
52
|
+
// Trigger zeigt aktiven Tenant ("Tenant tenant-a") + Dropdown-Items
|
|
53
|
+
// listen ALLE Tenants — nutze getAllByText um Mehrdeutigkeit
|
|
54
|
+
// explizit zu erlauben, dann Roles-Strings als eindeutigen Anker.
|
|
55
|
+
expect(screen.getAllByText("Tenant tenant-a").length).toBeGreaterThan(0);
|
|
56
|
+
expect(screen.getByText("Tenant tenant-b")).toBeTruthy();
|
|
57
|
+
expect(screen.getByText("Admin")).toBeTruthy();
|
|
58
|
+
expect(screen.getByText("User, Billing")).toBeTruthy();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("clicking a tenant triggers switchTenant", async () => {
|
|
62
|
+
const user = userEvent.setup();
|
|
63
|
+
const session = makeSessionApi({
|
|
64
|
+
activeTenantId: "tenant-a",
|
|
65
|
+
tenants: [
|
|
66
|
+
{ tenantId: "tenant-a", roles: ["Admin"] },
|
|
67
|
+
{ tenantId: "tenant-b", roles: ["User"] },
|
|
68
|
+
],
|
|
69
|
+
});
|
|
70
|
+
renderWithProviders(<TenantSwitcher tenantName={(id) => `Tenant ${id}`} />, {
|
|
71
|
+
session,
|
|
72
|
+
});
|
|
73
|
+
await user.click(screen.getByRole("button", { name: /Tenant tenant-a/ }));
|
|
74
|
+
await user.click(screen.getByText("Tenant tenant-b"));
|
|
75
|
+
expect(session.switchTenant).toHaveBeenCalledWith("tenant-b");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("clicking the active tenant is a no-op (closes menu, no switch call)", async () => {
|
|
79
|
+
const user = userEvent.setup();
|
|
80
|
+
const session = makeSessionApi({
|
|
81
|
+
activeTenantId: "tenant-a",
|
|
82
|
+
tenants: [
|
|
83
|
+
{ tenantId: "tenant-a", roles: ["Admin"] },
|
|
84
|
+
{ tenantId: "tenant-b", roles: ["User"] },
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
renderWithProviders(<TenantSwitcher tenantName={(id) => `Tenant ${id}`} />, {
|
|
88
|
+
session,
|
|
89
|
+
});
|
|
90
|
+
await user.click(screen.getByRole("button", { name: /Tenant tenant-a/ }));
|
|
91
|
+
// Im Dropdown gibt's einen menuitemcheckbox für tenant-a (Radix-Role
|
|
92
|
+
// bei CheckboxItem) — nicht den Trigger erwischen, sondern den im
|
|
93
|
+
// role="menu".
|
|
94
|
+
const items = screen.getAllByRole("menuitemcheckbox");
|
|
95
|
+
const activeItem = items.find((el) => el.textContent?.includes("Tenant tenant-a"));
|
|
96
|
+
expect(activeItem).toBeDefined();
|
|
97
|
+
if (activeItem) await user.click(activeItem);
|
|
98
|
+
expect(session.switchTenant).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// Shared test setup für die Web-UI-Components. Mountet das Minimum
|
|
4
|
+
// an Provider-Tree den die Components zur Laufzeit voraussetzen
|
|
5
|
+
// (LocaleProvider mit Bundle, SessionContext mit injizierbarem Wert).
|
|
6
|
+
|
|
7
|
+
import type { LocaleResolver } from "@cosmicdrift/kumiko-headless";
|
|
8
|
+
import {
|
|
9
|
+
createStaticLocaleResolver,
|
|
10
|
+
LocaleProvider,
|
|
11
|
+
PrimitivesProvider,
|
|
12
|
+
} from "@cosmicdrift/kumiko-renderer";
|
|
13
|
+
import { defaultPrimitives } from "@cosmicdrift/kumiko-renderer-web";
|
|
14
|
+
import { render as _render, type RenderResult } from "@testing-library/react";
|
|
15
|
+
import type { ReactElement } from "react";
|
|
16
|
+
import { vi } from "vitest";
|
|
17
|
+
import { defaultTranslations } from "../../i18n";
|
|
18
|
+
import type { SessionApi, SessionState } from "../session";
|
|
19
|
+
import { SessionContext } from "../session";
|
|
20
|
+
|
|
21
|
+
// Stateless Resolver — module-level cached, weil renderWithProviders
|
|
22
|
+
// ihn pro Mount sonst neu konstruiert (~0.5ms × N Tests). Tests die
|
|
23
|
+
// einen *anderen* Locale brauchen, übergeben ihren eigenen Resolver
|
|
24
|
+
// über options.resolver.
|
|
25
|
+
const sharedDeResolver = createStaticLocaleResolver({ locale: "de" });
|
|
26
|
+
|
|
27
|
+
export type MakeSessionApiOptions = Partial<SessionState> & {
|
|
28
|
+
readonly login?: SessionApi["login"];
|
|
29
|
+
readonly logout?: SessionApi["logout"];
|
|
30
|
+
readonly switchTenant?: SessionApi["switchTenant"];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function makeSessionApi(overrides: MakeSessionApiOptions = {}): SessionApi {
|
|
34
|
+
const { login, logout, switchTenant, ...stateOverrides } = overrides;
|
|
35
|
+
const base: SessionState = {
|
|
36
|
+
status: "authenticated",
|
|
37
|
+
user: {
|
|
38
|
+
id: "test-user",
|
|
39
|
+
email: "user@example.com",
|
|
40
|
+
displayName: "Test User",
|
|
41
|
+
globalRoles: [],
|
|
42
|
+
},
|
|
43
|
+
activeTenantId: "tenant-1",
|
|
44
|
+
tenants: [{ tenantId: "tenant-1", roles: ["Admin"] }],
|
|
45
|
+
roles: ["Admin"],
|
|
46
|
+
...stateOverrides,
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
...base,
|
|
50
|
+
login: login ?? vi.fn<SessionApi["login"]>(async () => ({ ok: true })),
|
|
51
|
+
logout: logout ?? vi.fn<SessionApi["logout"]>(async () => {}),
|
|
52
|
+
switchTenant: switchTenant ?? vi.fn<SessionApi["switchTenant"]>(async () => {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function renderWithProviders(
|
|
57
|
+
ui: ReactElement,
|
|
58
|
+
options: {
|
|
59
|
+
readonly resolver?: LocaleResolver;
|
|
60
|
+
readonly session?: SessionApi;
|
|
61
|
+
} = {},
|
|
62
|
+
): RenderResult & { readonly session: SessionApi } {
|
|
63
|
+
const resolver = options.resolver ?? sharedDeResolver;
|
|
64
|
+
const session = options.session ?? makeSessionApi();
|
|
65
|
+
const result = _render(
|
|
66
|
+
<PrimitivesProvider value={defaultPrimitives}>
|
|
67
|
+
<LocaleProvider resolver={resolver} fallbackBundles={[defaultTranslations]}>
|
|
68
|
+
<SessionContext.Provider value={session}>{ui}</SessionContext.Provider>
|
|
69
|
+
</LocaleProvider>
|
|
70
|
+
</PrimitivesProvider>,
|
|
71
|
+
);
|
|
72
|
+
return { ...result, session };
|
|
73
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import { describe, expect, test } from "vitest";
|
|
5
|
+
import { UserMenu } from "../user-menu";
|
|
6
|
+
import { makeSessionApi, renderWithProviders } from "./test-utils";
|
|
7
|
+
|
|
8
|
+
// Radix-DropdownMenu reagiert auf pointerdown — fireEvent.click greift
|
|
9
|
+
// dort nicht. userEvent simuliert die volle Pointer-Sequenz und Radix
|
|
10
|
+
// öffnet sauber.
|
|
11
|
+
|
|
12
|
+
describe("UserMenu", () => {
|
|
13
|
+
test("renders nothing when user is null", () => {
|
|
14
|
+
const session = makeSessionApi({ status: "unauthenticated", user: null });
|
|
15
|
+
const { container } = renderWithProviders(<UserMenu />, { session });
|
|
16
|
+
expect(container.firstChild).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("shows displayName + initials when authenticated", () => {
|
|
20
|
+
const session = makeSessionApi({
|
|
21
|
+
user: { id: "u1", email: "alice@example.com", displayName: "Alice Wonder", globalRoles: [] },
|
|
22
|
+
});
|
|
23
|
+
renderWithProviders(<UserMenu />, { session });
|
|
24
|
+
// Avatar = "AW", Display-Name "Alice Wonder"
|
|
25
|
+
expect(screen.getByText("AW")).toBeTruthy();
|
|
26
|
+
expect(screen.getByText("Alice Wonder")).toBeTruthy();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("falls back to email-based initials when displayName empty", () => {
|
|
30
|
+
const session = makeSessionApi({
|
|
31
|
+
user: { id: "u1", email: "bob@example.com", displayName: "", globalRoles: [] },
|
|
32
|
+
});
|
|
33
|
+
renderWithProviders(<UserMenu />, { session });
|
|
34
|
+
// Trim "" → leerer displayName → fallback auf email → erste 2 Chars
|
|
35
|
+
expect(screen.getByText("BO")).toBeTruthy();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("opens dropdown on click and shows logout button", async () => {
|
|
39
|
+
const user = userEvent.setup();
|
|
40
|
+
const session = makeSessionApi();
|
|
41
|
+
renderWithProviders(<UserMenu />, { session });
|
|
42
|
+
await user.click(screen.getByRole("button", { name: /Test User/ }));
|
|
43
|
+
expect(screen.getByText("Abmelden")).toBeTruthy();
|
|
44
|
+
expect(screen.getByText("user@example.com")).toBeTruthy();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("logout-click triggers session.logout", async () => {
|
|
48
|
+
const user = userEvent.setup();
|
|
49
|
+
const session = makeSessionApi();
|
|
50
|
+
renderWithProviders(<UserMenu />, { session });
|
|
51
|
+
await user.click(screen.getByRole("button", { name: /Test User/ }));
|
|
52
|
+
await user.click(screen.getByText("Abmelden"));
|
|
53
|
+
expect(session.logout).toHaveBeenCalledOnce();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
4
|
+
import { VerifyEmailScreen } from "../verify-email-screen";
|
|
5
|
+
import { renderWithProviders } from "./test-utils";
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
vi.stubGlobal(
|
|
9
|
+
"fetch",
|
|
10
|
+
vi.fn(async () => new Response(null, { status: 200 })),
|
|
11
|
+
);
|
|
12
|
+
});
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
vi.unstubAllGlobals();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("VerifyEmailScreen", () => {
|
|
18
|
+
test("ohne Token → missing-token-Page", () => {
|
|
19
|
+
renderWithProviders(<VerifyEmailScreen />);
|
|
20
|
+
expect(screen.getByText(/enthält keinen Token/i)).toBeTruthy();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("mit Token + 200 → success-state nach auto-submit", async () => {
|
|
24
|
+
const fetchMock = vi.fn(async () => new Response(null, { status: 200 }));
|
|
25
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
26
|
+
|
|
27
|
+
renderWithProviders(<VerifyEmailScreen token="t-abc" />);
|
|
28
|
+
|
|
29
|
+
await waitFor(() => {
|
|
30
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
31
|
+
"/api/auth/verify-email",
|
|
32
|
+
expect.objectContaining({
|
|
33
|
+
method: "POST",
|
|
34
|
+
body: JSON.stringify({ token: "t-abc" }),
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
expect(screen.getByText("E-Mail bestätigt")).toBeTruthy();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("mit Token + 422 → error-state", async () => {
|
|
42
|
+
const errBody = JSON.stringify({
|
|
43
|
+
error: {
|
|
44
|
+
code: "invalid_verification_token",
|
|
45
|
+
details: { reason: "invalid_verification_token" },
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
vi.stubGlobal(
|
|
49
|
+
"fetch",
|
|
50
|
+
vi.fn(async () => new Response(errBody, { status: 422 })),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
renderWithProviders(<VerifyEmailScreen token="bad" />);
|
|
54
|
+
|
|
55
|
+
await waitFor(() => {
|
|
56
|
+
expect(screen.getByText("Bestätigung fehlgeschlagen")).toBeTruthy();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|