Skip to content

Commit

Permalink
N21 1212 remove user from group (#4454)
Browse files Browse the repository at this point in the history
* remove user from group:
* remove emptx groups
  • Loading branch information
IgorCapCoder authored Oct 6, 2023
1 parent 6f7f09c commit d3a0975
Show file tree
Hide file tree
Showing 11 changed files with 553 additions and 14 deletions.
138 changes: 138 additions & 0 deletions apps/server/src/modules/group/domain/group.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { groupFactory, roleFactory, userDoFactory } from '@shared/testing';

import { ObjectId } from 'bson';
import { RoleReference, UserDO } from '@shared/domain';
import { Group } from './group';
import { GroupUser } from './group-user';

describe('Group (Domain Object)', () => {
describe('removeUser', () => {
describe('when the user is in the group', () => {
const setup = () => {
const user: UserDO = userDoFactory.buildWithId();
const groupUser1 = new GroupUser({
userId: user.id as string,
roleId: new ObjectId().toHexString(),
});
const groupUser2 = new GroupUser({
userId: new ObjectId().toHexString(),
roleId: new ObjectId().toHexString(),
});
const group: Group = groupFactory.build({
users: [groupUser1, groupUser2],
});

return {
user,
groupUser1,
groupUser2,
group,
};
};

it('should remove the user', () => {
const { user, group, groupUser1 } = setup();

group.removeUser(user);

expect(group.users).not.toContain(groupUser1);
});

it('should keep all other users', () => {
const { user, group, groupUser2 } = setup();

group.removeUser(user);

expect(group.users).toContain(groupUser2);
});
});

describe('when the user is not in the group', () => {
const setup = () => {
const user: UserDO = userDoFactory.buildWithId();
const groupUser2 = new GroupUser({
userId: new ObjectId().toHexString(),
roleId: new ObjectId().toHexString(),
});
const group: Group = groupFactory.build({
users: [groupUser2],
});

return {
user,
groupUser2,
group,
};
};

it('should do nothing', () => {
const { user, group, groupUser2 } = setup();

group.removeUser(user);

expect(group.users).toEqual([groupUser2]);
});
});

describe('when the group is empty', () => {
const setup = () => {
const user: UserDO = userDoFactory.buildWithId();
const group: Group = groupFactory.build({ users: [] });

return {
user,
group,
};
};

it('should stay empty', () => {
const { user, group } = setup();

group.removeUser(user);

expect(group.users).toEqual([]);
});
});
});

describe('isEmpty', () => {
describe('when no users in group exist', () => {
const setup = () => {
const group: Group = groupFactory.build({ users: [] });

return {
group,
};
};

it('should return true', () => {
const { group } = setup();

const isEmpty = group.isEmpty();

expect(isEmpty).toEqual(true);
});
});

describe('when users in group exist', () => {
const setup = () => {
const externalUserId = 'externalUserId';
const role: RoleReference = roleFactory.buildWithId();
const user: UserDO = userDoFactory.buildWithId({ roles: [role], externalId: externalUserId });
const group: Group = groupFactory.build({ users: [{ userId: user.id as string, roleId: role.id }] });

return {
group,
};
};

it('should return false', () => {
const { group } = setup();

const isEmpty = group.isEmpty();

expect(isEmpty).toEqual(false);
});
});
});
});
10 changes: 9 additions & 1 deletion apps/server/src/modules/group/domain/group.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EntityId, ExternalSource } from '@shared/domain';
import { EntityId, ExternalSource, type UserDO } from '@shared/domain';
import { AuthorizableObject, DomainObject } from '@shared/domain/domain-object';
import { GroupTypes } from './group-types';
import { GroupUser } from './group-user';
Expand Down Expand Up @@ -37,4 +37,12 @@ export class Group extends DomainObject<GroupProps> {
get organizationId(): string | undefined {
return this.props.organizationId;
}

removeUser(user: UserDO): void {
this.props.users = this.props.users.filter((groupUser: GroupUser): boolean => groupUser.userId !== user.id);
}

isEmpty(): boolean {
return this.props.users.length === 0;
}
}
68 changes: 66 additions & 2 deletions apps/server/src/modules/group/repo/group.repo.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { EntityManager, ObjectId } from '@mikro-orm/mongodb';
import { Test, TestingModule } from '@nestjs/testing';
import { ExternalSource, SchoolEntity } from '@shared/domain';
import { ExternalSource, SchoolEntity, UserDO, User } from '@shared/domain';
import { MongoMemoryDatabaseModule } from '@shared/infra/database';
import { cleanupCollections, groupEntityFactory, groupFactory, schoolFactory } from '@shared/testing';
import {
cleanupCollections,
groupEntityFactory,
groupFactory,
roleFactory,
schoolFactory,
userDoFactory,
userFactory,
} from '@shared/testing';
import { Group, GroupProps, GroupTypes, GroupUser } from '../domain';
import { GroupEntity, GroupEntityTypes } from '../entity';
import { GroupRepo } from './group.repo';
Expand Down Expand Up @@ -82,6 +90,62 @@ describe('GroupRepo', () => {
});
});

describe('findByUser', () => {
describe('when the user has groups', () => {
const setup = async () => {
const userEntity: User = userFactory.buildWithId();
const user: UserDO = userDoFactory.build({ id: userEntity.id });
const groups: GroupEntity[] = groupEntityFactory.buildListWithId(3, {
users: [{ user: userEntity, role: roleFactory.buildWithId() }],
});

const otherGroups: GroupEntity[] = groupEntityFactory.buildListWithId(2);

await em.persistAndFlush([userEntity, ...groups, ...otherGroups]);
em.clear();

return {
user,
groups,
};
};

it('should return the groups', async () => {
const { user, groups } = await setup();

const result: Group[] = await repo.findByUser(user);

expect(result.map((group) => group.id).sort((a, b) => a.localeCompare(b))).toEqual(
groups.map((group) => group.id).sort((a, b) => a.localeCompare(b))
);
});
});

describe('when the user has no groups exists', () => {
const setup = async () => {
const userEntity: User = userFactory.buildWithId();
const user: UserDO = userDoFactory.build({ id: userEntity.id });

const otherGroups: GroupEntity[] = groupEntityFactory.buildListWithId(2);

await em.persistAndFlush([userEntity, ...otherGroups]);
em.clear();

return {
user,
};
};

it('should return an empty array', async () => {
const { user } = await setup();

const result: Group[] = await repo.findByUser(user);

expect(result).toHaveLength(0);
});
});
});

describe('findClassesForSchool', () => {
describe('when groups of type class for the school exist', () => {
const setup = async () => {
Expand Down
17 changes: 16 additions & 1 deletion apps/server/src/modules/group/repo/group.repo.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EntityManager } from '@mikro-orm/mongodb';
import { EntityManager, ObjectId } from '@mikro-orm/mongodb';
import { Injectable } from '@nestjs/common';
import { EntityId } from '@shared/domain/types';
import { type UserDO } from '@shared/domain';
import { Group, GroupProps } from '../domain';
import { GroupEntity, GroupEntityProps, GroupEntityTypes } from '../entity';
import { GroupDomainMapper } from './group-domain.mapper';
Expand Down Expand Up @@ -42,6 +43,20 @@ export class GroupRepo {
return domainObject;
}

public async findByUser(user: UserDO): Promise<Group[]> {
const entities: GroupEntity[] = await this.em.find(GroupEntity, {
users: { user: new ObjectId(user.id) },
});

const domainObjects = entities.map((entity) => {
const props: GroupProps = GroupDomainMapper.mapEntityToDomainObjectProperties(entity);

return new Group(props);
});

return domainObjects;
}

public async findClassesForSchool(schoolId: EntityId): Promise<Group[]> {
const entities: GroupEntity[] = await this.em.find(GroupEntity, {
type: GroupEntityTypes.CLASS,
Expand Down
47 changes: 46 additions & 1 deletion apps/server/src/modules/group/service/group.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { ObjectId } from '@mikro-orm/mongodb';
import { Test, TestingModule } from '@nestjs/testing';
import { NotFoundLoggableException } from '@shared/common/loggable-exception';
import { groupFactory } from '@shared/testing';
import { groupFactory, userDoFactory } from '@shared/testing';
import { UserDO } from '@shared/domain';
import { Group } from '../domain';
import { GroupRepo } from '../repo';
import { GroupService } from './group.service';
Expand Down Expand Up @@ -120,6 +121,50 @@ describe('GroupService', () => {
});
});

describe('findByUser', () => {
describe('when groups with the user exists', () => {
const setup = () => {
const user: UserDO = userDoFactory.buildWithId();
const groups: Group[] = groupFactory.buildList(2);

groupRepo.findByUser.mockResolvedValue(groups);

return {
user,
groups,
};
};

it('should return the groups', async () => {
const { user, groups } = setup();

const result: Group[] = await service.findByUser(user);

expect(result).toEqual(groups);
});
});

describe('when no groups with the user exists', () => {
const setup = () => {
const user: UserDO = userDoFactory.buildWithId();

groupRepo.findByUser.mockResolvedValue([]);

return {
user,
};
};

it('should return empty array', async () => {
const { user } = setup();

const result: Group[] = await service.findByUser(user);

expect(result).toEqual([]);
});
});
});

describe('findClassesForSchool', () => {
describe('when the school has groups of type class', () => {
const setup = () => {
Expand Down
8 changes: 7 additions & 1 deletion apps/server/src/modules/group/service/group.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { NotFoundLoggableException } from '@shared/common/loggable-exception';
import { EntityId } from '@shared/domain';
import { EntityId, type UserDO } from '@shared/domain';
import { AuthorizationLoaderServiceGeneric } from '@src/modules/authorization';
import { Group } from '../domain';
import { GroupRepo } from '../repo';
Expand Down Expand Up @@ -31,6 +31,12 @@ export class GroupService implements AuthorizationLoaderServiceGeneric<Group> {
return group;
}

public async findByUser(user: UserDO): Promise<Group[]> {
const groups: Group[] = await this.groupRepo.findByUser(user);

return groups;
}

public async findClassesForSchool(schoolId: EntityId): Promise<Group[]> {
const group: Group[] = await this.groupRepo.findClassesForSchool(schoolId);

Expand Down
Loading

0 comments on commit d3a0975

Please sign in to comment.