proxy-auth-cookie-passthrough.test.ts 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import { describe, expect, it, vi } from "vitest";
  2. // Hoist mocks before imports -- mock transitive dependencies to avoid
  3. // next-intl pulling in next/navigation (not resolvable in vitest)
  4. const mockIntlMiddleware = vi.hoisted(() => vi.fn());
  5. vi.mock("next-intl/middleware", () => ({
  6. default: () => mockIntlMiddleware,
  7. }));
  8. vi.mock("@/i18n/routing", () => ({
  9. routing: {
  10. locales: ["zh-CN", "en"],
  11. defaultLocale: "zh-CN",
  12. },
  13. }));
  14. vi.mock("@/lib/config/env.schema", () => ({
  15. isDevelopment: () => false,
  16. }));
  17. vi.mock("@/lib/logger", () => ({
  18. logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
  19. }));
  20. function makeRequest(pathname: string, cookies: Record<string, string> = {}) {
  21. const url = new URL(`http://localhost:13500${pathname}`);
  22. return {
  23. method: "GET",
  24. nextUrl: { pathname, clone: () => url },
  25. cookies: {
  26. get: (name: string) => (name in cookies ? { name, value: cookies[name] } : undefined),
  27. },
  28. headers: new Headers(),
  29. } as unknown as import("next/server").NextRequest;
  30. }
  31. describe("proxy auth cookie passthrough", () => {
  32. it("redirects to login when no auth cookie is present", async () => {
  33. const localeResponse = new Response(null, { status: 200 });
  34. mockIntlMiddleware.mockReturnValue(localeResponse);
  35. const { default: proxyHandler } = await import("@/proxy");
  36. const response = proxyHandler(makeRequest("/zh-CN/dashboard"));
  37. expect(response.status).toBeGreaterThanOrEqual(300);
  38. expect(response.status).toBeLessThan(400);
  39. const location = response.headers.get("location");
  40. expect(location).toContain("/login");
  41. expect(location).toContain("from=");
  42. });
  43. it("passes through when auth cookie exists without deleting it", async () => {
  44. const localeResponse = new Response(null, {
  45. status: 200,
  46. headers: { "x-test": "locale-response" },
  47. });
  48. mockIntlMiddleware.mockReturnValue(localeResponse);
  49. const { default: proxyHandler } = await import("@/proxy");
  50. const response = proxyHandler(
  51. makeRequest("/zh-CN/dashboard", { "auth-token": "sid_test-session-id" })
  52. );
  53. // Should return the locale response, not a redirect
  54. expect(response.headers.get("x-test")).toBe("locale-response");
  55. // Should NOT have a Set-Cookie header that deletes the auth cookie
  56. const setCookie = response.headers.get("set-cookie");
  57. expect(setCookie).toBeNull();
  58. });
  59. it("allows public paths without any cookie", async () => {
  60. const localeResponse = new Response(null, {
  61. status: 200,
  62. headers: { "x-test": "public-ok" },
  63. });
  64. mockIntlMiddleware.mockReturnValue(localeResponse);
  65. const { default: proxyHandler } = await import("@/proxy");
  66. const response = proxyHandler(makeRequest("/zh-CN/login"));
  67. expect(response.headers.get("x-test")).toBe("public-ok");
  68. });
  69. });