import { describe, expect, it, vi } from "vitest"; import { parseRuntimeConfig } from "@/shared/config/env"; import { initializeAuthentication, toAuthenticatedUser, toKeycloakConfig } from "./keycloak-auth"; const requiredConfig = parseRuntimeConfig({ TJWATER_AUTH_MODE: "required", TJWATER_KEYCLOAK_ISSUER: "https://auth.example.test/auth/realms/tjwater", TJWATER_KEYCLOAK_CLIENT_ID: "next-tjwater" }); describe("Keycloak authentication", () => { it("derives the Keycloak server and Realm from the issuer", () => { expect(toKeycloakConfig(requiredConfig.TJWATER_KEYCLOAK_ISSUER, "next-tjwater")).toEqual({ url: "https://auth.example.test/auth", realm: "tjwater", clientId: "next-tjwater" }); }); it("initializes the public SPA flow and exposes the refreshed token", async () => { const client = { token: "access-token", idTokenParsed: { sub: "user-1", name: "张调度", email: "operator@example.test" }, init: vi.fn().mockResolvedValue(true), login: vi.fn().mockResolvedValue(undefined), logout: vi.fn().mockResolvedValue(undefined), updateToken: vi.fn().mockResolvedValue(false) }; const authentication = await initializeAuthentication(requiredConfig, () => client); expect(client.init).toHaveBeenCalledWith({ onLoad: "login-required", flow: "standard", pkceMethod: "S256", checkLoginIframe: false }); expect(authentication.user).toEqual({ name: "张调度", role: "operator@example.test" }); await expect(authentication.getAccessToken()).resolves.toBe("access-token"); expect(client.updateToken).toHaveBeenCalledWith(30); await authentication.logout?.(); expect(client.logout).toHaveBeenCalledWith({ redirectUri: "http://localhost:3000/" }); }); it("uses stable user claim fallbacks", () => { expect(toAuthenticatedUser({ sub: "user-2", preferred_username: "dispatcher" })).toEqual({ name: "dispatcher", role: "统一认证用户" }); }); it("returns to Keycloak instead of using a token after refresh failure", async () => { const refreshError = new Error("refresh failed"); const client = { token: "expired-token", tokenParsed: { sub: "user-3" }, init: vi.fn().mockResolvedValue(true), login: vi.fn().mockResolvedValue(undefined), logout: vi.fn().mockResolvedValue(undefined), updateToken: vi.fn().mockRejectedValue(refreshError) }; const authentication = await initializeAuthentication(requiredConfig, () => client); await expect(authentication.getAccessToken()).rejects.toBe(refreshError); expect(client.login).toHaveBeenCalledWith({ redirectUri: "http://localhost:3000/" }); }); it("keeps authentication inert when explicitly disabled", async () => { const createClient = vi.fn(); const authentication = await initializeAuthentication( parseRuntimeConfig({ TJWATER_AUTH_MODE: "disabled" }), createClient ); expect(authentication.enabled).toBe(false); await expect(authentication.getAccessToken()).resolves.toBeNull(); expect(createClient).not.toHaveBeenCalled(); }); });