Merge pull request #1993 from Molecule-AI/fix/auth-redirect-loop-regression-tests

test(auth): add regression tests for redirect loop guards
This commit is contained in:
Hongming Wang 2026-04-24 06:57:12 +00:00 committed by GitHub
commit 0ef5dad1b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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);
});
});