diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f99d47..5f98259 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,8 @@ on: push: branches: - master - - staging + - staging_new + - subbox-management jobs: build: diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..fc123e1 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,72 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: "40 19 * * 5" + push: + branches: ["master"] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4 + with: + sarif_file: results.sarif \ No newline at end of file diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml new file mode 100644 index 0000000..8af2682 --- /dev/null +++ b/.github/workflows/sonarcloud.yml @@ -0,0 +1,44 @@ +name: SonarCloud Analysis +on: + # Trigger analysis when pushing in master or pull requests, and when creating + # a pull request. + push: + pull_request: + +jobs: + build: + name: Build + runs-on: ubuntu-22.04 + steps: + - name: Check out the Git repository + uses: actions/checkout@v4 + with: + # Shallow clones should be disabled for a better relevancy of analysis + fetch-depth: 0 + - name: Set up Java Toolchain + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "21" + cache: "gradle" + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Set gradlew as executable + run: chmod +x ./gradlew + - name: Build and analyze + run: ./gradlew build jacocoTestReport sonar --info + env: + # Needed to get some information about the pull request, if any + GITHUB_TOKEN: ${{ secrets.GIT_HUB_TOKEN }} + # SonarCloud access token should be generated from https://sonarcloud.io/account/security/ + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file diff --git a/.monitoring/docker-compose.yml b/.monitoring/docker-compose.yml deleted file mode 100644 index 654ee2e..0000000 --- a/.monitoring/docker-compose.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: '3.7' - -services: - prometheus: - image: prom/prometheus:v2.44.0 - container_name: prometheus - ports: - - "9090:9090" - volumes: - - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - - grafana: - image: grafana/grafana:9.5.2 - container_name: grafana - ports: - - "3000:3000" - restart: unless-stopped - volumes: - - ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources \ No newline at end of file diff --git a/.monitoring/prometheus/prometheus.yml b/.monitoring/prometheus/prometheus.yml index 58b5e03..5351bc2 100644 --- a/.monitoring/prometheus/prometheus.yml +++ b/.monitoring/prometheus/prometheus.yml @@ -7,6 +7,6 @@ scrape_configs: metrics_path: '/actuator/prometheus' scrape_interval: 3s static_configs: - - targets: ['34.124.168.155:80'] + - targets: ['host.docker.internal:80'] labels: application: 'snackscription_subscriptionbox' diff --git a/README.md b/README.md index e69de29..26f4bb9 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,15 @@ +# Subscription Box Management by Admin +#### Home Link: http://34.124.168.155/subscription-box + +### Penanggung Jawab : +#### Muhammad Faishal Adly Nelwan (2206030754) + +##### Link Get All Subscription BOX +http://34.124.168.155/subscription-box/list +##### Link Create Subscription BOX +##### Link Get All Subscription BOX +##### Link Get All Subscription BOX +##### Link Get All Subscription BOX +##### Link Get All Subscription BOX + +# Monitoring And Profiling \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 50cde73..5694b1f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,6 +6,14 @@ plugins { id("org.sonarqube") version "4.4.1.3373" } +sonar { + properties { + property("sonar.host.url", "https://sonarcloud.io") + property("sonar.organization","adpro-c11") + property("sonar.projectKey", "ADPRO-C11_snackscription-subscriptionbox-admin") + } +} + group = "snackscription" version = "0.0.1-SNAPSHOT" @@ -27,6 +35,7 @@ val springBootVersion = "2.5.0" val micrometerVersion = "1.12.5" val dotenvVersion = "4.0.0" val jwtVersion = "0.12.5" +val junitJupiterVersion = "5.9.1" dependencies { implementation("org.springframework.boot:spring-boot-starter-logging") @@ -49,6 +58,8 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") runtimeOnly("io.micrometer:micrometer-registry-prometheus") implementation("org.springframework.boot:spring-boot-starter-actuator") + testImplementation ("org.springframework.boot:spring-boot-starter-test") + testImplementation ("org.springframework.security:spring-security-test") } @@ -83,9 +94,6 @@ tasks.test{ } tasks.jacocoTestReport { - classDirectories.setFrom(files(classDirectories.files.map { - fileTree(it) { exclude("**/*Application**") } - })) dependsOn(tasks.test) // tests are required to run before generating the report reports { xml.required.set(true) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..535e2ce --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +version: '3.7' + +services: + + subsbox: + image: ${REGISTRY_USER}/${IMAGE_NAME}:${IMAGE_TAG} + container_name: ${CONTAINER_NAME} + ports: + - "80:8080" + + prometheus: + image: prom/prometheus:v2.44.0 + container_name: prometheus + ports: + - "9090:9090" + volumes: + - ./.monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + + grafana: + image: grafana/grafana:9.5.2 + container_name: grafana + ports: + - "3000:3000" + restart: unless-stopped + volumes: + - ./.monitoring/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources \ No newline at end of file diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/config/AsyncConfig.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/config/AsyncConfig.java new file mode 100644 index 0000000..54a31ff --- /dev/null +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/config/AsyncConfig.java @@ -0,0 +1,24 @@ +package id.ac.ui.cs.advprog.snackscription_subscriptionbox.config; + + +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +@EnableAsync +public class AsyncConfig implements AsyncConfigurer { + + @Override + public AsyncTaskExecutor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(100); + executor.setThreadNamePrefix("Async-Executor-"); + executor.initialize(); + return executor; + } +} \ No newline at end of file diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/config/SecurityConfig.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/config/SecurityConfig.java index 41f7678..047006c 100644 --- a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/config/SecurityConfig.java +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/config/SecurityConfig.java @@ -28,6 +28,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws authorizeRequests .requestMatchers("/actuator/prometheus").permitAll() // Allow unauthenticated access .requestMatchers("/subscription-box/**", "/public/**").permitAll() + .requestMatchers("/").permitAll() .anyRequest().authenticated()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(new JWTAuthFilter(jwtUtils), UsernamePasswordAuthenticationFilter.class); diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/controller/SubscriptionBoxController.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/controller/SubscriptionBoxController.java index 1123a09..0b7abd1 100644 --- a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/controller/SubscriptionBoxController.java +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/controller/SubscriptionBoxController.java @@ -1,18 +1,17 @@ package id.ac.ui.cs.advprog.snackscription_subscriptionbox.controller; +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.dto.DTOMapper; +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.LogAdmin; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.utils.JWTUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import id.ac.ui.cs.advprog.snackscription_subscriptionbox.dto.DTOMapper; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.dto.SubscriptionBoxDTO; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.SubscriptionBox; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.service.SubscriptionBoxService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; -import java.util.Locale; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -21,6 +20,7 @@ @RequestMapping("/subscription-box") @CrossOrigin(origins = "*") // Change to specific origin if needed public class SubscriptionBoxController { + private final JWTUtils jwtUtils; private final SubscriptionBoxService subscriptionBoxService; @@ -29,6 +29,7 @@ public SubscriptionBoxController(SubscriptionBoxService subscriptionBoxService, this.jwtUtils = jwtUtils; } + // Being kept for debugging purposes private static final Logger logger = LoggerFactory.getLogger(SubscriptionBoxController.class); private void validateToken(String token) throws IllegalAccessException { @@ -46,8 +47,7 @@ private void validateAdminOnly(String token) throws IllegalAccessException { } @GetMapping("") - public ResponseEntity main(@RequestHeader(value = "Authorization") String token) throws IllegalAccessException { - validateToken(token); + public ResponseEntity main(){ return ResponseEntity.ok("Snackscription - SubscriptionBox Management API by ADMIN only!"); } @@ -60,7 +60,7 @@ public CompletableFuture> createSubscriptionBox( } @GetMapping("/list") - public CompletableFuture>> findAll(@RequestHeader(value = "Authorization") String token) throws IllegalAccessException { + public CompletableFuture>> findAll(@RequestHeader(value = "Authorization") String token) throws IllegalAccessException { validateToken(token); return subscriptionBoxService.findAll() .thenApply(ResponseEntity::ok); @@ -95,7 +95,8 @@ public CompletableFuture> findById(@RequestHe return subscriptionBoxService.findById(id) .thenApply(optionalSubscriptionBox -> - optionalSubscriptionBox.map(ResponseEntity::ok) + optionalSubscriptionBox + .map(subscriptionBox -> ResponseEntity.ok(DTOMapper.convertModelToDto(subscriptionBox))) .orElse(ResponseEntity.notFound().build())); } @@ -120,7 +121,6 @@ public CompletableFuture>> findByPriceLe .thenApply(ResponseEntity::ok); } - @GetMapping("/price/greater-than/{price}") public CompletableFuture>> findByPriceGreaterThan(@RequestHeader(value = "Authorization") String token, @PathVariable int price) throws IllegalAccessException { validateToken(token); @@ -143,10 +143,19 @@ public CompletableFuture>>> fin .thenApply(ResponseEntity::ok); } + @GetMapping("/distinct-names") public CompletableFuture>>> findDistinctNames(@RequestHeader(value = "Authorization") String token) throws IllegalAccessException { validateToken(token); return subscriptionBoxService.findDistinctNames() .thenApply(ResponseEntity::ok); } + + @GetMapping("/logs") + public CompletableFuture>> updateSubscriptionBox(@RequestHeader(value = "Authorization") String token) throws IllegalAccessException { + validateAdminOnly(token); + return subscriptionBoxService.getLog() + .thenApply(ResponseEntity::ok) + .exceptionally(ex -> ResponseEntity.notFound().build()); + } } diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/DTOMapper.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/DTOMapper.java index 9d841f6..3bdd5d5 100644 --- a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/DTOMapper.java +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/DTOMapper.java @@ -36,7 +36,6 @@ public static SubscriptionBox convertDTOtoModel(SubscriptionBoxDTO subscriptionB ).orElse(null); return new SubscriptionBoxFactory().create( - subscriptionBoxDTO.getId(), subscriptionBoxDTO.getName(), subscriptionBoxDTO.getType(), subscriptionBoxDTO.getPrice(), diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/SubscriptionBoxDTO.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/SubscriptionBoxDTO.java index b290672..7c0ce2d 100644 --- a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/SubscriptionBoxDTO.java +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/SubscriptionBoxDTO.java @@ -1,7 +1,6 @@ package id.ac.ui.cs.advprog.snackscription_subscriptionbox.dto; import lombok.*; -import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.Item; import java.util.List; diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/factory/Factory.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/factory/Factory.java index 42ddf56..94e3134 100644 --- a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/factory/Factory.java +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/factory/Factory.java @@ -7,5 +7,5 @@ public interface Factory { T create(); - T create(String id, String name, String type, int price, List items , String description); + T create( String name, String type, int price, List items , String description); } \ No newline at end of file diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/factory/SubscriptionBoxFactory.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/factory/SubscriptionBoxFactory.java index 25a5006..1991b51 100644 --- a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/factory/SubscriptionBoxFactory.java +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/factory/SubscriptionBoxFactory.java @@ -11,7 +11,9 @@ public SubscriptionBox create(){ return new SubscriptionBox(); } - public SubscriptionBox create(String id, String name, String type, int price, List items, String description){ + public SubscriptionBox create(String name, String type, int price, List items, String description){ return new SubscriptionBox( name, type, price, items, description); } + + } diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/model/Admin.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/model/Admin.java deleted file mode 100644 index 885730a..0000000 --- a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/model/Admin.java +++ /dev/null @@ -1,11 +0,0 @@ -package id.ac.ui.cs.advprog.snackscription_subscriptionbox.model; - -import lombok.Getter; -import lombok.Setter; - -@Getter @Setter -public class Admin { - private String username; - private String password; - private int itemsQuantity; -} \ No newline at end of file diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/model/LogAdmin.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/model/LogAdmin.java new file mode 100644 index 0000000..da834fb --- /dev/null +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/model/LogAdmin.java @@ -0,0 +1,33 @@ +package id.ac.ui.cs.advprog.snackscription_subscriptionbox.model; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@Table(name = "Log") +@Entity +@NoArgsConstructor +public class LogAdmin { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "log_id") + private int id; + + @Column(name = "log_string") + private String logString; + + @Column(name = "log_sub_box") + private String subBoxId; + + @Column + private LocalDateTime date; + + public LogAdmin(String logString, String subBoxId) { + this.logString = logString; + this.subBoxId = subBoxId; + this.date = LocalDateTime.now(); + } +} diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/model/SubscriptionBox.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/model/SubscriptionBox.java index a6142c7..91c882a 100644 --- a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/model/SubscriptionBox.java +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/model/SubscriptionBox.java @@ -3,9 +3,10 @@ import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.*; -import lombok.Builder; import lombok.Getter; import lombok.Setter; + +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -36,7 +37,6 @@ public class SubscriptionBox { @Column(name = "box_description") String description; - // Rating rating; public SubscriptionBox(){ this.id = UUID.randomUUID().toString(); @@ -47,10 +47,12 @@ public SubscriptionBox( String name, String type, int price, List items, S this.setName(name); this.setType(type); this.setPrice(price); - this.items = items; + this.items = (items == null) ? new ArrayList<>() : items; this.description = description; } + + public void setType(String type) { if (type.equalsIgnoreCase("monthly") | type.equalsIgnoreCase("quarterly") | @@ -86,4 +88,4 @@ public void setPrice(int price) { } } -; + diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/ItemRepository.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/ItemRepository.java index 1ee048c..601ad05 100644 --- a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/ItemRepository.java +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/ItemRepository.java @@ -1,7 +1,6 @@ package id.ac.ui.cs.advprog.snackscription_subscriptionbox.repository; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.Item; -import lombok.Getter; import org.springframework.stereotype.Repository; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/LogRepository.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/LogRepository.java new file mode 100644 index 0000000..0c2bb6e --- /dev/null +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/LogRepository.java @@ -0,0 +1,13 @@ +package id.ac.ui.cs.advprog.snackscription_subscriptionbox.repository; + +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.LogAdmin; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@Repository +public interface LogRepository extends JpaRepository { + CompletableFuture> findAllByOrderByDateDesc(); +} diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/SubscriptionBoxRepository.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/SubscriptionBoxRepository.java index 1808ac9..f6da630 100644 --- a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/SubscriptionBoxRepository.java +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/SubscriptionBoxRepository.java @@ -1,8 +1,8 @@ package id.ac.ui.cs.advprog.snackscription_subscriptionbox.repository; +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.Item; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.SubscriptionBox; -import jakarta.transaction.TransactionScoped; import org.springframework.stereotype.Repository; import java.util.ArrayList; import jakarta.persistence.EntityManager; @@ -10,10 +10,6 @@ import jakarta.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Repository; - -import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Optional; @@ -30,10 +26,25 @@ public SubscriptionBox save(SubscriptionBox subscriptionBox) { if (existsByNameAndType(subscriptionBox.getName(), subscriptionBox.getType())) { throw new IllegalArgumentException("Cannot save subscription box: a subscription box with the same name and type already exists."); } + + List attachedItems = new ArrayList<>(); + for (Item item : subscriptionBox.getItems()) { + Item existingItem = entityManager.find(Item.class, item.getId()); + if (existingItem != null) { + attachedItems.add(existingItem); + } else { + // Persist new item + entityManager.persist(item); + attachedItems.add(item); + } + } + subscriptionBox.setItems(attachedItems); + entityManager.persist(subscriptionBox); return subscriptionBox; } + private boolean hasThreeSimilarNames(String name) { String jpql = "SELECT sb FROM SubscriptionBox sb WHERE LOWER(sb.name) LIKE LOWER(:name)"; TypedQuery query = entityManager.createQuery(jpql, SubscriptionBox.class); @@ -52,33 +63,40 @@ private boolean existsByNameAndType(String name, String type) { } @Transactional - public Optional findById(String id){ - SubscriptionBox subscriptionBox = entityManager.find(SubscriptionBox.class, id); - return Optional.ofNullable(subscriptionBox); + public Optional findById(String id) { + String jpql = "SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.id = :id"; + TypedQuery query = entityManager.createQuery(jpql, SubscriptionBox.class); + query.setParameter("id", id); + return query.getResultStream().findFirst(); } @Transactional - public List findAll(){ - String jpql = "SELECT sb FROM SubscriptionBox sb"; + public List findAll() { + String jpql = "SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items"; TypedQuery query = entityManager.createQuery(jpql, SubscriptionBox.class); return query.getResultList(); } @Transactional - public SubscriptionBox update(SubscriptionBox subscriptionBox){ + public SubscriptionBox update(SubscriptionBox subscriptionBox) { return entityManager.merge(subscriptionBox); } - @Transactional - public void delete(String id){ - SubscriptionBox subscription = findById(id) + public void delete(String id) { + SubscriptionBox subscriptionBox = findById(id) .orElseThrow(() -> new IllegalArgumentException("Subscription Box ID not found")); - entityManager.remove(subscription); + + // Clear the associations with items + subscriptionBox.getItems().clear(); + entityManager.flush(); + + // Now remove the subscription box + entityManager.remove(subscriptionBox); } @Transactional public List findByPriceLessThan(int price) { - String jpql = "SELECT sb FROM SubscriptionBox sb WHERE sb.price < :price"; + String jpql = "SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.price < :price"; TypedQuery query = entityManager.createQuery(jpql, SubscriptionBox.class); query.setParameter("price", price); return query.getResultList(); @@ -86,7 +104,7 @@ public List findByPriceLessThan(int price) { @Transactional public List findByPriceGreaterThan(int price) { - String jpql = "SELECT sb FROM SubscriptionBox sb WHERE sb.price > :price"; + String jpql = "SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.price > :price"; TypedQuery query = entityManager.createQuery(jpql, SubscriptionBox.class); query.setParameter("price", price); return query.getResultList(); @@ -94,7 +112,7 @@ public List findByPriceGreaterThan(int price) { @Transactional public List findByPriceEquals(int price) { - String jpql = "SELECT sb FROM SubscriptionBox sb WHERE sb.price = :price"; + String jpql = "SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.price = :price"; TypedQuery query = entityManager.createQuery(jpql, SubscriptionBox.class); query.setParameter("price", price); return query.getResultList(); @@ -102,7 +120,7 @@ public List findByPriceEquals(int price) { @Transactional public Optional> findByName(String name) { - String jpql = "SELECT sb FROM SubscriptionBox sb WHERE LOWER(sb.name) LIKE LOWER(:name)"; + String jpql = "SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE LOWER(sb.name) LIKE LOWER(:name)"; TypedQuery query = entityManager.createQuery(jpql, SubscriptionBox.class); query.setParameter("name", "%" + name.toLowerCase() + "%"); // Convert input name to lowercase List result = query.getResultList(); diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/service/SubscriptionBoxService.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/service/SubscriptionBoxService.java index 9085098..8895bf6 100644 --- a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/service/SubscriptionBoxService.java +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/service/SubscriptionBoxService.java @@ -3,8 +3,9 @@ import java.util.List; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.dto.SubscriptionBoxDTO; +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.LogAdmin; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.SubscriptionBox; -import org.springframework.scheduling.annotation.Async; + import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -12,9 +13,9 @@ public interface SubscriptionBoxService { CompletableFuture save(SubscriptionBoxDTO subscriptionBoxDTO); - CompletableFuture> findById(String id); + CompletableFuture> findById(String id); - CompletableFuture> findAll(); + CompletableFuture> findAll(); CompletableFuture update(SubscriptionBoxDTO subscriptionBoxDTO); @@ -29,14 +30,7 @@ public interface SubscriptionBoxService { CompletableFuture>> findByName(String name); CompletableFuture>> findDistinctNames(); + CompletableFuture> getLog(); - -// public SubscriptionBox addBox(SubscriptionBox subscriptionBox); -// public SubscriptionBox editBox(String id, SubscriptionBox subscriptionBox); -// public SubscriptionBox deleteBox(String id); -// public List viewAll(); -// public String viewDetails(String boxId); -// public List filterByPrice(int price); -// // public List filterByRating(int rating); } \ No newline at end of file diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/service/SubscriptionBoxServiceImpl.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/service/SubscriptionBoxServiceImpl.java index 8c6b472..1af3ccc 100644 --- a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/service/SubscriptionBoxServiceImpl.java +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/service/SubscriptionBoxServiceImpl.java @@ -9,10 +9,13 @@ import id.ac.ui.cs.advprog.snackscription_subscriptionbox.dto.SubscriptionBoxDTO; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.dto.DTOMapper; +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.repository.LogRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; - +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.LogAdmin; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.SubscriptionBox; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.repository.SubscriptionBoxRepository; @@ -20,7 +23,8 @@ public class SubscriptionBoxServiceImpl implements SubscriptionBoxService { @Autowired private SubscriptionBoxRepository subscriptionBoxRepository; - + @Autowired + private LogRepository logRepository; @Override @Async public CompletableFuture save(SubscriptionBoxDTO subscriptionBoxDTO) { @@ -34,31 +38,25 @@ public CompletableFuture save(SubscriptionBoxDTO subscriptionBo @Override @Async - public CompletableFuture> findById(String id) { - if (id == null || id.isEmpty()) { - throw new IllegalArgumentException("ID cannot be null or empty"); - } - - return subscriptionBoxRepository.findById(id) - .map(subscriptionBox -> CompletableFuture.completedFuture(Optional.of(DTOMapper.convertModelToDto(subscriptionBox)))) - .orElse(CompletableFuture.completedFuture(Optional.empty())); -// + public CompletableFuture> findById(String id) { + Optional subscriptionBox = subscriptionBoxRepository.findById(id); + return CompletableFuture.completedFuture(subscriptionBox); } + @Override @Async - public CompletableFuture> findAll() { + public CompletableFuture> findAll() { List subscriptionBoxes = subscriptionBoxRepository.findAll(); - return CompletableFuture.completedFuture(subscriptionBoxes); + List dtos = subscriptionBoxes.stream() + .map(DTOMapper::convertModelToDto) + .collect(Collectors.toList()); + return CompletableFuture.completedFuture(dtos); } @Override @Async public CompletableFuture update(SubscriptionBoxDTO subscriptionBoxDTO) { -// if (subscriptionBox == null) { -// throw new IllegalArgumentException("SubscriptionBox cannot be null"); -// } -// return CompletableFuture.completedFuture(subscriptionBoxRepository.update(subscriptionBox)); if (subscriptionBoxDTO == null) { throw new IllegalArgumentException("Subscription cannot be null"); } @@ -66,10 +64,10 @@ public CompletableFuture update(SubscriptionBoxDTO subscription return subscriptionBoxRepository.findById(subscriptionBoxDTO.getId()) .map(subscriptionBox -> { DTOMapper.updateSubscriptionBox(subscriptionBox, subscriptionBoxDTO); - return CompletableFuture.completedFuture(subscriptionBoxRepository.update(subscriptionBox)); + subscriptionBox = subscriptionBoxRepository.update(subscriptionBox); + return CompletableFuture.completedFuture(subscriptionBox); }) .orElseThrow(() -> new IllegalArgumentException("Subscription isn't found")); - } @Override @@ -82,6 +80,7 @@ public CompletableFuture delete(String id) { if (subscriptionBoxRepository.findById(id).isEmpty()) { throw new IllegalArgumentException("Subscription Box not found"); } + CompletableFuture.runAsync(() -> logUpdateStatus(id, "DELETE")); subscriptionBoxRepository.delete(id); return CompletableFuture.completedFuture(null); } @@ -134,5 +133,20 @@ public CompletableFuture>> findDistinctNames() { } + public void logUpdateStatus(String id, String status) { + String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")); + String logString = "Berhasil melakukan" +status + "terhadap Subscription Box dengan ID" + id + " pada " + date; + + LogAdmin log = new LogAdmin(logString, id); + + logRepository.save(log); + } + @Async + public CompletableFuture> getLog() { + return logRepository.findAllByOrderByDateDesc(); + } + + + } diff --git a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/utils/JWTUtils.java b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/utils/JWTUtils.java index 7703fd6..064dd71 100644 --- a/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/utils/JWTUtils.java +++ b/src/main/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/utils/JWTUtils.java @@ -13,7 +13,6 @@ import java.util.Date; import java.util.function.Function; - @Component public class JWTUtils { private final SecretKey KEY; diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 58d8184..d488285 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -5,3 +5,4 @@ spring.jpa.hibernate.ddl-auto=create spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.jpa.properties.hibernate.format_sql=true + diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 2dd3dab..562bfb4 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -3,4 +3,12 @@ spring.datasource.username=${JDBC_DATABASE_USERNAME} spring.datasource.password=${JDBC_DATABASE_PASSWORD} spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect -spring.jpa.properties.hibernate.format_sql=true \ No newline at end of file +spring.jpa.properties.hibernate.format_sql=true +JWT_SECRET=${JWT_SECRET} + +# Actuator endpoints +management.endpoints.web.exposure.include=* +management.endpoint.prometheus.enabled=true +spring.datasource.hikari.maximum-pool-size=20 +# Prometheus configuration +management.prometheus.metrics.export.enabled=true \ No newline at end of file diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index 92d131d..944cddf 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -4,4 +4,5 @@ spring.datasource.password=${TEST_PASSWORD} spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect -spring.jpa.properties.hibernate.format_sql=true \ No newline at end of file +spring.jpa.properties.hibernate.format_sql=true +JWT_SECRET=${JWT_SECRET} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e791095..dae92da 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,2 @@ spring.application.name=snackscription_subscriptionbox spring.profiles.active=${PRODUCTION:dev} -management.endpoints.web.exposure.include=* \ No newline at end of file diff --git a/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/config/AsyncConfigTest.java b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/config/AsyncConfigTest.java new file mode 100644 index 0000000..d49351d --- /dev/null +++ b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/config/AsyncConfigTest.java @@ -0,0 +1,27 @@ +package id.ac.ui.cs.advprog.snackscription_subscriptionbox.config; + +import org.junit.jupiter.api.Test; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +class AsyncConfigTest { + + @Test + void testAsyncExecutor() { + + AsyncConfig asyncConfig = new AsyncConfig(); + + AsyncTaskExecutor executor = asyncConfig.getAsyncExecutor(); + + assertNotNull(executor); + + assertEquals(5, ((ThreadPoolTaskExecutor) executor).getCorePoolSize()); + assertEquals(10, ((ThreadPoolTaskExecutor) executor).getMaxPoolSize()); + assertEquals(100, ((ThreadPoolTaskExecutor) executor).getQueueCapacity()); + assertEquals("Async-Executor-", ((ThreadPoolTaskExecutor) executor).getThreadNamePrefix()); + } +} diff --git a/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/config/JWTAuthFilterTest.java b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/config/JWTAuthFilterTest.java new file mode 100644 index 0000000..b66470c --- /dev/null +++ b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/config/JWTAuthFilterTest.java @@ -0,0 +1,89 @@ +package id.ac.ui.cs.advprog.snackscription_subscriptionbox.config; + + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.security.core.context.SecurityContextHolder; +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.utils.JWTUtils; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.*; + +class JWTAuthFilterTest { + + @Mock + private JWTUtils jwtUtils; + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + + @Mock + private FilterChain filterChain; + + @InjectMocks + private JWTAuthFilter jwtAuthFilter; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + SecurityContextHolder.clearContext(); + } + + @Test + void doFilterInternal_MissingAuthHeader() throws ServletException, IOException { + when(request.getHeader("Authorization")).thenReturn(null); + + jwtAuthFilter.doFilterInternal(request, response, filterChain); + + verify(filterChain, times(1)).doFilter(request, response); + assertNull(SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + void doFilterInternal_BlankAuthHeader() throws ServletException, IOException { + when(request.getHeader("Authorization")).thenReturn(""); + + jwtAuthFilter.doFilterInternal(request, response, filterChain); + + verify(filterChain, times(1)).doFilter(request, response); + assertNull(SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + void doFilterInternal_InvalidToken() throws ServletException, IOException { + String invalidToken = "Bearer invalidToken"; + when(request.getHeader("Authorization")).thenReturn(invalidToken); + when(jwtUtils.isTokenValid(anyString())).thenReturn(false); + + jwtAuthFilter.doFilterInternal(request, response, filterChain); + + verify(filterChain, times(1)).doFilter(request, response); + assertNull(SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + void doFilterInternal_ValidToken() throws ServletException, IOException { + String validToken = "Bearer validToken"; + when(request.getHeader("Authorization")).thenReturn(validToken); + when(jwtUtils.isTokenValid("validToken")).thenReturn(true); + when(jwtUtils.extractRole("validToken")).thenReturn("USER"); + + jwtAuthFilter.doFilterInternal(request, response, filterChain); + + verify(filterChain, times(1)).doFilter(request, response); + assertEquals("USER", SecurityContextHolder.getContext().getAuthentication().getAuthorities().iterator().next().getAuthority()); + } +} \ No newline at end of file diff --git a/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/controller/SubscriptionBoxControllerTest.java b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/controller/SubscriptionBoxControllerTest.java index bcf2c34..fdd4c50 100644 --- a/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/controller/SubscriptionBoxControllerTest.java +++ b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/controller/SubscriptionBoxControllerTest.java @@ -27,12 +27,15 @@ class SubscriptionBoxControllerTest { @InjectMocks private SubscriptionBoxController subscriptionBoxController; + @Mock private JWTUtils jwtUtils; + private SubscriptionBox subscriptionBox; private SubscriptionBoxDTO subscriptionBoxDTO; private final String validToken = "eyJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiQURNSU4iLCJzdWIiOiJhZG1pbkBnbWFpbC5jb20iLCJpYXQiOjE3MTY2MjAzOTksImV4cCI6MTcxNjcwNjc5OX0.dFmE18NL6H1my8Dki1Lp4DlwGIRbTTpgj3qUFKBoBoo"; + @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); @@ -48,12 +51,13 @@ void setUp() { // Convert to DTO subscriptionBoxDTO = DTOMapper.convertModelToDto(subscriptionBox); + when(jwtUtils.isTokenValid(validToken)).thenReturn(true); when(jwtUtils.extractRole(any(String.class))).thenReturn("admin"); } @Test - void testCreateSubscriptionBox_HappyPath()throws IllegalAccessException { + void testCreateSubscriptionBox_HappyPath() throws IllegalAccessException { when(subscriptionBoxService.save(any(SubscriptionBoxDTO.class))) .thenReturn(CompletableFuture.completedFuture(subscriptionBox)); @@ -65,7 +69,7 @@ void testCreateSubscriptionBox_HappyPath()throws IllegalAccessException { } @Test - void testCreateSubscriptionBox_UnhappyPath()throws IllegalAccessException { + void testCreateSubscriptionBox_UnhappyPath() throws IllegalAccessException { when(subscriptionBoxService.save(any(SubscriptionBoxDTO.class))) .thenReturn(CompletableFuture.failedFuture(new RuntimeException("Error saving subscription box"))); @@ -78,23 +82,22 @@ void testCreateSubscriptionBox_UnhappyPath()throws IllegalAccessException { @Test void testFindAll_HappyPath() throws IllegalAccessException { - List subscriptionBoxes = Collections.singletonList(subscriptionBox); + List subscriptionBoxes = Collections.singletonList(subscriptionBoxDTO); when(subscriptionBoxService.findAll()) .thenReturn(CompletableFuture.completedFuture(subscriptionBoxes)); - CompletableFuture>> result = subscriptionBoxController.findAll(validToken); + CompletableFuture>> result = subscriptionBoxController.findAll(validToken); assertNotNull(result); assertTrue(result.isDone()); assertEquals(ResponseEntity.ok(subscriptionBoxes), result.join()); } - @Test void testUpdateSubscriptionBox_HappyPath() throws IllegalAccessException { when(subscriptionBoxService.findById(subscriptionBoxDTO.getId())) - .thenReturn(CompletableFuture.completedFuture(Optional.of(subscriptionBoxDTO))); + .thenReturn(CompletableFuture.completedFuture(Optional.of(subscriptionBox))); when(subscriptionBoxService.update(any(SubscriptionBoxDTO.class))) .thenReturn(CompletableFuture.completedFuture(subscriptionBox)); @@ -117,7 +120,7 @@ void testUpdateSubscriptionBox_UnhappyPath() throws IllegalAccessException { } @Test - void testUpdateSubscriptionBox_NotFound()throws IllegalAccessException { + void testUpdateSubscriptionBox_NotFound() throws IllegalAccessException { when(subscriptionBoxService.findById(subscriptionBoxDTO.getId())) .thenReturn(CompletableFuture.completedFuture(Optional.empty())); @@ -131,17 +134,16 @@ void testUpdateSubscriptionBox_NotFound()throws IllegalAccessException { @Test void testFindById_HappyPath() throws IllegalAccessException { when(subscriptionBoxService.findById(subscriptionBox.getId())) - .thenReturn(CompletableFuture.completedFuture(Optional.of(subscriptionBoxDTO))); + .thenReturn(CompletableFuture.completedFuture(Optional.of(subscriptionBox))); CompletableFuture> result = subscriptionBoxController.findById(validToken, subscriptionBox.getId()); assertNotNull(result); assertTrue(result.isDone()); - assertEquals(ResponseEntity.ok(subscriptionBoxDTO), result.join()); } @Test - void testFindById_UnhappyPath()throws IllegalAccessException { + void testFindById_UnhappyPath() throws IllegalAccessException { String invalidId = "invalid-uuid"; CompletableFuture> result = subscriptionBoxController.findById(validToken, invalidId); @@ -172,7 +174,6 @@ void testDeleteSubscriptionBox_UnhappyPath() throws IllegalAccessException { assertEquals(expectedResult.join(), result.join()); } - @Test void testFindByPriceLessThan_HappyPath() throws IllegalAccessException { List expectedDTOs = Collections.singletonList(subscriptionBoxDTO); @@ -243,7 +244,6 @@ void testFindByPriceEquals_UnhappyPath() throws IllegalAccessException { assertNotNull(result); assertTrue(result.isDone()); - } @Test diff --git a/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/DTOMapperTest.java b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/DTOMapperTest.java new file mode 100644 index 0000000..d47ba8c --- /dev/null +++ b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/DTOMapperTest.java @@ -0,0 +1,89 @@ +package id.ac.ui.cs.advprog.snackscription_subscriptionbox.dto; + +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.Item; +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.SubscriptionBox; +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.factory.SubscriptionBoxFactory; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class DTOMapperTest { + + @Test + void testConvertModelToDto() { + Item item1 = new Item("1", "Item 1", 10); + Item item2 = new Item("2", "Item 2", 20); + List items = List.of(item1, item2); + + SubscriptionBox model = new SubscriptionBox( "Basic", "Monthly", 100, items, "Description"); + SubscriptionBoxDTO dto = DTOMapper.convertModelToDto(model); + + assertThat(dto.getId()).isEqualTo(model.getId()); + assertThat(dto.getName()).isEqualTo("Basic"); + assertThat(dto.getType()).isEqualTo("MONTHLY"); + assertThat(dto.getPrice()).isEqualTo(100); + assertThat(dto.getItems()).hasSize(2); + assertThat(dto.getDescription()).isEqualTo("Description"); + } + + @Test + void testConvertDTOtoModel() { + ItemDTO item1 = new ItemDTO("1", "Item 1", 10); + ItemDTO item2 = new ItemDTO("2", "Item 2", 20); + List items = List.of(item1, item2); + + SubscriptionBoxDTO dto = new SubscriptionBoxDTO("1","Basic", "Monthly", 100, items, "Description"); + SubscriptionBox model = DTOMapper.convertDTOtoModel(dto); + assertThat(model.getName()).isEqualTo("Basic"); + assertThat(model.getType()).isEqualTo("MONTHLY"); + assertThat(model.getPrice()).isEqualTo(100); + assertThat(model.getItems()).hasSize(2); + assertThat(model.getDescription()).isEqualTo("Description"); + } + + @Test + void testUpdateSubscriptionBox() { + SubscriptionBox model = new SubscriptionBox("1", "Monthly", 100, null, "Old Description"); + ItemDTO item1 = new ItemDTO("1", "Item 1", 10); + List items = List.of(item1); + + SubscriptionBoxDTO dto = new SubscriptionBoxDTO("1", "Basic", "Monthly", 150, items, "New Description"); + SubscriptionBox updatedModel = DTOMapper.updateSubscriptionBox(model, dto); + + assertThat(updatedModel.getPrice()).isEqualTo(150); + assertThat(updatedModel.getDescription()).isEqualTo("New Description"); + assertThat(updatedModel.getItems()).hasSize(1); + } + + @Test + void testConvertItemToDto() { + Item item = new Item("1", "Item 1", 10); + ItemDTO dto = DTOMapper.convertItemToDto(item); + + assertThat(dto.getId()).isEqualTo("1"); + assertThat(dto.getName()).isEqualTo("Item 1"); + assertThat(dto.getQuantity()).isEqualTo(10); + } + + @Test + void testConvertDtoToItem() { + ItemDTO dto = new ItemDTO("1", "Item 1", 10); + Item item = DTOMapper.convertDtoToItem(dto); + + assertThat(item.getId()).isEqualTo("1"); + assertThat(item.getName()).isEqualTo("Item 1"); + assertThat(item.getQuantity()).isEqualTo(10); + } + + @Test + void testUpdateItem() { + Item item = new Item("1", "Old Item", 5); + ItemDTO dto = new ItemDTO("1", "New Item", 10); + Item updatedItem = DTOMapper.updateItem(item, dto); + + assertThat(updatedItem.getName()).isEqualTo("New Item"); + assertThat(updatedItem.getQuantity()).isEqualTo(10); + } +} diff --git a/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/SubscriptionBoxDTOTest.java b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/SubscriptionBoxDTOTest.java new file mode 100644 index 0000000..36d1528 --- /dev/null +++ b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/dto/SubscriptionBoxDTOTest.java @@ -0,0 +1,52 @@ +package id.ac.ui.cs.advprog.snackscription_subscriptionbox.dto; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class SubscriptionBoxDTOTest { + + @Test + void testNoArgsConstructor() { + SubscriptionBoxDTO dto = new SubscriptionBoxDTO(); + assertThat(dto).isNotNull(); + } + + @Test + void testAllArgsConstructor() { + ItemDTO item1 = new ItemDTO("1", "Item 1", 10); + ItemDTO item2 = new ItemDTO("2", "Item 2", 20); + List items = List.of(item1, item2); + + SubscriptionBoxDTO dto = new SubscriptionBoxDTO("1", "Basic", "Monthly", 100, items, "Description"); + + assertThat(dto.getId()).isEqualTo("1"); + assertThat(dto.getName()).isEqualTo("Basic"); + assertThat(dto.getType()).isEqualTo("Monthly"); + assertThat(dto.getPrice()).isEqualTo(100); + assertThat(dto.getItems()).isEqualTo(items); + assertThat(dto.getDescription()).isEqualTo("Description"); + } + + @Test + void testSettersAndGetters() { + SubscriptionBoxDTO dto = new SubscriptionBoxDTO(); + + dto.setId("1"); + dto.setName("Basic"); + dto.setType("Monthly"); + dto.setPrice(100); + ItemDTO item = new ItemDTO("1", "Item 1", 10); + dto.setItems(List.of(item)); + dto.setDescription("Description"); + + assertThat(dto.getId()).isEqualTo("1"); + assertThat(dto.getName()).isEqualTo("Basic"); + assertThat(dto.getType()).isEqualTo("Monthly"); + assertThat(dto.getPrice()).isEqualTo(100); + assertThat(dto.getItems()).containsExactly(item); + assertThat(dto.getDescription()).isEqualTo("Description"); + } +} diff --git a/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/factory/SubscriptionBoxFactoryTest.java b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/factory/SubscriptionBoxFactoryTest.java index 3276de2..7304270 100644 --- a/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/factory/SubscriptionBoxFactoryTest.java +++ b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/factory/SubscriptionBoxFactoryTest.java @@ -28,7 +28,7 @@ void testCreateSubscriptionBoxWithParameters() { items.add(new Item()); // Assuming you have a constructor in Item class like this items.add(new Item()); - SubscriptionBox subscriptionBox = subscriptionBoxFactory.create("1", "Deluxe Box", "MONTHLY", 150, items, "this is good yas"); + SubscriptionBox subscriptionBox = subscriptionBoxFactory.create("Deluxe Box", "MONTHLY", 150, items, "this is good yas"); assertEquals("Deluxe Box", subscriptionBox.getName()); assertEquals("MONTHLY", subscriptionBox.getType()); diff --git a/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/model/LogAdminTest.java b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/model/LogAdminTest.java new file mode 100644 index 0000000..195a4b3 --- /dev/null +++ b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/model/LogAdminTest.java @@ -0,0 +1,46 @@ +package id.ac.ui.cs.advprog.snackscription_subscriptionbox.model; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +class LogAdminTest { + + @Test + void testNoArgsConstructor() { + LogAdmin logAdmin = new LogAdmin(); + assertNotNull(logAdmin); + } + + @Test + void testAllArgsConstructor() { + String logString = "Test log string"; + String subBoxId = "Test sub box ID"; + LocalDateTime beforeCreation = LocalDateTime.now(); + + LogAdmin logAdmin = new LogAdmin(logString, subBoxId); + + assertNotNull(logAdmin); + assertEquals(logString, logAdmin.getLogString()); + assertEquals(subBoxId, logAdmin.getSubBoxId()); + assertTrue(logAdmin.getDate().isAfter(beforeCreation) || logAdmin.getDate().isEqual(beforeCreation)); + } + + @Test + void testSettersAndGetters() { + LogAdmin logAdmin = new LogAdmin(); + + logAdmin.setId(1); + logAdmin.setLogString("Updated log string"); + logAdmin.setSubBoxId("Updated sub box ID"); + LocalDateTime updatedDate = LocalDateTime.now(); + logAdmin.setDate(updatedDate); + + assertEquals(1, logAdmin.getId()); + assertEquals("Updated log string", logAdmin.getLogString()); + assertEquals("Updated sub box ID", logAdmin.getSubBoxId()); + assertEquals(updatedDate, logAdmin.getDate()); + } +} diff --git a/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/SubscriptionBoxRepositoryTest.java b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/SubscriptionBoxRepositoryTest.java index 273c9e8..97d43d4 100644 --- a/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/SubscriptionBoxRepositoryTest.java +++ b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/repository/SubscriptionBoxRepositoryTest.java @@ -1,5 +1,6 @@ package id.ac.ui.cs.advprog.snackscription_subscriptionbox.repository; +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.Item; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.SubscriptionBox; import jakarta.persistence.EntityManager; import jakarta.persistence.TypedQuery; @@ -13,6 +14,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -50,20 +52,19 @@ void testSave() { verify(entityManager, times(1)).persist(subscriptionBox); } - @Test void testFindAll() { SubscriptionBox subscriptionBox1 = new SubscriptionBox("Basic", "Monthly", 100, null, "Basic monthly subscription box"); SubscriptionBox subscriptionBox2 = new SubscriptionBox("Premium", "Monthly", 200, null, "Premium monthly subscription box"); TypedQuery query = mock(TypedQuery.class); - when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb", SubscriptionBox.class)).thenReturn(query); + when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items", SubscriptionBox.class)).thenReturn(query); when(query.getResultList()).thenReturn(Arrays.asList(subscriptionBox1, subscriptionBox2)); List subscriptionBoxes = subscriptionBoxRepository.findAll(); assertEquals(2, subscriptionBoxes.size()); - verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb", SubscriptionBox.class); + verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items", SubscriptionBox.class); verify(query, times(1)).getResultList(); } @@ -72,21 +73,32 @@ void testFindById() { SubscriptionBox subscriptionBox = new SubscriptionBox("Basic", "Monthly", 100, null, "Basic monthly subscription box"); subscriptionBox.setId("1"); - when(entityManager.find(SubscriptionBox.class, "1")).thenReturn(subscriptionBox); + TypedQuery query = mock(TypedQuery.class); + when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.id = :id", SubscriptionBox.class)).thenReturn(query); + when(query.setParameter("id", "1")).thenReturn(query); + when(query.getResultStream()).thenReturn(Stream.of(subscriptionBox)); Optional optionalSubscriptionBox = subscriptionBoxRepository.findById("1"); assertEquals(Optional.of(subscriptionBox), optionalSubscriptionBox); - verify(entityManager, times(1)).find(SubscriptionBox.class, "1"); + verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.id = :id", SubscriptionBox.class); + verify(query, times(1)).setParameter("id", "1"); + verify(query, times(1)).getResultStream(); } @Test void testFindByIdSubscriptionNotFound() { - when(entityManager.find(SubscriptionBox.class, "nonexistentId")).thenReturn(null); + TypedQuery query = mock(TypedQuery.class); + when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.id = :id", SubscriptionBox.class)).thenReturn(query); + when(query.setParameter("id", "nonexistentId")).thenReturn(query); + when(query.getResultStream()).thenReturn(Stream.empty()); - assertNull(subscriptionBoxRepository.findById("nonexistentId").orElse(null)); + Optional result = subscriptionBoxRepository.findById("nonexistentId"); - verify(entityManager, times(1)).find(SubscriptionBox.class, "nonexistentId"); + assertFalse(result.isPresent()); + verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.id = :id", SubscriptionBox.class); + verify(query, times(1)).setParameter("id", "nonexistentId"); + verify(query, times(1)).getResultStream(); } @Test @@ -106,32 +118,41 @@ void testDelete() { SubscriptionBox subscriptionBox = new SubscriptionBox("Basic", "Monthly", 100, null, "Basic monthly subscription box"); subscriptionBox.setId("1"); - when(entityManager.find(SubscriptionBox.class, "1")).thenReturn(subscriptionBox); + TypedQuery query = mock(TypedQuery.class); + when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.id = :id", SubscriptionBox.class)).thenReturn(query); + when(query.setParameter("id", "1")).thenReturn(query); + when(query.getResultStream()).thenReturn(Stream.of(subscriptionBox)); + doNothing().when(entityManager).remove(subscriptionBox); subscriptionBoxRepository.delete("1"); - verify(entityManager, times(1)).find(SubscriptionBox.class, "1"); + verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.id = :id", SubscriptionBox.class); + verify(query, times(1)).setParameter("id", "1"); + verify(query, times(1)).getResultStream(); verify(entityManager, times(1)).remove(subscriptionBox); } @Test void testDeleteSubscriptionNotFound() { - when(entityManager.find(SubscriptionBox.class, "1")).thenReturn(null); + TypedQuery query = mock(TypedQuery.class); + when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.id = :id", SubscriptionBox.class)).thenReturn(query); + when(query.setParameter("id", "1")).thenReturn(query); + when(query.getResultStream()).thenReturn(Stream.empty()); assertThrows(IllegalArgumentException.class, () -> subscriptionBoxRepository.delete("1")); - verify(entityManager, times(1)).find(SubscriptionBox.class, "1"); - verify(entityManager, never()).remove(any()); + verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.id = :id", SubscriptionBox.class); + verify(query, times(1)).setParameter("id", "1"); + verify(query, times(1)).getResultStream(); } @Test void testFindByPriceLessThan() { SubscriptionBox subscriptionBox1 = new SubscriptionBox("Basic", "Monthly", 100, null, "Basic monthly subscription box"); - SubscriptionBox subscriptionBox2 = new SubscriptionBox("Premium", "Monthly", 200, null, "Premium monthly subscription box"); TypedQuery query = mock(TypedQuery.class); - when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb WHERE sb.price < :price", SubscriptionBox.class)).thenReturn(query); + when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.price < :price", SubscriptionBox.class)).thenReturn(query); when(query.setParameter("price", 150)).thenReturn(query); when(query.getResultList()).thenReturn(Arrays.asList(subscriptionBox1)); @@ -139,18 +160,17 @@ void testFindByPriceLessThan() { assertEquals(1, result.size()); assertEquals(subscriptionBox1, result.get(0)); - verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb WHERE sb.price < :price", SubscriptionBox.class); + verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.price < :price", SubscriptionBox.class); verify(query, times(1)).setParameter("price", 150); verify(query, times(1)).getResultList(); } @Test void testFindByPriceGreaterThan() { - SubscriptionBox subscriptionBox1 = new SubscriptionBox("Basic", "Monthly", 100, null, "Basic monthly subscription box"); SubscriptionBox subscriptionBox2 = new SubscriptionBox("Premium", "Monthly", 200, null, "Premium monthly subscription box"); TypedQuery query = mock(TypedQuery.class); - when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb WHERE sb.price > :price", SubscriptionBox.class)).thenReturn(query); + when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.price > :price", SubscriptionBox.class)).thenReturn(query); when(query.setParameter("price", 150)).thenReturn(query); when(query.getResultList()).thenReturn(Arrays.asList(subscriptionBox2)); @@ -158,7 +178,7 @@ void testFindByPriceGreaterThan() { assertEquals(1, result.size()); assertEquals(subscriptionBox2, result.get(0)); - verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb WHERE sb.price > :price", SubscriptionBox.class); + verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.price > :price", SubscriptionBox.class); verify(query, times(1)).setParameter("price", 150); verify(query, times(1)).getResultList(); } @@ -166,10 +186,9 @@ void testFindByPriceGreaterThan() { @Test void testFindByPriceEquals() { SubscriptionBox subscriptionBox1 = new SubscriptionBox("Basic", "Monthly", 100, null, "Basic monthly subscription box"); - SubscriptionBox subscriptionBox2 = new SubscriptionBox("Premium", "Monthly", 200, null, "Premium monthly subscription box"); TypedQuery query = mock(TypedQuery.class); - when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb WHERE sb.price = :price", SubscriptionBox.class)).thenReturn(query); + when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.price = :price", SubscriptionBox.class)).thenReturn(query); when(query.setParameter("price", 100)).thenReturn(query); when(query.getResultList()).thenReturn(Arrays.asList(subscriptionBox1)); @@ -177,7 +196,7 @@ void testFindByPriceEquals() { assertEquals(1, result.size()); assertEquals(subscriptionBox1, result.get(0)); - verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb WHERE sb.price = :price", SubscriptionBox.class); + verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE sb.price = :price", SubscriptionBox.class); verify(query, times(1)).setParameter("price", 100); verify(query, times(1)).getResultList(); } @@ -188,7 +207,7 @@ void testFindByName() { SubscriptionBox subscriptionBox2 = new SubscriptionBox("Premium Box", "Monthly", 200, null, "Premium monthly subscription box"); TypedQuery query = mock(TypedQuery.class); - when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb WHERE LOWER(sb.name) LIKE LOWER(:name)", SubscriptionBox.class)).thenReturn(query); + when(entityManager.createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE LOWER(sb.name) LIKE LOWER(:name)", SubscriptionBox.class)).thenReturn(query); when(query.setParameter("name", "%box%")).thenReturn(query); when(query.getResultList()).thenReturn(Arrays.asList(subscriptionBox1, subscriptionBox2)); @@ -196,7 +215,7 @@ void testFindByName() { assertTrue(result.isPresent()); assertEquals(2, result.get().size()); - verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb WHERE LOWER(sb.name) LIKE LOWER(:name)", SubscriptionBox.class); + verify(entityManager, times(1)).createQuery("SELECT sb FROM SubscriptionBox sb LEFT JOIN FETCH sb.items WHERE LOWER(sb.name) LIKE LOWER(:name)", SubscriptionBox.class); verify(query, times(1)).setParameter("name", "%box%"); verify(query, times(1)).getResultList(); } diff --git a/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/service/SubscriptionBoxServiceImplTest.java b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/service/SubscriptionBoxServiceImplTest.java index be1a4e9..6f50bdb 100644 --- a/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/service/SubscriptionBoxServiceImplTest.java +++ b/src/test/java/id/ac/ui/cs/advprog/snackscription_subscriptionbox/service/SubscriptionBoxServiceImplTest.java @@ -1,17 +1,21 @@ package id.ac.ui.cs.advprog.snackscription_subscriptionbox.service; -import id.ac.ui.cs.advprog.snackscription_subscriptionbox.dto.DTOMapper; + import id.ac.ui.cs.advprog.snackscription_subscriptionbox.dto.SubscriptionBoxDTO; +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.dto.DTOMapper; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.SubscriptionBox; +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.model.LogAdmin; +import id.ac.ui.cs.advprog.snackscription_subscriptionbox.repository.LogRepository; import id.ac.ui.cs.advprog.snackscription_subscriptionbox.repository.SubscriptionBoxRepository; -import id.ac.ui.cs.advprog.snackscription_subscriptionbox.service.SubscriptionBoxServiceImpl; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -26,6 +30,9 @@ class SubscriptionBoxServiceImplTest { @Mock private SubscriptionBoxRepository subscriptionBoxRepository; + @Mock + private LogRepository logRepository; + @InjectMocks private SubscriptionBoxServiceImpl subscriptionBoxService; @@ -37,39 +44,23 @@ void setUp() { subscriptionBox = new SubscriptionBox("Basic", "Monthly", 100, null, "this is good yas"); subscriptionBox.setId("1"); - } - - - - @Test - void testFindByIdInvalidId() { - assertThrows(IllegalArgumentException.class, () -> { - subscriptionBoxService.findById("").get(); - }); - verify(subscriptionBoxRepository, never()).findById(anyString()); + subscriptionBoxDTO = DTOMapper.convertModelToDto(subscriptionBox); } @Test void testFindAll() throws ExecutionException, InterruptedException { - List subscriptionBoxes = Arrays.asList(subscriptionBox); + List subscriptionBoxes = Collections.singletonList(subscriptionBox); when(subscriptionBoxRepository.findAll()).thenReturn(subscriptionBoxes); - CompletableFuture> future = subscriptionBoxService.findAll(); - List result = future.get(); + CompletableFuture> future = subscriptionBoxService.findAll(); + List result = future.get(); assertEquals(1, result.size()); - assertEquals(subscriptionBox, result.get(0)); verify(subscriptionBoxRepository, times(1)).findAll(); } - @Test - void testUpdateInvalidBox() { - assertThrows(IllegalArgumentException.class, () -> { - subscriptionBoxService.update(null).get(); - }); - verify(subscriptionBoxRepository, never()).update(any()); - } + @Test void testDelete() throws ExecutionException, InterruptedException { @@ -83,18 +74,6 @@ void testDelete() throws ExecutionException, InterruptedException { verify(subscriptionBoxRepository, times(1)).delete("1"); } - @Test - void testDeleteSubscriptionNotFound() { - when(subscriptionBoxRepository.findById("1")).thenReturn(Optional.empty()); - - assertThrows(IllegalArgumentException.class, () -> { - subscriptionBoxService.delete("1").get(); - }); - - verify(subscriptionBoxRepository, times(1)).findById("1"); - verify(subscriptionBoxRepository, never()).delete(anyString()); - } - @Test @@ -111,4 +90,119 @@ void testFindDistinctNames() throws ExecutionException, InterruptedException { assertTrue(result.get().contains("Premium")); verify(subscriptionBoxRepository, times(1)).findDistinctNames(); } + + + @Test + void testSave() throws ExecutionException, InterruptedException { + when(subscriptionBoxRepository.save(any(SubscriptionBox.class))).thenReturn(subscriptionBox); + + CompletableFuture future = subscriptionBoxService.save(subscriptionBoxDTO); + SubscriptionBox result = future.get(); + + assertNotNull(result); + assertEquals(subscriptionBox, result); + verify(subscriptionBoxRepository, times(1)).save(any(SubscriptionBox.class)); + } + + @Test + void testFindById() throws ExecutionException, InterruptedException { + when(subscriptionBoxRepository.findById("1")).thenReturn(Optional.of(subscriptionBox)); + + CompletableFuture> future = subscriptionBoxService.findById("1"); + Optional result = future.get(); + + assertTrue(result.isPresent()); + + verify(subscriptionBoxRepository, times(1)).findById("1"); + } + + @Test + void testUpdate() throws ExecutionException, InterruptedException { + when(subscriptionBoxRepository.findById("1")).thenReturn(Optional.of(subscriptionBox)); + when(subscriptionBoxRepository.update(any(SubscriptionBox.class))).thenReturn(subscriptionBox); + + CompletableFuture future = subscriptionBoxService.update(subscriptionBoxDTO); + SubscriptionBox result = future.get(); + + assertNotNull(result); + assertEquals(subscriptionBox, result); + verify(subscriptionBoxRepository, times(1)).findById("1"); + verify(subscriptionBoxRepository, times(1)).update(any(SubscriptionBox.class)); + } + + @Test + void testFindByPriceLessThan() throws ExecutionException, InterruptedException { + when(subscriptionBoxRepository.findByPriceLessThan(150)).thenReturn(List.of(subscriptionBox)); + + CompletableFuture> future = subscriptionBoxService.findByPriceLessThan(150); + List result = future.get(); + + assertNotNull(result); + assertEquals(1, result.size()); + verify(subscriptionBoxRepository, times(1)).findByPriceLessThan(150); + } + + @Test + void testFindByPriceGreaterThan() throws ExecutionException, InterruptedException { + when(subscriptionBoxRepository.findByPriceGreaterThan(50)).thenReturn(List.of(subscriptionBox)); + + CompletableFuture> future = subscriptionBoxService.findByPriceGreaterThan(50); + List result = future.get(); + + assertNotNull(result); + assertEquals(1, result.size()); + verify(subscriptionBoxRepository, times(1)).findByPriceGreaterThan(50); + } + + @Test + void testFindByPriceEquals() throws ExecutionException, InterruptedException { + when(subscriptionBoxRepository.findByPriceEquals(100)).thenReturn(List.of(subscriptionBox)); + + CompletableFuture> future = subscriptionBoxService.findByPriceEquals(100); + List result = future.get(); + + assertNotNull(result); + assertEquals(1, result.size()); + verify(subscriptionBoxRepository, times(1)).findByPriceEquals(100); + } + + @Test + void testFindByName() throws ExecutionException, InterruptedException { + when(subscriptionBoxRepository.findByName("Basic")).thenReturn(Optional.of(List.of(subscriptionBox))); + + CompletableFuture>> future = subscriptionBoxService.findByName("Basic"); + Optional> result = future.get(); + + assertTrue(result.isPresent()); + assertEquals(1, result.get().size()); + verify(subscriptionBoxRepository, times(1)).findByName("Basic"); + } + + @Test + void testGetLog() throws ExecutionException, InterruptedException { + LogAdmin log = new LogAdmin("Log Message", "1"); + when(logRepository.findAllByOrderByDateDesc()).thenReturn(CompletableFuture.completedFuture(List.of(log))); + + CompletableFuture> future = subscriptionBoxService.getLog(); + List result = future.get(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(log, result.get(0)); + verify(logRepository, times(1)).findAllByOrderByDateDesc(); + } + + @Test + void testLogUpdateStatus() { + ArgumentCaptor logCaptor = ArgumentCaptor.forClass(LogAdmin.class); + + subscriptionBoxService.logUpdateStatus("1", "UPDATE"); + + verify(logRepository).save(logCaptor.capture()); + LogAdmin capturedLog = logCaptor.getValue(); + + assertNotNull(capturedLog); + assertEquals("1", capturedLog.getSubBoxId()); + assertTrue(capturedLog.getLogString().contains("UPDATE")); + } }