server/models/Group.test.ts
Rama main · réplica de lectura del repositorio original.
import { GroupUser } from "@server/models";
import { sequelize } from "@server/storage/database";
import { buildGroup, buildGroupUser, buildUser } from "@server/test/factories";
describe("Group", () => {
describe("isUniqueNameInTeam", () => {
it("rejects a name already used in the team, ignoring case", async () => {
const group = await buildGroup({ name: "Engineering" });
await expect(
buildGroup({ teamId: group.teamId, name: "engineering" })
).rejects.toThrow("The name of this group is already in use");
});
it("allows the same name for groups with different externalIds", async () => {
const user = await buildUser();
await buildGroup({
teamId: user.teamId,
name: "All Company",
externalId: "ext-1",
});
const second = await buildGroup({
teamId: user.teamId,
name: "All Company",
externalId: "ext-2",
});
expect(second.name).toEqual("All Company");
await expect(
buildGroup({
teamId: user.teamId,
name: "all company",
externalId: "ext-2",
})
).rejects.toThrow("The name of this group is already in use");
});
});
describe("updatedAt", () => {
const previousUpdatedAt = new Date("2020-01-01T00:00:00.000Z");
it("updates when a user is added", async () => {
const group = await buildGroup();
await sequelize
.getQueryInterface()
.bulkUpdate(
"groups",
{ updatedAt: previousUpdatedAt },
{ id: group.id },
{}
);
await buildGroupUser({ groupId: group.id, teamId: group.teamId });
await group.reload();
expect(group.updatedAt.getTime()).toBeGreaterThan(
previousUpdatedAt.getTime()
);
});
it("updates when a user is removed by a bulk destroy", async () => {
const group = await buildGroup();
const groupUser = await buildGroupUser({
groupId: group.id,
teamId: group.teamId,
});
await sequelize
.getQueryInterface()
.bulkUpdate(
"groups",
{ updatedAt: previousUpdatedAt },
{ id: group.id },
{}
);
await GroupUser.destroy({
where: { groupId: group.id, userId: groupUser.userId },
individualHooks: true,
});
await group.reload();
expect(group.updatedAt.getTime()).toBeGreaterThan(
previousUpdatedAt.getTime()
);
});
it("does not update when a membership creation is rolled back", async () => {
const group = await buildGroup();
const user = await buildUser({ teamId: group.teamId });
await sequelize
.getQueryInterface()
.bulkUpdate(
"groups",
{ updatedAt: previousUpdatedAt },
{ id: group.id },
{}
);
await expect(
sequelize.transaction(async (transaction) => {
await GroupUser.create(
{
groupId: group.id,
userId: user.id,
createdById: user.id,
},
{ transaction }
);
throw new Error("rollback");
})
).rejects.toThrow("rollback");
await group.reload();
expect(group.updatedAt).toEqual(previousUpdatedAt);
expect(
await GroupUser.count({
where: { groupId: group.id, userId: user.id },
})
).toBe(0);
});
});
describe("memberCount", () => {
it("returns 0 for a group with no members", async () => {
const group = await buildGroup();
expect(await group.memberCount).toEqual(0);
});
it("counts active members", async () => {
const group = await buildGroup();
await buildGroupUser({ groupId: group.id, teamId: group.teamId });
await buildGroupUser({ groupId: group.id, teamId: group.teamId });
expect(await group.memberCount).toEqual(2);
});
it("excludes suspended members", async () => {
const group = await buildGroup();
await buildGroupUser({ groupId: group.id, teamId: group.teamId });
const suspendedUser = await buildUser({
teamId: group.teamId,
suspendedAt: new Date(),
});
await buildGroupUser({
groupId: group.id,
teamId: group.teamId,
userId: suspendedUser.id,
});
expect(await group.memberCount).toEqual(1);
});
it("excludes soft-deleted members", async () => {
const group = await buildGroup();
await buildGroupUser({ groupId: group.id, teamId: group.teamId });
const deletedUser = await buildUser({ teamId: group.teamId });
await buildGroupUser({
groupId: group.id,
teamId: group.teamId,
userId: deletedUser.id,
});
await deletedUser.destroy();
expect(await group.memberCount).toEqual(1);
});
it("invalidates the cached count when a member is suspended", async () => {
const group = await buildGroup();
const groupUser = await buildGroupUser({
groupId: group.id,
teamId: group.teamId,
});
await buildGroupUser({ groupId: group.id, teamId: group.teamId });
// Prime the cache.
expect(await group.memberCount).toEqual(2);
const user = (await groupUser.$get("user"))!;
await user.update({ suspendedAt: new Date() });
expect(await group.memberCount).toEqual(1);
});
it("invalidates the cached count when a suspended member is restored", async () => {
const group = await buildGroup();
const suspendedUser = await buildUser({
teamId: group.teamId,
suspendedAt: new Date(),
});
await buildGroupUser({
groupId: group.id,
teamId: group.teamId,
userId: suspendedUser.id,
});
await buildGroupUser({ groupId: group.id, teamId: group.teamId });
// Prime the cache (suspended user is excluded).
expect(await group.memberCount).toEqual(1);
await suspendedUser.update({ suspendedAt: null });
expect(await group.memberCount).toEqual(2);
});
});
});