test(auth): add regression tests for redirect loop guards

AuthGate now skips session fetch for /cp/auth/* paths, and
redirectToLogin guards against re-setting window.location when
already on an auth path. Both guards had no test coverage —
a future refactor could silently reintroduce the redirect loop.

Added:
- AuthGate.test.tsx: 2 cases covering /cp/auth/login and
  /cp/auth/signup path skipping (no fetchSession call, no
  redirectToLogin call, children rendered)
- auth.test.ts: 2 cases covering redirectToLogin early return
  for /cp/auth/login and /cp/auth/signup paths

Fixes: Molecule-AI/molecule-core#1541

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Molecule AI · core-fe 2026-04-24 06:30:35 +00:00
parent e4e389950f
commit e9be12210f
2 changed files with 89 additions and 2 deletions

View File

@ -105,10 +105,64 @@ describe("AuthGate — authenticated state", () => {
});
});
describe("AuthGate — /cp/auth/* skip guard (redirect loop regression)", () => {
it("renders children without calling fetchSession or redirect when pathname starts with /cp/auth/", async () => {
mockGetTenantSlug.mockReturnValue("acme");
mockFetchSession.mockResolvedValue(null);
// Simulate being on the login page
Object.defineProperty(window, "location", {
writable: true,
value: { ...window.location, pathname: "/cp/auth/login" },
});
let result: ReturnType<typeof render>;
await act(async () => {
result = render(
<AuthGate>
<div data-testid="child">Protected content</div>
</AuthGate>
);
});
// Children should render — AuthGate skips session fetch for auth paths
expect(result!.getByTestId("child")).toBeTruthy();
expect(mockFetchSession).not.toHaveBeenCalled();
expect(mockRedirectToLogin).not.toHaveBeenCalled();
});
it("renders children without calling redirect for /cp/auth/signup path", async () => {
mockGetTenantSlug.mockReturnValue("acme");
mockFetchSession.mockResolvedValue(null);
Object.defineProperty(window, "location", {
writable: true,
value: { ...window.location, pathname: "/cp/auth/signup" },
});
let result: ReturnType<typeof render>;
await act(async () => {
result = render(
<AuthGate>
<div data-testid="child">Protected content</div>
</AuthGate>
);
});
expect(result!.getByTestId("child")).toBeTruthy();
expect(mockRedirectToLogin).not.toHaveBeenCalled();
});
});
describe("AuthGate — anonymous / redirect state", () => {
it("calls redirectToLogin when session fetch returns null", async () => {
mockGetTenantSlug.mockReturnValue("acme");
mockFetchSession.mockResolvedValue(null);
// Ensure pathname is NOT on /cp/auth/* so the redirect guard fires
Object.defineProperty(window, "location", {
writable: true,
value: { ...window.location, pathname: "/dashboard" },
});
await act(async () => {
render(

View File

@ -55,8 +55,6 @@ describe("redirectToLogin", () => {
},
});
redirectToLogin("sign-in");
// href now holds the redirect target. encodeURIComponent(href) must
// appear in the query.
expect((window.location as unknown as { href: string }).href).toContain("/cp/auth/login");
expect((window.location as unknown as { href: string }).href).toContain(
encodeURIComponent(href),
@ -76,4 +74,39 @@ describe("redirectToLogin", () => {
redirectToLogin("sign-up");
expect((window.location as unknown as { href: string }).href).toContain("/cp/auth/signup");
});
// Regression: AuthGate + redirectToLogin mutual recursion on /cp/auth/login
// caused double-encoded return_to that grew until the URL exceeded 431.
// Guard: redirectToLogin must NOT set window.location when already on an
// auth path, otherwise each call adds another encoding layer.
it("does NOT set window.location when already on /cp/auth/login (redirect loop guard)", () => {
const loginHref = "https://app.moleculesai.app/cp/auth/login?return_to=https%3A%2F%2Facme.moleculesai.app%2Fdashboard";
Object.defineProperty(window, "location", {
writable: true,
value: {
href: loginHref,
pathname: "/cp/auth/login",
hostname: "app.moleculesai.app",
protocol: "https:",
},
});
redirectToLogin("sign-in");
// href must be unchanged — any mutation means the guard is missing
expect((window.location as unknown as { href: string }).href).toBe(loginHref);
});
it("does NOT set window.location when already on /cp/auth/signup (redirect loop guard)", () => {
const signupHref = "https://app.moleculesai.app/cp/auth/signup";
Object.defineProperty(window, "location", {
writable: true,
value: {
href: signupHref,
pathname: "/cp/auth/signup",
hostname: "app.moleculesai.app",
protocol: "https:",
},
});
redirectToLogin("sign-up");
expect((window.location as unknown as { href: string }).href).toBe(signupHref);
});
});