1
0
forked from baron/baron-sso

devfront 테스트 커버리지 추가 보강

This commit is contained in:
2026-06-01 17:37:13 +09:00
parent a4d457073a
commit 38605ac8a3
13 changed files with 1513 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const getUserMock = vi.fn();
const findPersistedOidcUserMock = vi.fn();
const removeUserMock = vi.fn();
const shouldStartLoginRedirectMock = vi.fn();
const shouldSuppressDevelopmentSessionRedirectMock = vi.fn();
vi.mock("./auth", () => ({
userManager: {
getUser: (...args: unknown[]) => getUserMock(...args),
removeUser: (...args: unknown[]) => removeUserMock(...args),
},
}));
vi.mock("./oidcStorage", () => ({
findPersistedOidcUser: (...args: unknown[]) =>
findPersistedOidcUserMock(...args),
}));
vi.mock("../../../common/core/auth", () => ({
shouldStartLoginRedirect: (...args: unknown[]) =>
shouldStartLoginRedirectMock(...args),
}));
vi.mock("../../../common/core/session", () => ({
shouldSuppressDevelopmentSessionRedirect: (...args: unknown[]) =>
shouldSuppressDevelopmentSessionRedirectMock(...args),
}));
describe("apiClient", () => {
beforeEach(() => {
vi.resetModules();
vi.stubEnv("MODE", "test");
(window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean })._IS_TEST_MODE =
true;
window.localStorage.clear();
getUserMock.mockResolvedValue(null);
findPersistedOidcUserMock.mockReturnValue(undefined);
removeUserMock.mockResolvedValue(undefined);
shouldStartLoginRedirectMock.mockReturnValue(true);
shouldSuppressDevelopmentSessionRedirectMock.mockReturnValue(false);
});
it("injects authorization and tenant headers into requests", async () => {
getUserMock.mockResolvedValueOnce({ access_token: "live-token" });
window.localStorage.setItem("dev_tenant_id", "tenant-1");
const { default: apiClient } = await import("./apiClient");
const requestHandler = apiClient.interceptors.request.handlers[0]?.fulfilled;
const result = await requestHandler?.({ headers: {} });
expect(result.headers.Authorization).toBe("Bearer live-token");
expect(result.headers["X-Tenant-ID"]).toBe("tenant-1");
});
it("rejects non-auth response errors without redirecting", async () => {
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
const { default: apiClient } = await import("./apiClient");
const responseHandler = apiClient.interceptors.response.handlers[0]?.rejected;
const error = { response: { status: 500, data: { error: "boom" } } };
await expect(responseHandler?.(error)).rejects.toBe(error);
expect(warnSpy).not.toHaveBeenCalled();
expect(removeUserMock).not.toHaveBeenCalled();
});
it("warns and rejects auth failures in test mode", async () => {
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
const { default: apiClient } = await import("./apiClient");
const responseHandler = apiClient.interceptors.response.handlers[0]?.rejected;
const error = {
response: {
status: 403,
data: { error: "authentication required" },
},
};
await expect(responseHandler?.(error)).rejects.toBe(error);
expect(warnSpy).toHaveBeenCalled();
expect(removeUserMock).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,76 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { findPersistedOidcUser } from "./oidcStorage";
class MemoryStorage implements Storage {
private data = new Map<string, string>();
get length() {
return this.data.size;
}
clear(): void {
this.data.clear();
}
getItem(key: string): string | null {
return this.data.get(key) ?? null;
}
key(index: number): string | null {
return Array.from(this.data.keys())[index] ?? null;
}
removeItem(key: string): void {
this.data.delete(key);
}
setItem(key: string, value: string): void {
this.data.set(key, value);
}
}
describe("findPersistedOidcUser", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-06-01T00:00:00.000Z"));
});
afterEach(() => {
vi.useRealTimers();
});
it("returns the first valid, unexpired devfront user entry", () => {
const storage = new MemoryStorage();
storage.setItem("oidc.user:issuer:other-client", JSON.stringify({}));
const expiresAt = Math.floor(Date.now() / 1000) + 3600;
storage.setItem(
"oidc.user:issuer:devfront",
JSON.stringify({
access_token: "token-1",
expires_at: expiresAt,
profile: { name: "Dev Admin" },
}),
);
expect(findPersistedOidcUser(storage)).toEqual({
access_token: "token-1",
expires_at: expiresAt,
profile: { name: "Dev Admin" },
});
});
it("skips malformed, empty, and expired entries", () => {
const storage = new MemoryStorage();
storage.setItem("random", "value");
storage.setItem("oidc.user:issuer:devfront", "not-json");
storage.setItem(
"oidc.user:issuer:devfront",
JSON.stringify({
access_token: "expired",
expires_at: Math.floor(Date.now() / 1000) - 1,
}),
);
expect(findPersistedOidcUser(storage)).toBeNull();
});
});

View File

@@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import { normalizeRole, resolveProfileRole } from "./role";
describe("normalizeRole", () => {
it("normalizes known role aliases", () => {
expect(normalizeRole("tenant_member")).toBe("user");
expect(normalizeRole("admin")).toBe("tenant_admin");
expect(normalizeRole("superadmin")).toBe("super_admin");
expect(normalizeRole("tenantadmin")).toBe("tenant_admin");
expect(normalizeRole("rpadmin")).toBe("rp_admin");
});
it("returns a trimmed lowercase role for unknown values", () => {
expect(normalizeRole(" custom_role ")).toBe("custom_role");
expect(normalizeRole(123)).toBe("");
});
});
describe("resolveProfileRole", () => {
it("prefers the first non-empty normalized role candidate", () => {
expect(
resolveProfileRole({
role: " ",
grade: "tenant_member",
"custom:role": "admin",
}),
).toBe("user");
});
it("returns an empty string when no role is present", () => {
expect(resolveProfileRole(undefined)).toBe("");
});
});