-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add exam service and end exam action
- Loading branch information
Showing
12 changed files
with
340 additions
and
0 deletions.
There are no files selected for viewing
43 changes: 43 additions & 0 deletions
43
src/main/java/info/pionas/quiz/domain/exam/ExamFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package info.pionas.quiz.domain.exam; | ||
|
||
|
||
import info.pionas.quiz.domain.exam.api.AnswerForQuestionNotFoundException; | ||
import info.pionas.quiz.domain.exam.api.ExamAnswer; | ||
import info.pionas.quiz.domain.exam.api.ExamDetails; | ||
import info.pionas.quiz.domain.exam.api.PassExamAnswer; | ||
import info.pionas.quiz.domain.quiz.api.Question; | ||
import info.pionas.quiz.domain.quiz.api.Quiz; | ||
import lombok.AccessLevel; | ||
import lombok.NoArgsConstructor; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
@Component | ||
@NoArgsConstructor(access = AccessLevel.PACKAGE) | ||
class ExamFactory { | ||
|
||
public ExamDetails endExam(String username, Quiz quiz, List<PassExamAnswer> answers) { | ||
return ExamDetails.of(username, quiz, getAnswerResult(quiz.getQuestions(), answers)); | ||
} | ||
|
||
private List<ExamAnswer> getAnswerResult(List<Question> questions, List<PassExamAnswer> answers) { | ||
return Optional.ofNullable(questions) | ||
.orElseGet(Collections::emptyList) | ||
.stream() | ||
.map(question -> ExamAnswer.of(question, getAnswerForQuestion(question, answers))) | ||
.toList(); | ||
} | ||
|
||
private PassExamAnswer getAnswerForQuestion(Question question, List<PassExamAnswer> answers) { | ||
final var questionId = question.getId(); | ||
return Optional.ofNullable(answers) | ||
.orElseGet(Collections::emptyList) | ||
.stream() | ||
.filter(passExamAnswer -> passExamAnswer.isAnswerForQuestion(questionId)) | ||
.findAny() | ||
.orElseThrow(() -> new AnswerForQuestionNotFoundException(questionId)); | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
src/main/java/info/pionas/quiz/domain/exam/ExamServiceImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package info.pionas.quiz.domain.exam; | ||
|
||
import info.pionas.quiz.domain.exam.api.PassExamAnswer; | ||
import info.pionas.quiz.domain.exam.api.ExamRepository; | ||
import info.pionas.quiz.domain.exam.api.ExamResult; | ||
import info.pionas.quiz.domain.exam.api.ExamService; | ||
import info.pionas.quiz.domain.quiz.QuizNotFoundException; | ||
import info.pionas.quiz.domain.quiz.api.QuizRepository; | ||
import info.pionas.quiz.domain.shared.UuidGenerator; | ||
import lombok.AllArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
@Service | ||
@AllArgsConstructor | ||
class ExamServiceImpl implements ExamService { | ||
|
||
private final QuizRepository quizRepository; | ||
private final ExamRepository examRepository; | ||
private final ExamFactory examFactory; | ||
private final UuidGenerator uuidGenerator; | ||
|
||
@Override | ||
public ExamResult endExam(String username, UUID quizId, List<PassExamAnswer> answers) { | ||
final var quiz = quizRepository.findById(quizId) | ||
.orElseThrow(() -> new QuizNotFoundException(quizId)); | ||
return examRepository.save(uuidGenerator.generate(), examFactory.endExam(username, quiz, answers)); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
src/main/java/info/pionas/quiz/domain/exam/api/AnswerForQuestionNotFoundException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package info.pionas.quiz.domain.exam.api; | ||
|
||
import info.pionas.quiz.domain.shared.exception.NotFoundException; | ||
|
||
import java.util.UUID; | ||
|
||
public class AnswerForQuestionNotFoundException extends NotFoundException { | ||
public AnswerForQuestionNotFoundException(UUID questionId) { | ||
super(String.format("There is no answer to the question %s", questionId)); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
src/main/java/info/pionas/quiz/domain/exam/api/ExamAnswer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package info.pionas.quiz.domain.exam.api; | ||
|
||
import info.pionas.quiz.domain.quiz.api.Question; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
import java.util.UUID; | ||
|
||
@Getter | ||
@Builder | ||
public class ExamAnswer { | ||
|
||
private UUID questionId; | ||
private UUID answerId; | ||
private Boolean correct; | ||
|
||
public static ExamAnswer of(Question question, PassExamAnswer answerForQuestion) { | ||
return ExamAnswer.builder() | ||
.questionId(question.getId()) | ||
.answerId(answerForQuestion.getAnswerId()) | ||
.correct(question.isCorrectAnswer(answerForQuestion)) | ||
.build(); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/info/pionas/quiz/domain/exam/api/ExamDetails.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package info.pionas.quiz.domain.exam.api; | ||
|
||
import info.pionas.quiz.domain.quiz.api.Quiz; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
import java.util.List; | ||
|
||
@Builder | ||
@Getter | ||
@AllArgsConstructor(staticName = "of") | ||
public class ExamDetails { | ||
|
||
private String username; | ||
private Quiz quiz; | ||
private List<ExamAnswer> answers; | ||
} |
8 changes: 8 additions & 0 deletions
8
src/main/java/info/pionas/quiz/domain/exam/api/ExamRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package info.pionas.quiz.domain.exam.api; | ||
|
||
import java.util.UUID; | ||
|
||
public interface ExamRepository { | ||
|
||
ExamResult save(UUID id, ExamDetails examDetails); | ||
} |
23 changes: 23 additions & 0 deletions
23
src/main/java/info/pionas/quiz/domain/exam/api/ExamResult.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package info.pionas.quiz.domain.exam.api; | ||
|
||
import info.pionas.quiz.domain.quiz.api.Quiz; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
@Builder | ||
@Getter | ||
public class ExamResult { | ||
|
||
private UUID id; | ||
private Quiz quiz; | ||
private List<ExamAnswer> answers; | ||
|
||
public long getCorrectAnswer() { | ||
return answers.stream() | ||
.filter(ExamAnswer::getCorrect) | ||
.count(); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/info/pionas/quiz/domain/exam/api/ExamService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package info.pionas.quiz.domain.exam.api; | ||
|
||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
public interface ExamService { | ||
|
||
ExamResult endExam(String username, UUID uuid, List<PassExamAnswer> answers); | ||
} |
19 changes: 19 additions & 0 deletions
19
src/main/java/info/pionas/quiz/domain/exam/api/PassExamAnswer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package info.pionas.quiz.domain.exam.api; | ||
|
||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
import java.util.Objects; | ||
import java.util.UUID; | ||
|
||
@Getter | ||
@Builder | ||
public class PassExamAnswer { | ||
|
||
private UUID questionId; | ||
private UUID answerId; | ||
|
||
public boolean isAnswerForQuestion(UUID questionId) { | ||
return Objects.equals(this.questionId, questionId); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
src/test/java/info/pionas/quiz/domain/exam/ExamServiceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package info.pionas.quiz.domain.exam; | ||
|
||
import info.pionas.quiz.domain.exam.api.*; | ||
import info.pionas.quiz.domain.quiz.QuizNotFoundException; | ||
import info.pionas.quiz.domain.quiz.api.Answer; | ||
import info.pionas.quiz.domain.quiz.api.Question; | ||
import info.pionas.quiz.domain.quiz.api.Quiz; | ||
import info.pionas.quiz.domain.quiz.api.QuizRepository; | ||
import info.pionas.quiz.domain.shared.UuidGenerator; | ||
import org.junit.jupiter.api.Test; | ||
import org.mockito.Mockito; | ||
|
||
import java.util.*; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
import static org.mockito.Mockito.when; | ||
|
||
class ExamServiceTest { | ||
|
||
private final QuizRepository quizRepository = Mockito.mock(QuizRepository.class); | ||
private final ExamRepository examRepository = Mockito.mock(ExamRepository.class); | ||
private final UuidGenerator uuidGenerator = Mockito.mock(UuidGenerator.class); | ||
private final ExamFactory examFactory = new ExamFactory(); | ||
private final ExamService service = new ExamServiceImpl(quizRepository, examRepository, examFactory, uuidGenerator); | ||
|
||
@Test | ||
void should_throw_not_found_exception_when_quiz_by_id_not_exist() { | ||
//given | ||
final var quizId = UUID.fromString("b83d5c22-7b78-4435-9daa-17bb532c0f63"); | ||
final var answers = Collections.<PassExamAnswer>emptyList(); | ||
when(quizRepository.findById(quizId)).thenReturn(Optional.empty()); | ||
//when | ||
QuizNotFoundException exception = assertThrows(QuizNotFoundException.class, () -> service.endExam("username", quizId, answers)); | ||
//then | ||
assertThat(exception).isNotNull(); | ||
assertThat(exception.getMessage()) | ||
.isEqualTo(String.format("Quiz by ID %s not exist", quizId)); | ||
} | ||
|
||
@Test | ||
void should_throw_answer_for_question_not_found_exception_when_question_has_not_answer() { | ||
//given | ||
final var quizId = UUID.fromString("b83d5c22-7b78-4435-9daa-17bb532c0f63"); | ||
final var answers = Collections.<PassExamAnswer>emptyList(); | ||
when(quizRepository.findById(quizId)).thenReturn(Optional.of(getQuiz(quizId))); | ||
//when | ||
AnswerForQuestionNotFoundException exception = assertThrows(AnswerForQuestionNotFoundException.class, () -> service.endExam("username", quizId, answers)); | ||
//then | ||
assertThat(exception).isNotNull(); | ||
assertThat(exception.getMessage()) | ||
.isEqualTo("There is no answer to the question 03df4c0e-a760-4b23-aebe-ac0fd8761804"); | ||
} | ||
|
||
@Test | ||
void should_end_exam() { | ||
//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(); | ||
when(quizRepository.findById(quizId)).thenReturn(Optional.of(getQuiz(quizId))); | ||
when(examRepository.save(Mockito.any(UUID.class), Mockito.any(ExamDetails.class))).thenReturn(createExamResult(examResultId, getQuiz(quizId), answers)); | ||
when(uuidGenerator.generate()).thenReturn(examResultId); | ||
//when | ||
ExamResult examResult = service.endExam("username", quizId, answers); | ||
//then | ||
assertThat(examResult).isNotNull(); | ||
assertThat(examResult.getId()).isEqualTo(examResultId); | ||
assertThat(examResult.getCorrectAnswer()).isEqualTo(1L); | ||
} | ||
|
||
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"); | ||
final var answerId1 = UUID.fromString("1f12ef49-9a35-45f2-9824-edb673602a7f"); | ||
final var answerId2 = UUID.fromString("126b0faa-1de1-4b9e-b79c-8b03361e8839"); | ||
final var answerId3 = UUID.fromString("9f5e41ad-9614-4a14-ac8a-dad55d4ce89f"); | ||
final var answerId4 = UUID.fromString("5e02b9e3-c1d4-43da-b7df-45899da5b9a2"); | ||
final var content1 = "Content 1"; | ||
final var content2 = "Content 2"; | ||
final var content3 = "Content 3"; | ||
final var content4 = "Content 4"; | ||
|
||
final var question1 = Question.builder() | ||
.id(questionId1) | ||
.content("Spring is the best JAVA framework") | ||
.answers(new ArrayList<>(Arrays.asList(createAnswer(answerId1, content1, true), createAnswer(answerId2, content2, false)))) | ||
.build(); | ||
final var question2 = Question.builder() | ||
.id(questionId2) | ||
.content("What is the best Front End framework") | ||
.answers(new ArrayList<>(Arrays.asList(createAnswer(answerId3, content3, false), createAnswer(answerId4, content4, true)))) | ||
.build(); | ||
return Quiz.builder() | ||
.id(quizId) | ||
.title("Title") | ||
.description("Description") | ||
.questions(new ArrayList<>(Arrays.asList(question1, question2))) | ||
.build(); | ||
} | ||
|
||
private Answer createAnswer(UUID id, String content, Boolean correct) { | ||
return Answer.builder() | ||
.id(id) | ||
.content(content) | ||
.correct(correct) | ||
.build(); | ||
} | ||
|
||
private List<PassExamAnswer> getAnswers() { | ||
final var questionId1 = UUID.fromString("03df4c0e-a760-4b23-aebe-ac0fd8761804"); | ||
final var answerId1 = UUID.fromString("1f12ef49-9a35-45f2-9824-edb673602a7f"); | ||
final var questionId2 = UUID.fromString("0feb6e91-a25c-4c9a-8f6c-acf489a5af6b"); | ||
final var answerId2 = UUID.fromString("9f5e41ad-9614-4a14-ac8a-dad55d4ce89f"); | ||
return List.of( | ||
createPassExamAnswer(questionId1, answerId1), | ||
createPassExamAnswer(questionId2, answerId2) | ||
); | ||
} | ||
|
||
private PassExamAnswer createPassExamAnswer(UUID questionId, UUID answerId) { | ||
return PassExamAnswer.builder() | ||
.questionId(questionId) | ||
.answerId(answerId) | ||
.build(); | ||
} | ||
|
||
private ExamResult createExamResult(UUID examResultId, Quiz quiz, List<PassExamAnswer> answers) { | ||
final var questions = quiz.getQuestions(); | ||
return ExamResult.builder() | ||
.id(examResultId) | ||
.quiz(quiz) | ||
.answers(List.of( | ||
crateExamAnswer(questions.getFirst(), answers.getFirst()), | ||
crateExamAnswer(questions.getLast(), answers.getLast()) | ||
)) | ||
.build(); | ||
} | ||
|
||
private ExamAnswer crateExamAnswer(Question question, PassExamAnswer passExamAnswer) { | ||
return ExamAnswer.of(question, passExamAnswer); | ||
} | ||
} |