Ctrl K
ComunidadGuías…

server/commands/groupsSyncer.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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);
  });
});