Skip to content

Commit

Permalink
N21-1464 extends provisioning of group types
Browse files Browse the repository at this point in the history
  • Loading branch information
arnegns committed Dec 6, 2023
1 parent 1df15ec commit 60afca7
Show file tree
Hide file tree
Showing 17 changed files with 331 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export enum GroupTypeResponse {
CLASS = 'class',
COURSE = 'course',
OTHER = 'other',
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {

const typeMapping: Record<GroupTypes, GroupTypeResponse> = {
[GroupTypes.CLASS]: GroupTypeResponse.CLASS,
[GroupTypes.COURSE]: GroupTypeResponse.COURSE,
[GroupTypes.OTHER]: GroupTypeResponse.OTHER,
};

export class GroupResponseMapper {
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/modules/group/domain/group-types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export enum GroupTypes {
CLASS = 'class',
COURSE = 'course',
OTHER = 'other',
}
2 changes: 2 additions & 0 deletions apps/server/src/modules/group/entity/group.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { GroupValidPeriodEntity } from './group-valid-period.entity';

export enum GroupEntityTypes {
CLASS = 'class',
COURSE = 'course',
OTHER = 'other',
}

export interface GroupEntityProps {
Expand Down
6 changes: 5 additions & 1 deletion apps/server/src/modules/group/repo/group-domain.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import { GroupEntity, GroupEntityProps, GroupEntityTypes, GroupUserEntity, Group

const GroupEntityTypesToGroupTypesMapping: Record<GroupEntityTypes, GroupTypes> = {
[GroupEntityTypes.CLASS]: GroupTypes.CLASS,
[GroupEntityTypes.COURSE]: GroupTypes.COURSE,
[GroupEntityTypes.OTHER]: GroupTypes.OTHER,
};

const GroupTypesToGroupEntityTypesMapping: Record<GroupTypes, GroupEntityTypes> = {
export const GroupTypesToGroupEntityTypesMapping: Record<GroupTypes, GroupEntityTypes> = {
[GroupTypes.CLASS]: GroupEntityTypes.CLASS,
[GroupTypes.COURSE]: GroupEntityTypes.COURSE,
[GroupTypes.OTHER]: GroupEntityTypes.OTHER,
};

export class GroupDomainMapper {
Expand Down
75 changes: 66 additions & 9 deletions apps/server/src/modules/group/repo/group.repo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,16 @@ describe('GroupRepo', () => {
});
});

describe('findByUser', () => {
describe('findByUserAndGroupTypes', () => {
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() }],
});
groups[1].type = GroupEntityTypes.COURSE;
groups[2].type = GroupEntityTypes.OTHER;

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

Expand All @@ -114,12 +116,36 @@ describe('GroupRepo', () => {
it('should return the groups', async () => {
const { user, groups } = await setup();

const result: Group[] = await repo.findByUser(user);
const result: Group[] = await repo.findByUserAndGroupTypes(user, [
GroupTypes.CLASS,
GroupTypes.COURSE,
GroupTypes.OTHER,
]);

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

it('should return only groups of the given group types', async () => {
const { user } = await setup();

const result: Group[] = await repo.findByUserAndGroupTypes(user, [GroupTypes.CLASS]);

expect(result).toEqual([expect.objectContaining<Partial<Group>>({ type: GroupTypes.CLASS })]);
});

describe('when no group type is given', () => {
it('should return all groups', async () => {
const { user, groups } = await setup();

const result: Group[] = await repo.findByUserAndGroupTypes(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', () => {
Expand All @@ -140,21 +166,27 @@ describe('GroupRepo', () => {
it('should return an empty array', async () => {
const { user } = await setup();

const result: Group[] = await repo.findByUser(user);
const result: Group[] = await repo.findByUserAndGroupTypes(user, [
GroupTypes.CLASS,
GroupTypes.COURSE,
GroupTypes.OTHER,
]);

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

describe('findClassesForSchool', () => {
describe('when groups of type class for the school exist', () => {
describe('findBySchoolIdAndGroupTypes', () => {
describe('when groups for the school exist', () => {
const setup = async () => {
const school: SchoolEntity = schoolFactory.buildWithId();
const groups: GroupEntity[] = groupEntityFactory.buildListWithId(3, {
type: GroupEntityTypes.CLASS,
organization: school,
});
groups[1].type = GroupEntityTypes.COURSE;
groups[2].type = GroupEntityTypes.OTHER;

const otherSchool: SchoolEntity = schoolFactory.buildWithId();
const otherGroups: GroupEntity[] = groupEntityFactory.buildListWithId(2, {
Expand All @@ -172,21 +204,46 @@ describe('GroupRepo', () => {
};
};

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

const result: Group[] = await repo.findClassesForSchool(school.id);
const result: Group[] = await repo.findBySchoolIdAndGroupTypes(school.id, [
GroupTypes.CLASS,
GroupTypes.COURSE,
GroupTypes.OTHER,
]);

expect(result).toHaveLength(groups.length);
});

it('should not return groups from another school', async () => {
const { school, otherSchool } = await setup();

const result: Group[] = await repo.findClassesForSchool(school.id);
const result: Group[] = await repo.findBySchoolIdAndGroupTypes(school.id, [
GroupTypes.CLASS,
GroupTypes.COURSE,
]);

expect(result.map((group) => group.organizationId)).not.toContain(otherSchool.id);
});

it('should return only groups of the given group types', async () => {
const { school } = await setup();

const result: Group[] = await repo.findBySchoolIdAndGroupTypes(school.id, [GroupTypes.CLASS]);

expect(result).toEqual([expect.objectContaining<Partial<Group>>({ type: GroupTypes.CLASS })]);
});

describe('when no group type is given', () => {
it('should return all groups', async () => {
const { school, groups } = await setup();

const result: Group[] = await repo.findBySchoolIdAndGroupTypes(school.id);

expect(result).toHaveLength(groups.length);
});
});
});

describe('when no group exists', () => {
Expand All @@ -204,7 +261,7 @@ describe('GroupRepo', () => {
it('should return an empty array', async () => {
const { school } = await setup();

const result: Group[] = await repo.findClassesForSchool(school.id);
const result: Group[] = await repo.findBySchoolIdAndGroupTypes(school.id, [GroupTypes.CLASS]);

expect(result).toHaveLength(0);
});
Expand Down
37 changes: 24 additions & 13 deletions apps/server/src/modules/group/repo/group.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { EntityManager, ObjectId } from '@mikro-orm/mongodb';
import { Injectable } from '@nestjs/common';
import { type UserDO } from '@shared/domain/domainobject';
import { EntityId } from '@shared/domain/types';
import { Group, GroupProps } from '../domain';
import { Scope } from '@shared/repo';
import { Group, GroupProps, GroupTypes } from '../domain';
import { GroupEntity, GroupEntityProps, GroupEntityTypes } from '../entity';
import { GroupDomainMapper } from './group-domain.mapper';
import { GroupDomainMapper, GroupTypesToGroupEntityTypesMapping } from './group-domain.mapper';
import { GroupScope } from './group.scope';

@Injectable()
export class GroupRepo {
Expand Down Expand Up @@ -43,12 +45,17 @@ 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) },
});
public async findByUserAndGroupTypes(user: UserDO, groupTypes?: GroupTypes[]): Promise<Group[]> {
let groupEntityTypes: GroupEntityTypes[] | undefined;
if (groupTypes) {
groupEntityTypes = groupTypes.map((type: GroupTypes) => GroupTypesToGroupEntityTypesMapping[type]);
}

const scope: Scope<GroupEntity> = new GroupScope().byUserId(user.id).byTypes(groupEntityTypes);

const entities: GroupEntity[] = await this.em.find(GroupEntity, scope.query);

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

return new Group(props);
Expand All @@ -57,13 +64,17 @@ export class GroupRepo {
return domainObjects;
}

public async findClassesForSchool(schoolId: EntityId): Promise<Group[]> {
const entities: GroupEntity[] = await this.em.find(GroupEntity, {
type: GroupEntityTypes.CLASS,
organization: schoolId,
});
public async findBySchoolIdAndGroupTypes(schoolId: EntityId, groupTypes?: GroupTypes[]): Promise<Group[]> {
let groupEntityTypes: GroupEntityTypes[] | undefined;
if (groupTypes) {
groupEntityTypes = groupTypes.map((type: GroupTypes) => GroupTypesToGroupEntityTypesMapping[type]);
}

const scope: Scope<GroupEntity> = new GroupScope().byOrganizationId(schoolId).byTypes(groupEntityTypes);

const entities: GroupEntity[] = await this.em.find(GroupEntity, scope.query);

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

return new Group(props);
Expand Down
82 changes: 82 additions & 0 deletions apps/server/src/modules/group/repo/group.scope.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { ObjectId } from '@mikro-orm/mongodb';
import { GroupEntityTypes } from '../entity';
import { GroupScope } from './group.scope';

describe(GroupScope.name, () => {
let scope: GroupScope;

beforeEach(() => {
scope = new GroupScope();
scope.allowEmptyQuery(true);
});

describe('byTypes', () => {
describe('when types is undefined', () => {
it('should not add query', () => {
scope.byTypes(undefined);

expect(scope.query).toEqual({});
});
});

describe('when types is defined', () => {
it('should add query', () => {
scope.byTypes([GroupEntityTypes.COURSE, GroupEntityTypes.CLASS]);

expect(scope.query).toEqual({ type: { $in: [GroupEntityTypes.COURSE, GroupEntityTypes.CLASS] } });
});
});
});

describe('byOrganizationId', () => {
describe('when id is undefined', () => {
it('should not add query', () => {
scope.byOrganizationId(undefined);

expect(scope.query).toEqual({});
});
});

describe('when id is defined', () => {
const setup = () => {
return {
id: new ObjectId().toHexString(),
};
};

it('should add query', () => {
const { id } = setup();

scope.byOrganizationId(id);

expect(scope.query).toEqual({ organization: id });
});
});
});

describe('byUserId', () => {
describe('when id is undefined', () => {
it('should not add query', () => {
scope.byUserId(undefined);

expect(scope.query).toEqual({});
});
});

describe('when id is defined', () => {
const setup = () => {
return {
id: new ObjectId().toHexString(),
};
};

it('should add query', () => {
const { id } = setup();

scope.byUserId(id);

expect(scope.query).toEqual({ users: { user: new ObjectId(id) } });
});
});
});
});
27 changes: 27 additions & 0 deletions apps/server/src/modules/group/repo/group.scope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ObjectId } from '@mikro-orm/mongodb';
import { EntityId } from '@shared/domain/types';
import { Scope } from '@shared/repo';
import { GroupEntity, GroupEntityTypes } from '../entity';

export class GroupScope extends Scope<GroupEntity> {
byTypes(types: GroupEntityTypes[] | undefined): this {
if (types) {
this.addQuery({ type: { $in: types } });
}
return this;
}

byOrganizationId(id: EntityId | undefined): this {
if (id) {
this.addQuery({ organization: id });
}
return this;
}

byUserId(id: EntityId | undefined): this {
if (id) {
this.addQuery({ users: { user: new ObjectId(id) } });
}
return this;
}
}
Loading

0 comments on commit 60afca7

Please sign in to comment.