diff --git a/src/main/java/info/pionas/quiz/api/exam/ExamApiMapper.java b/src/main/java/info/pionas/quiz/api/exam/ExamApiMapper.java index 0eb4a30..c287847 100644 --- a/src/main/java/info/pionas/quiz/api/exam/ExamApiMapper.java +++ b/src/main/java/info/pionas/quiz/api/exam/ExamApiMapper.java @@ -14,4 +14,6 @@ interface ExamApiMapper { ExamResponseDto map(ExamResult examResult); List map(List answers); + + List mapToDto(List userExams); } diff --git a/src/main/java/info/pionas/quiz/api/exam/ExamRestController.java b/src/main/java/info/pionas/quiz/api/exam/ExamRestController.java index 7e357ca..6df702b 100644 --- a/src/main/java/info/pionas/quiz/api/exam/ExamRestController.java +++ b/src/main/java/info/pionas/quiz/api/exam/ExamRestController.java @@ -6,10 +6,14 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.List; + @RestController @RequestMapping("exam") @RequiredArgsConstructor @@ -25,4 +29,10 @@ Mono endExam(@Valid @RequestBody NewExamDto examDto, Authentica return Mono.just(examApiMapper.map(examService.getExamDetails(examId))); } + @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) + @ResponseStatus(HttpStatus.OK) + Flux exams(Authentication authentication) { + return Flux.fromIterable(examApiMapper.mapToDto(examService.getUserExams(String.valueOf(authentication.getPrincipal())))); + } + } diff --git a/src/main/java/info/pionas/quiz/domain/exam/ExamServiceImpl.java b/src/main/java/info/pionas/quiz/domain/exam/ExamServiceImpl.java index 2ddacc0..b31e1bf 100644 --- a/src/main/java/info/pionas/quiz/domain/exam/ExamServiceImpl.java +++ b/src/main/java/info/pionas/quiz/domain/exam/ExamServiceImpl.java @@ -37,12 +37,18 @@ public UUID endExam(String username, UUID quizId, List answers) } @Override - @org.springframework.transaction.annotation.Transactional(readOnly = true) + @Transactional(readOnly = true) public ExamResult getExamDetails(UUID id) { return examRepository.getById(id) .orElseThrow(() -> new ExamResultNotFoundException(id)); } + @Override + @Transactional(readOnly = true) + public List getUserExams(String username) { + return examRepository.getUserExams(username); + } + private NewExamDetails getNewExamDetails(UUID resultId, UUID quizId, String username, LocalDateTime dateTime) { return NewExamDetails.builder() .id(resultId) diff --git a/src/main/java/info/pionas/quiz/domain/exam/api/ExamRepository.java b/src/main/java/info/pionas/quiz/domain/exam/api/ExamRepository.java index 411e681..8f49a6c 100644 --- a/src/main/java/info/pionas/quiz/domain/exam/api/ExamRepository.java +++ b/src/main/java/info/pionas/quiz/domain/exam/api/ExamRepository.java @@ -1,5 +1,6 @@ package info.pionas.quiz.domain.exam.api; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -8,4 +9,6 @@ public interface ExamRepository { void save(NewExamDetails newExamDetails); Optional getById(UUID id); + + List getUserExams(String username); } diff --git a/src/main/java/info/pionas/quiz/domain/exam/api/ExamService.java b/src/main/java/info/pionas/quiz/domain/exam/api/ExamService.java index 6161e8c..7185571 100644 --- a/src/main/java/info/pionas/quiz/domain/exam/api/ExamService.java +++ b/src/main/java/info/pionas/quiz/domain/exam/api/ExamService.java @@ -1,5 +1,7 @@ package info.pionas.quiz.domain.exam.api; +import info.pionas.quiz.api.exam.api.NewExamAnswerDto; + import java.util.List; import java.util.UUID; @@ -8,4 +10,6 @@ public interface ExamService { UUID endExam(String username, UUID uuid, List answers); ExamResult getExamDetails(UUID examId); + + List getUserExams(String username); } diff --git a/src/main/java/info/pionas/quiz/infrastructure/database/exam/ExamJpaMapper.java b/src/main/java/info/pionas/quiz/infrastructure/database/exam/ExamJpaMapper.java index 70e02a0..90e5d0c 100644 --- a/src/main/java/info/pionas/quiz/infrastructure/database/exam/ExamJpaMapper.java +++ b/src/main/java/info/pionas/quiz/infrastructure/database/exam/ExamJpaMapper.java @@ -5,6 +5,8 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import java.util.List; + @Mapper(componentModel = "spring") interface ExamJpaMapper { @@ -15,5 +17,7 @@ interface ExamJpaMapper { @Mapping(target = "created", source = "created") ExamEntity map(NewExamDetails newExamDetails); - ExamResult map(ExamResultReadModel examEntity); + ExamResult map(ExamResultReadModel examResultReadModel); + + List map(List examResultReadModels); } diff --git a/src/main/java/info/pionas/quiz/infrastructure/database/exam/ExamRepositoryImpl.java b/src/main/java/info/pionas/quiz/infrastructure/database/exam/ExamRepositoryImpl.java index 516e1d6..0af5afb 100644 --- a/src/main/java/info/pionas/quiz/infrastructure/database/exam/ExamRepositoryImpl.java +++ b/src/main/java/info/pionas/quiz/infrastructure/database/exam/ExamRepositoryImpl.java @@ -6,6 +6,7 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -27,4 +28,9 @@ public Optional getById(UUID id) { return examResultJpaRepository.findById(id) .map(examJpaMapper::map); } + + @Override + public List getUserExams(String username) { + return examJpaMapper.map(examResultJpaRepository.findAllByUsername(username)); + } } diff --git a/src/main/java/info/pionas/quiz/infrastructure/database/exam/ExamResultJpaRepository.java b/src/main/java/info/pionas/quiz/infrastructure/database/exam/ExamResultJpaRepository.java index bf642c5..6d53171 100644 --- a/src/main/java/info/pionas/quiz/infrastructure/database/exam/ExamResultJpaRepository.java +++ b/src/main/java/info/pionas/quiz/infrastructure/database/exam/ExamResultJpaRepository.java @@ -3,8 +3,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.UUID; @Repository interface ExamResultJpaRepository extends JpaRepository { + + List findAllByUsername(String username); } \ No newline at end of file diff --git a/src/main/resources/db/changelog/init.yaml b/src/main/resources/db/changelog/init.yaml index 5c55161..661face 100644 --- a/src/main/resources/db/changelog/init.yaml +++ b/src/main/resources/db/changelog/init.yaml @@ -78,18 +78,27 @@ databaseChangeLog: - column: name: result_id type: UUID + constraints: + nullable: false - column: name: question_id type: UUID + constraints: + nullable: false - column: name: answer_id type: UUID - - column: - name: username - type: VARCHAR(255) + constraints: + nullable: false - column: name: correct type: BOOLEAN + constraints: + nullable: false - column: name: created type: DATETIME + - addPrimaryKey: + columnNames: result_id, question_id, answer_id + constraintName: pk_quiz_exam_answer + tableName: quiz_exam_answers diff --git a/src/test/java/info/pionas/quiz/api/exam/ExamRestControllerIT.java b/src/test/java/info/pionas/quiz/api/exam/ExamRestControllerIT.java index 6d722bd..f454826 100644 --- a/src/test/java/info/pionas/quiz/api/exam/ExamRestControllerIT.java +++ b/src/test/java/info/pionas/quiz/api/exam/ExamRestControllerIT.java @@ -79,11 +79,6 @@ void should_end_exam() { .exchange() .returnResult(ExamResponseDto.class); //then - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } ExamResponseDto examResponseDto = response.getResponseBody().blockLast(); assertThat(response.getStatus()).isEqualTo(HttpStatus.CREATED); assertThat(examResponseDto).isNotNull(); @@ -122,6 +117,33 @@ void should_throw_bad_request_when_answer_for_question_not_exist() throws IOExce .containsExactlyInAnyOrder("There is no answer to the question ce703f5b-274c-4398-b855-d461887c7ed5"); } + @Test + @Sql({"/db/quiz.sql", "/db/question.sql", "/db/answer.sql", "/db/exam_result.sql"}) + void should_return_user_exam_list() { + //given + final var user = new User("admin", "admin", Collections.emptyList()); + //when + final var response = webTestClient + .get().uri("/api/v1/exam") + .accept(MediaType.TEXT_EVENT_STREAM) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + generateToken(user)) + .exchange(); + //then + response.expectStatus().isOk() + .expectHeader().contentType("text/event-stream;charset=UTF-8") + .expectBodyList(ExamResponseDto.class) + .consumeWith(listEntityExchangeResult -> { + List examResponseDtos = listEntityExchangeResult.getResponseBody(); + assertThat(examResponseDtos).hasSize(2); + ExamResponseDto examResponseDto1 = examResponseDtos.getFirst(); + assertThat(examResponseDto1).isNotNull(); + assertThat(examResponseDto1.getId()).isEqualTo(UUID.fromString("a375ca97-33f9-4251-aaa0-f9d95b55003f")); + ExamResponseDto examResponseDto2 = examResponseDtos.getLast(); + assertThat(examResponseDto2).isNotNull(); + assertThat(examResponseDto2.getId()).isEqualTo(UUID.fromString("b8acee24-edaf-4725-876c-c37ec4512a8c")); + }); + } + private NewExamDto getNewExamDto() { final var newExamDto = new NewExamDto(); newExamDto.setQuizId(UUID.fromString("b4a63897-60f7-4e94-846e-e199d8734144")); diff --git a/src/test/java/info/pionas/quiz/domain/exam/ExamServiceTest.java b/src/test/java/info/pionas/quiz/domain/exam/ExamServiceTest.java index 65dd59c..b57269c 100644 --- a/src/test/java/info/pionas/quiz/domain/exam/ExamServiceTest.java +++ b/src/test/java/info/pionas/quiz/domain/exam/ExamServiceTest.java @@ -112,6 +112,23 @@ void should_throw_exam_not_found_exception_when_exam_by_id_not_exist() { .isEqualTo("Exam result by ID 7a398eb6-1d20-4a05-b13b-c752c3c7c5d3 not exist"); } + @Test + void should_return_exam_list_for_user() { + //given + final var examResultId = UUID.fromString("7a398eb6-1d20-4a05-b13b-c752c3c7c5d3"); + final var quizId = UUID.fromString("b83d5c22-7b78-4435-9daa-17bb532c0f63"); + final var answers = getAnswers(); + final var quiz = getQuiz(quizId); + when(examRepository.getUserExams("username")).thenReturn(List.of(createExamResult(examResultId, quiz, answers))); + //when + final var examResults = service.getUserExams("username"); + //then + assertThat(examResults).hasSize(1); + ExamResult examResult = examResults.getFirst(); + assertThat(examResult.getUsername()).isEqualTo("username"); + assertThat(examResult.getCorrectAnswer()).isEqualTo(1); + } + private Quiz getQuiz(UUID quizId) { final var questionId1 = UUID.fromString("03df4c0e-a760-4b23-aebe-ac0fd8761804"); final var questionId2 = UUID.fromString("0feb6e91-a25c-4c9a-8f6c-acf489a5af6b"); diff --git a/src/test/resources/db/exam_result.sql b/src/test/resources/db/exam_result.sql new file mode 100644 index 0000000..3119df6 --- /dev/null +++ b/src/test/resources/db/exam_result.sql @@ -0,0 +1,8 @@ +insert into quiz_exams (id, username, quiz_id, created) values ('a375ca97-33f9-4251-aaa0-f9d95b55003f', 'admin', 'b4a63897-60f7-4e94-846e-e199d8734144', '2023-12-18T13:47:02.159316600') +insert into quiz_exam_answers (correct,created,answer_id,question_id,result_id) values (false,'2023-12-18T13:47:02.159316600','a4bd2133-d4e8-4b73-9264-4afb02096ffd','ce703f5b-274c-4398-b855-d461887c7ed5','a375ca97-33f9-4251-aaa0-f9d95b55003f') + +insert into quiz_exams (id, username, quiz_id, created) values ('c6639ddd-8602-4e4b-acb3-c0f138a7f1f1', 'adminek', 'b4a63897-60f7-4e94-846e-e199d8734144', '2023-12-18T13:47:03.159316600') +insert into quiz_exam_answers (correct,created,answer_id,question_id,result_id) values (false,'2023-12-18T13:47:03.159316600','a4bd2133-d4e8-4b73-9264-4afb02096ffd','ce703f5b-274c-4398-b855-d461887c7ed5','c6639ddd-8602-4e4b-acb3-c0f138a7f1f1') + +insert into quiz_exams (id, username, quiz_id, created) values ('b8acee24-edaf-4725-876c-c37ec4512a8c', 'admin', 'b4a63897-60f7-4e94-846e-e199d8734144', '2023-12-18T13:47:04.159316600') +insert into quiz_exam_answers (correct,created,answer_id,question_id,result_id) values (false,'2023-12-18T13:47:04.159316600','a4bd2133-d4e8-4b73-9264-4afb02096ffd','ce703f5b-274c-4398-b855-d461887c7ed5','b8acee24-edaf-4725-876c-c37ec4512a8c') \ No newline at end of file