forked from baron/baron-sso
Merge branch 'dev' into feature/rbac-simplification-and-remove-dev-switcher
This commit is contained in:
88
devfront/src/lib/apiClient.test.ts
Normal file
88
devfront/src/lib/apiClient.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
76
devfront/src/lib/oidcStorage.test.ts
Normal file
76
devfront/src/lib/oidcStorage.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
33
devfront/src/lib/role.test.ts
Normal file
33
devfront/src/lib/role.test.ts
Normal 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("");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user