forked from baron/baron-sso
테넌트 비소속 개발자 권한 신청/부여 가능
This commit is contained in:
@@ -186,7 +186,7 @@ describe("DeveloperRequestPage", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a tenant-required notice and hides the request button when tenant is missing", async () => {
|
||||
it("allows requesting developer access even when tenant context is missing", async () => {
|
||||
authState = {
|
||||
user: {
|
||||
access_token: "access-token",
|
||||
@@ -206,12 +206,90 @@ describe("DeveloperRequestPage", () => {
|
||||
phone: "010-1234-5678",
|
||||
role: "user",
|
||||
});
|
||||
fetchMyTenantsMock.mockResolvedValue([]);
|
||||
|
||||
const container = await renderPage();
|
||||
expect(container.textContent).toContain(
|
||||
expect(container.textContent).toContain("신규 신청하기");
|
||||
expect(container.textContent).not.toContain(
|
||||
"개발자 권한을 신청하려면 먼저 테넌트에 소속되어 있어야 합니다.",
|
||||
);
|
||||
expect(container.textContent).not.toContain("신규 신청하기");
|
||||
expect(requestDeveloperAccessMock).not.toHaveBeenCalled();
|
||||
|
||||
const actionButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(button) => button.textContent?.includes("신규 신청하기"),
|
||||
);
|
||||
expect(actionButton).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
actionButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
});
|
||||
|
||||
expect(container.textContent).toContain("개발자 등록 신청");
|
||||
|
||||
const reasonField = container.querySelector(
|
||||
"textarea",
|
||||
) as HTMLTextAreaElement | null;
|
||||
if (!reasonField) {
|
||||
throw new Error("Expected reason textarea to be rendered");
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
await setTextAreaValue(reasonField, "Need RP access");
|
||||
});
|
||||
|
||||
const submitButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(button) => button.textContent === "신청하기",
|
||||
);
|
||||
expect(submitButton).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
submitButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
});
|
||||
|
||||
expect(requestDeveloperAccessMock.mock.calls[0]?.[0]).toEqual({
|
||||
name: "Requester",
|
||||
organization: "HANMAC",
|
||||
reason: "Need RP access",
|
||||
tenantId: "",
|
||||
});
|
||||
});
|
||||
|
||||
it("shows '없음' when organization is unavailable", async () => {
|
||||
authState = {
|
||||
user: {
|
||||
access_token: "access-token",
|
||||
profile: {
|
||||
role: "user",
|
||||
name: "Requester",
|
||||
email: "requester@example.com",
|
||||
phone: "010-1234-5678",
|
||||
},
|
||||
},
|
||||
};
|
||||
fetchMeMock.mockResolvedValue({
|
||||
id: "user-1",
|
||||
name: "Requester",
|
||||
email: "requester@example.com",
|
||||
phone: "010-1234-5678",
|
||||
role: "user",
|
||||
});
|
||||
fetchMyTenantsMock.mockResolvedValue([]);
|
||||
|
||||
const container = await renderPage();
|
||||
const actionButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(button) => button.textContent?.includes("신규 신청하기"),
|
||||
);
|
||||
expect(actionButton).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
actionButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
});
|
||||
|
||||
const orgField = container.querySelector("#org") as HTMLInputElement | null;
|
||||
if (!orgField) {
|
||||
throw new Error("Expected organization input to be rendered");
|
||||
}
|
||||
|
||||
expect(orgField.value).toBe("없음");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,7 +56,6 @@ export default function DeveloperRequestPage() {
|
||||
const role = resolveProfileRole(userProfile);
|
||||
const tenantId = userProfile?.tenant_id as string | undefined;
|
||||
const companyCode = userProfile?.companyCode as string | undefined;
|
||||
const isTenantContextMissing = !tenantId?.trim();
|
||||
|
||||
const [isRequestModalOpen, setIsRequestModalOpen] = useState(false);
|
||||
const [adminNotes, setAdminNotes] = useState<Record<number, string>>({});
|
||||
@@ -180,7 +179,7 @@ export default function DeveloperRequestPage() {
|
||||
)
|
||||
}
|
||||
actions={
|
||||
!isSuperAdmin && !isTenantContextMissing && !hasActiveRequest ? (
|
||||
!isSuperAdmin && !hasActiveRequest ? (
|
||||
<Button onClick={() => setIsRequestModalOpen(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
{t("ui.dev.welcome.btn_request", "신규 신청하기")}
|
||||
@@ -189,28 +188,6 @@ export default function DeveloperRequestPage() {
|
||||
}
|
||||
/>
|
||||
|
||||
{!isSuperAdmin && isTenantContextMissing ? (
|
||||
<Card className="border-amber-500/30 bg-amber-500/10">
|
||||
<CardContent className="flex items-start gap-3 p-4">
|
||||
<ShieldAlert className="mt-0.5 h-5 w-5 shrink-0 text-amber-600" />
|
||||
<div className="space-y-1 text-left">
|
||||
<p className="font-semibold text-foreground">
|
||||
{t(
|
||||
"msg.dev.request.tenant_required",
|
||||
"개발자 권한을 신청하려면 먼저 테넌트에 소속되어 있어야 합니다.",
|
||||
)}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(
|
||||
"msg.dev.request.tenant_required_detail",
|
||||
"현재 계정은 테넌트와 연결되어 있지 않아 개발자 권한을 신청할 수 없습니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
<Card className="glass-panel">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">
|
||||
@@ -282,7 +259,9 @@ export default function DeveloperRequestPage() {
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell>{req.organization}</TableCell>
|
||||
<TableCell>
|
||||
{req.organization?.trim() || t("ui.common.na", "없음")}
|
||||
</TableCell>
|
||||
<TableCell className="max-w-md">
|
||||
<div className="truncate" title={req.reason}>
|
||||
{req.reason}
|
||||
@@ -470,6 +449,7 @@ function RequestAccessModal({
|
||||
const [name, setName] = useState(initialName);
|
||||
const [organization, setOrganization] = useState(initialOrg);
|
||||
const [reason, setReason] = useState("");
|
||||
const organizationDisplay = organization.trim() || t("ui.common.na", "없음");
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
@@ -486,15 +466,6 @@ function RequestAccessModal({
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!tenantId.trim()) {
|
||||
alert(
|
||||
t(
|
||||
"msg.dev.request.tenant_required",
|
||||
"개발자 권한을 신청하려면 먼저 테넌트에 소속되어 있어야 합니다.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
mutation.mutate({
|
||||
name,
|
||||
organization,
|
||||
@@ -550,7 +521,7 @@ function RequestAccessModal({
|
||||
</Label>
|
||||
<Input
|
||||
id="org"
|
||||
value={organization}
|
||||
value={organizationDisplay}
|
||||
readOnly
|
||||
className="focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:border-input"
|
||||
required
|
||||
|
||||
Reference in New Issue
Block a user