1
0
forked from baron/baron-sso

custom claim 타입보정 UI. 대표테넌트 노출 보정

This commit is contained in:
2026-06-11 11:27:11 +09:00
parent 0bb3ccb850
commit f60b15a17b
37 changed files with 2952 additions and 417 deletions

View File

@@ -126,6 +126,26 @@ async function setInputValue(input: HTMLInputElement, value: string) {
await flush();
}
async function setTextareaValue(textarea: HTMLTextAreaElement, value: string) {
const descriptor = Object.getOwnPropertyDescriptor(
HTMLTextAreaElement.prototype,
"value",
);
descriptor?.set?.call(textarea, value);
textarea.dispatchEvent(new Event("input", { bubbles: true }));
await flush();
}
async function setSelectValue(select: HTMLSelectElement, value: string) {
const descriptor = Object.getOwnPropertyDescriptor(
HTMLSelectElement.prototype,
"value",
);
descriptor?.set?.call(select, value);
select.dispatchEvent(new Event("change", { bubbles: true }));
await flush();
}
async function renderPage() {
const container = document.createElement("div");
document.body.appendChild(container);
@@ -229,4 +249,225 @@ describe("ClientGeneralPage RP claims", () => {
},
]);
});
it("forces user read permission on when user write permission is enabled for RP claims", async () => {
const { container } = await renderPage();
const switches = Array.from(
container.querySelectorAll<HTMLButtonElement>('[role="switch"]'),
);
const readSwitch = switches.find((button) =>
/Read|읽기/.test(button.getAttribute("aria-label") ?? ""),
);
const writeSwitch = switches.find((button) =>
/Write|쓰기/.test(button.getAttribute("aria-label") ?? ""),
);
expect(readSwitch).toBeDefined();
expect(writeSwitch).toBeDefined();
expect(readSwitch?.getAttribute("aria-checked")).toBe("false");
expect(writeSwitch?.getAttribute("aria-checked")).toBe("false");
await act(async () => {
writeSwitch?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await flush();
expect(readSwitch?.getAttribute("aria-checked")).toBe("true");
expect(writeSwitch?.getAttribute("aria-checked")).toBe("true");
const saveButton = Array.from(container.querySelectorAll("button")).find(
(button) =>
button.textContent?.includes("저장") ||
button.textContent?.includes("Save"),
);
expect(saveButton).toBeDefined();
await act(async () => {
saveButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await flush();
expect(updateClientMock).toHaveBeenCalledWith(
"client-claims",
expect.objectContaining({
metadata: expect.objectContaining({
id_token_claims: [
expect.objectContaining({
readPermission: "user_and_admin",
writePermission: "user_and_admin",
}),
],
}),
}),
);
});
it("keeps nullable and default value as separate RP claim settings", async () => {
const { container } = await renderPage();
expect(container.textContent).toContain("Nullable");
expect(container.textContent).toContain("Default Value");
expect(container.textContent).not.toContain("Nullable/default");
expect(container.textContent).toContain(
"RP 전용 확장 claim을 구분해서 관리합니다",
);
});
it("blocks saving a number RP claim default value that is not numeric", async () => {
const { container } = await renderPage();
const valueTypeSelect = container.querySelector<HTMLSelectElement>(
'select[aria-label="Claim 값 타입"]',
);
expect(valueTypeSelect).not.toBeNull();
await setSelectValue(valueTypeSelect as HTMLSelectElement, "number");
const saveButton = Array.from(container.querySelectorAll("button")).find(
(button) =>
button.textContent?.includes("저장") ||
button.textContent?.includes("Save"),
);
expect(saveButton).toBeDefined();
await act(async () => {
saveButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await flush();
expect(updateClientMock).not.toHaveBeenCalled();
});
it("blocks saving a number RP claim default value that is not an integer", async () => {
const { container } = await renderPage();
const valueTypeSelect = container.querySelector<HTMLSelectElement>(
'select[aria-label="Claim 값 타입"]',
);
expect(valueTypeSelect).not.toBeNull();
await setSelectValue(valueTypeSelect as HTMLSelectElement, "number");
const defaultValueInput = container.querySelector<HTMLInputElement>(
'input[placeholder="Enter the default value"]',
);
expect(defaultValueInput).not.toBeNull();
await setInputValue(defaultValueInput as HTMLInputElement, "3.14");
expect(container.textContent).toContain(
"Claim 기본값이 타입과 맞지 않습니다",
);
const saveButton = Array.from(container.querySelectorAll("button")).find(
(button) =>
button.textContent?.includes("저장") ||
button.textContent?.includes("Save"),
);
expect(saveButton).toBeDefined();
await act(async () => {
saveButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await flush();
expect(updateClientMock).not.toHaveBeenCalled();
});
it("saves a float RP claim default value", async () => {
const { container } = await renderPage();
const valueTypeSelect = container.querySelector<HTMLSelectElement>(
'select[aria-label="Claim 값 타입"]',
);
expect(valueTypeSelect).not.toBeNull();
expect(
valueTypeSelect?.querySelector('option[value="float"]'),
).not.toBeNull();
await setSelectValue(valueTypeSelect as HTMLSelectElement, "float");
const defaultValueInput = container.querySelector<HTMLInputElement>(
'input[placeholder="Enter the default value"]',
);
expect(defaultValueInput).not.toBeNull();
expect(defaultValueInput?.getAttribute("inputmode")).toBe("decimal");
await setInputValue(defaultValueInput as HTMLInputElement, "3.14");
const saveButton = Array.from(container.querySelectorAll("button")).find(
(button) =>
button.textContent?.includes("저장") ||
button.textContent?.includes("Save"),
);
expect(saveButton).toBeDefined();
await act(async () => {
saveButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await flush();
expect(updateClientMock).toHaveBeenCalledWith(
"client-claims",
expect.objectContaining({
metadata: expect.objectContaining({
id_token_claims: [
expect.objectContaining({
value: "3.14",
valueType: "float",
}),
],
}),
}),
);
});
it("renders constrained default value controls for boolean and date RP claims", async () => {
const { container } = await renderPage();
const valueTypeSelect = container.querySelector<HTMLSelectElement>(
'select[aria-label="Claim 값 타입"]',
);
expect(valueTypeSelect).not.toBeNull();
await setSelectValue(valueTypeSelect as HTMLSelectElement, "boolean");
const booleanDefaultSelect = Array.from(
container.querySelectorAll<HTMLSelectElement>("select"),
).find((select) =>
Array.from(select.options).some((option) => option.value === "false"),
);
expect(booleanDefaultSelect).toBeDefined();
await setSelectValue(valueTypeSelect as HTMLSelectElement, "date");
expect(container.querySelector('input[type="date"]')).not.toBeNull();
});
it("blocks saving an object RP claim default value that is not a JSON object", async () => {
const { container } = await renderPage();
const valueTypeSelect = container.querySelector<HTMLSelectElement>(
'select[aria-label="Claim 값 타입"]',
);
expect(valueTypeSelect).not.toBeNull();
await setSelectValue(valueTypeSelect as HTMLSelectElement, "object");
const defaultValueInput = container.querySelector<HTMLTextAreaElement>(
'textarea[placeholder="{\\"key\\": \\"value\\"}"]',
);
expect(defaultValueInput).not.toBeNull();
await setTextareaValue(
defaultValueInput as HTMLTextAreaElement,
"not-json",
);
const saveButton = Array.from(container.querySelectorAll("button")).find(
(button) =>
button.textContent?.includes("저장") ||
button.textContent?.includes("Save"),
);
expect(saveButton).toBeDefined();
await act(async () => {
saveButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
await flush();
expect(updateClientMock).not.toHaveBeenCalled();
});
});