1
0
forked from baron/baron-sso

orgfront 버그 픽스

This commit is contained in:
2026-06-10 09:36:57 +09:00
parent 28478309fa
commit c880b3c333
33 changed files with 853 additions and 130 deletions

View File

@@ -143,6 +143,7 @@ vi.mock("../../lib/adminApi", () => ({
login_count: 3,
},
]),
fetchGlobalCustomClaimDefinitions: vi.fn(async () => ({ items: [] })),
fetchPasswordPolicy: vi.fn(async () => ({
minLength: 12,
lowercase: true,
@@ -196,6 +197,7 @@ vi.mock("../../lib/adminApi", () => ({
worksmobileId: "works-user-1",
worksmobileName: "Engineer User",
worksmobileEmail: "engineer@example.com",
worksmobileDomainId: 1001,
worksmobilePrimaryOrgId: "works-org-1",
worksmobilePrimaryOrgName: "기술연구팀",
status: "matched",
@@ -380,17 +382,19 @@ describe("adminfront large page coverage smoke", () => {
fireEvent.click(
screen.getByRole("button", { name: "선택 구성원 WORKS에 생성" }),
);
fireEvent.change(screen.getByLabelText("초기 비밀번호"), {
target: { value: "InitialPassword!1" },
});
fireEvent.click(screen.getByRole("button", { name: "생성 작업 등록" }));
await waitFor(() =>
expect(adminApi.enqueueWorksmobileUserSync).toHaveBeenCalledWith(
"tenant-company",
"user-2",
expect.any(String),
undefined,
"InitialPassword!1",
),
);
const credentialBatchId = vi.mocked(
adminApi.enqueueWorksmobileUserSync,
).mock.calls[0][2];
expect(adminApi.downloadWorksmobileInitialPasswordsCSV).not.toHaveBeenCalled();
});
@@ -416,6 +420,10 @@ describe("adminfront large page coverage smoke", () => {
fireEvent.click(
screen.getByRole("button", { name: "선택 구성원 WORKS에 생성" }),
);
fireEvent.change(screen.getByLabelText("초기 비밀번호"), {
target: { value: "InitialPassword!1" },
});
fireEvent.click(screen.getByRole("button", { name: "생성 작업 등록" }));
await waitFor(() =>
expect(adminApi.enqueueWorksmobileUserSync).toHaveBeenCalledTimes(2),
@@ -424,21 +432,20 @@ describe("adminfront large page coverage smoke", () => {
1,
"tenant-company",
"user-2",
expect.any(String),
undefined,
"InitialPassword!1",
);
expect(adminApi.enqueueWorksmobileUserSync).toHaveBeenNthCalledWith(
2,
"tenant-company",
"user-3",
expect.any(String),
undefined,
"InitialPassword!1",
);
expect(adminApi.downloadWorksmobileInitialPasswordsCSV).not.toHaveBeenCalled();
});
it("downloads or deletes Worksmobile credential batches from history", async () => {
vi.spyOn(window.URL, "createObjectURL").mockReturnValue("blob:test");
vi.spyOn(window.URL, "revokeObjectURL").mockImplementation(() => {});
vi.spyOn(window, "confirm").mockReturnValue(true);
it("renders and retries Worksmobile jobs from history", async () => {
renderWithProviders(
<Routes>
<Route
@@ -450,45 +457,20 @@ describe("adminfront large page coverage smoke", () => {
);
fireEvent.click(screen.getByRole("tab", { name: "이력" }));
await screen.findByText("credential-batch-1");
expect(
screen.getByRole("button", {
name: "credential-batch-pending 비밀번호 CSV 다운로드",
}),
).toBeDisabled();
fireEvent.click(
screen.getByRole("button", {
name: "credential-batch-1 비밀번호 CSV 다운로드",
}),
);
await waitFor(() =>
expect(
adminApi.downloadWorksmobileInitialPasswordsCSV,
).toHaveBeenCalledWith("tenant-company", "credential-batch-1"),
);
expect((await screen.findAllByText("user-1")).length).toBeGreaterThan(0);
expect(screen.getByText("failed")).toBeInTheDocument();
fireEvent.click(
screen.getByRole("button", {
name: "credential-batch-1 비밀번호 값 삭제",
}),
);
fireEvent.click(screen.getAllByRole("button", { name: "" })[0]);
await waitFor(() =>
expect(
adminApi.deleteWorksmobileCredentialBatchPasswords,
).toHaveBeenCalledWith("tenant-company", "credential-batch-1"),
expect(adminApi.retryWorksmobileJob).toHaveBeenCalledWith(
"tenant-company",
"job-1",
),
);
fireEvent.click(
screen.getByRole("button", {
name: "credential-batch-1 실패 사유 보기",
}),
);
expect(await screen.findByText("failed-user@samaneng.com")).toBeInTheDocument();
expect(screen.getByText("worksmobile api failed")).toBeInTheDocument();
});
it("enqueues Worksmobile password reset as a credential batch", async () => {
vi.spyOn(window, "confirm").mockReturnValue(true);
it("opens Worksmobile password management for matched users", async () => {
const openSpy = vi.spyOn(window, "open").mockReturnValue(null);
renderWithProviders(
<Routes>
<Route
@@ -504,17 +486,21 @@ describe("adminfront large page coverage smoke", () => {
await screen.findAllByText("Engineer User");
fireEvent.click(
screen.getByRole("button", {
name: "Engineer User 비밀번호 재설정",
name: "Engineer User 비밀번호 관리",
}),
);
await waitFor(() =>
expect(adminApi.resetWorksmobileUserPassword).toHaveBeenCalledWith(
"tenant-company",
"user-1",
expect.any(String),
expect(openSpy).toHaveBeenCalledWith(
expect.stringContaining(
"https://auth.worksmobile.com/integrate/password/manage",
),
"_blank",
"noopener,noreferrer",
);
expect(adminApi.downloadWorksmobileInitialPasswordsCSV).not.toHaveBeenCalled();
const [url] = openSpy.mock.calls[0] ?? [];
const parsed = new URL(String(url));
expect(parsed.searchParams.get("targetUserTenantId")).toBe("works-admin");
expect(parsed.searchParams.get("targetUserDomainId")).toBe("1001");
expect(parsed.searchParams.get("targetUserIdNo")).toBe("works-user-1");
});
});

View File

@@ -149,9 +149,13 @@ describe("DataIntegrityPage", () => {
it("renders Ory SSOT cache management inside data integrity", async () => {
renderPage();
fireEvent.click(await screen.findByRole("tab", { name: "Ory SSOT 시스템" }));
fireEvent.click(
await screen.findByRole("tab", { name: "Ory SSOT 시스템" }),
);
expect((await screen.findAllByText("Ory SSOT 시스템")).length).toBeGreaterThan(0);
expect(
(await screen.findAllByText("Ory SSOT 시스템")).length,
).toBeGreaterThan(0);
expect(await screen.findByText("Redis identity cache")).toBeInTheDocument();
expect(screen.getAllByText("준비됨").length).toBeGreaterThan(0);
expect(screen.getByText("152")).toBeInTheDocument();

View File

@@ -84,7 +84,9 @@ describe("UserProjectionPage", () => {
await screen.findByText("Ory SSOT 시스템");
expect(screen.queryByRole("button", { name: /재동기화/ })).toBeNull();
expect(screen.queryByRole("button", { name: /초기화 후 재구축/ })).toBeNull();
expect(
screen.queryByRole("button", { name: /초기화 후 재구축/ }),
).toBeNull();
fireEvent.click(screen.getByRole("button", { name: /Redis cache flush/ }));
await waitFor(() => {

View File

@@ -209,6 +209,7 @@ export default function GlobalCustomClaimsPage() {
>
<Input
value={claim.key}
name={`global-claim-definition-key-${claim.id}`}
className="font-mono text-xs"
placeholder="claim_key"
data-testid={`global-claim-definition-key-${claim.key || claim.id}`}
@@ -218,6 +219,7 @@ export default function GlobalCustomClaimsPage() {
/>
<Input
value={claim.label}
name={`global-claim-definition-label-${claim.id}`}
placeholder={t(
"ui.admin.users.global_custom_claims.label_placeholder",
"표시 이름",
@@ -233,6 +235,7 @@ export default function GlobalCustomClaimsPage() {
"Claim 타입",
)}
value={claim.valueType}
name={`global-claim-definition-value-type-${claim.id}`}
className="h-10 rounded-md border border-input bg-background px-3 text-sm"
onChange={(event) =>
updateClaim(claim.id, {
@@ -253,6 +256,7 @@ export default function GlobalCustomClaimsPage() {
"읽기 권한",
)}
value={claim.readPermission}
name={`global-claim-definition-read-permission-${claim.id}`}
className="h-10 rounded-md border border-input bg-background px-3 text-sm"
data-testid={`global-claim-definition-read-permission-${claim.key || claim.id}`}
onChange={(event) =>
@@ -274,6 +278,7 @@ export default function GlobalCustomClaimsPage() {
"쓰기 권한",
)}
value={claim.writePermission}
name={`global-claim-definition-write-permission-${claim.id}`}
className="h-10 rounded-md border border-input bg-background px-3 text-sm"
data-testid={`global-claim-definition-write-permission-${claim.key || claim.id}`}
onChange={(event) =>
@@ -291,6 +296,7 @@ export default function GlobalCustomClaimsPage() {
</select>
<Input
value={claim.description || ""}
name={`global-claim-definition-description-${claim.id}`}
placeholder={t(
"ui.admin.users.global_custom_claims.description_placeholder",
"설명",

View File

@@ -186,7 +186,9 @@ describe("UserDetailPage Worksmobile employee number", () => {
expect(valueInput).toHaveAttribute("type", "date");
fireEvent.change(valueInput, { target: { value: "2026-07-01" } });
fireEvent.click(screen.getByRole("button", { name: /사용자 Claim 값 저장/ }));
fireEvent.click(
screen.getByRole("button", { name: /사용자 Claim 값 저장/ }),
);
await waitFor(() => expect(updateUserMock).toHaveBeenCalled());
expect(updateUserMock).toHaveBeenCalledWith(

View File

@@ -86,9 +86,7 @@ describe("tenantTree utility", () => {
expect(currentBase?.recursiveMemberCount).toBe(17);
expect(currentBase?.children[0]?.recursiveMemberCount).toBe(7);
expect(currentBase?.children[0]?.children[0]?.recursiveMemberCount).toBe(
2,
);
expect(currentBase?.children[0]?.children[0]?.recursiveMemberCount).toBe(2);
});
it("keeps total member counts when descendants are not loaded on the current page", () => {

View File

@@ -26,8 +26,11 @@ describe("adminfront form field diagnostics", () => {
for (const file of sourceFiles("src")) {
const source = readFileSync(file, "utf8");
let match: RegExpExecArray | null;
while ((match = formFieldTagPattern.exec(source))) {
for (
let match = formFieldTagPattern.exec(source);
match !== null;
match = formFieldTagPattern.exec(source)
) {
const tag = match[0];
if (/\b(id|name)\s*=/.test(tag)) continue;
if (/\{\.\.\s*[^}]+\}/.test(tag)) continue;

View File

@@ -1,10 +1,11 @@
import { expect } from "vitest";
export function anonymousFormFields(container: ParentNode) {
return Array.from(container.querySelectorAll("input, select, textarea")).filter(
return Array.from(
container.querySelectorAll("input, select, textarea"),
).filter(
(field) =>
!field.getAttribute("id")?.trim() &&
!field.getAttribute("name")?.trim(),
!field.getAttribute("id")?.trim() && !field.getAttribute("name")?.trim(),
);
}

View File

@@ -67,12 +67,14 @@ const translations: Record<"ko" | "en", Record<string, string>> = {
"ui.admin.ory_ssot.title": "Ory SSOT 시스템",
"msg.admin.ory_ssot.flush_confirm":
"Redis identity cache 키만 비우시겠습니까?",
"msg.admin.ory_ssot.flush_error": "Redis identity cache flush에 실패했습니다.",
"msg.admin.ory_ssot.flush_error":
"Redis identity cache flush에 실패했습니다.",
"msg.admin.ory_ssot.flush_success":
"Redis identity cache key {{count}}개를 비웠습니다.",
"msg.admin.ory_ssot.forbidden.description":
"이 화면은 super_admin 권한으로만 접근할 수 있습니다.",
"msg.admin.ory_ssot.load_error": "Ory SSOT 시스템 상태를 불러오지 못했습니다.",
"msg.admin.ory_ssot.load_error":
"Ory SSOT 시스템 상태를 불러오지 못했습니다.",
"msg.admin.ory_ssot.subtitle":
"Kratos 원장과 Redis identity cache 상태를 분리해서 확인합니다.",
"msg.admin.users.list.subtitle": "시스템 사용자를 조회하고 관리합니다.",
@@ -156,8 +158,7 @@ const translations: Record<"ko" | "en", Record<string, string>> = {
"ui.admin.ory_ssot.summary.status": "Status",
"ui.admin.ory_ssot.summary.updated_at": "Updated at",
"ui.admin.ory_ssot.title": "Ory SSOT System",
"msg.admin.ory_ssot.flush_confirm":
"Flush only Redis identity cache keys?",
"msg.admin.ory_ssot.flush_confirm": "Flush only Redis identity cache keys?",
"msg.admin.ory_ssot.flush_error": "Redis identity cache flush failed.",
"msg.admin.ory_ssot.flush_success":
"Flushed {{count}} Redis identity cache keys.",