diff --git a/backend/Dockerfile b/backend/Dockerfile index fd634e1..14463aa 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -22,11 +22,8 @@ CMD ["tail","-f","/dev/null"] #---Building FROM base AS build COPY . . -RUN mvn clean package -DskipTests - -#--Testing -FROM build AS testing -RUN mvn clean test +RUN mvn clean package +# Add -DskipTests for slightly faster build, but no testing #---Production # Pulled again to get rid of maven, to decrease the size diff --git a/backend/pom.xml b/backend/pom.xml index ac725c2..a1fe9a5 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -32,10 +32,10 @@ 3.1.8 - + org.springframework.boot spring-boot-starter-web @@ -47,11 +47,11 @@ runtime true - + org.springframework.boot spring-boot-starter-test diff --git a/backend/src/main/java/com/ninjaws/quiz/models/Question.java b/backend/src/main/java/com/ninjaws/quiz/models/Question.java index 36ee786..6c0a32d 100644 --- a/backend/src/main/java/com/ninjaws/quiz/models/Question.java +++ b/backend/src/main/java/com/ninjaws/quiz/models/Question.java @@ -7,10 +7,20 @@ import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.persistence.*; import lombok.Getter; +import lombok.Setter; +@Entity +@Table(name = "question") @Getter +@Setter public class Question { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String type; private String difficulty; private String category; diff --git a/backend/src/main/java/com/ninjaws/quiz/repositories/QuestionRepository.java b/backend/src/main/java/com/ninjaws/quiz/repositories/QuestionRepository.java new file mode 100644 index 0000000..31e331a --- /dev/null +++ b/backend/src/main/java/com/ninjaws/quiz/repositories/QuestionRepository.java @@ -0,0 +1,8 @@ +package com.ninjaws.quiz.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.ninjaws.quiz.models.Question; + +public interface QuestionRepository extends JpaRepository { +} diff --git a/backend/src/main/java/com/ninjaws/quiz/services/DataService.java b/backend/src/main/java/com/ninjaws/quiz/services/DataService.java new file mode 100644 index 0000000..ceefcd0 --- /dev/null +++ b/backend/src/main/java/com/ninjaws/quiz/services/DataService.java @@ -0,0 +1,27 @@ +package com.ninjaws.quiz.services; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.ninjaws.quiz.models.Question; +import com.ninjaws.quiz.repositories.QuestionRepository; + +@Service +public class DataService { + @Autowired + private QuestionRepository questionRepository; + + public List getAllQuestions() { + return questionRepository.findAll(); + } + + public Question saveQuestion(Question question) { + return questionRepository.save(question); + } + + public List saveQuestions(List questions) { + return questionRepository.saveAll(questions); + } +} diff --git a/backend/src/main/java/com/ninjaws/quiz/services/ResponseHandlingService.java b/backend/src/main/java/com/ninjaws/quiz/services/ResponseHandlingService.java index af391f0..07abae9 100644 --- a/backend/src/main/java/com/ninjaws/quiz/services/ResponseHandlingService.java +++ b/backend/src/main/java/com/ninjaws/quiz/services/ResponseHandlingService.java @@ -11,6 +11,8 @@ public class ResponseHandlingService { @Autowired private CacheService cacheService; + @Autowired + private DataService dataService; /** * If there is no sessionId, then this is a request from the scheduler @@ -62,6 +64,7 @@ public class ResponseHandlingService { switch (response.getResponseCode()) { case 0: // TODO: Store in DB + dataService.saveQuestions(response.getResults()); break; case 4: // Signal DataService that we have extracted all the data. Stop gathering, switch to caching-mode diff --git a/backend/src/main/java/com/ninjaws/quiz/services/SchedulerService.java b/backend/src/main/java/com/ninjaws/quiz/services/SchedulerService.java index a57f3c9..41b7dc4 100644 --- a/backend/src/main/java/com/ninjaws/quiz/services/SchedulerService.java +++ b/backend/src/main/java/com/ninjaws/quiz/services/SchedulerService.java @@ -27,6 +27,8 @@ public class SchedulerService { Request request = queueService.popFromQueue(); if (request != null) { apiService.handleRequest(request); + } else { + apiService.collectBulk(); } } diff --git a/backend/src/main/java/com/ninjaws/quiz/services/StorageService.java b/backend/src/main/java/com/ninjaws/quiz/services/StorageService.java deleted file mode 100644 index d670143..0000000 --- a/backend/src/main/java/com/ninjaws/quiz/services/StorageService.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.ninjaws.quiz.services; - -public class StorageService { - -} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 8d23e20..5fa94c6 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,2 +1,12 @@ spring.application.name=quiz -server.port=9090 \ No newline at end of file +server.port=9090 + +# H2 Database configuration +spring.h2.console.enabled=true +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= + +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect \ No newline at end of file diff --git a/backend/src/test/java/com/ninjaws/quiz/repositories/QuestRepositoryTest.java b/backend/src/test/java/com/ninjaws/quiz/repositories/QuestRepositoryTest.java new file mode 100644 index 0000000..a11b441 --- /dev/null +++ b/backend/src/test/java/com/ninjaws/quiz/repositories/QuestRepositoryTest.java @@ -0,0 +1,91 @@ +package com.ninjaws.quiz.repositories; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.annotation.Rollback; + +import com.ninjaws.quiz.models.Question; + +@DataJpaTest +public class QuestRepositoryTest { + @Autowired + private QuestionRepository questionRepository; + + private Question question; + + @BeforeEach + public void setUp() { + question = new Question(); + question.setType("multiple choice"); + question.setDifficulty("easy"); + question.setCategory("general knowledge"); + question.setQuestion("What is the capital of France?"); + question.setCorrectAnswer("Paris"); + question.setIncorrectAnswers(Arrays.asList("Berlin", "Madrid", "Rome")); + } + + @Test + @Rollback(false) + public void testSaveQuestion() { + Question savedQuestion = questionRepository.save(question); + assertThat(savedQuestion).isNotNull(); + assertThat(savedQuestion.getId()).isGreaterThan(1); + } + + @Test + public void testFindQuestionById() { + questionRepository.save(question); + Optional foundQuestion = questionRepository.findById(question.getId()); + assertThat(foundQuestion).isPresent(); + assertThat(foundQuestion.get().getQuestion()).isEqualTo(question.getQuestion()); + } + + @Test + public void testFindAllQuestions() { + questionRepository.save(question); + List questions = questionRepository.findAll(); + assertThat(questions).isNotEmpty(); + } + + @Test + public void testDeleteQuestion() { + questionRepository.save(question); + Long id = question.getId(); + questionRepository.deleteById(id); + Optional deletedQuestion = questionRepository.findById(id); + assertThat(deletedQuestion).isNotPresent(); + } + + @Test + @Rollback(false) + public void testSaveQuestions() { + Question question1 = new Question(); + question1.setType("multiple choice"); + question1.setDifficulty("easy"); + question1.setCategory("general knowledge"); + question1.setQuestion("What is the capital of France?"); + question1.setCorrectAnswer("Paris"); + question1.setIncorrectAnswers(Arrays.asList("Berlin", "Madrid", "Rome")); + + Question question2 = new Question(); + question2.setType("multiple choice"); + question2.setDifficulty("medium"); + question2.setCategory("science"); + question2.setQuestion("What is the chemical symbol for water?"); + question2.setCorrectAnswer("H2O"); + question2.setIncorrectAnswers(Arrays.asList("CO2", "O2", "NaCl")); + + List savedQuestions = questionRepository.saveAll(Arrays.asList(question1, question2)); + + assertThat(savedQuestions).hasSize(2); + assertThat(savedQuestions.get(0).getQuestion()).isEqualTo("What is the capital of France?"); + assertThat(savedQuestions.get(1).getQuestion()).isEqualTo("What is the chemical symbol for water?"); + } +} diff --git a/backend/src/test/java/com/ninjaws/quiz/services/CacheServiceTest.java b/backend/src/test/java/com/ninjaws/quiz/services/CacheServiceTest.java new file mode 100644 index 0000000..63570e8 --- /dev/null +++ b/backend/src/test/java/com/ninjaws/quiz/services/CacheServiceTest.java @@ -0,0 +1,59 @@ +package com.ninjaws.quiz.services; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.ninjaws.quiz.models.Session; + +public class CacheServiceTest { + + private CacheService cacheService; + + @BeforeEach + public void setUp() { + cacheService = new CacheService(); + } + + @Test + public void testAddToCache() { + String key = "session1"; + Session session = new Session(); + + cacheService.addToCache(key, session); + + assertNotNull(cacheService.getFromCache(key)); + assertEquals(session, cacheService.getFromCache(key)); + } + + @Test + public void testGetFromCache() { + String key = "session1"; + Session session = new Session(); + + cacheService.addToCache(key, session); + + Session retrievedSession = cacheService.getFromCache(key); + assertNotNull(retrievedSession); + assertEquals(session, retrievedSession); + } + + @Test + public void testRemoveFromCache() { + String key = "session1"; + Session session = new Session(); + + cacheService.addToCache(key, session); + cacheService.removeFromCache(key); + + assertNull(cacheService.getFromCache(key)); + } + + @Test + public void testGetFromCacheWithNonExistentKey() { + assertNull(cacheService.getFromCache("nonExistentKey")); + } +} diff --git a/backend/src/test/java/com/ninjaws/quiz/services/CleanerServiceTest.java b/backend/src/test/java/com/ninjaws/quiz/services/CleanerServiceTest.java new file mode 100644 index 0000000..8c0f904 --- /dev/null +++ b/backend/src/test/java/com/ninjaws/quiz/services/CleanerServiceTest.java @@ -0,0 +1,58 @@ +package com.ninjaws.quiz.services; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ninjaws.quiz.models.ApiResponse; +import com.ninjaws.quiz.models.Question; + +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 java.io.IOException; +import java.util.Arrays; +import java.util.List; + +public class CleanerServiceTest { + + @InjectMocks + private CleanerService cleanerService; + + @Mock + private ObjectMapper objectMapper; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + public void testExtractApiResponse() throws IOException { + String json = "{\"response_code\":200,\"results\":[{\"type\":\"multiple choice\",\"difficulty\":\"easy\",\"category\":\"general knowledge\",\"question\":\"What is the capital of France?\",\"correct_answer\":\"Paris\",\"incorrect_answers\":[\"Berlin\",\"Madrid\",\"Rome\"]}]}"; // Example JSON input + + Question question = new Question(); + question.setType("multiple choice"); + question.setDifficulty("easy"); + question.setCategory("general knowledge"); + question.setQuestion("What is the capital of France?"); + question.setCorrectAnswer("Paris"); + question.setIncorrectAnswers(Arrays.asList("Berlin", "Madrid", "Rome")); + + ApiResponse expectedResponse = new ApiResponse(); + expectedResponse.setResponseCode(200); + expectedResponse.setResults(Arrays.asList(question)); + when(objectMapper.readValue(json, ApiResponse.class)).thenReturn(expectedResponse); + + ApiResponse actualResponse = cleanerService.extractApiResponse(json); + assertThat(actualResponse).isNotNull(); + assertThat(actualResponse.getResponseCode()).isEqualTo(expectedResponse.getResponseCode()); + assertThat(actualResponse.getResults()).isNotEmpty(); + assertThat(actualResponse.getResults().get(0).getQuestion()).isEqualTo(question.getQuestion()); + verify(objectMapper, times(1)).readValue(json, ApiResponse.class); + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/ninjaws/quiz/services/DataServiceTest.java b/backend/src/test/java/com/ninjaws/quiz/services/DataServiceTest.java new file mode 100644 index 0000000..2fe0683 --- /dev/null +++ b/backend/src/test/java/com/ninjaws/quiz/services/DataServiceTest.java @@ -0,0 +1,105 @@ +package com.ninjaws.quiz.services; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +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 com.ninjaws.quiz.models.Question; +import com.ninjaws.quiz.repositories.QuestionRepository; + +public class DataServiceTest { + + @InjectMocks + private DataService dataService; + + @Mock + private QuestionRepository questionRepository; + + private Question question; + + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + + question = new Question(); + question.setType("multiple choice"); + question.setDifficulty("easy"); + question.setCategory("general knowledge"); + question.setQuestion("What is the capital of France?"); + question.setCorrectAnswer("Paris"); + question.setIncorrectAnswers(Arrays.asList("Berlin", "Madrid", "Rome")); + } + + @Test + public void testSaveQuestion() { + when(questionRepository.save(any(Question.class))).thenReturn(question); + + Question savedQuestion = dataService.saveQuestion(question); + assertThat(savedQuestion).isNotNull(); + assertThat(savedQuestion.getQuestion()).isEqualTo("What is the capital of France?"); + verify(questionRepository, times(1)).save(question); + } + + @Test + public void testGetAllQuestions() { + when(questionRepository.findAll()).thenReturn(Arrays.asList(question)); + + List questions = dataService.getAllQuestions(); + assertThat(questions).isNotEmpty(); + assertThat(questions.size()).isEqualTo(1); + assertThat(questions.get(0).getQuestion()).isEqualTo("What is the capital of France?"); + verify(questionRepository, times(1)).findAll(); + } + + @Test + public void testFindQuestionById() { + when(questionRepository.findById(any(Long.class))).thenReturn(Optional.of(question)); + + Optional foundQuestion = questionRepository.findById(1L); + assertThat(foundQuestion).isPresent(); + assertThat(foundQuestion.get().getQuestion()).isEqualTo("What is the capital of France?"); + verify(questionRepository, times(1)).findById(1L); + } + + @Test + public void testSaveQuestions() { + Question question1 = new Question(); + question1.setType("multiple choice"); + question1.setDifficulty("easy"); + question1.setCategory("general knowledge"); + question1.setQuestion("What is the capital of France?"); + question1.setCorrectAnswer("Paris"); + question1.setIncorrectAnswers(Arrays.asList("Berlin", "Madrid", "Rome")); + + Question question2 = new Question(); + question2.setType("multiple choice"); + question2.setDifficulty("medium"); + question2.setCategory("science"); + question2.setQuestion("What is the chemical symbol for water?"); + question2.setCorrectAnswer("H2O"); + question2.setIncorrectAnswers(Arrays.asList("CO2", "O2", "NaCl")); + + when(questionRepository.saveAll(anyList())).thenReturn(Arrays.asList(question1, question2)); + + List questions = dataService.saveQuestions(Arrays.asList(question1, question2)); + + assertThat(questions).hasSize(2); + assertThat(questions.get(0).getQuestion()).isEqualTo("What is the capital of France?"); + assertThat(questions.get(1).getQuestion()).isEqualTo("What is the chemical symbol for water?"); + verify(questionRepository, times(1)).saveAll(anyList()); + } +} diff --git a/backend/src/test/java/com/ninjaws/quiz/services/QueueServiceTest.java b/backend/src/test/java/com/ninjaws/quiz/services/QueueServiceTest.java new file mode 100644 index 0000000..e921c04 --- /dev/null +++ b/backend/src/test/java/com/ninjaws/quiz/services/QueueServiceTest.java @@ -0,0 +1,63 @@ +package com.ninjaws.quiz.services; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.ninjaws.quiz.models.QuizSettings; +import com.ninjaws.quiz.models.Request; + +public class QueueServiceTest { + + private QueueService queueService; + + @BeforeEach + public void setUp() { + queueService = new QueueService(); + } + + @Test + public void testAddToQueue() { + QuizSettings quizSettings = new QuizSettings(); + Request request = new Request("session1", quizSettings); + + boolean added = queueService.addToQueue(request); + assertTrue(added); + + for (int i = 0; i < 4; i++) { + added = queueService.addToQueue(new Request("session" + (i + 2), new QuizSettings())); + assertTrue(added); + } + + added = queueService.addToQueue(new Request("session6", new QuizSettings())); + assertFalse(added); + } + + @Test + public void testPopFromQueue() { + QuizSettings quizSettings1 = new QuizSettings(); + QuizSettings quizSettings2 = new QuizSettings(); + + Request request1 = new Request("session1", quizSettings1); + Request request2 = new Request("session2", quizSettings2); + + queueService.addToQueue(request1); + queueService.addToQueue(request2); + + Request poppedRequest = queueService.popFromQueue(); + assertEquals(request1, poppedRequest); + + poppedRequest = queueService.popFromQueue(); + assertEquals(request2, poppedRequest); + + assertNull(queueService.popFromQueue()); + } + + @Test + public void testPopFromEmptyQueue() { + assertNull(queueService.popFromQueue()); + } +} \ No newline at end of file