From 133d55c498219f369cdb43832fe3873e035ec0b7 Mon Sep 17 00:00:00 2001 From: Johannes Wiest Date: Tue, 24 Sep 2024 16:02:04 +0200 Subject: [PATCH 1/2] add startedByStudent flag --- .../atlas/domain/competency/LearningPath.java | 16 ++++- .../artemis/atlas/dto/LearningPathDTO.java | 13 ++++ .../learningpath/LearningPathService.java | 45 ++++++++++++ .../atlas/web/LearningPathResource.java | 49 +++++++------ .../changelog/20240924125742_changelog.xml | 12 ++++ .../resources/config/liquibase/master.xml | 2 +- .../learning-path-student-page.component.html | 10 +-- .../learning-path-student-page.component.ts | 36 +++++----- .../services/base-api-http.service.ts | 30 ++++++++ .../services/learning-path-api.service.ts | 13 ++-- .../competency/learning-path.model.ts | 6 ++ .../LearningPathIntegrationTest.java | 19 +++--- ...arning-path-student-page.component.spec.ts | 68 +++++++++++++------ .../learning-path-api.service.spec.ts | 17 +++-- 14 files changed, 252 insertions(+), 84 deletions(-) create mode 100644 src/main/java/de/tum/cit/aet/artemis/atlas/dto/LearningPathDTO.java create mode 100644 src/main/resources/config/liquibase/changelog/20240924125742_changelog.xml diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/competency/LearningPath.java b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/competency/LearningPath.java index 267efe722982..ab4c961be143 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/competency/LearningPath.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/competency/LearningPath.java @@ -31,6 +31,12 @@ public class LearningPath extends DomainObject { @Column(name = "progress") private int progress; + /** + * flag indicating if a student started the learning path + */ + @Column(name = "started_by_student") + private boolean startedByStudent = false; + @ManyToOne @JoinColumn(name = "user_id") private User user; @@ -89,8 +95,16 @@ public void removeCompetency(CourseCompetency competency) { this.competencies.remove(competency); } + public boolean isStartedByStudent() { + return startedByStudent; + } + + public void setStartedByStudent(boolean startedByStudent) { + this.startedByStudent = startedByStudent; + } + @Override public String toString() { - return "LearningPath{" + "id=" + getId() + ", user=" + user + ", course=" + course + ", competencies=" + competencies + '}'; + return "LearningPath{" + "id=" + getId() + ", user=" + user + ", course=" + course + ", competencies=" + competencies + ", startedByStudent=" + startedByStudent + "}"; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/dto/LearningPathDTO.java b/src/main/java/de/tum/cit/aet/artemis/atlas/dto/LearningPathDTO.java new file mode 100644 index 000000000000..f61598b30cf4 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/dto/LearningPathDTO.java @@ -0,0 +1,13 @@ +package de.tum.cit.aet.artemis.atlas.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.cit.aet.artemis.atlas.domain.competency.LearningPath; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record LearningPathDTO(long id, boolean startedByStudent, int progress) { + + public static LearningPathDTO of(LearningPath learningPath) { + return new LearningPathDTO(learningPath.getId(), learningPath.isStartedByStudent(), learningPath.getProgress()); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java index 937123a30fe1..7b92b207d867 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java @@ -11,6 +11,7 @@ import java.util.stream.Collectors; import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.BadRequestException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,6 +26,7 @@ import de.tum.cit.aet.artemis.atlas.dto.CompetencyGraphEdgeDTO; import de.tum.cit.aet.artemis.atlas.dto.CompetencyGraphNodeDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathCompetencyGraphDTO; +import de.tum.cit.aet.artemis.atlas.dto.LearningPathDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathHealthDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathInformationDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathNavigationOverviewDTO; @@ -243,6 +245,49 @@ private void updateLearningPathProgress(@NotNull LearningPath learningPath) { log.debug("Updated LearningPath (id={}) for user (id={})", learningPath.getId(), userId); } + /** + * Get the learning path for the current user in the given course. + * + * @param courseId the id of the course + * @return the learning path of the current user + */ + public LearningPathDTO getLearningPathForCurrentUser(long courseId) { + final var currentUser = userRepository.getUser(); + final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(courseId, currentUser.getId()); + return LearningPathDTO.of(learningPath); + } + + /** + * Generate a learning path for the current user in the given course. + * + * @param courseId the id of the course + * @return the generated learning path + */ + public LearningPathDTO generateLearningPathForCurrentUser(long courseId) { + final var currentUser = userRepository.getUser(); + final var course = courseRepository.findWithEagerCompetenciesAndPrerequisitesByIdElseThrow(courseId); + if (learningPathRepository.findByCourseIdAndUserId(courseId, currentUser.getId()).isPresent()) { + throw new BadRequestException("Learning path already exists."); + } + final var learningPath = generateLearningPathForUser(course, currentUser); + return LearningPathDTO.of(learningPath); + } + + /** + * Start the learning path for the current user in the given course. + * + * @param learningPathId the id of the learning path + */ + public void startLearningPathForCurrentUser(long learningPathId) { + final var learningPath = learningPathRepository.findByIdElseThrow(learningPathId); + final var currentUser = userRepository.getUser(); + if (!learningPath.getUser().equals(currentUser)) { + throw new AccessForbiddenException("You are not allowed to start this learning path."); + } + learningPath.setStartedByStudent(true); + learningPathRepository.save(learningPath); + } + /** * Gets the health status of learning paths for the given course. * diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/web/LearningPathResource.java b/src/main/java/de/tum/cit/aet/artemis/atlas/web/LearningPathResource.java index 709ac913cdf9..d940e50acc51 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/web/LearningPathResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/web/LearningPathResource.java @@ -17,6 +17,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -28,6 +29,7 @@ import de.tum.cit.aet.artemis.atlas.dto.CompetencyNameDTO; import de.tum.cit.aet.artemis.atlas.dto.CompetencyProgressForLearningPathDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathCompetencyGraphDTO; +import de.tum.cit.aet.artemis.atlas.dto.LearningPathDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathHealthDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathInformationDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathNavigationDTO; @@ -301,19 +303,31 @@ private ResponseEntity getLearningPathNgx(@PathVariable long } /** - * GET courses/:courseId/learning-path-id : Gets the id of the learning path. + * GET courses/:courseId/learning-path/me : Gets the learning path of the current user in the course. * - * @param courseId the id of the course from which the learning path id should be fetched - * @return the ResponseEntity with status 200 (OK) and with body the id of the learning path + * @param courseId the id of the course for which the learning path should be fetched + * @return the ResponseEntity with status 200 (OK) and with body the learning path */ - @GetMapping("courses/{courseId}/learning-path-id") + @GetMapping("courses/{courseId}/learning-path/me") @EnforceAtLeastStudentInCourse - public ResponseEntity getLearningPathId(@PathVariable long courseId) { - log.debug("REST request to get learning path id for course with id: {}", courseId); + public ResponseEntity getLearningPathForCurrentUser(@PathVariable long courseId) { + log.debug("REST request to get learning path of current user for course with id: {}", courseId); courseService.checkLearningPathsEnabledElseThrow(courseId); - User user = userRepository.getUser(); - final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(courseId, user.getId()); - return ResponseEntity.ok(learningPath.getId()); + return ResponseEntity.ok(learningPathService.getLearningPathForCurrentUser(courseId)); + } + + /** + * PATCH learning-path/:learningPathId/start : Starts the learning path for the current user. + * + * @param learningPathId the id of the learning path to start + * @return the ResponseEntity with status 204 (NO_CONTENT) + */ + @PatchMapping("learning-path/{learningPathId}/start") + @EnforceAtLeastStudent + public ResponseEntity startLearningPathForCurrentUser(@PathVariable long learningPathId) { + log.debug("REST request to start learning path with id: {}", learningPathId); + learningPathService.startLearningPathForCurrentUser(learningPathId); + return ResponseEntity.noContent().build(); } /** @@ -324,20 +338,11 @@ public ResponseEntity getLearningPathId(@PathVariable long courseId) { */ @PostMapping("courses/{courseId}/learning-path") @EnforceAtLeastStudentInCourse - public ResponseEntity generateLearningPath(@PathVariable long courseId) throws URISyntaxException { - log.debug("REST request to generate learning path for user in course with id: {}", courseId); + public ResponseEntity generateLearningPathForCurrentUser(@PathVariable long courseId) throws URISyntaxException { + log.debug("REST request to generate learning path for current user in course with id: {}", courseId); courseService.checkLearningPathsEnabledElseThrow(courseId); - - User user = userRepository.getUser(); - final var learningPathOptional = learningPathRepository.findByCourseIdAndUserId(courseId, user.getId()); - - if (learningPathOptional.isPresent()) { - throw new BadRequestException("Learning path already exists."); - } - - final var course = courseRepository.findWithEagerCompetenciesAndPrerequisitesByIdElseThrow(courseId); - final var learningPath = learningPathService.generateLearningPathForUser(course, user); - return ResponseEntity.created(new URI("api/learning-path/" + learningPath.getId())).body(learningPath.getId()); + final var learningPathDTO = learningPathService.generateLearningPathForCurrentUser(courseId); + return ResponseEntity.created(new URI("api/learning-path/" + learningPathDTO.id())).body(learningPathDTO); } /** diff --git a/src/main/resources/config/liquibase/changelog/20240924125742_changelog.xml b/src/main/resources/config/liquibase/changelog/20240924125742_changelog.xml new file mode 100644 index 000000000000..c74139c77c6d --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20240924125742_changelog.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 2c204094c0ff..bb5ed2fee196 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -23,7 +23,7 @@ - + diff --git a/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.html b/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.html index b6a8f8255ef3..7e116bc6a169 100644 --- a/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.html +++ b/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.html @@ -1,12 +1,12 @@
- @if (isLearningPathIdLoading()) { + @if (isLearningPathLoading()) {
- } @else if (learningPathId()) { - + } @else if (learningPath() && learningPath()!.startedByStudent) { +
@if (currentLearningObject()?.type === LearningObjectType.LECTURE) { @@ -27,10 +27,10 @@

diff --git a/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts b/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts index 7df486af265c..5a80b346dc30 100644 --- a/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts +++ b/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts @@ -1,5 +1,5 @@ import { Component, effect, inject, signal } from '@angular/core'; -import { LearningObjectType } from 'app/entities/competency/learning-path.model'; +import { LearningObjectType, LearningPathDTO } from 'app/entities/competency/learning-path.model'; import { map } from 'rxjs'; import { toSignal } from '@angular/core/rxjs-interop'; import { CommonModule } from '@angular/common'; @@ -32,45 +32,49 @@ import { ArtemisSharedModule } from 'app/shared/shared.module'; export class LearningPathStudentPageComponent { protected readonly LearningObjectType = LearningObjectType; - private readonly learningApiService: LearningPathApiService = inject(LearningPathApiService); + private readonly learningApiService = inject(LearningPathApiService); private readonly learningPathNavigationService = inject(LearningPathNavigationService); - private readonly alertService: AlertService = inject(AlertService); - private readonly activatedRoute: ActivatedRoute = inject(ActivatedRoute); + private readonly alertService = inject(AlertService); + private readonly activatedRoute = inject(ActivatedRoute); - readonly isLearningPathIdLoading = signal(false); - readonly learningPathId = signal(undefined); + readonly isLearningPathLoading = signal(false); + readonly learningPath = signal(undefined); readonly courseId = toSignal(this.activatedRoute.parent!.parent!.params.pipe(map((params) => Number(params.courseId))), { requireSync: true }); readonly currentLearningObject = this.learningPathNavigationService.currentLearningObject; readonly isLearningPathNavigationLoading = this.learningPathNavigationService.isLoading; constructor() { - effect(async () => await this.loadLearningPathId(this.courseId()), { allowSignalWrites: true }); + effect(() => this.loadLearningPathId(this.courseId()), { allowSignalWrites: true }); } private async loadLearningPathId(courseId: number): Promise { try { - this.isLearningPathIdLoading.set(true); - const learningPathId = await this.learningApiService.getLearningPathId(courseId); - this.learningPathId.set(learningPathId); + this.isLearningPathLoading.set(true); + const learningPath = await this.learningApiService.getLearningPathForCurrentUser(courseId); + this.learningPath.set(learningPath); } catch (error) { // If learning path does not exist (404) ignore the error if (!(error instanceof EntityNotFoundError)) { this.alertService.error(error); } } finally { - this.isLearningPathIdLoading.set(false); + this.isLearningPathLoading.set(false); } } - async generateLearningPath(courseId: number): Promise { + async startLearningPath(): Promise { try { - this.isLearningPathIdLoading.set(true); - const learningPathId = await this.learningApiService.generateLearningPath(courseId); - this.learningPathId.set(learningPathId); + this.isLearningPathLoading.set(true); + if (!this.learningPath()) { + const learningPath = await this.learningApiService.generateLearningPathForCurrentUser(this.courseId()); + this.learningPath.set(learningPath); + } + await this.learningApiService.startLearningPathForCurrentUser(this.learningPath()!.id); + this.learningPath.update((learningPath) => ({ ...learningPath!, startedByStudent: true })); } catch (error) { this.alertService.error(error); } finally { - this.isLearningPathIdLoading.set(false); + this.isLearningPathLoading.set(false); } } } diff --git a/src/main/webapp/app/course/learning-paths/services/base-api-http.service.ts b/src/main/webapp/app/course/learning-paths/services/base-api-http.service.ts index cb298f6ebf6c..07354f314eab 100644 --- a/src/main/webapp/app/course/learning-paths/services/base-api-http.service.ts +++ b/src/main/webapp/app/course/learning-paths/services/base-api-http.service.ts @@ -122,4 +122,34 @@ export abstract class BaseApiHttpService { ): Promise { return await this.request(HttpMethod.Post, url, { body: body, ...options }); } + + /** + * Constructs a `PUT` request that interprets the body as JSON and + * returns a Promise of an object of type `T`. + * + * @param url The endpoint URL excluding the base server url (/api). + * @param body The content to include in the body of the request. + * @param options The HTTP options to send with the request. + * @protected + * + * @return An `Promise` of type `Object` (T), + */ + protected async patch( + url: string, + body?: any, + options?: { + headers?: + | HttpHeaders + | { + [header: string]: string | string[]; + }; + params?: + | HttpParams + | { + [param: string]: string | number | boolean | ReadonlyArray; + }; + }, + ): Promise { + return await this.request(HttpMethod.Patch, url, { body: body, ...options }); + } } diff --git a/src/main/webapp/app/course/learning-paths/services/learning-path-api.service.ts b/src/main/webapp/app/course/learning-paths/services/learning-path-api.service.ts index 35dcf071e505..01a43398f1c8 100644 --- a/src/main/webapp/app/course/learning-paths/services/learning-path-api.service.ts +++ b/src/main/webapp/app/course/learning-paths/services/learning-path-api.service.ts @@ -3,6 +3,7 @@ import { CompetencyGraphDTO, LearningObjectType, LearningPathCompetencyDTO, + LearningPathDTO, LearningPathNavigationDTO, LearningPathNavigationObjectDTO, LearningPathNavigationOverviewDTO, @@ -14,8 +15,12 @@ import { BaseApiHttpService } from 'app/course/learning-paths/services/base-api- providedIn: 'root', }) export class LearningPathApiService extends BaseApiHttpService { - async getLearningPathId(courseId: number): Promise { - return await this.get(`courses/${courseId}/learning-path-id`); + async getLearningPathForCurrentUser(courseId: number): Promise { + return await this.get(`courses/${courseId}/learning-path/me`); + } + + async startLearningPathForCurrentUser(learningPathId: number): Promise { + return await this.patch(`learning-path/${learningPathId}/start`); } async getLearningPathNavigation(learningPathId: number): Promise { @@ -35,8 +40,8 @@ export class LearningPathApiService extends BaseApiHttpService { return await this.get(`learning-path/${learningPathId}/relative-navigation`, { params: params }); } - async generateLearningPath(courseId: number): Promise { - return await this.post(`courses/${courseId}/learning-path`); + async generateLearningPathForCurrentUser(courseId: number): Promise { + return await this.post(`courses/${courseId}/learning-path`); } async getLearningPathNavigationOverview(learningPathId: number): Promise { diff --git a/src/main/webapp/app/entities/competency/learning-path.model.ts b/src/main/webapp/app/entities/competency/learning-path.model.ts index 3f5c0e7775de..2c55868d0af0 100644 --- a/src/main/webapp/app/entities/competency/learning-path.model.ts +++ b/src/main/webapp/app/entities/competency/learning-path.model.ts @@ -32,6 +32,12 @@ export interface LearningPathCompetencyDTO { masteryProgress: number; } +export interface LearningPathDTO { + id: number; + progress: number; + startedByStudent: boolean; +} + export interface LearningPathNavigationObjectDTO { id: number; completed: boolean; diff --git a/src/test/java/de/tum/cit/aet/artemis/competency/LearningPathIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/competency/LearningPathIntegrationTest.java index 1d3db7ad24e7..27162ed5f7a1 100644 --- a/src/test/java/de/tum/cit/aet/artemis/competency/LearningPathIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/competency/LearningPathIntegrationTest.java @@ -36,6 +36,7 @@ import de.tum.cit.aet.artemis.atlas.dto.CompetencyNameDTO; import de.tum.cit.aet.artemis.atlas.dto.CompetencyWithTailRelationDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathCompetencyGraphDTO; +import de.tum.cit.aet.artemis.atlas.dto.LearningPathDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathHealthDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathInformationDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathNavigationDTO; @@ -571,16 +572,16 @@ void testGetLearningPathNgxAsInstructor(LearningPathResource.NgxRequestType type } @Nested - class GetLearningPathId { + class GetLearningPath { @Test @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") - void shouldReturnExistingId() throws Exception { + void shouldReturnExisting() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); - final var result = request.get("/api/courses/" + course.getId() + "/learning-path-id", HttpStatus.OK, Long.class); - assertThat(result).isEqualTo(learningPath.getId()); + final var result = request.get("/api/courses/" + course.getId() + "/learning-path/me", HttpStatus.OK, LearningPathDTO.class); + assertThat(result).isEqualTo(LearningPathDTO.of(learningPath)); } @Test @@ -591,7 +592,7 @@ void shouldReturnNotFoundIfNotExists() throws Exception { var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); student = userRepository.findWithLearningPathsByIdElseThrow(student.getId()); learningPathRepository.deleteAll(student.getLearningPaths()); - request.get("/api/courses/" + course.getId() + "/learning-path-id", HttpStatus.NOT_FOUND, Long.class); + request.get("/api/courses/" + course.getId() + "/learning-path/me", HttpStatus.NOT_FOUND, LearningPathDTO.class); } } @@ -601,14 +602,14 @@ class GenerateLearningPath { @Test @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") void shouldReturnForbiddenIfNotEnabled() throws Exception { - request.postWithResponseBody("/api/courses/" + course.getId() + "/learning-path", null, Long.class, HttpStatus.BAD_REQUEST); + request.postWithResponseBody("/api/courses/" + course.getId() + "/learning-path", null, LearningPathDTO.class, HttpStatus.BAD_REQUEST); } @Test @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") void shouldReturnBadRequestIfAlreadyExists() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - request.postWithResponseBody("/api/courses/" + course.getId() + "/learning-path", null, Long.class, HttpStatus.BAD_REQUEST); + request.postWithResponseBody("/api/courses/" + course.getId() + "/learning-path", null, LearningPathDTO.class, HttpStatus.BAD_REQUEST); } @Test @@ -616,7 +617,7 @@ void shouldReturnBadRequestIfAlreadyExists() throws Exception { void shouldGenerateLearningPath() throws Exception { course.setLearningPathsEnabled(true); course = courseRepository.save(course); - final var response = request.postWithResponseBody("/api/courses/" + course.getId() + "/learning-path", null, Long.class, HttpStatus.CREATED); + final var response = request.postWithResponseBody("/api/courses/" + course.getId() + "/learning-path", null, LearningPathDTO.class, HttpStatus.CREATED); assertThat(response).isNotNull(); final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); @@ -624,6 +625,8 @@ void shouldGenerateLearningPath() throws Exception { } } + // TODO: Write tests for start learning path + @Test @WithMockUser(username = TEST_PREFIX + "student2", roles = "USER") void testGetCompetencyProgressForLearningPathByOtherStudent() throws Exception { diff --git a/src/test/javascript/spec/component/learning-paths/pages/learning-path-student-page.component.spec.ts b/src/test/javascript/spec/component/learning-paths/pages/learning-path-student-page.component.spec.ts index c3609648d0d8..f5a5abbf3efc 100644 --- a/src/test/javascript/spec/component/learning-paths/pages/learning-path-student-page.component.spec.ts +++ b/src/test/javascript/spec/component/learning-paths/pages/learning-path-student-page.component.spec.ts @@ -13,6 +13,7 @@ import { LearningPathApiService } from 'app/course/learning-paths/services/learn import { AlertService } from 'app/core/util/alert.service'; import { MockAlertService } from '../../../helpers/mocks/service/mock-alert.service'; import { EntityNotFoundError } from 'app/course/learning-paths/exceptions/entity-not-found.error'; +import { LearningPathDTO } from 'app/entities/competency/learning-path.model'; describe('LearningPathStudentPageComponent', () => { let component: LearningPathStudentPageComponent; @@ -20,7 +21,11 @@ describe('LearningPathStudentPageComponent', () => { let learningPathApiService: LearningPathApiService; let alertService: AlertService; - const learningPathId = 1; + const learningPath: LearningPathDTO = { + id: 1, + progress: 0, + startedByStudent: false, + }; const courseId = 2; beforeEach(async () => { @@ -42,6 +47,14 @@ describe('LearningPathStudentPageComponent', () => { }, { provide: TranslateService, useClass: MockTranslateService }, { provide: AlertService, useClass: MockAlertService }, + { + provide: LearningPathApiService, + useValue: { + getLearningPathForCurrentUser: jest.fn().mockResolvedValue(learningPath), + generateLearningPathForCurrentUser: jest.fn().mockResolvedValue(learningPath), + startLearningPathForCurrentUser: jest.fn().mockReturnValue(() => Promise.resolve()), + }, + }, ], }) .overrideComponent(LearningPathStudentPageComponent, { @@ -70,18 +83,23 @@ describe('LearningPathStudentPageComponent', () => { expect(component.courseId()).toBe(courseId); }); - it('should get learning path id', async () => { - const getLearningPathIdSpy = jest.spyOn(learningPathApiService, 'getLearningPathId').mockResolvedValue(learningPathId); + it('should get learning path', async () => { + const getLearningPathIdSpy = jest.spyOn(learningPathApiService, 'getLearningPathForCurrentUser').mockResolvedValue(learningPath); fixture.detectChanges(); await fixture.whenStable(); expect(getLearningPathIdSpy).toHaveBeenCalledWith(courseId); - expect(component.learningPathId()).toEqual(learningPathId); + expect(component.learningPath()).toEqual(learningPath); }); - it('should show navigation on successful load', async () => { - jest.spyOn(learningPathApiService, 'getLearningPathId').mockResolvedValue(learningPathId); + it('should show navigation when learning path has been started', async () => { + const learningPath: LearningPathDTO = { + id: 1, + progress: 0, + startedByStudent: true, + }; + jest.spyOn(learningPathApiService, 'getLearningPathForCurrentUser').mockResolvedValueOnce(learningPath); fixture.detectChanges(); await fixture.whenStable(); @@ -91,8 +109,8 @@ describe('LearningPathStudentPageComponent', () => { expect(navComponent).toBeTruthy(); }); - it('should show error when learning path id could not be loaded', async () => { - const getLearningPathIdSpy = jest.spyOn(learningPathApiService, 'getLearningPathId').mockRejectedValue(new Error()); + it('should show error when learning path could not be loaded', async () => { + const getLearningPathIdSpy = jest.spyOn(learningPathApiService, 'getLearningPathForCurrentUser').mockRejectedValue(new Error()); const alertServiceErrorSpy = jest.spyOn(alertService, 'error'); fixture.detectChanges(); @@ -103,8 +121,8 @@ describe('LearningPathStudentPageComponent', () => { }); it('should set isLoading correctly during learning path load', async () => { - jest.spyOn(learningPathApiService, 'getLearningPathId').mockResolvedValue(learningPathId); - const loadingSpy = jest.spyOn(component.isLearningPathIdLoading, 'set'); + jest.spyOn(learningPathApiService, 'getLearningPathForCurrentUser').mockResolvedValue(learningPath); + const loadingSpy = jest.spyOn(component.isLearningPathLoading, 'set'); fixture.detectChanges(); await fixture.whenStable(); @@ -113,28 +131,34 @@ describe('LearningPathStudentPageComponent', () => { expect(loadingSpy).toHaveBeenNthCalledWith(2, false); }); - it('should generate learning path on click', async () => { - jest.spyOn(learningPathApiService, 'getLearningPathId').mockRejectedValue(new EntityNotFoundError()); - const generateLearningPathSpy = jest.spyOn(learningPathApiService, 'generateLearningPath').mockResolvedValue(learningPathId); + it('should generate learning path on start when not found', async () => { + jest.spyOn(learningPathApiService, 'getLearningPathForCurrentUser').mockReturnValueOnce(Promise.reject(new EntityNotFoundError())); + const generateLearningPathSpy = jest.spyOn(learningPathApiService, 'generateLearningPathForCurrentUser').mockResolvedValue(learningPath); + const startSpy = jest.spyOn(learningPathApiService, 'startLearningPathForCurrentUser'); fixture.detectChanges(); await fixture.whenStable(); - fixture.detectChanges(); - const generateLearningPathButton = fixture.debugElement.query(By.css('#generate-learning-path-button')); - generateLearningPathButton.nativeElement.click(); + await component.startLearningPath(); + + expect(component.learningPath()).toEqual({ ...learningPath, startedByStudent: true }); + expect(generateLearningPathSpy).toHaveBeenCalledExactlyOnceWith(courseId); + expect(startSpy).toHaveBeenCalledExactlyOnceWith(learningPath.id); + }); + it('should set learning path to started', async () => { + const startedSpy = jest.spyOn(learningPathApiService, 'startLearningPathForCurrentUser'); fixture.detectChanges(); await fixture.whenStable(); - expect(component.learningPathId()).toEqual(learningPathId); - expect(generateLearningPathSpy).toHaveBeenCalledWith(courseId); + await component.startLearningPath(); + + expect(component.learningPath()).toEqual({ ...learningPath, startedByStudent: true }); + expect(startedSpy).toHaveBeenCalledExactlyOnceWith(learningPath.id); }); - it('should set isLoading correctly during learning path generation', async () => { - jest.spyOn(learningPathApiService, 'getLearningPathId').mockRejectedValue(new EntityNotFoundError()); - jest.spyOn(learningPathApiService, 'generateLearningPath').mockResolvedValue(learningPathId); - const loadingSpy = jest.spyOn(component.isLearningPathIdLoading, 'set'); + it('should set isLoading correctly during learning path start', async () => { + const loadingSpy = jest.spyOn(component.isLearningPathLoading, 'set'); fixture.detectChanges(); await fixture.whenStable(); diff --git a/src/test/javascript/spec/service/learning-path/learning-path-api.service.spec.ts b/src/test/javascript/spec/service/learning-path/learning-path-api.service.spec.ts index 57d2487cefdb..d7f34259716e 100644 --- a/src/test/javascript/spec/service/learning-path/learning-path-api.service.spec.ts +++ b/src/test/javascript/spec/service/learning-path/learning-path-api.service.spec.ts @@ -26,11 +26,11 @@ describe('LearningPathApiService', () => { httpClient.verify(); }); - it('should get learning path id', async () => { + it('should get learning path for current user', async () => { const courseId = 1; - const methodCall = learningPathApiService.getLearningPathId(courseId); - const response = httpClient.expectOne({ method: 'GET', url: `${baseUrl}/courses/${courseId}/learning-path-id` }); + const methodCall = learningPathApiService.getLearningPathForCurrentUser(courseId); + const response = httpClient.expectOne({ method: 'GET', url: `${baseUrl}/courses/${courseId}/learning-path/me` }); response.flush({}); await methodCall; }); @@ -55,6 +55,13 @@ describe('LearningPathApiService', () => { await methodCall; }); + it('should start learning path for current user', async () => { + const methodCall = learningPathApiService.startLearningPathForCurrentUser(learningPathId); + const response = httpClient.expectOne({ method: 'PATCH', url: `${baseUrl}/learning-path/${learningPathId}/start` }); + response.flush({}); + await methodCall; + }); + it('should get learning path navigation overview', async () => { const methodCall = learningPathApiService.getLearningPathNavigationOverview(learningPathId); const response = httpClient.expectOne({ method: 'GET', url: `${baseUrl}/learning-path/${learningPathId}/navigation-overview` }); @@ -62,8 +69,8 @@ describe('LearningPathApiService', () => { await methodCall; }); - it('should generate learning path', async () => { - const methodCall = learningPathApiService.generateLearningPath(courseId); + it('should generate learning path for current user', async () => { + const methodCall = learningPathApiService.generateLearningPathForCurrentUser(courseId); const response = httpClient.expectOne({ method: 'POST', url: `${baseUrl}/courses/${courseId}/learning-path` }); response.flush({}); await methodCall; From a083623d35c27f1826b890bef9c0f3dc5c0e47fc Mon Sep 17 00:00:00 2001 From: Johannes Wiest Date: Tue, 24 Sep 2024 20:02:25 +0200 Subject: [PATCH 2/2] add server tests --- .../learningpath/LearningPathService.java | 3 + .../LearningPathIntegrationTest.java | 141 +++++++++++------- 2 files changed, 92 insertions(+), 52 deletions(-) diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java index 7b92b207d867..d2e5caef82c9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java @@ -284,6 +284,9 @@ public void startLearningPathForCurrentUser(long learningPathId) { if (!learningPath.getUser().equals(currentUser)) { throw new AccessForbiddenException("You are not allowed to start this learning path."); } + else if (learningPath.isStartedByStudent()) { + throw new BadRequestException("Learning path already started."); + } learningPath.setStartedByStudent(true); learningPathRepository.save(learningPath); } diff --git a/src/test/java/de/tum/cit/aet/artemis/competency/LearningPathIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/competency/LearningPathIntegrationTest.java index 27162ed5f7a1..0d613a2c935d 100644 --- a/src/test/java/de/tum/cit/aet/artemis/competency/LearningPathIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/competency/LearningPathIntegrationTest.java @@ -120,7 +120,9 @@ class LearningPathIntegrationTest extends AbstractSpringIntegrationIndependentTe private static final int NUMBER_OF_STUDENTS = 5; - private static final String STUDENT_OF_COURSE = TEST_PREFIX + "student1"; + private static final String STUDENT1_OF_COURSE = TEST_PREFIX + "student1"; + + private static final String STUDENT2_OF_COURSE = TEST_PREFIX + "student2"; private static final String TUTOR_OF_COURSE = TEST_PREFIX + "tutor1"; @@ -157,7 +159,7 @@ void setupTestScenario() throws Exception { lecture.setCourse(course); lectureRepository.save(lecture); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); textUnit = createAndLinkTextUnit(student, competencies[0], true); textExercise = createAndLinkTextExercise(competencies[1], false); @@ -215,7 +217,7 @@ private void deleteCompetencyRESTCall(Competency competency) throws Exception { } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testAll_asStudent() throws Exception { this.testAllPreAuthorize(); request.get("/api/courses/" + course.getId() + "/learning-path-id", HttpStatus.BAD_REQUEST, Long.class); @@ -314,7 +316,7 @@ void testGetLearningPathsOnPageForCourseLearningPathsDisabled() throws Exception @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR") void testGetLearningPathsOnPageForCourseEmpty() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var search = pageableSearchUtilService.configureSearch(STUDENT_OF_COURSE + "SuffixThatAllowsTheResultToBeEmpty"); + final var search = pageableSearchUtilService.configureSearch(STUDENT1_OF_COURSE + "SuffixThatAllowsTheResultToBeEmpty"); final var result = request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.OK, LearningPathInformationDTO.class, pageableSearchUtilService.searchMapping(search)); assertThat(result.getResultsOnPage()).isNullOrEmpty(); @@ -324,7 +326,7 @@ void testGetLearningPathsOnPageForCourseEmpty() throws Exception { @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR") void testGetLearningPathsOnPageForCourseExactlyStudent() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var search = pageableSearchUtilService.configureSearch(STUDENT_OF_COURSE); + final var search = pageableSearchUtilService.configureSearch(STUDENT1_OF_COURSE); final var result = request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.OK, LearningPathInformationDTO.class, pageableSearchUtilService.searchMapping(search)); assertThat(result.getResultsOnPage()).hasSize(1); @@ -358,7 +360,7 @@ void addCompetencyToLearningPaths(Function competencyProgressService.updateCompetencyProgress(competency.getId(), student)); @@ -487,10 +489,10 @@ void testGetLearningPathCompetencyGraph() throws Exception { @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") @EnumSource(LearningPathResource.NgxRequestType.class) - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningPathNgxForLearningPathsDisabled(LearningPathResource.NgxRequestType type) throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); course.setLearningPathsEnabled(false); courseRepository.save(course); @@ -502,7 +504,7 @@ void testGetLearningPathNgxForLearningPathsDisabled(LearningPathResource.NgxRequ @WithMockUser(username = TEST_PREFIX + "student2", roles = "USER") void testGetLearningPathNgxForOtherStudent(LearningPathResource.NgxRequestType type) throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); request.get("/api/learning-path/" + learningPath.getId() + "/" + type, HttpStatus.FORBIDDEN, NgxLearningPathDTO.class); } @@ -515,10 +517,10 @@ void testGetLearningPathNgxForOtherStudent(LearningPathResource.NgxRequestType t */ @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") @EnumSource(LearningPathResource.NgxRequestType.class) - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningPathNgxAsStudent(LearningPathResource.NgxRequestType type) throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); request.get("/api/learning-path/" + learningPath.getId() + "/" + type, HttpStatus.OK, NgxLearningPathDTO.class); } @@ -566,7 +568,7 @@ void testGetLearningPathNgxAsEditor(LearningPathResource.NgxRequestType type) th @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR") void testGetLearningPathNgxAsInstructor(LearningPathResource.NgxRequestType type) throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); request.get("/api/learning-path/" + learningPath.getId() + "/" + type, HttpStatus.OK, NgxLearningPathDTO.class); } @@ -575,21 +577,21 @@ void testGetLearningPathNgxAsInstructor(LearningPathResource.NgxRequestType type class GetLearningPath { @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void shouldReturnExisting() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); final var result = request.get("/api/courses/" + course.getId() + "/learning-path/me", HttpStatus.OK, LearningPathDTO.class); assertThat(result).isEqualTo(LearningPathDTO.of(learningPath)); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void shouldReturnNotFoundIfNotExists() throws Exception { course.setLearningPathsEnabled(true); course = courseRepository.save(course); - var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); student = userRepository.findWithLearningPathsByIdElseThrow(student.getId()); learningPathRepository.deleteAll(student.getLearningPaths()); request.get("/api/courses/" + course.getId() + "/learning-path/me", HttpStatus.NOT_FOUND, LearningPathDTO.class); @@ -600,44 +602,79 @@ void shouldReturnNotFoundIfNotExists() throws Exception { class GenerateLearningPath { @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void shouldReturnForbiddenIfNotEnabled() throws Exception { request.postWithResponseBody("/api/courses/" + course.getId() + "/learning-path", null, LearningPathDTO.class, HttpStatus.BAD_REQUEST); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void shouldReturnBadRequestIfAlreadyExists() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); request.postWithResponseBody("/api/courses/" + course.getId() + "/learning-path", null, LearningPathDTO.class, HttpStatus.BAD_REQUEST); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void shouldGenerateLearningPath() throws Exception { course.setLearningPathsEnabled(true); course = courseRepository.save(course); final var response = request.postWithResponseBody("/api/courses/" + course.getId() + "/learning-path", null, LearningPathDTO.class, HttpStatus.CREATED); assertThat(response).isNotNull(); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); assertThat(learningPath).isNotNull(); } } - // TODO: Write tests for start learning path + @Nested + class StartLearningPath { + + @BeforeEach + void setup() { + course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); + } + + @Test + @WithMockUser(username = STUDENT2_OF_COURSE, roles = "USER") + void shouldReturnForbiddenIfNotOwn() throws Exception { + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); + final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); + request.patch("/api/learning-path/" + learningPath.getId() + "/start", null, HttpStatus.FORBIDDEN); + } + + @Test + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") + void shouldReturnBadRequestIfAlreadyStarted() throws Exception { + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); + final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); + learningPath.setStartedByStudent(true); + learningPathRepository.save(learningPath); + request.patch("/api/learning-path/" + learningPath.getId() + "/start", null, HttpStatus.BAD_REQUEST); + } + + @Test + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") + void shouldStartLearningPath() throws Exception { + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); + final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); + request.patch("/api/learning-path/" + learningPath.getId() + "/start", null, HttpStatus.NO_CONTENT); + final var updatedLearningPath = learningPathRepository.findByIdElseThrow(learningPath.getId()); + assertThat(updatedLearningPath.isStartedByStudent()).isTrue(); + } + } @Test @WithMockUser(username = TEST_PREFIX + "student2", roles = "USER") void testGetCompetencyProgressForLearningPathByOtherStudent() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); request.get("/api/learning-path/" + learningPath.getId() + "/competency-progress", HttpStatus.FORBIDDEN, Set.class); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetCompetencyProgressForLearningPathByOwner() throws Exception { testGetCompetencyProgressForLearningPath(); } @@ -649,10 +686,10 @@ void testGetCompetencyProgressForLearningPathByInstructor() throws Exception { } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningPathNavigation() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); competencyProgressService.updateProgressByLearningObjectSync(textUnit, Set.of(student)); @@ -664,10 +701,10 @@ void testGetLearningPathNavigation() throws Exception { } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningPathNavigationEmptyCompetencies() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); textExercise.setCompetencies(Set.of()); @@ -689,10 +726,10 @@ void testGetLearningPathNavigationEmptyCompetencies() throws Exception { } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningPathNavigationDoesNotLeakUnreleasedLearningObjects() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); textExercise.setCompetencies(Set.of()); @@ -737,10 +774,10 @@ private void verifyNavigationObjectResult(LearningObject expectedObject, Learnin } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetRelativeLearningPathNavigation() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); final var result = request.get("/api/learning-path/" + learningPath.getId() + "/relative-navigation?learningObjectId=" + textUnit.getId() + "&learningObjectType=" + LearningPathNavigationObjectDTO.LearningObjectType.LECTURE + "&competencyId=" + competencies[0].getId(), HttpStatus.OK, LearningPathNavigationDTO.class); @@ -754,16 +791,16 @@ void testGetRelativeLearningPathNavigation() throws Exception { @WithMockUser(username = TEST_PREFIX + "student1337", roles = "USER") void testGetLearningPathNavigationForOtherStudent() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); request.get("/api/learning-path/" + learningPath.getId() + "/navigation", HttpStatus.FORBIDDEN, LearningPathNavigationDTO.class); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningPathNavigationOverview() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); final var result = request.get("/api/learning-path/" + learningPath.getId() + "/navigation-overview", HttpStatus.OK, LearningPathNavigationOverviewDTO.class); @@ -777,26 +814,26 @@ void testGetLearningPathNavigationOverview() throws Exception { @WithMockUser(username = TEST_PREFIX + "student1337", roles = "USER") void testGetLearningPathNavigationOverviewForOtherStudent() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); request.get("/api/learning-path/" + learningPath.getId() + "/navigation-overview", HttpStatus.FORBIDDEN, LearningPathNavigationOverviewDTO.class); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetCompetencyOrderForLearningPath() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); final var result = request.getList("/api/learning-path/" + learningPath.getId() + "/competencies", HttpStatus.OK, CompetencyNameDTO.class); assertThat(result).containsExactlyElementsOf(Arrays.stream(competencies).map(CompetencyNameDTO::of).toList()); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningObjectsForCompetency() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); var result = request.getList("/api/learning-path/" + learningPath.getId() + "/competencies/" + competencies[0].getId() + "/learning-objects", HttpStatus.OK, LearningPathNavigationObjectDTO.class); @@ -810,10 +847,10 @@ void testGetLearningObjectsForCompetency() throws Exception { } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningObjectsForCompetencyMultipleObjects() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); List completedLectureUnits = List.of(createAndLinkTextUnit(student, competencies[4], true), createAndLinkTextUnit(student, competencies[4], true)); @@ -844,7 +881,7 @@ void testGetLearningObjectsForCompetencyMultipleObjects() throws Exception { void testGetCompetencyProgressForLearningPath() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); final var result = request.get("/api/learning-path/" + learningPath.getId() + "/competency-progress", HttpStatus.OK, Set.class); assertThat(result).hasSize(5); @@ -855,7 +892,7 @@ private TextExercise createAndLinkTextExercise(Competency competency, boolean wi Set gradingCriteria = exerciseUtilService.addGradingInstructionsToExercise(textExercise); gradingCriterionRepository.saveAll(gradingCriteria); if (withAssessment) { - var student = userRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); studentScoreUtilService.createStudentScore(textExercise, student, 100.0); } competencyUtilService.linkExerciseToCompetency(competency, textExercise);