create-ailk 0.21.0 → 0.22.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/component-catalog.json +37 -3
- package/component-catalog.md +6 -5
- package/dist/add-module.js +9 -5
- package/dist/derive-architecture.d.ts +8 -1
- package/dist/derive-architecture.js +38 -3
- package/dist/design-package.js +7 -2
- package/dist/lib/env-catalog.js +5 -1
- package/dist/module-architecture.js +4 -2
- package/dist/programmatic.js +19 -5
- package/dist/site-chrome.d.ts +8 -0
- package/dist/site-chrome.js +17 -1
- package/dist/spoke-checkout.d.ts +39 -18
- package/dist/spoke-checkout.js +177 -236
- package/dist/surfaces.js +19 -14
- package/dist/sync-routes.js +3 -2
- package/dist/template-record.d.ts +4 -3
- package/dist/template-record.js +6 -3
- package/dist/topology.js +25 -1
- package/dist/update.js +5 -0
- package/package.json +3 -3
- package/site-templates/designs/minimalism/DESIGN.md +1 -1
- package/templates/apps/api/CLAUDE.md +15 -6
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +19 -0
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +149 -0
- package/templates/apps/api/src/lib/consent-store.ts +28 -0
- package/templates/apps/api/src/lib/flow-completion-store.ts +136 -0
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +38 -2
- package/templates/apps/api/src/lib/proposal-paid-store.ts +21 -4
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +14 -3
- package/templates/apps/api/src/middleware/auth.ts +13 -6
- package/templates/apps/api/src/openapi/spec.ts +30 -3
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +49 -0
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +41 -0
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +386 -0
- package/templates/apps/api/src/routes/consent/account.ts +266 -0
- package/templates/apps/api/src/routes/consent/index.ts +15 -7
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +36 -0
- package/templates/apps/api/src/routes/content/get-page.ts +4 -0
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +515 -0
- package/templates/apps/api/src/routes/leads/index.ts +161 -7
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +649 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +154 -19
- package/templates/apps/api/src/server.ts +28 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +1038 -118
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +389 -0
- package/templates/apps/api/src/services/checkout-buyer.ts +100 -4
- package/templates/apps/api/src/services/consent.ts +95 -0
- package/templates/apps/api/src/services/lead-routing.ts +337 -8
- package/templates/apps/api/src/services/magnet-completion.ts +242 -0
- package/templates/apps/api/src/services/person.ts +40 -8
- package/templates/apps/api/src/services/plan-offerings.ts +59 -0
- package/templates/apps/api/src/services/public-site-resolver.ts +8 -1
- package/templates/apps/web/.env.example +14 -0
- package/templates/apps/web/CLAUDE.md +120 -9
- package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +6 -2
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +5 -4
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +5 -1
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +2 -0
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +3 -14
- package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +2 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +5 -1
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +160 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/sign-in-card.test.tsx +12 -9
- package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +105 -2
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +36 -3
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +47 -1
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +60 -22
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +16 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +85 -31
- package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +18 -4
- package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +3 -1
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +7 -4
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +227 -207
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +17 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +143 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +4 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +385 -89
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +5 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +44 -5
- package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +68 -34
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +0 -8
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +39 -151
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-gate.ts +61 -0
- package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +143 -140
- package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +40 -6
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +68 -54
- package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +5 -1
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +293 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +115 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +146 -1
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +310 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +143 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +350 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +29 -3
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/checkout/__tests__/pages.test.tsx +139 -0
- package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +136 -0
- package/templates/apps/web/app/[locale]/checkout/thank-you/page.tsx +114 -0
- package/templates/apps/web/app/[locale]/checkout/unavailable/page.tsx +51 -0
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +17 -1
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +79 -4
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +41 -2
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +438 -31
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +7 -0
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +168 -57
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +26 -0
- package/templates/apps/web/app/[locale]/contact/page.tsx +49 -17
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +106 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.invite.test.tsx +257 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/account-invite.actions.test.ts +69 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/account-invite.actions.ts +35 -0
- package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/thanks/[slug]/AccountInviteLink.tsx +89 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/AccountInviteLink.test.tsx +104 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +114 -0
- package/templates/apps/web/app/api/auth/hub/authorize/route.ts +20 -3
- package/templates/apps/web/app/api/checkout/[offering]/route.ts +48 -0
- package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +120 -0
- package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +86 -0
- package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +44 -1
- package/templates/apps/web/app/api/hub-session/sign-in/route.ts +22 -3
- package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +92 -0
- package/templates/apps/web/app/api/webhooks/stripe/route.ts +31 -0
- package/templates/apps/web/auth.ts +28 -2
- package/templates/apps/web/lib/__tests__/account-invite.test.ts +63 -0
- package/templates/apps/web/lib/__tests__/checkout.test.ts +243 -0
- package/templates/apps/web/lib/__tests__/consent-policies.test.ts +23 -0
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +5 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +34 -17
- package/templates/apps/web/lib/account-invite.ts +37 -0
- package/templates/apps/web/lib/checkout-config.ts +27 -0
- package/templates/apps/web/lib/checkout.ts +93 -0
- package/templates/apps/web/lib/consent-policies.ts +22 -0
- package/templates/apps/web/lib/deck-page.ts +6 -0
- package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +111 -0
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +88 -32
- package/templates/apps/web/lib/prisma-for-auth.ts +8 -0
- package/templates/apps/web/lib/sign-up-agreements.ts +447 -0
- package/templates/apps/web/lib/site-chrome.ts +2 -0
- package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
- package/templates/apps/web/messages/en.json +24 -7
- package/templates/apps/web/package.json +1 -0
- package/templates/apps/web/src/lib/__tests__/lead-write.test.ts +187 -0
- package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -0
- package/templates/apps/web/src/lib/lead-write.ts +90 -0
- package/templates/apps/web/src/lib/rate-limit.ts +79 -0
- package/templates/database/CHANGELOG.md +38 -0
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +79 -0
- package/templates/database/__tests__/flow-completion-migration.test.ts +115 -0
- package/templates/database/__tests__/flow-completion.db.test.ts +136 -0
- package/templates/database/__tests__/seed.test.ts +34 -1
- package/templates/database/inbox/schema.prisma +17 -2
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +52 -0
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +27 -0
- package/templates/database/package.json +1 -1
- package/templates/database/seed.ts +34 -3
- package/versions.json +34 -34
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +0 -125
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
* (#8441): a sub-route of a settings path stays inside the shell.
|
|
27
27
|
* 8. One main landmark (#8441): every settings route and the Inbox, each
|
|
28
28
|
* rendered through this layout with its real page, draw exactly one.
|
|
29
|
+
* 9. The agreements interstitial (#8765 D7, AC-2, AC-3, AC-4): the layout
|
|
30
|
+
* reads `consent.accountAgreements`; `complete: false` renders the
|
|
31
|
+
* interstitial in place of the frame and the page; a read that fails
|
|
32
|
+
* (anything but a 2xx or a 404) fails closed to the registry's own
|
|
33
|
+
* agreements; an older hub's 404 renders the account as before.
|
|
29
34
|
*/
|
|
30
35
|
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
|
31
36
|
import type { ReactElement, ReactNode } from "react";
|
|
@@ -85,9 +90,19 @@ jest.mock("@working-theory/api-client", () => ({
|
|
|
85
90
|
requestForm: (...args: unknown[]) => mockMemberNotFound(...args),
|
|
86
91
|
},
|
|
87
92
|
hubSignIn: { me: jest.fn() },
|
|
93
|
+
// #8765 D7 — the interstitial's read; `complete: true` unless a case says otherwise.
|
|
94
|
+
consent: { accountAgreements: (...args: unknown[]) => mockAccountAgreements(...args) },
|
|
88
95
|
visitorForwardHeaders: () => null,
|
|
89
96
|
}));
|
|
90
97
|
|
|
98
|
+
const mockAccountAgreements = jest.fn();
|
|
99
|
+
const mockAcceptAction = jest.fn();
|
|
100
|
+
// eslint-disable-next-line no-restricted-syntax -- test-seam mock: the Server Action module is a "use server" boundary; the interstitial only passes it through.
|
|
101
|
+
jest.mock("../agreements-actions", () => ({
|
|
102
|
+
__esModule: true,
|
|
103
|
+
acceptAccountAgreementsAction: (...args: unknown[]) => mockAcceptAction(...args),
|
|
104
|
+
}));
|
|
105
|
+
|
|
91
106
|
const mockSignOut = jest.fn();
|
|
92
107
|
jest.mock("better-auth/react", () => ({
|
|
93
108
|
__esModule: true,
|
|
@@ -107,6 +122,8 @@ jest.mock("next/navigation", () => ({
|
|
|
107
122
|
}));
|
|
108
123
|
|
|
109
124
|
import { usePhoneTab } from "@working-theory/templates/account";
|
|
125
|
+
import { consentStatementText } from "@working-theory/ui/blocks";
|
|
126
|
+
import { CONTACT_FORM_TERMS_STATEMENT } from "@working-theory/validation";
|
|
110
127
|
|
|
111
128
|
import AccountLayout from "../layout";
|
|
112
129
|
import InboxPage from "../page";
|
|
@@ -127,6 +144,27 @@ const SESSION = {
|
|
|
127
144
|
},
|
|
128
145
|
};
|
|
129
146
|
|
|
147
|
+
/** One `account_terms` entry of `GET /v1/consent/account` (#8765 D5). */
|
|
148
|
+
function agreementsAnswer(entry: { acceptedVersion: number | null; current: boolean }) {
|
|
149
|
+
return {
|
|
150
|
+
ok: true,
|
|
151
|
+
body: {
|
|
152
|
+
agreements: [
|
|
153
|
+
{
|
|
154
|
+
id: "account_terms",
|
|
155
|
+
statement: CONTACT_FORM_TERMS_STATEMENT,
|
|
156
|
+
policyVersion: 2,
|
|
157
|
+
acceptedVersion: entry.acceptedVersion,
|
|
158
|
+
acceptedAt: entry.acceptedVersion === null ? null : "2026-09-01T00:00:00.000Z",
|
|
159
|
+
current: entry.current,
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
complete: entry.current,
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const COMPLETE = agreementsAnswer({ acceptedVersion: 2, current: true });
|
|
167
|
+
|
|
130
168
|
const NO_SUBSCRIPTION = {
|
|
131
169
|
ok: true,
|
|
132
170
|
body: { actionRequired: false, status: null, accessRevokesAt: null, portalAvailable: false },
|
|
@@ -169,6 +207,8 @@ beforeEach(() => {
|
|
|
169
207
|
mockSubscriptionMe.mockReset().mockResolvedValue(NO_SUBSCRIPTION);
|
|
170
208
|
mockMemberNotFound.mockReset().mockResolvedValue({ ok: false, status: 404, body: null });
|
|
171
209
|
mockSignOut.mockReset().mockResolvedValue({ data: {}, error: null });
|
|
210
|
+
mockAccountAgreements.mockReset().mockResolvedValue(COMPLETE);
|
|
211
|
+
mockAcceptAction.mockReset();
|
|
172
212
|
mockPush.mockReset();
|
|
173
213
|
mockRefresh.mockReset();
|
|
174
214
|
});
|
|
@@ -519,7 +559,7 @@ describe("the settings routes take the screen (#8430)", () => {
|
|
|
519
559
|
});
|
|
520
560
|
|
|
521
561
|
describe("one main landmark (#8441)", () => {
|
|
522
|
-
type Page = (props: { params: Promise<{ locale: string }> }) => Promise<ReactElement>;
|
|
562
|
+
type Page = (props: { params: Promise<{ locale: string }> }) => Promise<ReactElement | null>;
|
|
523
563
|
// Keyed by the list's own type, so a tab added to `SETTINGS_ROUTES` without
|
|
524
564
|
// its page here fails to compile. Billing loads with `import()`:
|
|
525
565
|
// architecture.yaml bans a static import of a relative billing/ path
|
|
@@ -575,3 +615,105 @@ describe("the shell marker (#8337 D4, AC-6)", () => {
|
|
|
575
615
|
expect(document.querySelector("[data-ailk-shell]")).not.toBeInTheDocument();
|
|
576
616
|
});
|
|
577
617
|
});
|
|
618
|
+
|
|
619
|
+
describe("the agreements interstitial (#8765 D7)", () => {
|
|
620
|
+
const PAGE_REFUSAL = (status: number) => ({
|
|
621
|
+
ok: false,
|
|
622
|
+
status,
|
|
623
|
+
body: { ok: false, error: { code: "x", message: "x" } },
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
function expectInterstitialAlone(title: string): void {
|
|
627
|
+
const page = screen.getByTestId("account-agreements-interstitial");
|
|
628
|
+
expect(within(page).getByRole("heading", { level: 1 })).toHaveTextContent(title);
|
|
629
|
+
expect(screen.queryByTestId("account-shell")).not.toBeInTheDocument();
|
|
630
|
+
expect(screen.queryByTestId("dashboard-shell")).not.toBeInTheDocument();
|
|
631
|
+
expect(screen.queryByTestId("account-child")).not.toBeInTheDocument();
|
|
632
|
+
expect(screen.queryByTestId("app-shell-aside")).not.toBeInTheDocument();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
it("reads the account-level agreements with the session's own init and locale", async () => {
|
|
636
|
+
await renderLayout("fr");
|
|
637
|
+
expect(mockAccountAgreements).toHaveBeenCalledWith(
|
|
638
|
+
{ headers: { cookie: "session=abc" } },
|
|
639
|
+
{ locale: "fr" },
|
|
640
|
+
);
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
it("complete: true → the frame and its children, and no interstitial (AC-3)", async () => {
|
|
644
|
+
await renderLayout();
|
|
645
|
+
expect(screen.getByTestId("account-shell")).toBeInTheDocument();
|
|
646
|
+
expect(screen.getByTestId("account-child")).toBeInTheDocument();
|
|
647
|
+
expect(screen.queryByTestId("account-agreements-interstitial")).not.toBeInTheDocument();
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
it("complete: false → the interstitial in place of the frame and the page (AC-2)", async () => {
|
|
651
|
+
mockAccountAgreements.mockResolvedValue(agreementsAnswer({ acceptedVersion: null, current: false }));
|
|
652
|
+
await renderLayout();
|
|
653
|
+
expectInterstitialAlone("Please accept our terms");
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
it("Accept calls the action once with every id, then refreshes (AC-2)", async () => {
|
|
657
|
+
mockAccountAgreements.mockResolvedValue(agreementsAnswer({ acceptedVersion: null, current: false }));
|
|
658
|
+
mockAcceptAction.mockResolvedValue({ ok: true });
|
|
659
|
+
await renderLayout();
|
|
660
|
+
fireEvent.click(screen.getByTestId("account-agreements-accept"));
|
|
661
|
+
await waitFor(() => expect(mockRefresh).toHaveBeenCalledTimes(1));
|
|
662
|
+
expect(mockAcceptAction).toHaveBeenCalledTimes(1);
|
|
663
|
+
expect(mockAcceptAction).toHaveBeenCalledWith(["account_terms"]);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
it("an older accepted version → 'We've updated our terms' (AC-4)", async () => {
|
|
667
|
+
mockAccountAgreements.mockResolvedValue(agreementsAnswer({ acceptedVersion: 1, current: false }));
|
|
668
|
+
await renderLayout();
|
|
669
|
+
expectInterstitialAlone("We've updated our terms");
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it("gates the settings routes too — no account page loads behind it", async () => {
|
|
673
|
+
mockPathname = "/account/general";
|
|
674
|
+
mockAccountAgreements.mockResolvedValue(agreementsAnswer({ acceptedVersion: null, current: false }));
|
|
675
|
+
await renderLayout();
|
|
676
|
+
expectInterstitialAlone("Please accept our terms");
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
describe("fails closed (SEC-8765-C)", () => {
|
|
680
|
+
let errorSpy: jest.SpyInstance;
|
|
681
|
+
beforeEach(() => {
|
|
682
|
+
errorSpy = jest.spyOn(console, "error").mockImplementation(() => undefined);
|
|
683
|
+
});
|
|
684
|
+
afterEach(() => errorSpy.mockRestore());
|
|
685
|
+
|
|
686
|
+
it.each([
|
|
687
|
+
["a 503", () => mockAccountAgreements.mockResolvedValue(PAGE_REFUSAL(503))],
|
|
688
|
+
["a 429", () => mockAccountAgreements.mockResolvedValue(PAGE_REFUSAL(429))],
|
|
689
|
+
["a 401", () => mockAccountAgreements.mockResolvedValue(PAGE_REFUSAL(401))],
|
|
690
|
+
["a 403", () => mockAccountAgreements.mockResolvedValue(PAGE_REFUSAL(403))],
|
|
691
|
+
["a thrown read", () => mockAccountAgreements.mockRejectedValue(new Error("down"))],
|
|
692
|
+
])("%s → the interstitial from the registry, with no account behind it", async (_label, arrange) => {
|
|
693
|
+
arrange();
|
|
694
|
+
await renderLayout();
|
|
695
|
+
expectInterstitialAlone("Please accept our terms");
|
|
696
|
+
const statement = screen.getByTestId("account-agreements-statement-account_terms");
|
|
697
|
+
expect(statement.textContent).toBe(consentStatementText(CONTACT_FORM_TERMS_STATEMENT));
|
|
698
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
699
|
+
expect.any(String),
|
|
700
|
+
{ event: "account-agreements", outcome: "read_failed" },
|
|
701
|
+
);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it("an older hub's 404 → the account as today, and no log line", async () => {
|
|
705
|
+
mockAccountAgreements.mockResolvedValue(PAGE_REFUSAL(404));
|
|
706
|
+
await renderLayout();
|
|
707
|
+
expect(screen.getByTestId("account-shell")).toBeInTheDocument();
|
|
708
|
+
expect(screen.getByTestId("account-child")).toBeInTheDocument();
|
|
709
|
+
expect(screen.queryByTestId("account-agreements-interstitial")).not.toBeInTheDocument();
|
|
710
|
+
expect(errorSpy).not.toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ event: "account-agreements" }));
|
|
711
|
+
});
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
it("with no session there is no read", async () => {
|
|
715
|
+
mockGetSession.mockResolvedValue(null);
|
|
716
|
+
await renderLayout();
|
|
717
|
+
expect(mockAccountAgreements).not.toHaveBeenCalled();
|
|
718
|
+
});
|
|
719
|
+
});
|
|
@@ -75,6 +75,10 @@ jest.mock("@working-theory/api-client", () => ({
|
|
|
75
75
|
meetingTypes: (...args: unknown[]) => mockMemberMeetingTypes(...args),
|
|
76
76
|
requestForm: (...args: unknown[]) => mockMemberRequestForm(...args),
|
|
77
77
|
},
|
|
78
|
+
// #8765 D7 — the layout's agreements read: complete, so the frame renders.
|
|
79
|
+
consent: {
|
|
80
|
+
accountAgreements: async () => ({ ok: true, body: { agreements: [], complete: true } }),
|
|
81
|
+
},
|
|
78
82
|
}));
|
|
79
83
|
|
|
80
84
|
jest.mock("better-auth/react", () => ({
|