forked from baron/baron-sso
277 lines
8.0 KiB
TypeScript
277 lines
8.0 KiB
TypeScript
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
import type React from "react";
|
|
import { MemoryRouter, Route, Routes } from "react-router-dom";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { createI18nMock } from "../../test/i18nMock";
|
|
import { TenantAdminsAndOwnersTab } from "../tenants/routes/TenantAdminsAndOwnersTab";
|
|
import { TenantFineGrainedPermissionsTab } from "../tenants/routes/TenantFineGrainedPermissionsTab";
|
|
import TenantUserGroupsTab from "../user-groups/routes/TenantUserGroupsTab";
|
|
|
|
const exportUsersCSVMock = vi.hoisted(() =>
|
|
vi.fn(async () => ({
|
|
blob: new Blob(["email,name\nmember@example.com,Member User\n"], {
|
|
type: "text/csv",
|
|
}),
|
|
filename: "users_export_20260609.csv",
|
|
})),
|
|
);
|
|
const bulkUpdateUsersMock = vi.hoisted(() => vi.fn(async () => ({ results: [] })));
|
|
|
|
const tenants = [
|
|
{
|
|
id: "tenant-root",
|
|
type: "COMPANY_GROUP",
|
|
name: "한맥 가족",
|
|
slug: "hanmac-family",
|
|
description: "",
|
|
status: "active",
|
|
memberCount: 0,
|
|
createdAt: "2026-05-01T00:00:00Z",
|
|
updatedAt: "2026-05-01T00:00:00Z",
|
|
},
|
|
{
|
|
id: "tenant-company",
|
|
type: "COMPANY",
|
|
parentId: "tenant-root",
|
|
name: "GPDTDC",
|
|
slug: "gpdtdc",
|
|
description: "",
|
|
status: "active",
|
|
memberCount: 2,
|
|
createdAt: "2026-05-01T00:00:00Z",
|
|
updatedAt: "2026-05-01T00:00:00Z",
|
|
},
|
|
{
|
|
id: "tenant-leaf",
|
|
type: "ORGANIZATION",
|
|
parentId: "tenant-company",
|
|
name: "기술연구팀",
|
|
slug: "gpdtdc-rnd",
|
|
description: "",
|
|
status: "active",
|
|
memberCount: 1,
|
|
createdAt: "2026-05-01T00:00:00Z",
|
|
updatedAt: "2026-05-01T00:00:00Z",
|
|
},
|
|
];
|
|
|
|
const users = [
|
|
{
|
|
id: "user-owner",
|
|
name: "Owner User",
|
|
email: "owner@example.com",
|
|
role: "super_admin",
|
|
status: "active",
|
|
},
|
|
{
|
|
id: "user-admin",
|
|
name: "Admin User",
|
|
email: "admin@example.com",
|
|
role: "tenant_admin",
|
|
status: "active",
|
|
},
|
|
{
|
|
id: "user-member",
|
|
name: "Member User",
|
|
email: "member@example.com",
|
|
role: "user",
|
|
status: "active",
|
|
tenantSlug: "gpdtdc-rnd",
|
|
tenant: tenants[2],
|
|
},
|
|
];
|
|
|
|
vi.mock("../../lib/i18n", () => createI18nMock());
|
|
|
|
vi.mock("react-oidc-context", () => ({
|
|
useAuth: () => ({
|
|
user: {
|
|
profile: {
|
|
sub: "admin-1",
|
|
},
|
|
},
|
|
}),
|
|
}));
|
|
|
|
vi.mock("../../lib/adminApi", () => ({
|
|
fetchMe: vi.fn(async () => users[0]),
|
|
fetchTenant: vi.fn(async (tenantId) => ({
|
|
id: tenantId,
|
|
name: "Test Tenant",
|
|
slug: "test-tenant",
|
|
userPermissions: { view: true, manage: true, manage_admins: true },
|
|
})),
|
|
fetchTenantOwners: vi.fn(async () => [users[0]]),
|
|
fetchTenantAdmins: vi.fn(async () => [users[1]]),
|
|
addTenantOwner: vi.fn(async () => undefined),
|
|
addTenantAdmin: vi.fn(async () => undefined),
|
|
removeTenantOwner: vi.fn(async () => undefined),
|
|
removeTenantAdmin: vi.fn(async () => undefined),
|
|
fetchTenantRelations: vi.fn(async () => [
|
|
{
|
|
userId: "user-relation-1",
|
|
name: "Relation User",
|
|
email: "relation@example.com",
|
|
relations: ["profile_managers", "schema_viewers"],
|
|
},
|
|
]),
|
|
addTenantRelation: vi.fn(async () => undefined),
|
|
removeTenantRelation: vi.fn(async () => undefined),
|
|
fetchUsers: vi.fn(async () => ({
|
|
items: users,
|
|
total: users.length,
|
|
})),
|
|
fetchAllTenants: vi.fn(async () => ({
|
|
items: tenants,
|
|
total: tenants.length,
|
|
})),
|
|
updateTenant: vi.fn(async () => tenants[2]),
|
|
updateUser: vi.fn(async () => users[2]),
|
|
bulkUpdateUsers: bulkUpdateUsersMock,
|
|
exportTenantsCSV: vi.fn(async () => ({
|
|
blob: new Blob(["name,slug"]),
|
|
filename: "tenants.csv",
|
|
})),
|
|
exportUsersCSV: exportUsersCSVMock,
|
|
}));
|
|
|
|
function renderWithProviders(ui: React.ReactElement, entry: string) {
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: { retry: false },
|
|
mutations: { retry: false },
|
|
},
|
|
});
|
|
|
|
return render(
|
|
<QueryClientProvider client={queryClient}>
|
|
<MemoryRouter initialEntries={[entry]}>{ui}</MemoryRouter>
|
|
</QueryClientProvider>,
|
|
);
|
|
}
|
|
|
|
describe("admin tenant tab coverage smoke", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.spyOn(window, "confirm").mockReturnValue(true);
|
|
vi.spyOn(window.URL, "createObjectURL").mockReturnValue(
|
|
"blob:tenant-users-export",
|
|
);
|
|
vi.spyOn(window.URL, "revokeObjectURL").mockImplementation(() => {});
|
|
});
|
|
|
|
it("renders tenant owners and admins lists", async () => {
|
|
renderWithProviders(
|
|
<Routes>
|
|
<Route
|
|
path="/tenants/:tenantId/permissions"
|
|
element={<TenantAdminsAndOwnersTab />}
|
|
/>
|
|
</Routes>,
|
|
"/tenants/tenant-company/permissions",
|
|
);
|
|
|
|
expect(await screen.findByText("Owner User")).toBeInTheDocument();
|
|
expect(screen.getByText("Admin User")).toBeInTheDocument();
|
|
expect(screen.getByText("owner@example.com")).toBeInTheDocument();
|
|
expect(screen.getByText("admin@example.com")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders tenant fine-grained relations list", async () => {
|
|
renderWithProviders(
|
|
<Routes>
|
|
<Route
|
|
path="/tenants/:tenantId/relations"
|
|
element={<TenantFineGrainedPermissionsTab />}
|
|
/>
|
|
</Routes>,
|
|
"/tenants/tenant-company/relations",
|
|
);
|
|
|
|
expect(await screen.findByText("Relation User")).toBeInTheDocument();
|
|
expect(screen.getByText("relation@example.com")).toBeInTheDocument();
|
|
expect(screen.getByText("세부 권한 설정 (Fine-grained Permissions)")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders tenant hierarchy and selected organization members", async () => {
|
|
renderWithProviders(
|
|
<Routes>
|
|
<Route
|
|
path="/tenants/:tenantId/organization"
|
|
element={<TenantUserGroupsTab />}
|
|
/>
|
|
</Routes>,
|
|
"/tenants/tenant-company/organization",
|
|
);
|
|
|
|
expect((await screen.findAllByText("GPDTDC")).length).toBeGreaterThan(0);
|
|
expect(screen.getAllByText("기술연구팀").length).toBeGreaterThan(0);
|
|
expect(await screen.findByText("Member User")).toBeInTheDocument();
|
|
});
|
|
|
|
it("exports selected organization users by tenant slug", async () => {
|
|
renderWithProviders(
|
|
<Routes>
|
|
<Route
|
|
path="/tenants/:tenantId/organization"
|
|
element={<TenantUserGroupsTab />}
|
|
/>
|
|
</Routes>,
|
|
"/tenants/tenant-company/organization",
|
|
);
|
|
|
|
expect(await screen.findByText("Member User")).toBeInTheDocument();
|
|
|
|
fireEvent.click(screen.getByTestId("tenant-current-users-export-btn"));
|
|
|
|
await waitFor(() => {
|
|
expect(exportUsersCSVMock).toHaveBeenCalledWith("", "gpdtdc", false);
|
|
});
|
|
});
|
|
|
|
it("queues searched users and bulk adds them to the selected organization", async () => {
|
|
renderWithProviders(
|
|
<Routes>
|
|
<Route
|
|
path="/tenants/:tenantId/organization"
|
|
element={<TenantUserGroupsTab />}
|
|
/>
|
|
</Routes>,
|
|
"/tenants/tenant-company/organization",
|
|
);
|
|
|
|
expect(await screen.findByText("Member User")).toBeInTheDocument();
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: /멤버 추가/ }));
|
|
fireEvent.change(screen.getByTestId("tenant-org-member-search-input"), {
|
|
target: { value: "user" },
|
|
});
|
|
fireEvent.click(screen.getByTestId("tenant-org-member-search-btn"));
|
|
|
|
fireEvent.click(
|
|
await screen.findByTestId("tenant-org-member-search-result-user-owner"),
|
|
);
|
|
fireEvent.click(
|
|
await screen.findByTestId("tenant-org-member-search-result-user-admin"),
|
|
);
|
|
|
|
expect(screen.getByTestId("tenant-org-member-add-queue")).toHaveTextContent(
|
|
"Owner User",
|
|
);
|
|
expect(screen.getByTestId("tenant-org-member-add-queue")).toHaveTextContent(
|
|
"Admin User",
|
|
);
|
|
|
|
fireEvent.click(screen.getByTestId("tenant-org-member-add-submit-btn"));
|
|
|
|
await waitFor(() => {
|
|
expect(bulkUpdateUsersMock).toHaveBeenCalledWith({
|
|
userIds: ["user-owner", "user-admin"],
|
|
tenantSlug: "gpdtdc",
|
|
isAddTenant: true,
|
|
});
|
|
});
|
|
});
|
|
});
|