Skip to content

Commit

Permalink
N21-1207 adds get /groups/:id
Browse files Browse the repository at this point in the history
  • Loading branch information
arnegns committed Oct 17, 2023
1 parent 2fc165e commit 1f25827
Show file tree
Hide file tree
Showing 22 changed files with 673 additions and 11 deletions.
8 changes: 8 additions & 0 deletions apps/server/src/modules/authorization/rule-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
TaskRule,
TeamRule,
UserRule,
GroupRule,
} from '@shared/domain/rules';
import { UserLoginMigrationRule } from '@shared/domain/rules/user-login-migration.rule';
import { courseFactory, setupEntities, userFactory } from '@shared/testing';
Expand All @@ -33,6 +34,7 @@ describe('RuleManager', () => {
let boardDoRule: DeepMocked<BoardDoRule>;
let contextExternalToolRule: DeepMocked<ContextExternalToolRule>;
let userLoginMigrationRule: DeepMocked<UserLoginMigrationRule>;
let groupRule: DeepMocked<GroupRule>;

beforeAll(async () => {
await setupEntities();
Expand All @@ -52,6 +54,7 @@ describe('RuleManager', () => {
{ provide: BoardDoRule, useValue: createMock<BoardDoRule>() },
{ provide: ContextExternalToolRule, useValue: createMock<ContextExternalToolRule>() },
{ provide: UserLoginMigrationRule, useValue: createMock<UserLoginMigrationRule>() },
{ provide: GroupRule, useValue: createMock<GroupRule>() },
],
}).compile();

Expand All @@ -68,6 +71,7 @@ describe('RuleManager', () => {
boardDoRule = await module.get(BoardDoRule);
contextExternalToolRule = await module.get(ContextExternalToolRule);
userLoginMigrationRule = await module.get(UserLoginMigrationRule);
groupRule = await module.get(GroupRule);
});

afterEach(() => {
Expand Down Expand Up @@ -98,6 +102,7 @@ describe('RuleManager', () => {
boardDoRule.isApplicable.mockReturnValueOnce(false);
contextExternalToolRule.isApplicable.mockReturnValueOnce(false);
userLoginMigrationRule.isApplicable.mockReturnValueOnce(false);
groupRule.isApplicable.mockReturnValueOnce(false);

return { user, object, context };
};
Expand All @@ -119,6 +124,7 @@ describe('RuleManager', () => {
expect(boardDoRule.isApplicable).toBeCalled();
expect(contextExternalToolRule.isApplicable).toBeCalled();
expect(userLoginMigrationRule.isApplicable).toBeCalled();
expect(groupRule.isApplicable).toBeCalled();
});

it('should return CourseRule', () => {
Expand Down Expand Up @@ -148,6 +154,7 @@ describe('RuleManager', () => {
boardDoRule.isApplicable.mockReturnValueOnce(false);
contextExternalToolRule.isApplicable.mockReturnValueOnce(false);
userLoginMigrationRule.isApplicable.mockReturnValueOnce(false);
groupRule.isApplicable.mockReturnValueOnce(false);

return { user, object, context };
};
Expand Down Expand Up @@ -177,6 +184,7 @@ describe('RuleManager', () => {
boardDoRule.isApplicable.mockReturnValueOnce(false);
contextExternalToolRule.isApplicable.mockReturnValueOnce(false);
userLoginMigrationRule.isApplicable.mockReturnValueOnce(false);
groupRule.isApplicable.mockReturnValueOnce(false);

return { user, object, context };
};
Expand Down
5 changes: 4 additions & 1 deletion apps/server/src/modules/authorization/rule-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
TaskRule,
TeamRule,
UserRule,
GroupRule,
} from '@shared/domain/rules';
import { ContextExternalToolRule } from '@shared/domain/rules/context-external-tool.rule';
import { UserLoginMigrationRule } from '@shared/domain/rules/user-login-migration.rule';
Expand All @@ -33,7 +34,8 @@ export class RuleManager {
private readonly schoolExternalToolRule: SchoolExternalToolRule,
private readonly boardDoRule: BoardDoRule,
private readonly contextExternalToolRule: ContextExternalToolRule,
private readonly userLoginMigrationRule: UserLoginMigrationRule
private readonly userLoginMigrationRule: UserLoginMigrationRule,
private readonly groupRule: GroupRule
) {
this.rules = [
this.courseRule,
Expand All @@ -48,6 +50,7 @@ export class RuleManager {
this.boardDoRule,
this.contextExternalToolRule,
this.userLoginMigrationRule,
this.groupRule,
];
}

Expand Down
118 changes: 118 additions & 0 deletions apps/server/src/modules/group/controller/api-test/group.api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { ClassEntity } from '@src/modules/class/entity';
import { classEntityFactory } from '@src/modules/class/entity/testing/factory/class.entity.factory';
import { ServerTestModule } from '@src/modules/server';
import { ObjectId } from 'bson';
import { GroupEntity, GroupEntityTypes } from '../../entity';
import { ClassRootType } from '../../uc/dto/class-root-type';
import { ClassInfoSearchListResponse, ClassSortBy } from '../dto';
Expand Down Expand Up @@ -157,4 +158,121 @@ describe('Group (API)', () => {
});
});
});

describe('getGroup', () => {
describe('when user with the required permission requests a group', () => {
describe('when group exists', () => {
const setup = async () => {
const school: SchoolEntity = schoolFactory.buildWithId();
const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher({ school });

const group: GroupEntity = groupEntityFactory.buildWithId({
users: [
{
user: teacherUser,
role: teacherUser.roles[0],
},
],
});

await em.persistAndFlush([teacherAccount, teacherUser, group]);
em.clear();

const loggedInClient = await testApiClient.login(teacherAccount);

return {
loggedInClient,
group,
teacherUser,
};
};

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

const response = await loggedInClient.get(`${group.id}`);

expect(response.status).toEqual(HttpStatus.OK);
expect(response.body).toEqual({
id: group.id,
name: group.name,
type: group.type,
users: [
{
id: teacherUser.id,
firstName: teacherUser.firstName,
lastName: teacherUser.lastName,
role: teacherUser.roles[0].name,
},
],
externalSource: {
externalId: group.externalSource?.externalId,
systemId: group.externalSource?.system.id,
},
});
});
});

describe('when group does not exist', () => {
const setup = async () => {
const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher();

await em.persistAndFlush([teacherAccount, teacherUser]);
em.clear();

const loggedInClient = await testApiClient.login(teacherAccount);

return {
loggedInClient,
};
};

it('should return not found', async () => {
const { loggedInClient } = await setup();

const response = await loggedInClient.get(`${new ObjectId().toHexString()}`);

expect(response.status).toEqual(HttpStatus.NOT_FOUND);
expect(response.body).toEqual({
code: HttpStatus.NOT_FOUND,
message: 'Not Found',
title: 'Not Found',
type: 'NOT_FOUND',
});
});
});
});

describe('when user without the required permission requests a group', () => {
const setup = async () => {
const { studentAccount, studentUser } = UserAndAccountTestFactory.buildStudent();

const group: GroupEntity = groupEntityFactory.buildWithId();

await em.persistAndFlush([studentAccount, studentUser, group]);
em.clear();

const loggedInClient = await testApiClient.login(studentAccount);

return {
loggedInClient,
groupId: group.id,
};
};

it('should return forbidden', async () => {
const { loggedInClient, groupId } = await setup();

const response = await loggedInClient.get(`${groupId}`);

expect(response.status).toEqual(HttpStatus.FORBIDDEN);
expect(response.body).toEqual({
code: HttpStatus.FORBIDDEN,
message: 'Forbidden',
title: 'Forbidden',
type: 'FORBIDDEN',
});
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsMongoId } from 'class-validator';

export class GroupIdParams {
@IsMongoId()
@ApiProperty({ nullable: false, required: true })
groupId!: string;
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './class-sort-params';
export * from './group-id-params';
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ApiProperty } from '@nestjs/swagger';

export class ExternalSourceResponse {
@ApiProperty()
externalId: string;

@ApiProperty()
systemId: string;

constructor(props: ExternalSourceResponse) {
this.externalId = props.externalId;
this.systemId = props.systemId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum GroupTypeResponse {
CLASS = 'class',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
import { RoleName } from '@shared/domain';

export class GroupUserResponse {
@ApiProperty()
id: string;

@ApiProperty()
firstName: string;

@ApiProperty()
lastName: string;

@ApiProperty()
role: RoleName;

constructor(user: GroupUserResponse) {
this.id = user.id;
this.firstName = user.firstName;
this.lastName = user.lastName;
this.role = user.role;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { ExternalSourceResponse } from './external-source.response';
import { GroupTypeResponse } from './group-type.response';
import { GroupUserResponse } from './group-user.response';

export class GroupResponse {
@ApiProperty()
id: string;

@ApiProperty()
name: string;

@ApiProperty()
type: GroupTypeResponse;

@ApiProperty({ type: [GroupUserResponse] })
users: GroupUserResponse[];

@ApiPropertyOptional()
externalSource?: ExternalSourceResponse;

@ApiPropertyOptional()
organizationId?: string;

constructor(group: GroupResponse) {
this.id = group.id;
this.name = group.name;
this.type = group.type;
this.users = group.users;
this.externalSource = group.externalSource;
this.organizationId = group.organizationId;
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export * from './class-info.response';
export * from './class-info-search-list.response';
export * from './external-source.response';
export * from './group.response';
export * from './group-type.response';
export * from './group-user.response';
22 changes: 19 additions & 3 deletions apps/server/src/modules/group/controller/group.controller.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Controller, Get, HttpStatus, Query } from '@nestjs/common';
import { Controller, Get, HttpStatus, Param, Query } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { PaginationParams } from '@shared/controller';
import { Page } from '@shared/domain';
import { ErrorResponse } from '@src/core/error/dto';
import { ICurrentUser } from '@src/modules/authentication';
import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator';
import { GroupUc } from '../uc';
import { ClassInfoDto } from '../uc/dto';
import { ClassInfoSearchListResponse, ClassSortParams } from './dto';
import { ClassInfoDto, ResolvedGroupDto } from '../uc/dto';
import { ClassInfoSearchListResponse, ClassSortParams, GroupIdParams, GroupResponse } from './dto';
import { GroupResponseMapper } from './mapper';

@ApiTags('Group')
Expand Down Expand Up @@ -43,4 +43,20 @@ export class GroupController {

return response;
}

@Get('/:groupId')
@ApiOperation({ summary: 'Get a group by id.' })
@ApiResponse({ status: HttpStatus.OK, type: GroupResponse })
@ApiResponse({ status: '4XX', type: ErrorResponse })
@ApiResponse({ status: '5XX', type: ErrorResponse })
public async getGroup(
@CurrentUser() currentUser: ICurrentUser,
@Param() params: GroupIdParams
): Promise<GroupResponse> {
const group: ResolvedGroupDto = await this.groupUc.getGroup(currentUser.userId, params.groupId);

const response: GroupResponse = GroupResponseMapper.mapToGroupResponse(group);

return response;
}
}
Loading

0 comments on commit 1f25827

Please sign in to comment.