import { afterEach, describe, expect, it, vi } from "vitest"; vi.mock("next/headers", () => ({ headers: async () => new Headers({ "x-forwarded-for": "203.0.113.7" }), cookies: async () => ({ get: () => undefined, set: () => {}, delete: () => {} }), })); import { getAdminLockState, isAdminAuthConfigured, isPasswordValid, registerFailedAdminAttempt, resetAdminFailedAttempts, } from "@/lib/admin-auth"; afterEach(() => { vi.unstubAllEnvs(); }); describe("admin auth configuration", () => { it("is configured only when both password and secret are set", () => { vi.stubEnv("ADMIN_PASSWORD", ""); vi.stubEnv("ADMIN_AUTH_SECRET", ""); expect(isAdminAuthConfigured()).toBe(false); vi.stubEnv("ADMIN_PASSWORD", "pw"); vi.stubEnv("ADMIN_AUTH_SECRET", ""); expect(isAdminAuthConfigured()).toBe(false); vi.stubEnv("ADMIN_PASSWORD", "pw"); vi.stubEnv("ADMIN_AUTH_SECRET", "secret"); expect(isAdminAuthConfigured()).toBe(true); }); }); describe("isPasswordValid", () => { it("returns false when auth is not configured", () => { vi.stubEnv("ADMIN_PASSWORD", ""); vi.stubEnv("ADMIN_AUTH_SECRET", ""); expect(isPasswordValid("anything")).toBe(false); }); it("accepts the correct password and rejects wrong ones", () => { vi.stubEnv("ADMIN_PASSWORD", "s3cret-password"); vi.stubEnv("ADMIN_AUTH_SECRET", "hmac-secret"); expect(isPasswordValid("s3cret-password")).toBe(true); expect(isPasswordValid("wrong")).toBe(false); expect(isPasswordValid("s3cret-passwordX")).toBe(false); // length mismatch }); }); describe("login lockout", () => { it("locks the IP after the failed-attempt threshold", async () => { expect((await getAdminLockState()).locked).toBe(false); for (let i = 0; i < 4; i += 1) { const state = await registerFailedAdminAttempt(); expect(state.locked).toBe(false); } expect((await getAdminLockState()).locked).toBe(false); const fifth = await registerFailedAdminAttempt(); expect(fifth.locked).toBe(true); expect(fifth.remainingSeconds).toBeGreaterThan(0); const lockState = await getAdminLockState(); expect(lockState.locked).toBe(true); expect(lockState.remainingSeconds).toBeGreaterThan(0); expect(lockState.remainingSeconds).toBeLessThanOrEqual(15 * 60); }); it("clears the lock on reset", async () => { for (let i = 0; i < 5; i += 1) { await registerFailedAdminAttempt(); } expect((await getAdminLockState()).locked).toBe(true); await resetAdminFailedAttempts(); expect((await getAdminLockState()).locked).toBe(false); }); });