Files
PolyWeather/frontend/components/auth/__tests__/passwordReset.test.ts
T
2026-06-05 16:50:52 +08:00

110 lines
3.7 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
function assert(condition: unknown, message: string) {
if (!condition) throw new Error(message);
}
export function runTests() {
const projectRoot = process.cwd();
const loginClientPath = path.join(
projectRoot,
"components",
"auth",
"LoginClient.tsx",
);
const resetPagePath = path.join(
projectRoot,
"app",
"auth",
"reset-password",
"page.tsx",
);
const resetClientPath = path.join(
projectRoot,
"components",
"auth",
"ResetPasswordClient.tsx",
);
const authCallbackPath = path.join(
projectRoot,
"app",
"auth",
"callback",
"route.ts",
);
const loginPagePath = path.join(
projectRoot,
"app",
"auth",
"login",
"page.tsx",
);
const loginClientSource = fs.readFileSync(loginClientPath, "utf8");
const loginPageSource = fs.readFileSync(loginPagePath, "utf8");
assert(
loginClientSource.includes("/auth/reset-password") &&
loginClientSource.indexOf("resetPasswordForEmail") <
loginClientSource.indexOf("/auth/reset-password"),
"forgot-password emails must land on a password reset page after auth callback",
);
assert(
fs.existsSync(resetPagePath),
"password reset page must exist at app/auth/reset-password/page.tsx",
);
assert(
fs.existsSync(resetClientPath),
"password reset page must use a dedicated client component",
);
assert(
loginClientSource.includes("loadingSpinner") &&
loginClientSource.includes("submittingLabel") &&
loginClientSource.includes("googleSubmittingLabel") &&
loginClientSource.includes('aria-busy={loading}') &&
loginClientSource.includes("animate-spin"),
"login submit and Google sign-in buttons must show a visible loading spinner and pending label",
);
const resetClientSource = fs.readFileSync(resetClientPath, "utf8");
assert(
resetClientSource.includes("supabase.auth.updateUser") &&
resetClientSource.includes("password"),
"password reset client must update the authenticated user's password",
);
assert(
resetClientSource.includes("getSession") &&
resetClientSource.includes("expired"),
"password reset client must detect an expired or invalid recovery session",
);
const authCallbackSource = fs.readFileSync(authCallbackPath, "utf8");
assert(
authCallbackSource.includes("warmSignupTrial") &&
authCallbackSource.includes("exchangeCodeForSession") &&
authCallbackSource.includes("/api/auth/me") &&
authCallbackSource.includes("Authorization") &&
authCallbackSource.includes("Bearer"),
"auth callback must warm /api/auth/me with the exchanged Supabase session so new users receive the signup trial immediately",
);
assert(
loginPageSource.includes("error?: string") &&
loginPageSource.includes("normalizeAuthError") &&
loginPageSource.includes("params.error") &&
loginPageSource.includes("initialError={initialError}") &&
loginClientSource.includes("initialError?: string") &&
loginClientSource.includes("useState(initialError || \"\")"),
"login page must surface auth callback errors in the login form instead of silently returning users to the terminal",
);
assert(
authCallbackSource.includes("redirectToLoginWithError") &&
authCallbackSource.includes('request.nextUrl.searchParams.get("error_description")') &&
authCallbackSource.includes('request.nextUrl.searchParams.get("error")') &&
authCallbackSource.includes("exchangeError") &&
authCallbackSource.includes("exchangeCodeForSession(code)") &&
authCallbackSource.includes("auth_error"),
"auth callback must redirect failed OAuth/session exchanges back to login with an error message",
);
}