Skip to content

Commit

Permalink
N21-1623 fix class visibility for course (#4659)
Browse files Browse the repository at this point in the history
* class visibility fix 
* use new schoolService
  • Loading branch information
IgorCapCoder authored Dec 21, 2023
1 parent 188b14d commit 612b029
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum ClassRequestContext {
COURSE = 'course',
CLASS_OVERVIEW = 'class-overview',
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './class-sort-by.enum';
export * from './school-year-query-type.enum';
export { ClassRequestContext } from './class-request-context.enum';
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsEnum, IsOptional } from 'class-validator';
import { ClassRequestContext } from '../interface';

export class ClassCallerParams {
@IsOptional()
@IsEnum(ClassRequestContext)
@ApiPropertyOptional({ enum: ClassRequestContext, enumName: 'ClassRequestContext' })
calledFrom?: ClassRequestContext;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './class-sort-params';
export * from './group-id-params';
export * from './class-filter-params';
export { GroupPaginationParams } from './group-pagination.params';
export { ClassCallerParams } from './class-caller-params';
3 changes: 3 additions & 0 deletions apps/server/src/modules/group/controller/group.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
GroupIdParams,
GroupResponse,
GroupPaginationParams,
ClassCallerParams,
} from './dto';
import { GroupResponseMapper } from './mapper';

Expand All @@ -30,12 +31,14 @@ export class GroupController {
@Query() pagination: GroupPaginationParams,
@Query() sortingQuery: ClassSortParams,
@Query() filterParams: ClassFilterParams,
@Query() callerParams: ClassCallerParams,
@CurrentUser() currentUser: ICurrentUser
): Promise<ClassInfoSearchListResponse> {
const board: Page<ClassInfoDto> = await this.groupUc.findAllClasses(
currentUser.userId,
currentUser.schoolId,
filterParams.type,
callerParams.calledFrom,
pagination.skip,
pagination.limit,
sortingQuery.sortBy,
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/modules/group/group-api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { LegacySchoolModule } from '@modules/legacy-school';
import { SystemModule } from '@modules/system';
import { UserModule } from '@modules/user';
import { LoggerModule } from '@src/core/logger';
import { SchoolModule } from '@modules/school';
import { GroupController } from './controller';
import { GroupModule } from './group.module';
import { GroupUc } from './uc';
Expand All @@ -17,6 +18,7 @@ import { GroupUc } from './uc';
UserModule,
RoleModule,
LegacySchoolModule,
SchoolModule,
AuthorizationModule,
SystemModule,
LoggerModule,
Expand Down
55 changes: 41 additions & 14 deletions apps/server/src/modules/group/uc/group.uc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Action, AuthorizationContext, AuthorizationService } from '@modules/aut
import { ClassService } from '@modules/class';
import { Class } from '@modules/class/domain';
import { classFactory } from '@modules/class/domain/testing/factory/class.factory';
import { LegacySchoolService, SchoolYearService } from '@modules/legacy-school';
import { SchoolYearService } from '@modules/legacy-school';
import { RoleService } from '@modules/role';
import { RoleDto } from '@modules/role/service/dto/role.dto';
import { LegacySystemService, SystemDto } from '@modules/system';
Expand All @@ -13,13 +13,12 @@ import { ForbiddenException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { ReferencedEntityNotFoundLoggable } from '@shared/common/loggable';
import { NotFoundLoggableException } from '@shared/common/loggable-exception';
import { LegacySchoolDo, Page, UserDO } from '@shared/domain/domainobject';
import { Page, UserDO } from '@shared/domain/domainobject';
import { SchoolYearEntity, User } from '@shared/domain/entity';
import { Permission, SortOrder } from '@shared/domain/interface';
import { EntityId } from '@shared/domain/types';
import {
groupFactory,
legacySchoolDoFactory,
roleDtoFactory,
schoolYearFactory,
setupEntities,
Expand All @@ -28,7 +27,9 @@ import {
userFactory,
} from '@shared/testing';
import { Logger } from '@src/core/logger';
import { SchoolYearQueryType } from '../controller/dto/interface';
import { School, SchoolService } from '@modules/school/domain';
import { schoolFactory } from '@modules/school/testing';
import { ClassRequestContext, SchoolYearQueryType } from '../controller/dto/interface';
import { Group, GroupTypes } from '../domain';
import { UnknownQueryTypeLoggableException } from '../loggable';
import { GroupService } from '../service';
Expand All @@ -45,7 +46,7 @@ describe('GroupUc', () => {
let systemService: DeepMocked<LegacySystemService>;
let userService: DeepMocked<UserService>;
let roleService: DeepMocked<RoleService>;
let schoolService: DeepMocked<LegacySchoolService>;
let schoolService: DeepMocked<SchoolService>;
let authorizationService: DeepMocked<AuthorizationService>;
let schoolYearService: DeepMocked<SchoolYearService>;
let logger: DeepMocked<Logger>;
Expand Down Expand Up @@ -75,8 +76,8 @@ describe('GroupUc', () => {
useValue: createMock<RoleService>(),
},
{
provide: LegacySchoolService,
useValue: createMock<LegacySchoolService>(),
provide: SchoolService,
useValue: createMock<SchoolService>(),
},
{
provide: AuthorizationService,
Expand All @@ -99,7 +100,7 @@ describe('GroupUc', () => {
systemService = module.get(LegacySystemService);
userService = module.get(UserService);
roleService = module.get(RoleService);
schoolService = module.get(LegacySchoolService);
schoolService = module.get(SchoolService);
authorizationService = module.get(AuthorizationService);
schoolYearService = module.get(SchoolYearService);
logger = module.get(Logger);
Expand All @@ -118,7 +119,7 @@ describe('GroupUc', () => {
describe('findAllClasses', () => {
describe('when the user has no permission', () => {
const setup = () => {
const school: LegacySchoolDo = legacySchoolDoFactory.buildWithId();
const school: School = schoolFactory.build();
const user: User = userFactory.buildWithId();
const error = new ForbiddenException();

Expand Down Expand Up @@ -146,7 +147,7 @@ describe('GroupUc', () => {

describe('when accessing as a normal user', () => {
const setup = () => {
const school: LegacySchoolDo = legacySchoolDoFactory.buildWithId();
const school: School = schoolFactory.build({ permissions: { teacher: { STUDENT_LIST: true } } });
const { studentUser } = UserAndAccountTestFactory.buildStudent();
const { teacherUser } = UserAndAccountTestFactory.buildTeacher();
const teacherRole: RoleDto = roleDtoFactory.buildWithId({
Expand Down Expand Up @@ -271,7 +272,7 @@ describe('GroupUc', () => {

await uc.findAllClasses(teacherUser.id, teacherUser.school.id, SchoolYearQueryType.CURRENT_YEAR);

expect(authorizationService.checkPermission).toHaveBeenCalledWith<[User, LegacySchoolDo, AuthorizationContext]>(
expect(authorizationService.checkPermission).toHaveBeenCalledWith<[User, School, AuthorizationContext]>(
teacherUser,
school,
{
Expand All @@ -292,6 +293,26 @@ describe('GroupUc', () => {
]);
});

describe('when accessing form course as a teacher', () => {
it('should call findClassesForSchool method from classService', async () => {
const { teacherUser } = setup();

await uc.findAllClasses(teacherUser.id, teacherUser.school.id, undefined, ClassRequestContext.COURSE);

expect(classService.findClassesForSchool).toHaveBeenCalled();
});
});

describe('when accessing form class overview as a teacher', () => {
it('should call findAllByUserId method from classService', async () => {
const { teacherUser } = setup();

await uc.findAllClasses(teacherUser.id, teacherUser.school.id, undefined, ClassRequestContext.CLASS_OVERVIEW);

expect(classService.findAllByUserId).toHaveBeenCalled();
});
});

describe('when no pagination is given', () => {
it('should return all classes sorted by name', async () => {
const {
Expand Down Expand Up @@ -385,6 +406,7 @@ describe('GroupUc', () => {
SchoolYearQueryType.CURRENT_YEAR,
undefined,
undefined,
undefined,
'externalSourceName',
SortOrder.desc
);
Expand Down Expand Up @@ -441,6 +463,7 @@ describe('GroupUc', () => {
teacherUser.id,
teacherUser.school.id,
SchoolYearQueryType.CURRENT_YEAR,
undefined,
2,
1,
'name',
Expand Down Expand Up @@ -523,7 +546,7 @@ describe('GroupUc', () => {

describe('when accessing as a user with elevated permission', () => {
const setup = (generateClasses = false) => {
const school: LegacySchoolDo = legacySchoolDoFactory.buildWithId();
const school: School = schoolFactory.build();
const { studentUser } = UserAndAccountTestFactory.buildStudent();
const { teacherUser } = UserAndAccountTestFactory.buildTeacher();
const { adminUser } = UserAndAccountTestFactory.buildAdmin();
Expand Down Expand Up @@ -652,7 +675,7 @@ describe('GroupUc', () => {

await uc.findAllClasses(adminUser.id, adminUser.school.id);

expect(authorizationService.checkPermission).toHaveBeenCalledWith<[User, LegacySchoolDo, AuthorizationContext]>(
expect(authorizationService.checkPermission).toHaveBeenCalledWith<[User, School, AuthorizationContext]>(
adminUser,
school,
{
Expand Down Expand Up @@ -733,6 +756,7 @@ describe('GroupUc', () => {
undefined,
undefined,
undefined,
undefined,
'externalSourceName',
SortOrder.desc
);
Expand Down Expand Up @@ -778,6 +802,7 @@ describe('GroupUc', () => {
adminUser.id,
adminUser.school.id,
undefined,
undefined,
1,
1,
'name',
Expand Down Expand Up @@ -805,6 +830,7 @@ describe('GroupUc', () => {
adminUser.id,
adminUser.school.id,
undefined,
undefined,
0,
5
);
Expand All @@ -819,6 +845,7 @@ describe('GroupUc', () => {
adminUser.id,
adminUser.school.id,
undefined,
undefined,
0,
-1
);
Expand All @@ -830,7 +857,7 @@ describe('GroupUc', () => {

describe('when class has a user referenced which is not existing', () => {
const setup = () => {
const school: LegacySchoolDo = legacySchoolDoFactory.buildWithId();
const school: School = schoolFactory.build();
const notFoundReferenceId = new ObjectId().toHexString();
const { teacherUser } = UserAndAccountTestFactory.buildTeacher();

Expand Down
17 changes: 11 additions & 6 deletions apps/server/src/modules/group/uc/group.uc.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { AuthorizationContextBuilder, AuthorizationService } from '@modules/authorization';
import { ClassService } from '@modules/class';
import { Class } from '@modules/class/domain';
import { LegacySchoolService, SchoolYearService } from '@modules/legacy-school';
import { SchoolYearService } from '@modules/legacy-school';
import { RoleService } from '@modules/role';
import { RoleDto } from '@modules/role/service/dto/role.dto';
import { UserService } from '@modules/user';
import { Injectable } from '@nestjs/common';
import { SortHelper } from '@shared/common';
import { ReferencedEntityNotFoundLoggable } from '@shared/common/loggable';
import { LegacySchoolDo, Page, UserDO } from '@shared/domain/domainobject';
import { Page, UserDO } from '@shared/domain/domainobject';
import { SchoolYearEntity, User } from '@shared/domain/entity';
import { Permission, SortOrder } from '@shared/domain/interface';
import { EntityId } from '@shared/domain/types';
import { Logger } from '@src/core/logger';
import { LegacySystemService, SystemDto } from '@src/modules/system';
import { SchoolYearQueryType } from '../controller/dto/interface';
import { School, SchoolService } from '@modules/school/domain';
import { ClassRequestContext, SchoolYearQueryType } from '../controller/dto/interface';
import { Group, GroupTypes, GroupUser } from '../domain';
import { UnknownQueryTypeLoggableException } from '../loggable';
import { GroupService } from '../service';
Expand All @@ -29,7 +30,7 @@ export class GroupUc {
private readonly systemService: LegacySystemService,
private readonly userService: UserService,
private readonly roleService: RoleService,
private readonly schoolService: LegacySchoolService,
private readonly schoolService: SchoolService,
private readonly authorizationService: AuthorizationService,
private readonly schoolYearService: SchoolYearService,
private readonly logger: Logger
Expand All @@ -41,12 +42,13 @@ export class GroupUc {
userId: EntityId,
schoolId: EntityId,
schoolYearQueryType?: SchoolYearQueryType,
calledFrom?: ClassRequestContext,
skip = 0,
limit?: number,
sortBy: keyof ClassInfoDto = 'name',
sortOrder: SortOrder = SortOrder.asc
): Promise<Page<ClassInfoDto>> {
const school: LegacySchoolDo = await this.schoolService.getSchoolById(schoolId);
const school: School = await this.schoolService.getSchoolById(schoolId);

const user: User = await this.authorizationService.getUserWithPermissions(userId);
this.authorizationService.checkPermission(
Expand All @@ -60,8 +62,11 @@ export class GroupUc {
Permission.GROUP_FULL_ADMIN,
]);

const calledFromCourse: boolean =
calledFrom === ClassRequestContext.COURSE && school.getPermissions()?.teacher?.STUDENT_LIST === true;

let combinedClassInfo: ClassInfoDto[];
if (canSeeFullList) {
if (canSeeFullList || calledFromCourse) {
combinedClassInfo = await this.findCombinedClassListForSchool(schoolId, schoolYearQueryType);
} else {
combinedClassInfo = await this.findCombinedClassListForUser(userId, schoolYearQueryType);
Expand Down
39 changes: 39 additions & 0 deletions apps/server/src/modules/school/domain/do/school.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,45 @@ describe('School', () => {
expect(school.getProps().features).not.toContain(feature);
});
});
// TODO N21-1623 add test for getPermissions
describe('getPermissions', () => {
describe('when permissions exist', () => {
const setup = () => {
const permissions = { teacher: { STUDENT_LIST: true } };
const school = schoolFactory.build({
permissions,
});

return { school, permissions };
};

it('should return permissions', () => {
const { school, permissions } = setup();

const result = school.getPermissions();

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

describe('when permissions are undefined', () => {
const setup = () => {
const school = schoolFactory.build({
permissions: undefined,
});

return { school };
};

it('should return undefined', () => {
const { school } = setup();

const result = school.getPermissions();

expect(result).toBeUndefined();
});
});
});

describe('isInMaintenance', () => {
describe('when inMaintenanceSince is in the past', () => {
Expand Down
6 changes: 6 additions & 0 deletions apps/server/src/modules/school/domain/do/school.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export class School extends DomainObject<SchoolProps> {
this.props.features.delete(feature);
}

public getPermissions(): SchoolPermissions | undefined {
const { permissions } = this.props;

return permissions;
}

public isInMaintenance(): boolean {
const result = this.props.inMaintenanceSince ? this.props.inMaintenanceSince <= new Date() : false;

Expand Down

0 comments on commit 612b029

Please sign in to comment.