From 121dff910dab4410617780e4572b4e272e57af5d Mon Sep 17 00:00:00 2001 From: Ninjaws Date: Sat, 2 Nov 2024 20:02:36 +0100 Subject: [PATCH] [Added] Functionality for getting data from the database [NOTE: swapping seems to difficult for now, might have to go for a cache approach] --- backend/pom.xml | 5 + .../quiz/components/StartupRunner.java | 27 +++ .../ninjaws/quiz/config/DataSourceConfig.java | 46 ++++ .../com/ninjaws/quiz/entities/Category.java | 25 +++ .../ninjaws/quiz/entities/QuestionEntity.java | 36 ++++ .../com/ninjaws/quiz/models/ApiResponse.java | 2 +- .../ninjaws/quiz/models/CategoryResponse.java | 14 ++ .../{Question.java => QuestionDTO.java} | 14 +- .../com/ninjaws/quiz/models/QuizSettings.java | 50 +++-- .../java/com/ninjaws/quiz/models/Session.java | 2 +- .../quiz/repositories/CategoryRepository.java | 11 + .../quiz/repositories/QuestionRepository.java | 22 +- .../com/ninjaws/quiz/services/ApiService.java | 25 ++- .../ninjaws/quiz/services/CleanerService.java | 67 +++++- .../ninjaws/quiz/services/DataService.java | 46 +++- .../quiz/services/DatabaseScheduler.java | 20 ++ .../ninjaws/quiz/services/QuizService.java | 21 ++ .../services/ResponseHandlingService.java | 10 +- .../src/main/resources/application.properties | 34 ++- .../repositories/QuestRepositoryTest.java | 91 -------- .../quiz/services/CleanerServiceTest.java | 58 ----- .../quiz/services/CleanerServiceTest.txt | 107 ++++++++++ .../quiz/services/DataServiceTest.java | 105 --------- .../ninjaws/quiz/services/DataServiceTest.txt | 202 ++++++++++++++++++ .../src/app/Quiz/services/quiz.service.ts | 2 +- scripts/LoadTestMultiple.sh | 2 +- scripts/LoadTestOne-WithParams.sh | 46 ++++ scripts/LoadTestOne.sh | 3 +- .../benchmarks/{basic.png => 1. Queue.png} | Bin .../2. DB and Queue - No Params.png | Bin 0 -> 69550 bytes 30 files changed, 791 insertions(+), 302 deletions(-) create mode 100644 backend/src/main/java/com/ninjaws/quiz/components/StartupRunner.java create mode 100644 backend/src/main/java/com/ninjaws/quiz/config/DataSourceConfig.java create mode 100644 backend/src/main/java/com/ninjaws/quiz/entities/Category.java create mode 100644 backend/src/main/java/com/ninjaws/quiz/entities/QuestionEntity.java create mode 100644 backend/src/main/java/com/ninjaws/quiz/models/CategoryResponse.java rename backend/src/main/java/com/ninjaws/quiz/models/{Question.java => QuestionDTO.java} (88%) create mode 100644 backend/src/main/java/com/ninjaws/quiz/repositories/CategoryRepository.java create mode 100644 backend/src/main/java/com/ninjaws/quiz/services/DatabaseScheduler.java delete mode 100644 backend/src/test/java/com/ninjaws/quiz/repositories/QuestRepositoryTest.java delete mode 100644 backend/src/test/java/com/ninjaws/quiz/services/CleanerServiceTest.java create mode 100644 backend/src/test/java/com/ninjaws/quiz/services/CleanerServiceTest.txt delete mode 100644 backend/src/test/java/com/ninjaws/quiz/services/DataServiceTest.java create mode 100644 backend/src/test/java/com/ninjaws/quiz/services/DataServiceTest.txt create mode 100755 scripts/LoadTestOne-WithParams.sh rename scripts/benchmarks/{basic.png => 1. Queue.png} (100%) create mode 100644 scripts/benchmarks/2. DB and Queue - No Params.png diff --git a/backend/pom.xml b/backend/pom.xml index a1fe9a5..2de204e 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -72,6 +72,11 @@ org.springframework.boot spring-boot-starter-validation + + org.apache.commons + commons-text + 1.10.0 + diff --git a/backend/src/main/java/com/ninjaws/quiz/components/StartupRunner.java b/backend/src/main/java/com/ninjaws/quiz/components/StartupRunner.java new file mode 100644 index 0000000..1885a81 --- /dev/null +++ b/backend/src/main/java/com/ninjaws/quiz/components/StartupRunner.java @@ -0,0 +1,27 @@ +package com.ninjaws.quiz.components; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * Sets up the database, and adds the categories + * + * Fills up the primary database + * + * Then, at night, creates a new database, adds categories, and then starts filling it up + * When full, swaps the databases, and wipes the old one + */ +public class StartupRunner implements CommandLineRunner { + private final JdbcTemplate primaryJdbcTemplate; + + public StartupRunner(JdbcTemplate primaryJdbcTemplate) { + this.primaryJdbcTemplate = primaryJdbcTemplate; + } + + @Override + public void run(String... args) { + // Initialization logic here + primaryJdbcTemplate.execute("CREATE TABLE IF NOT EXISTS test_table (id INT PRIMARY KEY, name VARCHAR(255))"); + primaryJdbcTemplate.update("INSERT INTO test_table (id, name) VALUES (?, ?)", 1, "Sample Data"); + } +} diff --git a/backend/src/main/java/com/ninjaws/quiz/config/DataSourceConfig.java b/backend/src/main/java/com/ninjaws/quiz/config/DataSourceConfig.java new file mode 100644 index 0000000..8645af9 --- /dev/null +++ b/backend/src/main/java/com/ninjaws/quiz/config/DataSourceConfig.java @@ -0,0 +1,46 @@ +package com.ninjaws.quiz.config; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.core.JdbcTemplate; + +public class DataSourceConfig { + @Primary + @Bean(name = "primaryDataSourceProperties") + @ConfigurationProperties("spring.datasource.primary") + public DataSourceProperties primaryDataSourceProperties() { + return new DataSourceProperties(); + } + + @Primary + @Bean(name = "primaryDataSource") + public DataSource primaryDataSource() { + return primaryDataSourceProperties().initializeDataSourceBuilder().build(); + } + + @Bean(name = "secondaryDataSourceProperties") + @ConfigurationProperties("spring.datasource.secondary") + public DataSourceProperties secondaryDataSourceProperties() { + return new DataSourceProperties(); + } + + @Bean(name = "secondaryDataSource") + public DataSource secondaryDataSource() { + return secondaryDataSourceProperties().initializeDataSourceBuilder().build(); + } + + @Bean + public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource ds) { + return new JdbcTemplate(ds); + } + + @Bean + public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource ds) { + return new JdbcTemplate(ds); + } +} diff --git a/backend/src/main/java/com/ninjaws/quiz/entities/Category.java b/backend/src/main/java/com/ninjaws/quiz/entities/Category.java new file mode 100644 index 0000000..3e20685 --- /dev/null +++ b/backend/src/main/java/com/ninjaws/quiz/entities/Category.java @@ -0,0 +1,25 @@ +package com.ninjaws.quiz.entities; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Table(name = "category") +@Getter +@Setter +public class Category { + @Id + private Integer id; + private String name; + + public Category() {} + + public Category(int id, String name) { + this.id = id; + this.name = name; + } + +} diff --git a/backend/src/main/java/com/ninjaws/quiz/entities/QuestionEntity.java b/backend/src/main/java/com/ninjaws/quiz/entities/QuestionEntity.java new file mode 100644 index 0000000..753254d --- /dev/null +++ b/backend/src/main/java/com/ninjaws/quiz/entities/QuestionEntity.java @@ -0,0 +1,36 @@ +package com.ninjaws.quiz.entities; + +import java.util.List; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Table(name = "question") +@Getter +@Setter +public class QuestionEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "category", nullable = false) + private Category category; + + private String difficulty; + private String type; + private String question; + + //* Hidden from the user */ + private String correctAnswer; + private List incorrectAnswers; +} diff --git a/backend/src/main/java/com/ninjaws/quiz/models/ApiResponse.java b/backend/src/main/java/com/ninjaws/quiz/models/ApiResponse.java index 776d0e0..9715b25 100644 --- a/backend/src/main/java/com/ninjaws/quiz/models/ApiResponse.java +++ b/backend/src/main/java/com/ninjaws/quiz/models/ApiResponse.java @@ -17,5 +17,5 @@ public class ApiResponse { @JsonProperty("response_code") private int responseCode; @JsonProperty("results") - private List results; + private List results; } diff --git a/backend/src/main/java/com/ninjaws/quiz/models/CategoryResponse.java b/backend/src/main/java/com/ninjaws/quiz/models/CategoryResponse.java new file mode 100644 index 0000000..2644a4b --- /dev/null +++ b/backend/src/main/java/com/ninjaws/quiz/models/CategoryResponse.java @@ -0,0 +1,14 @@ +package com.ninjaws.quiz.models; + +import java.util.List; + +import com.ninjaws.quiz.entities.Category; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CategoryResponse { + List trivia_categories; +} diff --git a/backend/src/main/java/com/ninjaws/quiz/models/Question.java b/backend/src/main/java/com/ninjaws/quiz/models/QuestionDTO.java similarity index 88% rename from backend/src/main/java/com/ninjaws/quiz/models/Question.java rename to backend/src/main/java/com/ninjaws/quiz/models/QuestionDTO.java index 6c0a32d..4127152 100644 --- a/backend/src/main/java/com/ninjaws/quiz/models/Question.java +++ b/backend/src/main/java/com/ninjaws/quiz/models/QuestionDTO.java @@ -7,25 +7,17 @@ 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 { +public class QuestionDTO { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String type; - private String difficulty; private String category; + private String difficulty; + private String type; private String question; - //* Hidden from the user */ private String correctAnswer; diff --git a/backend/src/main/java/com/ninjaws/quiz/models/QuizSettings.java b/backend/src/main/java/com/ninjaws/quiz/models/QuizSettings.java index 9ed2ff4..cb82d5e 100644 --- a/backend/src/main/java/com/ninjaws/quiz/models/QuizSettings.java +++ b/backend/src/main/java/com/ninjaws/quiz/models/QuizSettings.java @@ -48,21 +48,25 @@ public class QuizSettings { @Max(50) private int amount = 10; - private Type type = Type.ANY; + // private Type type = Type.ANY; - private Difficulty difficulty = Difficulty.ANY; - - private Category category = Category.ANY; + // private Difficulty difficulty = Difficulty.ANY; + private Integer category; + private String difficulty; + private String type; public QuizSettings(){} /** Inverse of toApiQuery */ - public QuizSettings(int amount, Integer category, String difficulty, String type) { + public QuizSettings(int amount, Integer category, String difficulty, String type) { this.amount = amount; - this.category = category == null ? Category.ANY : Category.values()[category]; - this.difficulty = difficulty == null ? Difficulty.ANY : Difficulty.valueOf(difficulty.toUpperCase()); - this.type = type == null ? Type.ANY : type.equals("boolean") ? Type.BOOLEAN : Type.MULTIPLE; + this.category = category; + this.difficulty = difficulty; + this.type = type; + // this.category = category == null ? Category.ANY : Category.values()[category]; + // this.difficulty = difficulty == null ? Difficulty.ANY : Difficulty.valueOf(difficulty.toUpperCase()); + // this.type = type == null ? Type.ANY : type.equals("boolean") ? Type.BOOLEAN : Type.MULTIPLE; } /** @@ -77,19 +81,37 @@ public class QuizSettings { * Converts the class to a format that can be provided as arguments for the api request * @return The resulting request arguments as String */ + // public String toApiQuery() { + // StringBuilder query = new StringBuilder("?amount=" + amount); + + // if (category != null && category != Category.ANY) { + // query.append("&category=").append(category.getApiValue()); + // } + + // if (difficulty != null && difficulty != Difficulty.ANY) { + // query.append("&difficulty=").append(difficulty.getApiValue()); + // } + + // if (type != null && type != Type.ANY) { + // query.append("&type=").append(type.getApiValue()); + // } + + // return query.toString(); + // } + public String toApiQuery() { StringBuilder query = new StringBuilder("?amount=" + amount); - if (category != null && category != Category.ANY) { - query.append("&category=").append(category.getApiValue()); + if (category != null) { + query.append("&category=").append(category); } - if (difficulty != null && difficulty != Difficulty.ANY) { - query.append("&difficulty=").append(difficulty.getApiValue()); + if (difficulty != null ) { + query.append("&difficulty=").append(difficulty); } - if (type != null && type != Type.ANY) { - query.append("&type=").append(type.getApiValue()); + if (type != null ) { + query.append("&type=").append(type); } return query.toString(); diff --git a/backend/src/main/java/com/ninjaws/quiz/models/Session.java b/backend/src/main/java/com/ninjaws/quiz/models/Session.java index 757467d..2916d6c 100644 --- a/backend/src/main/java/com/ninjaws/quiz/models/Session.java +++ b/backend/src/main/java/com/ninjaws/quiz/models/Session.java @@ -11,7 +11,7 @@ import lombok.Setter; public class Session { private final String id; private int statusCode = 0; - private List questions; + private List questions; public Session() { this.id = UUID.randomUUID().toString(); diff --git a/backend/src/main/java/com/ninjaws/quiz/repositories/CategoryRepository.java b/backend/src/main/java/com/ninjaws/quiz/repositories/CategoryRepository.java new file mode 100644 index 0000000..e9bf67d --- /dev/null +++ b/backend/src/main/java/com/ninjaws/quiz/repositories/CategoryRepository.java @@ -0,0 +1,11 @@ +package com.ninjaws.quiz.repositories; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.ninjaws.quiz.entities.Category; + +public interface CategoryRepository extends JpaRepository{ + Optional findByName(String name); +} diff --git a/backend/src/main/java/com/ninjaws/quiz/repositories/QuestionRepository.java b/backend/src/main/java/com/ninjaws/quiz/repositories/QuestionRepository.java index 31e331a..4ec9de2 100644 --- a/backend/src/main/java/com/ninjaws/quiz/repositories/QuestionRepository.java +++ b/backend/src/main/java/com/ninjaws/quiz/repositories/QuestionRepository.java @@ -1,8 +1,26 @@ package com.ninjaws.quiz.repositories; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; -import com.ninjaws.quiz.models.Question; +import com.ninjaws.quiz.entities.QuestionEntity; -public interface QuestionRepository extends JpaRepository { +public interface QuestionRepository extends JpaRepository{//, JpaSpecificationExecutor { + + @Query(value = "SELECT * FROM question AS q " + + "WHERE (?1 IS NULL OR q.category = ?1) " + + "AND (?2 IS NULL OR q.difficulty = ?2) " + + "AND (?3 IS NULL OR q.type = ?3) " + + "ORDER BY RAND() LIMIT ?4", nativeQuery = true) + List lookupQuestions(Integer categoryId, String difficulty, String type, int limit); + + // public List getQuestionsBy(String type, String category, String difficulty, int limit) { + // Specification spec = Specification.where(QuestionSpecification.hasType(type)) + // .and(QuestionSpecification.hasCategory(category)) + // .and(QuestionSpecification.hasDifficulty(difficulty)); + + // return productRepository.findAll(spec); + // } } diff --git a/backend/src/main/java/com/ninjaws/quiz/services/ApiService.java b/backend/src/main/java/com/ninjaws/quiz/services/ApiService.java index 7918f4f..219e6fb 100644 --- a/backend/src/main/java/com/ninjaws/quiz/services/ApiService.java +++ b/backend/src/main/java/com/ninjaws/quiz/services/ApiService.java @@ -9,6 +9,7 @@ import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import com.ninjaws.quiz.models.ApiResponse; +import com.ninjaws.quiz.models.CategoryResponse; import com.ninjaws.quiz.models.QuizSettings; import com.ninjaws.quiz.models.Request; @@ -26,13 +27,13 @@ public class ApiService { private static final System.Logger logger = System.getLogger(ApiService.class.getName()); - private final String apiUrl = "https://opentdb.com/api.php"; + private final String apiUrl = "https://opentdb.com"; public ApiService() { } private String buildRequestString(QuizSettings quizSettings) { - return apiUrl + quizSettings.toApiQuery(); + return apiUrl + "/api.php" + quizSettings.toApiQuery(); } public void handleRequest(@Valid Request request) { @@ -54,7 +55,9 @@ public class ApiService { * Store them in the DataService */ public void collectBulk() { - String url = apiUrl + "?amount=50"; + // TODO: Make this happen only once, when a new database is generated + getCategories(); + String url = apiUrl + "/api.php?amount=50"; try { ResponseEntity response = restTemplate.getForEntity(url, String.class); String json = response.getBody(); @@ -64,5 +67,21 @@ public class ApiService { } catch (RestClientException | IOException e) { logger.log(System.Logger.Level.ERROR, "Critical failure", e); } + + } + + + public void getCategories() { + String url = apiUrl + "/api_category.php"; + try { + ResponseEntity response = restTemplate.getForEntity(url, String.class); + String json = response.getBody(); + CategoryResponse categoryResponse = cleanerService.extractCategoryResponse(json); + responseHandler.handleCategoryResponse(categoryResponse); + + } catch (RestClientException | IOException e) { + logger.log(System.Logger.Level.ERROR, "Critical failure", e); + } + } } diff --git a/backend/src/main/java/com/ninjaws/quiz/services/CleanerService.java b/backend/src/main/java/com/ninjaws/quiz/services/CleanerService.java index 5385f67..9c08359 100644 --- a/backend/src/main/java/com/ninjaws/quiz/services/CleanerService.java +++ b/backend/src/main/java/com/ninjaws/quiz/services/CleanerService.java @@ -1,22 +1,87 @@ package com.ninjaws.quiz.services; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.lang3.StringEscapeUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.fasterxml.jackson.databind.ObjectMapper; +import com.ninjaws.quiz.entities.Category; +import com.ninjaws.quiz.entities.QuestionEntity; import com.ninjaws.quiz.models.ApiResponse; +import com.ninjaws.quiz.models.CategoryResponse; +import com.ninjaws.quiz.models.QuestionDTO; +import com.ninjaws.quiz.repositories.CategoryRepository; @Service public class CleanerService { @Autowired private ObjectMapper objectMapper; + @Autowired + private CategoryRepository categoryRepository; - public CleanerService() {} + public CleanerService() { + } public ApiResponse extractApiResponse(String json) throws IOException { return objectMapper.readValue(json, ApiResponse.class); } + + public CategoryResponse extractCategoryResponse(String json) throws IOException { + return objectMapper.readValue(json, CategoryResponse.class); + } + + public List questionListEntitytoDTO(List entities) { + List dtos = new ArrayList<>(); + for (int i = 0; i < entities.size(); i++) { + QuestionEntity entity = entities.get(i); + dtos.add(questionEntityToDTO(entity)); + } + return dtos; + } + + public QuestionDTO questionEntityToDTO(QuestionEntity entity) { + QuestionDTO dto = new QuestionDTO(); + dto.setQuestion(entity.getQuestion()); + dto.setCorrectAnswer(entity.getCorrectAnswer()); + dto.setIncorrectAnswer(entity.getIncorrectAnswers()); + dto.setCategory(entity.getCategory().getName()); + dto.setType(entity.getType()); + dto.setDifficulty(entity.getDifficulty()); + return dto; + } + + public List questionListDTOtoEntity(List dtos) { + List entities = new ArrayList<>(); + for (int i = 0; i < dtos.size(); i++) { + QuestionDTO dto = dtos.get(i); + entities.add(questionDTOtoEntity(dto)); + } + return entities; + } + + public QuestionEntity questionDTOtoEntity(QuestionDTO dto) { + QuestionEntity entity = new QuestionEntity(); + Category category = categoryRepository.findByName(decodeString(dto.getCategory())).orElseThrow(() -> new IllegalStateException("Category not in DB!")); + entity.setCategory(category); + entity.setDifficulty(dto.getDifficulty()); + entity.setType(dto.getType()); + entity.setQuestion(dto.getQuestion()); + entity.setCorrectAnswer(dto.getCorrectAnswer()); + entity.setIncorrectAnswers(dto.getIncorrectAnswers()); + return entity; + } + + /** + * For some reason, the categories come with special characters, + * But the categories from the questions dont + * So this has to be manually fixed if they are to be stored in the db (otherwise namematching doesnt work) + */ + private String decodeString(String text){ + return StringEscapeUtils.unescapeHtml4(text); + } } diff --git a/backend/src/main/java/com/ninjaws/quiz/services/DataService.java b/backend/src/main/java/com/ninjaws/quiz/services/DataService.java index ceefcd0..adb2469 100644 --- a/backend/src/main/java/com/ninjaws/quiz/services/DataService.java +++ b/backend/src/main/java/com/ninjaws/quiz/services/DataService.java @@ -5,23 +5,61 @@ 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.entities.Category; +import com.ninjaws.quiz.entities.QuestionEntity; +import com.ninjaws.quiz.models.QuizSettings; +import com.ninjaws.quiz.repositories.CategoryRepository; import com.ninjaws.quiz.repositories.QuestionRepository; @Service public class DataService { + @Autowired private QuestionRepository questionRepository; - public List getAllQuestions() { + @Autowired + private CategoryRepository categoryRepository; + + public List getAllQuestions() { return questionRepository.findAll(); } - public Question saveQuestion(Question question) { + public QuestionEntity saveQuestion(QuestionEntity question) { return questionRepository.save(question); } - public List saveQuestions(List questions) { + public List saveQuestions(List questions) { return questionRepository.saveAll(questions); } + + // public List getRandomQuestions(String type, String category, String difficulty, int limit) { + // return questionRepository.findRandomQuestions(type, limit); + // } + + public List lookupQuestions(QuizSettings quizSettings) { + // Long category = Long.valueOf(quizSettings.getCategory().getApiValue() == 1 ? null : quizSettings.getCategory().getApiValue()); + // String difficulty = quizSettings.getDifficulty() == QuizSettings.Difficulty.ANY ? null : quizSettings.getDifficulty().getApiValue(); + // String type = quizSettings.getType() == QuizSettings.Type.ANY ? null : quizSettings.getType().getApiValue(); + // System.out.println(category); + // System.out.println(difficulty); + // System.out.println(type); + + return questionRepository.lookupQuestions(quizSettings.getCategory(), quizSettings.getDifficulty(), quizSettings.getType(), quizSettings.getAmount()); + } + + /** + * Tries to get questions that match the specified QuizSettings + * @return The questions if the query was possible, and an empty array if not + */ + // public List getByQuizSettings(QuizSettings quizSettings) { + // Specification spec = Specification.where(QuestionSpecification.hasType(type)) + // .and(QuestionSpecification.hasCategory(category)) + // .and(QuestionSpecification.hasDifficulty(difficulty)); + + // return productRepository.findAll(spec); + // } + + public List saveCategories(List categories) { + return categoryRepository.saveAll(categories); + } } diff --git a/backend/src/main/java/com/ninjaws/quiz/services/DatabaseScheduler.java b/backend/src/main/java/com/ninjaws/quiz/services/DatabaseScheduler.java new file mode 100644 index 0000000..83686c1 --- /dev/null +++ b/backend/src/main/java/com/ninjaws/quiz/services/DatabaseScheduler.java @@ -0,0 +1,20 @@ +package com.ninjaws.quiz.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Service +public class DatabaseScheduler { + @Autowired + private JdbcTemplate secondaryJdbcTemplate; + + @Scheduled(fixedRate = 60000) // Runs every 60 seconds + public void createAndFillSecondaryDatabase() { + // Create tables and insert data into the secondary database + secondaryJdbcTemplate. + .execute("CREATE TABLE IF NOT EXISTS sample_table (id INT PRIMARY KEY, name VARCHAR(255))"); + secondaryJdbcTemplate.update("INSERT INTO sample_table (id, name) VALUES (?, ?)", 1, "Sample Data"); + } +} diff --git a/backend/src/main/java/com/ninjaws/quiz/services/QuizService.java b/backend/src/main/java/com/ninjaws/quiz/services/QuizService.java index 0b22c30..d511850 100644 --- a/backend/src/main/java/com/ninjaws/quiz/services/QuizService.java +++ b/backend/src/main/java/com/ninjaws/quiz/services/QuizService.java @@ -1,10 +1,12 @@ package com.ninjaws.quiz.services; +import java.util.List; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import com.ninjaws.quiz.entities.QuestionEntity; import com.ninjaws.quiz.models.Answers; import com.ninjaws.quiz.models.QuizSettings; import com.ninjaws.quiz.models.Request; @@ -18,6 +20,10 @@ public class QuizService { private CacheService cacheService; @Autowired private QueueService queueService; + @Autowired + private DataService dataService; + @Autowired + private CleanerService cleanerService; /** * Actions: @@ -29,7 +35,22 @@ public class QuizService { */ public Optional createSession(QuizSettings quizSettings) { Session session = new Session(); + + /** First, we check if there are already enough items in our db */ + List questionsRetrieved = dataService.lookupQuestions(quizSettings); + if(!questionsRetrieved.isEmpty()) { + session.setQuestions(cleanerService.questionListEntitytoDTO(questionsRetrieved)); + } + cacheService.addToCache(session.getId(), session); + + /** + * If the questions were retrieved from the DB, then it doesn't need to be added to the queue + */ + if(!questionsRetrieved.isEmpty()) { + return Optional.of(session.getId()); + } + boolean addedToQueue = queueService.addToQueue(new Request(session.getId(), quizSettings)); /** In this order, because doing the queue before the cache could lead to race conditions (request completion immediately calls cache, but then the item doesn't exist) */ if(!addedToQueue) { 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 07abae9..96b2dd0 100644 --- a/backend/src/main/java/com/ninjaws/quiz/services/ResponseHandlingService.java +++ b/backend/src/main/java/com/ninjaws/quiz/services/ResponseHandlingService.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ninjaws.quiz.models.ApiResponse; +import com.ninjaws.quiz.models.CategoryResponse; import com.ninjaws.quiz.models.Session; @Service @@ -13,6 +14,8 @@ public class ResponseHandlingService { private CacheService cacheService; @Autowired private DataService dataService; + @Autowired + private CleanerService cleanerService; /** * If there is no sessionId, then this is a request from the scheduler @@ -30,6 +33,7 @@ public class ResponseHandlingService { case 0: // Success updateSessionInCache(session, response); + dataService.saveQuestions(cleanerService.questionListDTOtoEntity(response.getResults())); break; case 1: // Not enough questions to satisfy the request. @@ -63,8 +67,7 @@ public class ResponseHandlingService { public void handleResponse(ApiResponse response) { switch (response.getResponseCode()) { case 0: - // TODO: Store in DB - dataService.saveQuestions(response.getResults()); + dataService.saveQuestions(cleanerService.questionListDTOtoEntity(response.getResults())); break; case 4: // Signal DataService that we have extracted all the data. Stop gathering, switch to caching-mode @@ -83,4 +86,7 @@ public class ResponseHandlingService { cacheService.addToCache(session.getId(), session); } + public void handleCategoryResponse(CategoryResponse categoryResponse) { + dataService.saveCategories(categoryResponse.getTrivia_categories()); + } } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 5fa94c6..f7d952d 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -3,10 +3,32 @@ 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.h2.console.path=/console -spring.jpa.hibernate.ddl-auto=create-drop -spring.jpa.database-platform=org.hibernate.dialect.H2Dialect \ No newline at end of file + +# 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 + + +# Primary database configuration +spring.datasource.primary.url=jdbc:h2:mem:testdb +# ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.primary.driver-class-name=org.h2.Driver +spring.datasource.primary.username=sa +spring.datasource.primary.password= +spring.jpa.primary.hibernate.ddl-auto=create-drop +spring.jpa.primary.database-platform=org.hibernate.dialect.H2Dialect + +# Secondary database configuration +spring.datasource.secondary.url=jdbc:h2:mem:secondarydb +# ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.secondary.driver-class-name=org.h2.Driver +spring.datasource.secondary.username=sa +spring.datasource.secondary.password= +spring.jpa.secondary.hibernate.ddl-auto=create-drop +spring.jpa.secondary.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 deleted file mode 100644 index a11b441..0000000 --- a/backend/src/test/java/com/ninjaws/quiz/repositories/QuestRepositoryTest.java +++ /dev/null @@ -1,91 +0,0 @@ -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/CleanerServiceTest.java b/backend/src/test/java/com/ninjaws/quiz/services/CleanerServiceTest.java deleted file mode 100644 index 8c0f904..0000000 --- a/backend/src/test/java/com/ninjaws/quiz/services/CleanerServiceTest.java +++ /dev/null @@ -1,58 +0,0 @@ -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/CleanerServiceTest.txt b/backend/src/test/java/com/ninjaws/quiz/services/CleanerServiceTest.txt new file mode 100644 index 0000000..bdb7919 --- /dev/null +++ b/backend/src/test/java/com/ninjaws/quiz/services/CleanerServiceTest.txt @@ -0,0 +1,107 @@ +package com.ninjaws.quiz.services; + +import java.io.IOException; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +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 static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ninjaws.quiz.entities.Category; +import com.ninjaws.quiz.models.ApiResponse; +import com.ninjaws.quiz.models.QuestionDTO; + +// 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); +// } +// } + +@ExtendWith(MockitoExtension.class) +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 + + // Create a Category object for the question + Category category = new Category(); + category.setId(1L); + category.setName("General Knowledge"); + + // Create the Question object with the category + QuestionDTO question = new QuestionDTO(); + question.setType("multiple choice"); + question.setDifficulty("easy"); + question.setCategory(category); // Set the Category object here + 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()); + assertThat(actualResponse.getResults().get(0).getCategory().getName()).isEqualTo(category.getName()); // Check category name + 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 deleted file mode 100644 index 2fe0683..0000000 --- a/backend/src/test/java/com/ninjaws/quiz/services/DataServiceTest.java +++ /dev/null @@ -1,105 +0,0 @@ -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/DataServiceTest.txt b/backend/src/test/java/com/ninjaws/quiz/services/DataServiceTest.txt new file mode 100644 index 0000000..faa94e2 --- /dev/null +++ b/backend/src/test/java/com/ninjaws/quiz/services/DataServiceTest.txt @@ -0,0 +1,202 @@ +package com.ninjaws.quiz.services; + +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.junit.jupiter.api.extension.ExtendWith; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +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.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.ninjaws.quiz.entities.Category; +import com.ninjaws.quiz.models.QuestionDTO; +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()); +// } +// } + +@ExtendWith(MockitoExtension.class) +public class DataServiceTest { + + @InjectMocks + private DataService dataService; + + @Mock + private QuestionRepository questionRepository; + + private QuestionDTO question; + private Category category; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + + category = new Category(); + category.setId(1L); + category.setName("General Knowledge"); + + question = new QuestionDTO(); + question.setType("multiple choice"); + question.setDifficulty("easy"); + question.setCategory(category); // Set the Category object here + 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(QuestionDTO.class))).thenReturn(question); + + QuestionDTO savedQuestion = dataService.saveQuestion(question); + assertThat(savedQuestion).isNotNull(); + assertThat(savedQuestion.getQuestion()).isEqualTo("What is the capital of France?"); + assertThat(savedQuestion.getCategory().getName()).isEqualTo("General Knowledge"); // Check category name + 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?"); + assertThat(questions.get(0).getCategory().getName()).isEqualTo("General Knowledge"); // Check category name + 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?"); + assertThat(foundQuestion.get().getCategory().getName()).isEqualTo("General Knowledge"); // Check category name + verify(questionRepository, times(1)).findById(1L); + } + + @Test + public void testSaveQuestions() { + QuestionDTO question1 = new QuestionDTO(); + question1.setType("multiple choice"); + question1.setDifficulty("easy"); + question1.setCategory(category); // Set the Category object here + question1.setQuestion("What is the capital of France?"); + question1.setCorrectAnswer("Paris"); + question1.setIncorrectAnswers(Arrays.asList("Berlin", "Madrid", "Rome")); + + QuestionDTO question2 = new QuestionDTO(); + Category category2 = new Category(); + category2.setId(2L); + category2.setName("Science"); + question2.setType("multiple choice"); + question2.setDifficulty("medium"); + question2.setCategory(category2); // Set the Category object here + 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/frontend/src/app/Quiz/services/quiz.service.ts b/frontend/src/app/Quiz/services/quiz.service.ts index 5be7448..184570f 100644 --- a/frontend/src/app/Quiz/services/quiz.service.ts +++ b/frontend/src/app/Quiz/services/quiz.service.ts @@ -71,7 +71,7 @@ export class QuizService { url += catIndex !== 0 ? `&category=${catIndex}` : ''; const diff = quizParams.difficulty.toLowerCase(); - url += diff !== 'any difficulty' ? `&category=${diff}` : ''; + url += diff !== 'any difficulty' ? `&difficulty=${diff}` : ''; const typeIndex = this.getTypes().indexOf(quizParams.type); url += diff --git a/scripts/LoadTestMultiple.sh b/scripts/LoadTestMultiple.sh index 9122900..352f191 100755 --- a/scripts/LoadTestMultiple.sh +++ b/scripts/LoadTestMultiple.sh @@ -3,7 +3,7 @@ start_time=$(date +%s) for i in {1..6}; do - ./LoadTestOne.sh & + ./LoadTestOne-WithParams.sh & sleep 0.1 done diff --git a/scripts/LoadTestOne-WithParams.sh b/scripts/LoadTestOne-WithParams.sh new file mode 100755 index 0000000..f23ce00 --- /dev/null +++ b/scripts/LoadTestOne-WithParams.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Step 1: Get the sessionId from the questions endpoint +sessionIdJson=$(curl -s "http://localhost:9090/quiz/questions?amount=25&category=27&difficulty=medium&type=boolean") + +statusCode=$(echo "$sessionIdJson" | grep -o '"statusCode":[0-9]*' | sed 's/"statusCode"://') + +if [ "$statusCode" -ne 0 ]; then + echo "Exiting: statusCode is not 0." + exit 1 +fi + +sessionId=$(echo "$sessionIdJson" | grep -o '"sessionId":"[^"]*"' | sed 's/"sessionId":"//;s/"//') + +if [ -z "$sessionId" ]; then + echo "Failed to get sessionId" + exit 1 +fi + +echo "Received sessionId: $sessionId" + +# Step 2: Poll the status endpoint every 2 seconds +while true; do + status_response=$(curl -s http://localhost:9090/quiz/status/"$sessionId") + + if [ $? -eq 0 ]; then + + statusCode=$(echo "$status_response" | grep -o '"statusCode":[0-9]*' | sed 's/"statusCode"://') + + if [ "$statusCode" -ne 0 ]; then + echo "Exiting: statusCode is $statusCode" + exit 1 + fi + + # Check if the response is JSON (starts with '{') + if [[ $status_response == \{* ]]; then +# echo "$statusCode" + echo "$sessionId: done! status: $statusCode" + break + fi + else + echo "Status check failed, retrying..." + fi + + sleep 2 +done diff --git a/scripts/LoadTestOne.sh b/scripts/LoadTestOne.sh index 1137ee1..feb32eb 100755 --- a/scripts/LoadTestOne.sh +++ b/scripts/LoadTestOne.sh @@ -30,7 +30,8 @@ while true; do break fi else - echo "Status check failed, retrying..." + echo "Status check failed, exiting..." + exit 1 fi sleep 2 diff --git a/scripts/benchmarks/basic.png b/scripts/benchmarks/1. Queue.png similarity index 100% rename from scripts/benchmarks/basic.png rename to scripts/benchmarks/1. Queue.png diff --git a/scripts/benchmarks/2. DB and Queue - No Params.png b/scripts/benchmarks/2. DB and Queue - No Params.png new file mode 100644 index 0000000000000000000000000000000000000000..d2a3811bde77f04a2c0bbdc9d512b61410a48c53 GIT binary patch literal 69550 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

Uv61U}PcArwG_YUI|l9C+;I_ame*qX7o|Bynv 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 T`O&UG0k1cb@)BiY`hNc(=bS