Skip to content

Commit

Permalink
fix [back] [QCMPLUS-40] update Questions endpoints
Browse files Browse the repository at this point in the history
Signed-off-by: teklit_tewolde <[email protected]>
  • Loading branch information
teklit_tewolde committed Aug 23, 2024
1 parent 7d8cb7b commit 51494ed
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 49 deletions.
10 changes: 10 additions & 0 deletions qcmplusweb/src/services/QuestionService.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import axiosInstance, {API_BASE_URL} from './AxiosInstance';

const QUESTION_REST_API_URL = `${API_BASE_URL}/api/questions`;

export const getQuestionsByQuizId = (quizId) => axiosInstance.get(`${QUESTION_REST_API_URL}/quizzes/${quizId}`);
export const getQuestionById = (quizId, questionId) => axiosInstance.get(`${QUESTION_REST_API_URL}/${questionId}/quizzes/${quizId}`);
export const createQuestion = (quizId, questionData) => axiosInstance.post(`${QUESTION_REST_API_URL}/quizzes/${quizId}`, questionData);
export const updateQuestion = (quizId, questionId, questionData) => axiosInstance.put(`${QUESTION_REST_API_URL}/${questionId}/quizzes/${quizId}`, questionData);
export const deleteQuestion = (quizId, questionId) => axiosInstance.delete(`${QUESTION_REST_API_URL}/${questionId}/quizzes/${quizId}`);
export const getAllQuestions = () => axiosInstance.get(`${QUESTION_REST_API_URL}/all`);
39 changes: 27 additions & 12 deletions src/main/java/com/pmn/qcmplus/controller/QuestionController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
import com.pmn.qcmplus.model.Quiz;
import com.pmn.qcmplus.service.QuestionService;
import com.pmn.qcmplus.service.QuizService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@PreAuthorize("hasRole('ADMIN')")
@RequestMapping("/api/quizzes/{quizId}/questions")
@RequestMapping("/api/questions")
public class QuestionController {

private final QuestionService questionService;
Expand All @@ -31,29 +31,44 @@ public QuestionController(QuestionService questionService, QuizService quizServi
this.quizService = quizService;
}

@GetMapping
@GetMapping("/quizzes/{quizId}")
public ResponseEntity<List<Question>> getQuestionsByQuizId(@PathVariable Integer quizId) {
List<Question> questions = questionService.getQuestionsByQuizId(quizId);
return ResponseEntity.ok(questions);
}

@PostMapping
@PostMapping("/quizzes/{quizId}")
public ResponseEntity<Question> createQuestion(@RequestBody Question question, @PathVariable Integer quizId) {
Quiz quiz = quizService.getQuizById(quizId);
question.setQuiz(quiz);
Question createdQuestion = questionService.saveQuestion(question);
return ResponseEntity.ok(createdQuestion);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteQuestion(@PathVariable Integer quizId, @PathVariable Integer id) {
questionService.deleteQuestion(id);
@PutMapping("/{questionId}/quizzes/{quizId}")
public ResponseEntity<Question> updateQuestion(@RequestBody Question question, @PathVariable Integer quizId, @PathVariable Integer questionId) {
Quiz quiz = quizService.getQuizById(quizId);
question.setQuiz(quiz);
question.setQuestionId(questionId);
Question updatedQuestion = questionService.updateQuestion(question);
return ResponseEntity.ok(updatedQuestion);
}

@DeleteMapping("/{questionId}/quizzes/{quizId}")
public ResponseEntity<Void> deleteQuestion(@PathVariable Integer quizId, @PathVariable Integer questionId) {
questionService.deleteQuestion(questionId);
return ResponseEntity.noContent().build();
}

@GetMapping("/{id}")
public ResponseEntity<Question> getQuestionById(@PathVariable Integer quizId, @PathVariable Integer id) {
Question question = questionService.getQuestionById(id);
@GetMapping("/{questionId}/quizzes/{quizId}")
public ResponseEntity<Question> getQuestionById(@PathVariable Integer quizId, @PathVariable Integer questionId) {
Question question = questionService.getQuestionById(questionId);
return ResponseEntity.ok(question);
}
}

@GetMapping("/all")
public ResponseEntity<List<Question>> getAllQuestions() {
List<Question> questions = questionService.getAllQuestions();
return ResponseEntity.ok(questions);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.pmn.qcmplus.exception;

public class QuestionNotFoundException extends RuntimeException {
public QuestionNotFoundException(Integer id) {
super("Question with id " + id + " not found.");
}
}
6 changes: 5 additions & 1 deletion src/main/java/com/pmn/qcmplus/service/QuestionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,9 @@ public interface QuestionService {

Question saveQuestion(Question question);

Question updateQuestion(Question question);

void deleteQuestion(Integer id);
}

List<Question> getAllQuestions(); // New method
}
53 changes: 41 additions & 12 deletions src/main/java/com/pmn/qcmplus/service/impl/QuestionServiceImpl.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.pmn.qcmplus.service.impl;

import com.pmn.qcmplus.exception.QuestionNotFoundException;
import com.pmn.qcmplus.model.Question;
import com.pmn.qcmplus.model.Quiz;
import com.pmn.qcmplus.repository.QuestionRepository;
import com.pmn.qcmplus.repository.QuizRepository;
import com.pmn.qcmplus.service.QuestionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class QuestionServiceImpl implements QuestionService {
Expand All @@ -31,31 +31,60 @@ public List<Question> getQuestionsByQuizId(Integer quizId) {
@Override
public Question getQuestionById(Integer id) {
return questionRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Question not found with id: " + id));
.orElseThrow(() -> new QuestionNotFoundException(id));
}


@Override
public Question saveQuestion(Question question) {
// Validate that the quiz exists
if (question == null) {

Check notice on line 39 in src/main/java/com/pmn/qcmplus/service/impl/QuestionServiceImpl.java

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Duplicated code fragment

Duplicated code

Check notice on line 39 in src/main/java/com/pmn/qcmplus/service/impl/QuestionServiceImpl.java

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Duplicated code fragment

Duplicated code
throw new IllegalArgumentException("Question cannot be null.");
}
if (question.getQuiz() == null || question.getQuiz().getQuizId() == null) {
throw new IllegalArgumentException("Quiz or Quiz ID cannot be null.");
}

Optional<Quiz> quiz = quizRepository.findById(question.getQuiz().getQuizId());
if (quiz.isEmpty()) {
throw new IllegalArgumentException("Quiz does not exist.");
}

return questionRepository.save(question);
}


@Override
public Question updateQuestion(Question question) {
if (question == null) {
throw new IllegalArgumentException("Question cannot be null.");
}
if (question.getQuiz() == null || question.getQuiz().getQuizId() == null) {
throw new IllegalArgumentException("Quiz or Quiz ID cannot be null.");
}

Optional<Quiz> quiz = quizRepository.findById(question.getQuiz().getQuizId());
if (quiz.isEmpty()) {
throw new IllegalArgumentException("Quiz does not exist.");
}

// Save the question
Optional<Question> existingQuestion = questionRepository.findById(question.getQuestionId());
if (existingQuestion.isEmpty()) {
throw new QuestionNotFoundException(question.getQuestionId());
}

return questionRepository.save(question);
}

@Override
public void deleteQuestion(Integer id) {
// Validate that the question exists
Optional<Question> question = questionRepository.findById(id);
if (question.isEmpty()) {
throw new IllegalArgumentException("Question does not exist.");
throw new QuestionNotFoundException(id);
}

// Delete the question
questionRepository.deleteById(id);
}
}

@Override
public List<Question> getAllQuestions() {
return questionRepository.findAll();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,22 @@
import com.pmn.qcmplus.model.Quiz;
import com.pmn.qcmplus.service.QuestionService;
import com.pmn.qcmplus.service.QuizService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.mockito.MockitoAnnotations;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

class QuestionControllerTest {

Expand All @@ -47,7 +45,7 @@ void setUp() {

@Test
void testGetQuestionsByQuizId() {
when(questionService.getQuestionsByQuizId(eq(1))).thenReturn(Arrays.asList(question));
when(questionService.getQuestionsByQuizId(eq(1))).thenReturn(Collections.singletonList(question));

ResponseEntity<List<Question>> response = questionController.getQuestionsByQuizId(1);

Expand All @@ -60,7 +58,6 @@ void testGetQuestionsByQuizId() {
void testGetQuestionById() {
when(questionService.getQuestionById(eq(1))).thenReturn(question);

// Update: Include quizId as a parameter in the method call
ResponseEntity<Question> response = questionController.getQuestionById(1, 1);

assertEquals(HttpStatus.OK, response.getStatusCode());
Expand All @@ -80,14 +77,46 @@ void testCreateQuestion() {
verify(questionService, times(1)).saveQuestion(any(Question.class));
}

@Test
void testUpdateQuestion() {
Quiz mockQuiz = new Quiz();
mockQuiz.setQuizId(1);
Question mockQuestion = new Question();
mockQuestion.setQuiz(mockQuiz);
mockQuestion.setQuestionId(1);

when(quizService.getQuizById(eq(1))).thenReturn(mockQuiz);
when(questionService.updateQuestion(any(Question.class))).thenReturn(mockQuestion);

ResponseEntity<Question> response = questionController.updateQuestion(mockQuestion, 1, 1);

assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(mockQuestion, response.getBody());
verify(quizService, times(1)).getQuizById(eq(1));
verify(questionService, times(1)).updateQuestion(any(Question.class));
}

@Test
void testDeleteQuestion() {
doNothing().when(questionService).deleteQuestion(eq(1));

// Update: Include quizId as a parameter in the method call
ResponseEntity<Void> response = questionController.deleteQuestion(1, 1);

assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode());
verify(questionService, times(1)).deleteQuestion(eq(1));
}

@Test
void testGetAllQuestions() {
when(questionService.getAllQuestions()).thenReturn(Collections.singletonList(question));

ResponseEntity<List<Question>> response = questionController.getAllQuestions();

assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(1, response.getBody().size());
verify(questionService, times(1)).getAllQuestions();
}



}
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
package com.pmn.qcmplus.repository;

import com.pmn.qcmplus.exception.QuestionNotFoundException;
import com.pmn.qcmplus.model.Question;
import com.pmn.qcmplus.model.Quiz;
import com.pmn.qcmplus.service.impl.QuestionServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.mockito.ArgumentMatchers.any;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.mockito.junit.jupiter.MockitoExtension;


@ExtendWith(MockitoExtension.class)
Expand Down Expand Up @@ -116,13 +115,13 @@ void testDeleteQuestion() {
void testDeleteQuestion_NotFound() {
when(questionRepository.findById(1)).thenReturn(Optional.empty());

IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
QuestionNotFoundException exception = assertThrows(QuestionNotFoundException.class, () -> {
questionService.deleteQuestion(1);
});

assertEquals("Question does not exist.", exception.getMessage());
assertEquals("Question with id 1 not found.", exception.getMessage());

verify(questionRepository, times(1)).findById(1);
verify(questionRepository, times(0)).deleteById(1);
}
}
}
Loading

0 comments on commit 51494ed

Please sign in to comment.