diff --git a/qcmplusweb/src/services/QuestionService.jsx b/qcmplusweb/src/services/QuestionService.jsx new file mode 100644 index 0000000..bee7de9 --- /dev/null +++ b/qcmplusweb/src/services/QuestionService.jsx @@ -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`); \ No newline at end of file diff --git a/src/main/java/com/pmn/qcmplus/controller/QuestionController.java b/src/main/java/com/pmn/qcmplus/controller/QuestionController.java index 7f90bbf..5d3c47a 100644 --- a/src/main/java/com/pmn/qcmplus/controller/QuestionController.java +++ b/src/main/java/com/pmn/qcmplus/controller/QuestionController.java @@ -4,6 +4,7 @@ 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; @@ -15,11 +16,9 @@ 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; @@ -31,13 +30,13 @@ public QuestionController(QuestionService questionService, QuizService quizServi this.quizService = quizService; } - @GetMapping + @GetMapping("/quizzes/{quizId}") public ResponseEntity> getQuestionsByQuizId(@PathVariable Integer quizId) { List questions = questionService.getQuestionsByQuizId(quizId); return ResponseEntity.ok(questions); } - @PostMapping + @PostMapping("/quizzes/{quizId}") public ResponseEntity createQuestion(@RequestBody Question question, @PathVariable Integer quizId) { Quiz quiz = quizService.getQuizById(quizId); question.setQuiz(quiz); @@ -45,15 +44,21 @@ public ResponseEntity createQuestion(@RequestBody Question question, @ return ResponseEntity.ok(createdQuestion); } - @DeleteMapping("/{id}") - public ResponseEntity deleteQuestion(@PathVariable Integer quizId, @PathVariable Integer id) { - questionService.deleteQuestion(id); + @DeleteMapping("/{questionId}/quizzes/{quizId}") + public ResponseEntity deleteQuestion(@PathVariable Integer quizId, @PathVariable Integer questionId) { + questionService.deleteQuestion(questionId); return ResponseEntity.noContent().build(); } - @GetMapping("/{id}") - public ResponseEntity getQuestionById(@PathVariable Integer quizId, @PathVariable Integer id) { - Question question = questionService.getQuestionById(id); + @GetMapping("/{questionId}/quizzes/{quizId}") + public ResponseEntity getQuestionById(@PathVariable Integer quizId, @PathVariable Integer questionId) { + Question question = questionService.getQuestionById(questionId); return ResponseEntity.ok(question); } -} + + @GetMapping("/all") + public ResponseEntity> getAllQuestions() { + List questions = questionService.getAllQuestions(); + return ResponseEntity.ok(questions); + } +} \ No newline at end of file diff --git a/src/main/java/com/pmn/qcmplus/exception/QuestionNotFoundException.java b/src/main/java/com/pmn/qcmplus/exception/QuestionNotFoundException.java new file mode 100644 index 0000000..a55b7a9 --- /dev/null +++ b/src/main/java/com/pmn/qcmplus/exception/QuestionNotFoundException.java @@ -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."); + } +} \ No newline at end of file diff --git a/src/main/java/com/pmn/qcmplus/service/QuestionService.java b/src/main/java/com/pmn/qcmplus/service/QuestionService.java index 5ca96ef..dea844c 100644 --- a/src/main/java/com/pmn/qcmplus/service/QuestionService.java +++ b/src/main/java/com/pmn/qcmplus/service/QuestionService.java @@ -12,4 +12,6 @@ public interface QuestionService { Question saveQuestion(Question question); void deleteQuestion(Integer id); -} + + List getAllQuestions(); // New method +} \ No newline at end of file diff --git a/src/main/java/com/pmn/qcmplus/service/impl/QuestionServiceImpl.java b/src/main/java/com/pmn/qcmplus/service/impl/QuestionServiceImpl.java index 332d7a5..27491c7 100644 --- a/src/main/java/com/pmn/qcmplus/service/impl/QuestionServiceImpl.java +++ b/src/main/java/com/pmn/qcmplus/service/impl/QuestionServiceImpl.java @@ -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 { @@ -31,31 +31,37 @@ public List 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) { + 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 = quizRepository.findById(question.getQuiz().getQuizId()); if (quiz.isEmpty()) { throw new IllegalArgumentException("Quiz does not exist."); } - // Save the question return questionRepository.save(question); } @Override public void deleteQuestion(Integer id) { - // Validate that the question exists Optional 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 getAllQuestions() { + return questionRepository.findAll(); + } +} \ No newline at end of file diff --git a/src/test/java/com/pmn/qcmplus/controller/QuestionControllerTest.java b/src/test/java/com/pmn/qcmplus/controller/QuestionControllerTest.java index d9664e5..ee7c831 100644 --- a/src/test/java/com/pmn/qcmplus/controller/QuestionControllerTest.java +++ b/src/test/java/com/pmn/qcmplus/controller/QuestionControllerTest.java @@ -4,24 +4,23 @@ 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 { @@ -47,7 +46,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> response = questionController.getQuestionsByQuizId(1); @@ -60,7 +59,6 @@ void testGetQuestionsByQuizId() { void testGetQuestionById() { when(questionService.getQuestionById(eq(1))).thenReturn(question); - // Update: Include quizId as a parameter in the method call ResponseEntity response = questionController.getQuestionById(1, 1); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -84,10 +82,23 @@ void testCreateQuestion() { void testDeleteQuestion() { doNothing().when(questionService).deleteQuestion(eq(1)); - // Update: Include quizId as a parameter in the method call ResponseEntity 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> response = questionController.getAllQuestions(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(1, response.getBody().size()); + verify(questionService, times(1)).getAllQuestions(); + } + + + } diff --git a/src/test/java/com/pmn/qcmplus/repository/QuestionRepositoryTest.java b/src/test/java/com/pmn/qcmplus/repository/QuestionRepositoryTest.java index 24d4170..c1730af 100644 --- a/src/test/java/com/pmn/qcmplus/repository/QuestionRepositoryTest.java +++ b/src/test/java/com/pmn/qcmplus/repository/QuestionRepositoryTest.java @@ -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) @@ -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); } -} +} \ No newline at end of file diff --git a/src/test/java/com/pmn/qcmplus/service/impl/QuestionServiceImplTest.java b/src/test/java/com/pmn/qcmplus/service/impl/QuestionServiceImplTest.java new file mode 100644 index 0000000..9078327 --- /dev/null +++ b/src/test/java/com/pmn/qcmplus/service/impl/QuestionServiceImplTest.java @@ -0,0 +1,132 @@ +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 java.util.List; +import java.util.Optional; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.ArgumentMatchers.any; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.MockitoAnnotations; + +class QuestionServiceImplTest { + + @Mock + private QuestionRepository questionRepository; + + @Mock + private QuizRepository quizRepository; + + @InjectMocks + private QuestionServiceImpl questionService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void testGetQuestionsByQuizId() { + int quizId = 1; + List mockQuestions = List.of(new Question()); + when(questionRepository.findByQuiz_QuizId(quizId)).thenReturn(mockQuestions); + + List questions = questionService.getQuestionsByQuizId(quizId); + + assertEquals(mockQuestions, questions); + verify(questionRepository, times(1)).findByQuiz_QuizId(quizId); + } + + @Test + void testGetQuestionById() { + int questionId = 1; + Question mockQuestion = new Question(); + when(questionRepository.findById(questionId)).thenReturn(Optional.of(mockQuestion)); + + Question question = questionService.getQuestionById(questionId); + + assertEquals(mockQuestion, question); + verify(questionRepository, times(1)).findById(questionId); + } + + @Test + void testGetQuestionByIdThrowsExceptionWhenNotFound() { + int questionId = 1; + when(questionRepository.findById(questionId)).thenReturn(Optional.empty()); + + assertThrows(QuestionNotFoundException.class, () -> questionService.getQuestionById(questionId)); + verify(questionRepository, times(1)).findById(questionId); + } + + @Test + void testSaveQuestion() { + Quiz mockQuiz = new Quiz(); + mockQuiz.setQuizId(1); + Question mockQuestion = new Question(); + mockQuestion.setQuiz(mockQuiz); + + when(quizRepository.findById(any(Integer.class))).thenReturn(Optional.of(mockQuiz)); + when(questionRepository.save(any(Question.class))).thenReturn(mockQuestion); + + Question savedQuestion = questionService.saveQuestion(mockQuestion); + + assertEquals(mockQuestion, savedQuestion); + verify(questionRepository, times(1)).save(mockQuestion); + } + + @Test + void testSaveQuestionThrowsExceptionWhenQuizNotFound() { + + Quiz mockQuiz = new Quiz(); + mockQuiz.setQuizId(1); + Question mockQuestion = new Question(); + mockQuestion.setQuiz(mockQuiz); + when(quizRepository.findById(any(Integer.class))).thenReturn(Optional.empty()); + + assertThrows(IllegalArgumentException.class, () -> questionService.saveQuestion(mockQuestion)); + verify(questionRepository, never()).save(any(Question.class)); + } + + + @Test + void testDeleteQuestion() { + int questionId = 1; + Question mockQuestion = new Question(); + when(questionRepository.findById(questionId)).thenReturn(Optional.of(mockQuestion)); + + questionService.deleteQuestion(questionId); + + verify(questionRepository, times(1)).deleteById(questionId); + } + + @Test + void testDeleteQuestionThrowsExceptionWhenNotFound() { + int questionId = 1; + when(questionRepository.findById(questionId)).thenReturn(Optional.empty()); + + assertThrows(QuestionNotFoundException.class, () -> questionService.deleteQuestion(questionId)); + verify(questionRepository, never()).deleteById(questionId); + } + + @Test + void testGetAllQuestions() { + List mockQuestions = List.of(new Question()); + when(questionRepository.findAll()).thenReturn(mockQuestions); + + List questions = questionService.getAllQuestions(); + + assertEquals(mockQuestions, questions); + verify(questionRepository, times(1)).findAll(); + } +}