Files
wehub-resource-sync 4cddfcf2f3
Backend Tests / Tests (other) (push) Failing after 1s
Backend Tests / Static Checks (push) Failing after 2s
Backend Tests / Tests (internal) (push) Failing after 2s
Backend Tests / Tests (server) (push) Failing after 1s
Backend Tests / Tests (store) (push) Failing after 1s
Build Canary Image / build-frontend (push) Failing after 1s
Frontend Tests / Lint (push) Failing after 1s
Build Canary Image / build-push (linux/amd64) (push) Has been skipped
Build Canary Image / build-push (linux/arm64) (push) Has been skipped
Build Canary Image / merge (push) Has been skipped
Frontend Tests / Build (push) Failing after 1s
Release Please / release-please (push) Failing after 0s
Proto Linter / Lint Protos (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:02:24 +08:00

116 lines
3.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("@/auth-state", () => ({
clearAccessToken: vi.fn(),
}));
import { clearAccessToken } from "@/auth-state";
import { redirectOnAuthFailure } from "@/utils/auth-redirect";
const mockedClearAccessToken = vi.mocked(clearAccessToken);
type NavigationStub = { replace: ReturnType<typeof vi.fn>; href: string };
function installLocation(href: string): NavigationStub {
const url = new URL(href);
const replace = vi.fn((next: string) => {
// Mirror real navigation: update the mutable href on subsequent inspection.
location.href = new URL(next, url).toString();
});
const location: NavigationStub = { replace, href: url.toString() };
Object.defineProperty(window, "location", {
configurable: true,
value: {
get href() {
return location.href;
},
set href(value: string) {
location.href = value;
},
pathname: url.pathname,
search: url.search,
hash: url.hash,
origin: url.origin,
replace,
},
});
return location;
}
describe("redirectOnAuthFailure", () => {
let originalLocation: Location;
beforeEach(() => {
originalLocation = window.location;
});
afterEach(() => {
Object.defineProperty(window, "location", {
configurable: true,
value: originalLocation,
});
});
it("does nothing when the user is already on an /auth page", () => {
const nav = installLocation("http://localhost/auth?foo=bar");
redirectOnAuthFailure();
expect(nav.replace).not.toHaveBeenCalled();
expect(mockedClearAccessToken).not.toHaveBeenCalled();
});
it("does nothing on a public route by default", () => {
const nav = installLocation("http://localhost/explore");
redirectOnAuthFailure();
expect(nav.replace).not.toHaveBeenCalled();
expect(mockedClearAccessToken).not.toHaveBeenCalled();
});
it("clears the token and redirects to /auth on a protected route", () => {
const nav = installLocation("http://localhost/home?tab=pins#latest");
redirectOnAuthFailure();
expect(mockedClearAccessToken).toHaveBeenCalledTimes(1);
expect(nav.replace).toHaveBeenCalledWith("/auth?redirect=%2Fhome%3Ftab%3Dpins%23latest");
});
it("honours forceRedirect even on a public route", () => {
const nav = installLocation("http://localhost/explore");
redirectOnAuthFailure(true);
expect(mockedClearAccessToken).toHaveBeenCalledTimes(1);
expect(nav.replace).toHaveBeenCalledWith("/auth?redirect=%2Fexplore");
});
it("embeds the reason parameter when provided", () => {
const nav = installLocation("http://localhost/home");
redirectOnAuthFailure(false, { reason: "protected-memo" });
expect(nav.replace).toHaveBeenCalledWith("/auth?redirect=%2Fhome&reason=protected-memo");
});
it("prefers an explicitly provided redirect target over the current location", () => {
const nav = installLocation("http://localhost/home");
redirectOnAuthFailure(false, { redirect: "/setting" });
expect(nav.replace).toHaveBeenCalledWith("/auth?redirect=%2Fsetting");
});
it("drops an unsafe redirect target silently", () => {
const nav = installLocation("http://localhost/home");
redirectOnAuthFailure(false, { redirect: "//evil.example/phish" });
expect(nav.replace).toHaveBeenCalledWith("/auth");
});
});