server/commands/groupsSyncer.test.ts
Rama main · réplica de lectura del repositorio original.
import { createContext } from "@server/context";
import {
AuthenticationProvider,
Event,
ExternalGroup,
Group,
GroupUser,
} from "@server/models";
import { sequelize } from "@server/storage/database";
import { buildGroup, buildUser } from "@server/test/factories";
import groupsSyncer from "./groupsSyncer";
describe("groupsSyncer", () => {
const ip = "127.0.0.1";
it("should create groups and memberships for new external groups", async () => {
const user = await buildUser();
const team = await user.$get("team")!;
const authenticationProvider = (await AuthenticationProvider.findOne({
where: { teamId: user.teamId },
}))!;
const result = await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: [
{ id: "ext-1", name: "Engineering" },
{ id: "ext-2", name: "Design" },
],
})
);
expect(result.groupsCreated).toEqual(2);
expect(result.membershipsAdded).toEqual(2);
expect(result.membershipsRemoved).toEqual(0);
const groups = await Group.findAll({ where: { teamId: user.teamId } });
expect(groups.map((g) => g.name).sort()).toEqual(["Design", "Engineering"]);
});
it("should update internal group name when external name changes", async () => {
const user = await buildUser();
const team = await user.$get("team")!;
const authenticationProvider = (await AuthenticationProvider.findOne({
where: { teamId: user.teamId },
}))!;
// Initial sync creates the group
await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: [{ id: "ext-1", name: "Engineering" }],
})
);
const groupBefore = await Group.findOne({
where: { teamId: user.teamId, name: "Engineering" },
});
expect(groupBefore).not.toBeNull();
// Second sync with updated name
await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: [{ id: "ext-1", name: "Platform Engineering" }],
})
);
const groupAfter = await Group.findByPk(groupBefore!.id);
expect(groupAfter!.name).toEqual("Platform Engineering");
const externalGroup = await ExternalGroup.findOne({
where: {
authenticationProviderId: authenticationProvider.id,
externalId: "ext-1",
},
});
expect(externalGroup!.name).toEqual("Platform Engineering");
});
it("should sync group description from the external provider", async () => {
const user = await buildUser();
const team = await user.$get("team")!;
const authenticationProvider = (await AuthenticationProvider.findOne({
where: { teamId: user.teamId },
}))!;
await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: [
{
id: "ext-1",
name: "Engineering",
description: " All engineers ",
},
],
})
);
const group = await Group.findOne({
where: { teamId: user.teamId, name: "Engineering" },
});
expect(group!.description).toEqual("All engineers");
// Second sync with updated description
await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: [
{ id: "ext-1", name: "Engineering", description: "Platform team" },
],
})
);
await group!.reload();
expect(group!.description).toEqual("Platform team");
// Provider reports no description: existing value is preserved
await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: [{ id: "ext-1", name: "Engineering" }],
})
);
await group!.reload();
expect(group!.description).toEqual("Platform team");
// Provider explicitly clears the description
await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: [
{ id: "ext-1", name: "Engineering", description: null },
],
})
);
await group!.reload();
expect(group!.description).toEqual("");
});
it("should sync groups with duplicate names accurately", async () => {
const user = await buildUser();
const team = await user.$get("team")!;
const authenticationProvider = (await AuthenticationProvider.findOne({
where: { teamId: user.teamId },
}))!;
await buildGroup({ teamId: user.teamId, name: "All Company" });
const sync = () =>
sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: [
{ id: "ext-1", name: "All Company", description: "Everyone" },
{ id: "ext-2", name: "All Company", description: "Also everyone" },
],
})
);
await sync();
// A second sync updates the existing groups in place.
const result = await sync();
expect(result.groupsCreated).toEqual(0);
const groups = await Group.findAll({
where: { teamId: user.teamId },
order: [["externalId", "ASC NULLS FIRST"]],
});
expect(
groups.map((g) => ({
name: g.name,
externalId: g.externalId,
description: g.description,
}))
).toEqual([
{ name: "All Company", externalId: null, description: null },
{ name: "All Company", externalId: "ext-1", description: "Everyone" },
{
name: "All Company",
externalId: "ext-2",
description: "Also everyone",
},
]);
});
it("should backfill externalId on previously synced groups", async () => {
const user = await buildUser();
const team = await user.$get("team")!;
const authenticationProvider = (await AuthenticationProvider.findOne({
where: { teamId: user.teamId },
}))!;
const group = await buildGroup({ teamId: user.teamId, name: "Design" });
await ExternalGroup.create({
externalId: "ext-1",
name: "Design",
groupId: group.id,
authenticationProviderId: authenticationProvider.id,
teamId: user.teamId,
});
await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: [{ id: "ext-1", name: "Design" }],
})
);
expect((await group.reload()).externalId).toEqual("ext-1");
});
it("should remove memberships when user is no longer in external group", async () => {
const user = await buildUser();
const team = await user.$get("team")!;
const authenticationProvider = (await AuthenticationProvider.findOne({
where: { teamId: user.teamId },
}))!;
// Initial sync with two groups
await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: [
{ id: "ext-1", name: "Engineering" },
{ id: "ext-2", name: "Design" },
],
})
);
// Second sync with only one group
const result = await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: [{ id: "ext-1", name: "Engineering" }],
})
);
expect(result.membershipsRemoved).toEqual(1);
const designGroup = await Group.findOne({
where: { teamId: user.teamId, name: "Design" },
});
const membership = await GroupUser.findOne({
where: { groupId: designGroup!.id, userId: user.id },
});
expect(membership).toBeNull();
const events = await Event.findAll({
where: { teamId: user.teamId, modelId: designGroup!.id },
order: [["createdAt", "ASC"]],
});
expect(
events
.filter((event) => event.name !== "groups.create")
.map((event) => event.name)
).toEqual(["groups.add_user", "groups.remove_user"]);
});
it("should not create duplicate memberships on re-sync", async () => {
const user = await buildUser();
const team = await user.$get("team")!;
const authenticationProvider = (await AuthenticationProvider.findOne({
where: { teamId: user.teamId },
}))!;
const groups = [{ id: "ext-1", name: "Engineering" }];
await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: groups,
})
);
const result = await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: groups,
})
);
expect(result.groupsCreated).toEqual(0);
expect(result.membershipsAdded).toEqual(0);
const memberships = await GroupUser.findAll({
where: { userId: user.id },
});
expect(memberships).toHaveLength(1);
});
it("should remove all memberships when user has no external groups", async () => {
const user = await buildUser();
const team = await user.$get("team")!;
const authenticationProvider = (await AuthenticationProvider.findOne({
where: { teamId: user.teamId },
}))!;
await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: [{ id: "ext-1", name: "Engineering" }],
})
);
const result = await sequelize.transaction(async (transaction) =>
groupsSyncer(createContext({ user, transaction, ip }), {
user,
team: team!,
authenticationProvider,
externalGroups: [],
})
);
expect(result.membershipsRemoved).toEqual(1);
});
});