Skip to content

Commit

Permalink
BC-8014 - fix 404 on course page (#5225)
Browse files Browse the repository at this point in the history
* due to some caching issue, some clients still accessed old routes renamed by BC-7904.
This commit restores the old endpoints, which now exist alongside the new ones.
  • Loading branch information
Metauriel committed Sep 4, 2024
1 parent e7fd835 commit 13dd655
Show file tree
Hide file tree
Showing 4 changed files with 304 additions and 2 deletions.
209 changes: 209 additions & 0 deletions apps/server/src/modules/learnroom/controller/rooms.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { createMock } from '@golevelup/ts-jest';
import { CopyApiResponse, CopyElementType, CopyStatus, CopyStatusEnum } from '@modules/copy-helper';
import { Test, TestingModule } from '@nestjs/testing';
import { EntityId } from '@shared/domain/types';
import { currentUserFactory } from '@shared/testing';
import { RoomBoardResponseMapper } from '../mapper/room-board-response.mapper';
import { RoomBoardDTO } from '../types';
import { CourseCopyUC } from '../uc/course-copy.uc';
import { LessonCopyUC } from '../uc/lesson-copy.uc';
import { CourseRoomsUc } from '../uc/course-rooms.uc';
import { SingleColumnBoardResponse } from './dto';
import { RoomsController } from './rooms.controller';

describe('rooms controller', () => {
let controller: RoomsController;
let mapper: RoomBoardResponseMapper;
let uc: CourseRoomsUc;
let courseCopyUc: CourseCopyUC;
let lessonCopyUc: LessonCopyUC;

beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [],
providers: [
RoomsController,
{
provide: CourseRoomsUc,
useValue: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getBoard(roomId: EntityId, userId: EntityId): Promise<RoomBoardDTO> {
throw new Error('please write mock for RoomsUc.getBoard');
},
updateVisibilityOfLegacyBoardElement(
roomId: EntityId, // eslint-disable-line @typescript-eslint/no-unused-vars
elementId: EntityId, // eslint-disable-line @typescript-eslint/no-unused-vars
userId: EntityId, // eslint-disable-line @typescript-eslint/no-unused-vars
visibility: boolean // eslint-disable-line @typescript-eslint/no-unused-vars
): Promise<void> {
throw new Error('please write mock for RoomsUc.updateVisibilityOfBoardElement');
},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
reorderBoardElements(roomId: EntityId, userId: EntityId, orderedList: EntityId[]): Promise<void> {
throw new Error('please write mock for RoomsUc.reorderBoardElements');
},
},
},
{
provide: CourseCopyUC,
useValue: createMock<CourseCopyUC>(),
},
{
provide: LessonCopyUC,
useValue: createMock<LessonCopyUC>(),
},
{
provide: RoomBoardResponseMapper,
useValue: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
mapToResponse(board: RoomBoardDTO): SingleColumnBoardResponse {
throw new Error('please write mock for Boardmapper.mapToResponse');
},
},
},
],
}).compile();
controller = module.get(RoomsController);
mapper = module.get(RoomBoardResponseMapper);
uc = module.get(CourseRoomsUc);
courseCopyUc = module.get(CourseCopyUC);
lessonCopyUc = module.get(LessonCopyUC);
});

describe('getRoomBoard', () => {
describe('when simple room is fetched', () => {
const setup = () => {
const currentUser = currentUserFactory.build();

const ucResult = {
roomId: 'id',
title: 'title',
displayColor: '#FFFFFF',
elements: [],
isArchived: false,
isSynchronized: false,
} as RoomBoardDTO;
const ucSpy = jest.spyOn(uc, 'getBoard').mockImplementation(() => Promise.resolve(ucResult));

const mapperResult = new SingleColumnBoardResponse({
roomId: 'id',
title: 'title',
displayColor: '#FFFFFF',
elements: [],
isArchived: false,
isSynchronized: false,
});
const mapperSpy = jest.spyOn(mapper, 'mapToResponse').mockImplementation(() => mapperResult);
return { currentUser, ucResult, ucSpy, mapperResult, mapperSpy };
};

it('should call uc with ids', async () => {
const { currentUser, ucSpy } = setup();

await controller.getRoomBoard({ roomId: 'roomId' }, currentUser);

expect(ucSpy).toHaveBeenCalledWith('roomId', currentUser.userId);
});

it('should call mapper with uc result', async () => {
const { currentUser, ucResult, mapperSpy } = setup();

await controller.getRoomBoard({ roomId: 'roomId' }, currentUser);

expect(mapperSpy).toHaveBeenCalledWith(ucResult);
});

it('should return mapped result', async () => {
const { currentUser, mapperResult } = setup();

const result = await controller.getRoomBoard({ roomId: 'roomId' }, currentUser);

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

describe('patchVisibility', () => {
it('should call uc', async () => {
const currentUser = currentUserFactory.build();
const ucSpy = jest.spyOn(uc, 'updateVisibilityOfLegacyBoardElement').mockImplementation(() => Promise.resolve());
await controller.patchElementVisibility(
{ roomId: 'roomid', elementId: 'elementId' },
{ visibility: true },
currentUser
);
expect(ucSpy).toHaveBeenCalled();
});
});

describe('patchOrder', () => {
it('should call uc', async () => {
const currentUser = currentUserFactory.build();
const ucSpy = jest.spyOn(uc, 'reorderBoardElements').mockImplementation(() => Promise.resolve());
await controller.patchOrderingOfElements({ roomId: 'roomid' }, { elements: ['id', 'id', 'id'] }, currentUser);
expect(ucSpy).toHaveBeenCalledWith('roomid', currentUser.userId, ['id', 'id', 'id']);
});
});

describe('copyCourse', () => {
describe('when course should be copied via API call', () => {
const setup = () => {
const currentUser = currentUserFactory.build();
const ucResult = {
title: 'example title',
type: 'COURSE' as CopyElementType,
status: 'SUCCESS' as CopyStatusEnum,
elements: [],
} as CopyStatus;
const ucSpy = jest.spyOn(courseCopyUc, 'copyCourse').mockImplementation(() => Promise.resolve(ucResult));
return { currentUser, ucSpy };
};
it('should call uc', async () => {
const { currentUser, ucSpy } = setup();

await controller.copyCourse(currentUser, { roomId: 'roomId' });
expect(ucSpy).toHaveBeenCalledWith(currentUser.userId, 'roomId');
});

it('should return result of correct type', async () => {
const { currentUser } = setup();

const result = await controller.copyCourse(currentUser, { roomId: 'roomId' });
expect(result).toBeInstanceOf(CopyApiResponse);
});
});
});

describe('copyLesson', () => {
describe('when lesson should be copied via API call', () => {
const setup = () => {
const currentUser = currentUserFactory.build();
const ucResult = {
title: 'example title',
type: 'LESSON' as CopyElementType,
status: 'SUCCESS' as CopyStatusEnum,
elements: [],
} as CopyStatus;
const ucSpy = jest.spyOn(lessonCopyUc, 'copyLesson').mockImplementation(() => Promise.resolve(ucResult));
return { currentUser, ucSpy };
};

it('should call uc with parentId', async () => {
const { currentUser, ucSpy } = setup();

await controller.copyLesson(currentUser, { lessonId: 'lessonId' }, { courseId: 'id' });
expect(ucSpy).toHaveBeenCalledWith(currentUser.userId, 'lessonId', {
courseId: 'id',
userId: currentUser.userId,
});
});

it('should return result of correct type', async () => {
const { currentUser } = setup();

const result = await controller.copyLesson(currentUser, { lessonId: 'lessonId' }, {});
expect(result).toBeInstanceOf(CopyApiResponse);
});
});
});
});
92 changes: 92 additions & 0 deletions apps/server/src/modules/learnroom/controller/rooms.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { CurrentUser, ICurrentUser, JwtAuthentication } from '@infra/auth-guard';
import { CopyApiResponse, CopyMapper } from '@modules/copy-helper';
import { Body, Controller, Get, Param, Patch, Post } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { RequestTimeout } from '@shared/common';
import { RoomBoardResponseMapper } from '../mapper/room-board-response.mapper';
import { CourseCopyUC } from '../uc/course-copy.uc';
import { LessonCopyUC } from '../uc/lesson-copy.uc';
import { CourseRoomsUc } from '../uc/course-rooms.uc';
import {
LessonCopyApiParams,
LessonUrlParams,
PatchOrderParams,
PatchVisibilityParams,
CourseRoomElementUrlParams,
CourseRoomUrlParams,
SingleColumnBoardResponse,
} from './dto';

// TODO: remove this file, and remove it from sonar-project.properties

@ApiTags('Rooms')
@JwtAuthentication()
@Controller('rooms')
export class RoomsController {
constructor(
private readonly roomsUc: CourseRoomsUc,
private readonly mapper: RoomBoardResponseMapper,
private readonly courseCopyUc: CourseCopyUC,
private readonly lessonCopyUc: LessonCopyUC
) {}

@Get(':roomId/board')
async getRoomBoard(
@Param() urlParams: CourseRoomUrlParams,
@CurrentUser() currentUser: ICurrentUser
): Promise<SingleColumnBoardResponse> {
const board = await this.roomsUc.getBoard(urlParams.roomId, currentUser.userId);
const mapped = this.mapper.mapToResponse(board);
return mapped;
}

@Patch(':roomId/elements/:elementId/visibility')
async patchElementVisibility(
@Param() urlParams: CourseRoomElementUrlParams,
@Body() params: PatchVisibilityParams,
@CurrentUser() currentUser: ICurrentUser
): Promise<void> {
await this.roomsUc.updateVisibilityOfLegacyBoardElement(
urlParams.roomId,
urlParams.elementId,
currentUser.userId,
params.visibility
);
}

@Patch(':roomId/board/order')
async patchOrderingOfElements(
@Param() urlParams: CourseRoomUrlParams,
@Body() params: PatchOrderParams,
@CurrentUser() currentUser: ICurrentUser
): Promise<void> {
await this.roomsUc.reorderBoardElements(urlParams.roomId, currentUser.userId, params.elements);
}

@Post(':roomId/copy')
@RequestTimeout('INCOMING_REQUEST_TIMEOUT_COPY_API')
async copyCourse(
@CurrentUser() currentUser: ICurrentUser,
@Param() urlParams: CourseRoomUrlParams
): Promise<CopyApiResponse> {
const copyStatus = await this.courseCopyUc.copyCourse(currentUser.userId, urlParams.roomId);
const dto = CopyMapper.mapToResponse(copyStatus);
return dto;
}

@Post('lessons/:lessonId/copy')
@RequestTimeout('INCOMING_REQUEST_TIMEOUT_COPY_API')
async copyLesson(
@CurrentUser() currentUser: ICurrentUser,
@Param() urlParams: LessonUrlParams,
@Body() params: LessonCopyApiParams
): Promise<CopyApiResponse> {
const copyStatus = await this.lessonCopyUc.copyLesson(
currentUser.userId,
urlParams.lessonId,
CopyMapper.mapLessonCopyToDomain(params, currentUser.userId)
);
const dto = CopyMapper.mapToResponse(copyStatus);
return dto;
}
}
3 changes: 2 additions & 1 deletion apps/server/src/modules/learnroom/learnroom-api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
CourseRoomsAuthorisationService,
CourseRoomsUc,
} from './uc';
import { RoomsController } from './controller/rooms.controller';

/**
* @deprecated - the learnroom module is deprecated and will be removed in the future
Expand All @@ -36,7 +37,7 @@ import {
AuthorizationReferenceModule,
RoleModule,
],
controllers: [DashboardController, CourseController, CourseRoomsController],
controllers: [DashboardController, CourseController, CourseRoomsController, RoomsController],
providers: [
DashboardUc,
CourseUc,
Expand Down
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ sonar.projectKey=hpi-schul-cloud_schulcloud-server
sonar.sources=.
sonar.tests=.
sonar.test.inclusions=**/*.spec.ts
sonar.exclusions=**/*.js,jest.config.ts,globalSetup.ts,globalTeardown.ts,**/*.app.ts,**/seed-data/*.ts,**/migrations/mikro-orm/*.ts,**/etherpad-api-client/**/*.ts,**/authorization-api-client/**/*.ts
sonar.exclusions=**/*.js,jest.config.ts,globalSetup.ts,globalTeardown.ts,**/*.app.ts,**/seed-data/*.ts,**/migrations/mikro-orm/*.ts,**/etherpad-api-client/**/*.ts,**/authorization-api-client/**/*.ts, **/learnroom/controller/rooms.controller.ts
sonar.coverage.exclusions=**/board-management.uc.ts,**/*.module.ts,**/*.factory.ts,**/migrations/mikro-orm/*.ts,**/globalSetup.ts,**/globalTeardown.ts,**/etherpad-api-client/**/*.ts,**/authorization-api-client/**/*.ts
sonar.cpd.exclusions=**/controller/dto/*.ts
sonar.javascript.lcov.reportPaths=merged-lcov.info
Expand Down

0 comments on commit 13dd655

Please sign in to comment.