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, ): 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" }, ], }); }); });