254 lines
6.3 KiB
TypeScript
254 lines
6.3 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import type { ClientSummary, DevAuditLog } from "../../lib/devApi";
|
|
import {
|
|
buildRecentClientChangeDetails,
|
|
buildRecentClientChanges,
|
|
getRecentClientActionLabel,
|
|
} from "./recentClientChanges";
|
|
|
|
function makeClient(id: string, name = id): ClientSummary {
|
|
return {
|
|
id,
|
|
name,
|
|
type: "private",
|
|
status: "active",
|
|
createdAt: "2026-05-27T00:00:00.000Z",
|
|
redirectUris: [],
|
|
scopes: [],
|
|
};
|
|
}
|
|
|
|
function makeAuditLog(
|
|
eventId: string,
|
|
timestamp: string,
|
|
action: string,
|
|
targetId: string,
|
|
details: Record<string, unknown>,
|
|
): DevAuditLog {
|
|
return {
|
|
event_id: eventId,
|
|
timestamp,
|
|
user_id: "actor-1",
|
|
event_type: "AUDIT",
|
|
status: "success",
|
|
ip_address: "127.0.0.1",
|
|
user_agent: "vitest",
|
|
details: JSON.stringify({
|
|
action,
|
|
target_id: targetId,
|
|
...details,
|
|
}),
|
|
};
|
|
}
|
|
|
|
describe("recent client changes", () => {
|
|
beforeEach(() => {
|
|
window.localStorage.clear();
|
|
window.history.replaceState({}, "", "/");
|
|
});
|
|
|
|
function mockLocale(locale: "ko" | "en") {
|
|
window.localStorage.clear();
|
|
window.history.replaceState({}, "", `/${locale}`);
|
|
}
|
|
|
|
it("translates action labels and relation details by locale", () => {
|
|
mockLocale("en");
|
|
|
|
expect(getRecentClientActionLabel("CREATE_CLIENT")).toBe("App creation");
|
|
expect(getRecentClientActionLabel("UPDATE_CLIENT")).toBe(
|
|
"Settings changes",
|
|
);
|
|
expect(getRecentClientActionLabel("UPDATE_CLIENT_STATUS")).toBe(
|
|
"Status changes",
|
|
);
|
|
expect(getRecentClientActionLabel("ROTATE_SECRET")).toBe(
|
|
"Client secret rotation",
|
|
);
|
|
expect(getRecentClientActionLabel("ADD_RELATION")).toBe("Add Relationship");
|
|
expect(getRecentClientActionLabel("REMOVE_RELATION")).toBe(
|
|
"Remove Relationship",
|
|
);
|
|
expect(getRecentClientActionLabel("DELETE_CLIENT")).toBe("App deletion");
|
|
expect(getRecentClientActionLabel("OTHER_ACTION")).toBe("OTHER_ACTION");
|
|
|
|
expect(
|
|
buildRecentClientChangeDetails("ROTATE_SECRET", {
|
|
after: {},
|
|
}),
|
|
).toEqual([{ label: "Client Secret", value: "Secret Rotated" }]);
|
|
|
|
expect(
|
|
buildRecentClientChangeDetails("ADD_RELATION", {
|
|
after: {
|
|
relation: "admins",
|
|
subject: "User:1",
|
|
},
|
|
}),
|
|
).toEqual([
|
|
{ label: "Relation", value: "admins" },
|
|
{ label: "Subject", value: "User:1" },
|
|
]);
|
|
});
|
|
|
|
it("builds recent client changes with sorting, filtering, and detail slicing", () => {
|
|
mockLocale("ko");
|
|
|
|
const clients = [
|
|
makeClient("client-a", "Alpha"),
|
|
makeClient("client-b", ""),
|
|
];
|
|
const auditLogs = [
|
|
makeAuditLog(
|
|
"evt-1",
|
|
"2026-05-27T07:00:00.000Z",
|
|
"CREATE_CLIENT",
|
|
"client-a",
|
|
{
|
|
after: { name: "Alpha", type: "private", status: "active" },
|
|
},
|
|
),
|
|
makeAuditLog(
|
|
"evt-2",
|
|
"2026-05-27T08:00:00.000Z",
|
|
"UPDATE_CLIENT",
|
|
"client-a",
|
|
{
|
|
before: {
|
|
name: "Alpha old",
|
|
status: "inactive",
|
|
sameField: "same",
|
|
oldField: "old-value",
|
|
},
|
|
after: {
|
|
name: "Alpha new",
|
|
status: "active",
|
|
sameField: "same",
|
|
newField: "new-value",
|
|
},
|
|
},
|
|
),
|
|
makeAuditLog(
|
|
"evt-3",
|
|
"2026-05-27T09:00:00.000Z",
|
|
"UPDATE_CLIENT_STATUS",
|
|
"client-a",
|
|
{
|
|
before: { status: "inactive" },
|
|
after: { status: "active" },
|
|
},
|
|
),
|
|
makeAuditLog(
|
|
"evt-4",
|
|
"2026-05-27T10:00:00.000Z",
|
|
"ADD_RELATION",
|
|
"client-b",
|
|
{
|
|
after: {
|
|
relation: "audit_viewer",
|
|
subject: "User:89692983-f512-4d96-845d-ac6123d08b95",
|
|
},
|
|
},
|
|
),
|
|
makeAuditLog(
|
|
"evt-5",
|
|
"2026-05-27T11:00:00.000Z",
|
|
"REMOVE_RELATION",
|
|
"client-b",
|
|
{
|
|
before: {
|
|
relation: "admins",
|
|
subject: "User:89692983-f512-4d96-845d-ac6123d08b95",
|
|
},
|
|
},
|
|
),
|
|
makeAuditLog(
|
|
"evt-6",
|
|
"2026-05-27T12:00:00.000Z",
|
|
"ROTATE_SECRET",
|
|
"client-a",
|
|
{
|
|
after: {},
|
|
},
|
|
),
|
|
makeAuditLog(
|
|
"evt-7",
|
|
"2026-05-27T13:00:00.000Z",
|
|
"DELETE_CLIENT",
|
|
"client-a",
|
|
{
|
|
before: {
|
|
name: "Alpha",
|
|
status: "inactive",
|
|
},
|
|
},
|
|
),
|
|
makeAuditLog(
|
|
"evt-8",
|
|
"2026-05-27T14:00:00.000Z",
|
|
"UNSUPPORTED_ACTION",
|
|
"client-a",
|
|
{
|
|
after: { name: "Ignored" },
|
|
},
|
|
),
|
|
];
|
|
|
|
const changes = buildRecentClientChanges(auditLogs, clients);
|
|
|
|
expect(changes).toHaveLength(7);
|
|
expect(changes[0]).toMatchObject({
|
|
eventId: "evt-7",
|
|
clientName: "Alpha",
|
|
actionLabel: "앱 삭제",
|
|
});
|
|
expect(changes[1]).toMatchObject({
|
|
eventId: "evt-6",
|
|
clientName: "Alpha",
|
|
actionLabel: "클라이언트 시크릿 재발급",
|
|
detailLabels: [
|
|
{
|
|
label: "클라이언트 시크릿",
|
|
value: "Client Secret이 재발급되었습니다.",
|
|
},
|
|
],
|
|
});
|
|
expect(changes[2]).toMatchObject({
|
|
eventId: "evt-5",
|
|
clientName: "client-b",
|
|
actionLabel: "관계 삭제",
|
|
detailLabels: [
|
|
{ label: "관계", value: "admins" },
|
|
{
|
|
label: "주체",
|
|
value: "User:89692983-f512-4d96-845d-ac6123d08b95",
|
|
},
|
|
],
|
|
});
|
|
expect(changes[4]).toMatchObject({
|
|
eventId: "evt-3",
|
|
actionLabel: "상태 변경",
|
|
clientName: "Alpha",
|
|
detailLabels: [{ value: "inactive → active" }],
|
|
});
|
|
expect(changes[5]).toMatchObject({
|
|
eventId: "evt-2",
|
|
actionLabel: "설정 변경",
|
|
detailLabels: [
|
|
{ label: "애플리케이션", value: "Alpha old → Alpha new" },
|
|
{ label: "상태", value: "inactive → active" },
|
|
{ label: "oldField", value: "old-value" },
|
|
],
|
|
});
|
|
expect(changes[6]).toMatchObject({
|
|
eventId: "evt-1",
|
|
actionLabel: "앱 생성",
|
|
detailLabels: [
|
|
{ label: "애플리케이션", value: "Alpha" },
|
|
{ label: "유형", value: "private" },
|
|
{ label: "상태", value: "active" },
|
|
],
|
|
});
|
|
});
|
|
});
|