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 0000000..d2a3811 Binary files /dev/null and b/scripts/benchmarks/2. DB and Queue - No Params.png differ