forked from baron/baron-sso
네이버 계정 정합성 맞춤
This commit is contained in:
@@ -618,6 +618,120 @@ describe("org chart layout", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("maps appointment memberships by tenant UUID when the stored slug is stale", () => {
|
||||
const root = tenantNode("gpdtdc", "COMPANY", "GPDTDC", "gpdtdc");
|
||||
const division = {
|
||||
...tenantNode("division", "ORGANIZATION", "총괄기획실", "gpd"),
|
||||
parentId: "gpdtdc",
|
||||
};
|
||||
const migratedLeaf = {
|
||||
...tenantNode(
|
||||
"migrated-leaf",
|
||||
"ORGANIZATION",
|
||||
"통합시스템",
|
||||
"intigrated-system",
|
||||
),
|
||||
parentId: "division",
|
||||
};
|
||||
const rootNode = {
|
||||
...root,
|
||||
children: [{ ...division, children: [migratedLeaf] }],
|
||||
};
|
||||
|
||||
const usersMap = buildUsersMap(
|
||||
[
|
||||
{
|
||||
...member("migrated-user"),
|
||||
companyCode: undefined,
|
||||
tenantSlug: "gpdtdc",
|
||||
metadata: {
|
||||
additionalAppointments: [
|
||||
{
|
||||
tenantId: "migrated-leaf",
|
||||
tenantName: "기술기획",
|
||||
tenantSlug: "tech-planning",
|
||||
},
|
||||
],
|
||||
},
|
||||
joinedTenants: undefined,
|
||||
},
|
||||
],
|
||||
[rootNode],
|
||||
{ activeOnly: true },
|
||||
);
|
||||
|
||||
expect(usersMap.get("gpdtdc")).toBeUndefined();
|
||||
expect(usersMap.get("tech-planning")).toBeUndefined();
|
||||
expect(usersMap.get("intigrated-system")?.map((user) => user.id)).toEqual([
|
||||
"migrated-user",
|
||||
]);
|
||||
});
|
||||
|
||||
it("maps primary and joined memberships by tenant UUID when stored slugs are stale", () => {
|
||||
const root = tenantNode("root-company", "COMPANY", "Root", "root-company");
|
||||
const primaryLeaf = {
|
||||
...tenantNode("primary-leaf", "ORGANIZATION", "Primary", "primary-new"),
|
||||
parentId: "root-company",
|
||||
};
|
||||
const joinedLeaf = {
|
||||
...tenantNode("joined-leaf", "ORGANIZATION", "Joined", "joined-new"),
|
||||
parentId: "root-company",
|
||||
};
|
||||
const rootNode = {
|
||||
...root,
|
||||
children: [primaryLeaf, joinedLeaf],
|
||||
};
|
||||
|
||||
const usersMap = buildUsersMap(
|
||||
[
|
||||
{
|
||||
...member("primary-user"),
|
||||
companyCode: undefined,
|
||||
tenantSlug: undefined,
|
||||
tenant: {
|
||||
id: "primary-leaf",
|
||||
type: "ORGANIZATION",
|
||||
name: "Primary",
|
||||
slug: "primary-old",
|
||||
description: "",
|
||||
status: "active",
|
||||
createdAt: "2026-05-11T00:00:00.000Z",
|
||||
updatedAt: "2026-05-11T00:00:00.000Z",
|
||||
},
|
||||
joinedTenants: undefined,
|
||||
},
|
||||
{
|
||||
...member("joined-user"),
|
||||
companyCode: undefined,
|
||||
tenantSlug: undefined,
|
||||
joinedTenants: [
|
||||
{
|
||||
id: "joined-leaf",
|
||||
type: "ORGANIZATION",
|
||||
name: "Joined",
|
||||
slug: "joined-old",
|
||||
description: "",
|
||||
status: "active",
|
||||
createdAt: "2026-05-11T00:00:00.000Z",
|
||||
updatedAt: "2026-05-11T00:00:00.000Z",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[rootNode],
|
||||
{ activeOnly: true },
|
||||
);
|
||||
|
||||
expect(usersMap.get("primary-new")?.map((user) => user.id)).toEqual([
|
||||
"primary-user",
|
||||
]);
|
||||
expect(usersMap.get("joined-new")?.map((user) => user.id)).toEqual([
|
||||
"joined-user",
|
||||
]);
|
||||
expect(usersMap.get("primary-old")).toBeUndefined();
|
||||
expect(usersMap.get("joined-old")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not fall back to a visible parent for hidden leaf memberships", () => {
|
||||
const gpdtdc = tenantNode("gpdtdc", "COMPANY", "GPDTDC", "gpdtdc");
|
||||
const internalLeaf = {
|
||||
|
||||
@@ -1270,14 +1270,27 @@ function getUserOrgAppointmentRefs(user: UserSummary): UserOrgAppointmentRef[] {
|
||||
}
|
||||
|
||||
function addTenantSlugCandidate(
|
||||
slugs: Set<string>,
|
||||
tenantIds: Set<string>,
|
||||
tenantIndexes: TenantIndexes,
|
||||
slug: string,
|
||||
) {
|
||||
const normalizedSlug = normalizeOrgSlug(slug);
|
||||
if (!normalizedSlug) return;
|
||||
if (!tenantIndexes.bySlug.has(normalizedSlug)) return;
|
||||
slugs.add(normalizedSlug);
|
||||
const tenant = tenantIndexes.bySlug.get(normalizedSlug);
|
||||
if (!tenant) return;
|
||||
tenantIds.add(tenant.id);
|
||||
}
|
||||
|
||||
function addTenantIdCandidate(
|
||||
tenantIds: Set<string>,
|
||||
tenantIndexes: TenantIndexes,
|
||||
id: unknown,
|
||||
) {
|
||||
if (typeof id !== "string") return;
|
||||
const normalizedId = id.trim();
|
||||
if (!normalizedId) return;
|
||||
if (!tenantIndexes.byId.has(normalizedId)) return;
|
||||
tenantIds.add(normalizedId);
|
||||
}
|
||||
|
||||
function isDescendantTenant(
|
||||
@@ -1298,19 +1311,19 @@ function isDescendantTenant(
|
||||
return false;
|
||||
}
|
||||
|
||||
function getLeafMembershipSlugs(
|
||||
slugs: Set<string>,
|
||||
function getLeafMembershipIds(
|
||||
tenantIds: Set<string>,
|
||||
tenantIndexes: TenantIndexes,
|
||||
) {
|
||||
const memberships = Array.from(slugs);
|
||||
const memberships = Array.from(tenantIds);
|
||||
|
||||
return memberships.filter((slug) => {
|
||||
const tenant = tenantIndexes.bySlug.get(slug);
|
||||
return memberships.filter((id) => {
|
||||
const tenant = tenantIndexes.byId.get(id);
|
||||
if (!tenant) return true;
|
||||
|
||||
return !memberships.some((otherSlug) => {
|
||||
if (otherSlug === slug) return false;
|
||||
const otherTenant = tenantIndexes.bySlug.get(otherSlug);
|
||||
return !memberships.some((otherId) => {
|
||||
if (otherId === id) return false;
|
||||
const otherTenant = tenantIndexes.byId.get(otherId);
|
||||
if (!otherTenant) return false;
|
||||
return isDescendantTenant(otherTenant, tenant, tenantIndexes.byId);
|
||||
});
|
||||
@@ -1332,7 +1345,7 @@ export function buildUsersMap(
|
||||
if (options.activeOnly && user.status !== "active") continue;
|
||||
if (!isVisibleOrgChartUser(user)) continue;
|
||||
|
||||
const slugs = new Set<string>();
|
||||
const tenantIds = new Set<string>();
|
||||
const primarySlug = normalizeOrgSlug(user.tenantSlug);
|
||||
const legacyCompanySlug = normalizeOrgSlug(user.companyCode);
|
||||
if (
|
||||
@@ -1344,7 +1357,7 @@ export function buildUsersMap(
|
||||
name: primarySlug,
|
||||
})
|
||||
) {
|
||||
addTenantSlugCandidate(slugs, membershipTenantIndexes, primarySlug);
|
||||
addTenantSlugCandidate(tenantIds, membershipTenantIndexes, primarySlug);
|
||||
}
|
||||
if (
|
||||
legacyCompanySlug &&
|
||||
@@ -1355,24 +1368,51 @@ export function buildUsersMap(
|
||||
name: legacyCompanySlug,
|
||||
})
|
||||
) {
|
||||
addTenantSlugCandidate(slugs, membershipTenantIndexes, legacyCompanySlug);
|
||||
addTenantSlugCandidate(
|
||||
tenantIds,
|
||||
membershipTenantIndexes,
|
||||
legacyCompanySlug,
|
||||
);
|
||||
}
|
||||
if (user.tenant?.slug && !isSystemGlobalTenant(user.tenant)) {
|
||||
addTenantSlugCandidate(slugs, membershipTenantIndexes, user.tenant.slug);
|
||||
addTenantIdCandidate(tenantIds, membershipTenantIndexes, user.tenant.id);
|
||||
addTenantSlugCandidate(
|
||||
tenantIds,
|
||||
membershipTenantIndexes,
|
||||
user.tenant.slug,
|
||||
);
|
||||
}
|
||||
for (const joinedTenant of user.joinedTenants || []) {
|
||||
if (joinedTenant.slug && !isSystemGlobalTenant(joinedTenant)) {
|
||||
addTenantIdCandidate(
|
||||
tenantIds,
|
||||
membershipTenantIndexes,
|
||||
joinedTenant.id,
|
||||
);
|
||||
addTenantSlugCandidate(
|
||||
slugs,
|
||||
tenantIds,
|
||||
membershipTenantIndexes,
|
||||
joinedTenant.slug,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const appointment of getUserOrgAppointmentRefs(user)) {
|
||||
const hasTenantIdCandidate =
|
||||
appointment.tenantId &&
|
||||
membershipTenantIndexes.byId.has(appointment.tenantId);
|
||||
|
||||
if (hasTenantIdCandidate) {
|
||||
addTenantIdCandidate(
|
||||
tenantIds,
|
||||
membershipTenantIndexes,
|
||||
appointment.tenantId,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (appointment.tenantSlug) {
|
||||
addTenantSlugCandidate(
|
||||
slugs,
|
||||
tenantIds,
|
||||
membershipTenantIndexes,
|
||||
appointment.tenantSlug,
|
||||
);
|
||||
@@ -1383,12 +1423,14 @@ export function buildUsersMap(
|
||||
? membershipTenantIndexes.byId.get(appointment.tenantId)
|
||||
: undefined;
|
||||
if (tenantById) {
|
||||
addTenantSlugCandidate(slugs, membershipTenantIndexes, tenantById.slug);
|
||||
addTenantIdCandidate(tenantIds, membershipTenantIndexes, tenantById.id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const slug of getLeafMembershipSlugs(slugs, membershipTenantIndexes)) {
|
||||
if (!visibleTenantIndexes.bySlug.has(slug)) continue;
|
||||
for (const id of getLeafMembershipIds(tenantIds, membershipTenantIndexes)) {
|
||||
const visibleTenant = visibleTenantIndexes.byId.get(id);
|
||||
if (!visibleTenant) continue;
|
||||
const slug = visibleTenant.slug.toLowerCase();
|
||||
const list = map.get(slug) || [];
|
||||
if (!list.some((existing) => existing.id === user.id)) list.push(user);
|
||||
map.set(slug, list);
|
||||
@@ -1444,6 +1486,9 @@ export function TenantOrgChartPage() {
|
||||
queryKey: ["orgchart-snapshot", { cache: "redis" }],
|
||||
queryFn: () => fetchOrgChartSnapshot(),
|
||||
enabled: !shareToken,
|
||||
staleTime: 0,
|
||||
refetchOnMount: "always",
|
||||
refetchOnWindowFocus: true,
|
||||
});
|
||||
|
||||
const { rootNodes, usersMap, sharedWith } = React.useMemo(() => {
|
||||
|
||||
@@ -194,6 +194,24 @@ describe("OrgPickerEmbedPage orgchart data source", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("forces authenticated picker snapshots to refetch instead of trusting the in-memory query cache", async () => {
|
||||
adminApiMocks.fetchOrgChartSnapshot.mockResolvedValue(snapshot);
|
||||
|
||||
const rendered = renderPicker("/embed/picker?select=both");
|
||||
|
||||
await waitForExpect(() => {
|
||||
expect(adminApiMocks.fetchOrgChartSnapshot).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
const query = rendered.queryClient.getQueryCache().find({
|
||||
queryKey: ["org-picker-orgchart", "authenticated", undefined],
|
||||
});
|
||||
|
||||
expect(query?.options.staleTime).toBe(0);
|
||||
expect(query?.options.refetchOnMount).toBe("always");
|
||||
expect(query?.options.refetchOnWindowFocus).toBe(true);
|
||||
});
|
||||
|
||||
it("uses the public orgchart snapshot when a share token is present", async () => {
|
||||
adminApiMocks.fetchOrgChartSnapshot.mockResolvedValue(snapshot);
|
||||
adminApiMocks.fetchPublicOrgChart.mockResolvedValue(snapshot);
|
||||
|
||||
@@ -384,6 +384,9 @@ export function OrgPickerEmbedPage() {
|
||||
],
|
||||
queryFn: () =>
|
||||
shareToken ? fetchPublicOrgChart(shareToken) : fetchOrgChartSnapshot(),
|
||||
staleTime: 0,
|
||||
refetchOnMount: "always",
|
||||
refetchOnWindowFocus: true,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user