diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 31c266db..ceb5132a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,14 +38,14 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 - - name: Set up JDK 21 - uses: actions/setup-java@v3 + uses: actions/checkout@v4 + - name: Set up JDK 22 + uses: actions/setup-java@v4 with: - java-version: '21' + java-version: '22' distribution: 'corretto' # maven repo cache save and restore - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -53,7 +53,7 @@ jobs: ${{ runner.os }}-maven- # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -88,4 +88,4 @@ jobs: run: cd devops/GenericPiMetricsEndpoint; mvn -B compile --file pom.xml - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 0a407715..734a5a12 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -18,14 +18,14 @@ jobs: NVD_API_KEY: ${{ secrets.NVD_API_KEY }} steps: - - uses: actions/checkout@v3 - - name: Set up JDK 21 - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - name: Set up JDK 22 + uses: actions/setup-java@v4 with: java-version: '21' distribution: 'corretto' # maven repo cache save and restore - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -46,14 +46,14 @@ jobs: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 21 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '21' distribution: 'corretto' # maven repo cache save and restore - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/README.md b/README.md index 20a92ff5..96ceb7b8 100644 --- a/README.md +++ b/README.md @@ -41,10 +41,10 @@ The AI challenge here is, that if the human provided data on a given problem con I declare the Sabi Project climate-friendly because of: * You as a user, who is **not** demanding, that the Sabi Service is up and running 24/7 with 99% availability, accepting minor service outages, thus enabling the low-energy platform for the project. -* The decision to use raspberry pis as operation platform, which have a very low energy profile (Have a look at my pis here: https://github.com/StefanSchubert/sabi/wiki/07.-Deployment-View ) +* The decision to use raspberry pis as an operation platform, which have a very low energy profile (Have a look at my pis here: https://github.com/StefanSchubert/sabi/wiki/07.-Deployment-View ) The alternative would be running in a public cloud, which would allow 99% availability but surly a much bigger CO2 footprint (as well as bigger costs). -* The private cloud at my homesite is powered by a green electricity tariff of my power supply provider. - I in future I will generate my own electricity through solarcells on the roof. +* The private cloud at my home-site is powered by a green electricity tariff of my power supply provider. + In future, I will generate my own electricity through solar-cells on the roof. ## Project Planning diff --git a/captcha/pom.xml b/captcha/pom.xml index e4657869..e4901980 100644 --- a/captcha/pom.xml +++ b/captcha/pom.xml @@ -11,14 +11,14 @@ org.springframework.boot spring-boot-starter-parent - 3.2.2 + 3.3.3 4.0.0 de.bluewhale captcha-light - 1.2.5 + 1.2.6 jar A REST-full microservice service for CAPTCHA running as spring boot application @@ -41,16 +41,16 @@ - 21 + 22 2.20.0 UTF-8 UTF-8 - 2.3.0 - 1.12.3 - 1.18.30 - 9.0.9 - 3.2.5 - 2.16.2 + 2.6.0 + 1.13.5 + 1.18.34 + 10.0.4 + 3.5.0 + 2.17.1 @@ -145,6 +145,7 @@ maven-compiler-plugin ${maven-compiler-plugin.version} + true ${java.version} UTF-8 diff --git a/sabi-boundary/pom.xml b/sabi-boundary/pom.xml index 674c990b..bdfea9e4 100644 --- a/sabi-boundary/pom.xml +++ b/sabi-boundary/pom.xml @@ -12,7 +12,7 @@ de.bluewhale sabi-boundary - 1.2.6 + 1.2.9 jar Contains the DTOs and Utility classes which will be used by the server and client module. @@ -35,21 +35,20 @@ - 21 - 1.18.30 + 22 + 1.18.34 UTF-8 UTF-8 5.1.0.Final 1.5.4 - 2.3.0 - 9.0.7 + 2.6.0 + 10.0.3 4.0.0-M2 3.3.2 4.0.0-M13 - 3.12.1 - 3.5.0 - 2.16.2 - + 3.13.0 + 3.6.0 + 2.17.1 @@ -148,30 +147,46 @@ - - - org.owasp - dependency-check-maven - ${owasp.plugin.version} - - - ${nvd.api.key} - - - - - verify - - check - - - - + + + owasp-check + + + + + nvd.api.key + !@{nvd.api.key} + + + + + + org.owasp + dependency-check-maven + ${owasp.plugin.version} + + + ${nvd.api.key} + + + + verify + + check + + + + + + + + + \ No newline at end of file diff --git a/sabi-boundary/src/main/java/de/bluewhale/sabi/model/AquariumTo.java b/sabi-boundary/src/main/java/de/bluewhale/sabi/model/AquariumTo.java index f9438024..c9200abb 100644 --- a/sabi-boundary/src/main/java/de/bluewhale/sabi/model/AquariumTo.java +++ b/sabi-boundary/src/main/java/de/bluewhale/sabi/model/AquariumTo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -35,7 +35,7 @@ public class AquariumTo implements Serializable { private String description; @Schema(description = "API-Key which can be used to submit temperature measurements for this tank by an IoT device.", required = false) - private String temperatueApiKey; + private String temperatureApiKey; @Schema(description = "Flag telling if this tank is still in used, or if it meanwhile has been disolved.", required = false) private Boolean active; diff --git a/sabi-database/pom.xml b/sabi-database/pom.xml index 7fdf9f43..0ab4bc29 100644 --- a/sabi-database/pom.xml +++ b/sabi-database/pom.xml @@ -19,12 +19,12 @@ UTF-8 - 21 - 10.0.0 - 3.1.0 - 3.11.0 - 2.16.1 - 8.4.0 + 22 + 10.14.0 + 3.4.0 + 3.13.0 + 2.17.1 + 10.0.3 org.springframework.boot spring-boot-starter-parent - 3.2.2 + 3.3.3 de.bluewhale sabi-server - 1.3.5 + 1.3.7 jar Backend consistent of internal CRUD services which will be used by the REST service orchestration @@ -40,29 +40,36 @@ - 1.2.6 - UTF-8 - UTF-8 - 21 - 2.20.0 - 2.3.0 - 4.0.2 - 1.5.5.Final - 1.12.3 - 6.1.4 - 1.18.30 - 9.0.9 - 1.6.4 - 3.3.3 - 2.2.224 - - 1.6 1.2.1 + 3.26.3 4.4.0 - 4.0.1 - 24.1.0 - 2.16.2 - 3.2.5 + 1.6 + 4.0.2 + 10.19.0 + 4.0.2 + 22 + 1.3.2 + 25.0.0 + 5.10.2 + 1.11.1 + 1.11.1 + 1.20.2 + 2.20.0 + 1.18.34 + 1.6.2 + 3.4.1 + 1.20.2 + 3.5.0 + 1.13.5 + 10.0.3 + 1.6.5 + UTF-8 + UTF-8 + 1.2.9 + 6.1.13 + 2.6.0 + 2.17.1 + 0.2.0 @@ -188,6 +195,15 @@ provided + + + javax.annotation + javax.annotation-api + ${javax.annotation-api.version} + + org.mariadb.jdbc mariadb-java-client @@ -248,13 +264,69 @@ - - com.h2database - h2 - ${h2.version} + org.springframework.boot + spring-boot-testcontainers + test + + + + org.testcontainers + junit-jupiter + ${junit.testcontainer.version} + test + + + + org.junit.platform + junit-platform-suite-engine + ${junit-platform-suite-engine.version} + test + + + + org.junit.platform + junit-platform-suite-api + ${junit-platform-suite-api.version} + test + + + + org.testcontainers + mariadb + ${mariadb.testcontainer.version} + test + + + + + + + org.assertj + assertj-core + ${assertj-core.version} + test + + + + + org.flywaydb + flyway-core + ${flyway.version} + test + + + + org.flywaydb + flyway-mysql + ${flyway.version} test @@ -302,6 +374,20 @@ sabi-service + + + src/test/resources + + + + ../sabi-database/src/main/resources + + + + org.apache.maven.plugins @@ -326,6 +412,11 @@ mapstruct-processor ${mapstruct.version} + + org.projectlombok + lombok-mapstruct-binding + ${mapstruts-lombok-binding.version} + @@ -373,28 +464,6 @@ true - - - org.owasp - dependency-check-maven - ${owasp.plugin.version} - - - ${nvd.api.key} - - - - verify - - check - - - - @@ -415,4 +484,43 @@ + + + owasp-check + + + + + nvd.api.key + !@{nvd.api.key} + + + + + + org.owasp + dependency-check-maven + ${owasp.plugin.version} + + + ${nvd.api.key} + + + + verify + + check + + + + + + + + + \ No newline at end of file diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/configs/JPAConfig.java b/sabi-server/src/main/java/de/bluewhale/sabi/configs/JPAConfig.java index bf9b54f3..57859f71 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/configs/JPAConfig.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/configs/JPAConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -39,9 +39,6 @@ public class JPAConfig { @Value("${eclipselink.logging.level:INFO}") private String eclipseLinkLoggingLevel; - @Value("${h2.test.mode}") - private Boolean h2TestMode; - @Autowired private DataSource dataSource; @@ -77,13 +74,8 @@ public PersistenceExceptionTranslationPostProcessor exceptionTranslation() { Properties additionalProperties() { Properties properties = new Properties(); - if (h2TestMode) { - properties.setProperty("eclipselink.ddl-generation.output-mode", "database"); - properties.setProperty("eclipselink.ddl-generation", "drop-and-create-tables"); - } else { - properties.setProperty("eclipselink.ddl-generation", "create-or-extend-tables"); - properties.setProperty("eclipselink.ddl-generation.output-mode", "sql-script"); - } + properties.setProperty("eclipselink.ddl-generation", "create-or-extend-tables"); + properties.setProperty("eclipselink.ddl-generation.output-mode", "sql-script"); properties.setProperty("eclipselink.create-ddl-jdbc-file-name", "createDDL_ddlGeneration.jdbc"); properties.setProperty("eclipselink.drop-ddl-jdbc-file-name", "dropDDL_ddlGeneration.jdbc"); diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/AquariumMapper.java b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/AquariumMapper.java index 203c177b..ec405d0e 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/AquariumMapper.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/AquariumMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -28,7 +28,7 @@ public interface AquariumMapper { @Mapping(target ="description", source="description"), @Mapping(target ="userId", source="user.id"), @Mapping(target ="active", source="active"), - @Mapping(target ="temperatueApiKey", source="temperatureApiKey"), + @Mapping(target ="temperatureApiKey", source="temperatureApiKey"), @Mapping(target ="inceptionDate", source="inceptionDate"), }) AquariumTo mapAquariumEntity2To(@NotNull final AquariumEntity pAquariumEntity); @@ -40,8 +40,11 @@ public interface AquariumMapper { @Mapping(target ="size", source="size"), @Mapping(target ="description", source="description"), @Mapping(target ="active", source="active"), - @Mapping(target ="temperatureApiKey", source="temperatueApiKey"), + @Mapping(target ="temperatureApiKey", source="temperatureApiKey"), @Mapping(target ="inceptionDate", source="inceptionDate"), + @Mapping(target ="user.id", source="userId"), + @Mapping(target = "measurements", ignore = true), + @Mapping(target = "plagueRecords", ignore = true) }) AquariumEntity mapAquariumTo2Entity(@NotNull final AquariumTo pAquariumTo); diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/FishMapper.java b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/FishMapper.java index 9cf74c44..b174b203 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/FishMapper.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/FishMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -26,7 +26,8 @@ public interface FishMapper { @Mapping(target ="nickname", source="nickname"), @Mapping(target ="observedBehavior", source="observedBehavior"), @Mapping(target ="aquariumId", source="aquariumId"), - @Mapping(target ="fishCatalogueId", source="fishCatalogueId") + @Mapping(target ="fishCatalogueId", source="fishCatalogueId"), + @Mapping(target = "user", ignore = true) }) FishEntity mapFishTo2Entity(@NotNull final FishTo pFishTo); diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/MeasurementMapper.java b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/MeasurementMapper.java index 016c1630..abfe228a 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/MeasurementMapper.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/MeasurementMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -39,6 +39,8 @@ public interface MeasurementMapper { @Mapping(target ="measuredOn", source="measuredOn"), @Mapping(target ="measuredValue", source="measuredValue"), @Mapping(target ="unitId", source="unitId"), + @Mapping(target ="aquarium", ignore = true), + @Mapping(target ="user", ignore = true), }) MeasurementEntity mapMeasurementTo2EntityWithoutAquarium(@NotNull final MeasurementTo pMeasurementTo); @@ -47,6 +49,8 @@ public interface MeasurementMapper { @Mapping(target ="measuredOn", source="measuredOn"), @Mapping(target ="measuredValue", source="measuredValue"), @Mapping(target ="unitId", source="unitId"), + @Mapping(target ="aquarium", ignore = true), + @Mapping(target ="user", ignore = true), }) void mergeMeasurementTo2EntityWithoutAquarium(@NotNull final MeasurementTo pMeasurementTo, @NotNull @MappingTarget MeasurementEntity pMeasurementEntity); diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/MeasurementReminderMapper.java b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/MeasurementReminderMapper.java index 05419a02..a5586ec8 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/MeasurementReminderMapper.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/MeasurementReminderMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -26,6 +26,8 @@ public interface MeasurementReminderMapper { @Mapping(target ="pastDays", source="pastdays"), @Mapping(target ="active", source="active"), @Mapping(target ="unitId", source="unitId"), + @Mapping(target = "nextMeasureDate", ignore = true), + @Mapping(target = "unitName", ignore = true) }) MeasurementReminderTo mapUserMeasurementReminderEntity2TO(@NotNull UserMeasurementReminderEntity pUserMeasurementReminderEntity); @@ -33,6 +35,8 @@ public interface MeasurementReminderMapper { @Mapping(target ="pastdays", source="pastDays"), @Mapping(target ="active", source="active"), @Mapping(target ="unitId", source="unitId"), + @Mapping(target ="user", ignore = true), + @Mapping(target ="id", ignore = true) }) UserMeasurementReminderEntity mapUserMeasurementReminderTO2EntityWithoutUser(@NotNull MeasurementReminderTo pMeasurementReminderTo); @@ -40,6 +44,8 @@ public interface MeasurementReminderMapper { @Mapping(target ="pastdays", source="pastDays"), @Mapping(target ="active", source="active"), @Mapping(target ="unitId", source="unitId"), + @Mapping(target ="user", ignore = true), + @Mapping(target ="id", ignore = true) }) void mapUserMeasurementReminderTO2EntityWithoutUser(@NotNull MeasurementReminderTo pMeasurementReminderTo, @NotNull @MappingTarget UserMeasurementReminderEntity pUserMeasurementReminderEntity); diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/ParameterMapper.java b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/ParameterMapper.java index 7c25e33a..b39dbd40 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/ParameterMapper.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/ParameterMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -9,6 +9,7 @@ import de.bluewhale.sabi.persistence.model.ParameterEntity; import jakarta.validation.constraints.NotNull; import org.mapstruct.Mapper; +import org.mapstruct.Mappings; /** * Mappings for the Parameter Modell @@ -18,8 +19,22 @@ @Mapper(componentModel = "spring", uses = {MappingUtils.class}) public interface ParameterMapper { + @Mappings({ + @org.mapstruct.Mapping(target = "id", source = "id"), + @org.mapstruct.Mapping(target = "belongingUnitId", source = "belongingUnitId"), + @org.mapstruct.Mapping(target = "minThreshold", source = "minThreshold"), + @org.mapstruct.Mapping(target = "maxThreshold", source = "maxThreshold"), + @org.mapstruct.Mapping(target = "description", ignore = true) + }) ParameterTo mapParameterEntity2To(@NotNull final ParameterEntity pParameterEntity); + @Mappings({ + @org.mapstruct.Mapping(target = "id", source = "id"), + @org.mapstruct.Mapping(target = "belongingUnitId", source = "belongingUnitId"), + @org.mapstruct.Mapping(target = "minThreshold", source = "minThreshold"), + @org.mapstruct.Mapping(target = "maxThreshold", source = "maxThreshold"), + @org.mapstruct.Mapping(target = "localizedParameterEntities", ignore = true) + }) ParameterEntity mapParameterTo2Entity(ParameterTo pParameterTo); } diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/PlagueRecordMapper.java b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/PlagueRecordMapper.java index bf0485d8..88962402 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/PlagueRecordMapper.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/PlagueRecordMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -34,6 +34,9 @@ public interface PlagueRecordMapper { @Mapping(source ="plagueStatusId", target="observedPlagueStatus"), @Mapping(target ="observedOn", source="observedOn"), @Mapping(target ="plagueIntervallId", source="plagueIntervallId"), + @Mapping(target ="aquarium", ignore = true), + @Mapping(target ="id", ignore = true), + @Mapping(target ="user", ignore = true), }) PlagueRecordEntity mapPlagueRecordTo2EntityWithoutPrimaryKeyAndAquarium(PlagueRecordTo pPlagueRecordTo); diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/UnitMapper.java b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/UnitMapper.java index 0abed8de..40040610 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/UnitMapper.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/UnitMapper.java @@ -25,6 +25,7 @@ public interface UnitMapper { @Mappings({ @Mapping(target ="id", source="id"), @Mapping(target ="unitSign", source="name"), + @Mapping(target ="description", ignore = true) }) UnitTo mapUnitEntity2To(@NotNull final UnitEntity pUnitEntity); @@ -32,6 +33,7 @@ public interface UnitMapper { @Mappings({ @Mapping(target ="id", source="id"), @Mapping(target ="name", source="unitSign"), + @Mapping(target ="localizedUnitEntities", ignore = true) }) UnitEntity mapUnitToEntity(@NotNull final UnitTo pUnitTo); diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/UserMapper.java b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/UserMapper.java index 841f1470..1ab7fcc5 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/mapper/UserMapper.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/mapper/UserMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -32,6 +32,13 @@ public interface UserMapper { @Mapping(target ="validateToken", source="validationToken"), @Mapping(target ="language", source="language"), @Mapping(target ="country", source="country"), + @Mapping(target ="aquariums", ignore = true), + @Mapping(target ="corals", ignore = true), + @Mapping(target ="fishes", ignore = true), + @Mapping(target ="measurements", ignore = true), + @Mapping(target ="plagueRecords", ignore = true), + @Mapping(target ="treatments", ignore = true), + @Mapping(target ="userMeasurementReminders", ignore = true), }) UserEntity mapUserTo2Entity(@NotNull final UserTo pUserTo); @@ -48,6 +55,7 @@ public interface UserMapper { @Mapping(target ="validationToken", source="validateToken"), @Mapping(target ="language", source="language"), @Mapping(target ="country", source="country"), + @Mapping(target ="captchaCode", ignore = true), }) UserTo mapUserEntity2To(@NotNull final UserEntity pUserEntity); diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/CoralCatalogueEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/CoralCatalogueEntity.java index e25bcb87..edaf01ea 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/CoralCatalogueEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/CoralCatalogueEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -7,10 +7,12 @@ import jakarta.persistence.*; import lombok.Data; +import lombok.EqualsAndHashCode; @Table(name = "coral_catalogue", schema = "sabi") @Entity @Data +@EqualsAndHashCode(callSuper=false) public class CoralCatalogueEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/CoralEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/CoralEntity.java index 354ca9e7..49f9efb0 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/CoralEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/CoralEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -14,7 +14,7 @@ @Table(name = "coral", schema = "sabi") @Entity @Data -@EqualsAndHashCode(exclude = "user") +@EqualsAndHashCode(exclude = "user",callSuper = false) public class CoralEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/FishCatalogueEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/FishCatalogueEntity.java index 64c8c45d..d9acd9e8 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/FishCatalogueEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/FishCatalogueEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -7,6 +7,7 @@ import jakarta.persistence.*; import lombok.Data; +import lombok.EqualsAndHashCode; /** * @@ -16,6 +17,7 @@ @Table(name = "fish_catalogue", schema = "sabi") @Entity @Data +@EqualsAndHashCode(callSuper=false) public class FishCatalogueEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/FishEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/FishEntity.java index 015cb433..09f80113 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/FishEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/FishEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -16,7 +16,7 @@ @Table(name = "fish", schema = "sabi") @Entity @Data -@EqualsAndHashCode(exclude = "user") +@EqualsAndHashCode(exclude = "user",callSuper = false) public class FishEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedMotdEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedMotdEntity.java index a5cf00b6..d7b1f96e 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedMotdEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedMotdEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -7,6 +7,7 @@ import jakarta.persistence.*; import lombok.Data; +import lombok.EqualsAndHashCode; /** * Table which contains the translated message of today content. @@ -14,6 +15,7 @@ @Table(name = "localized_motd", schema = "sabi") @Entity @Data +@EqualsAndHashCode(callSuper=false) public class LocalizedMotdEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedParameterEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedParameterEntity.java index 9f527b81..dbf8f255 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedParameterEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedParameterEntity.java @@ -7,6 +7,7 @@ import jakarta.persistence.*; import lombok.Data; +import lombok.EqualsAndHashCode; /** * Table which contains the translated parameter descriptions. @@ -14,6 +15,7 @@ @Table(name = "localized_parameter", schema = "sabi") @Entity @Data +@EqualsAndHashCode(callSuper=false) public class LocalizedParameterEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedPlagueEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedPlagueEntity.java index 3e24a6a9..e6bfc2f5 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedPlagueEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedPlagueEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -7,6 +7,7 @@ import jakarta.persistence.*; import lombok.Data; +import lombok.EqualsAndHashCode; /** * Table which contains the translated plague names. @@ -14,6 +15,7 @@ @Table(name = "localized_plague", schema = "sabi") @Entity @Data +@EqualsAndHashCode(callSuper=false) public class LocalizedPlagueEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedPlagueStatusEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedPlagueStatusEntity.java index 3cb1f8ee..bdadccbc 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedPlagueStatusEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedPlagueStatusEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -7,6 +7,7 @@ import jakarta.persistence.*; import lombok.Data; +import lombok.EqualsAndHashCode; /** * Table which contains the translated plague status. @@ -14,6 +15,7 @@ @Table(name = "localized_plague_status", schema = "sabi") @Entity @Data +@EqualsAndHashCode(callSuper=false) public class LocalizedPlagueStatusEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedUnitEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedUnitEntity.java index dc727cf2..bef0e2ce 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedUnitEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/LocalizedUnitEntity.java @@ -7,6 +7,7 @@ import jakarta.persistence.*; import lombok.Data; +import lombok.EqualsAndHashCode; /** * Table which contains the translated parameter descriptions. @@ -14,6 +15,7 @@ @Table(name = "localized_unit", schema = "sabi") @Entity @Data +@EqualsAndHashCode(callSuper=false) public class LocalizedUnitEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/ParameterEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/ParameterEntity.java index 30b96d4b..a335e940 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/ParameterEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/ParameterEntity.java @@ -15,25 +15,25 @@ @Table(name = "parameter", schema = "sabi") @Entity @Data -@EqualsAndHashCode(exclude = "localizedParameterEntities") +@EqualsAndHashCode(exclude = "localizedParameterEntities",callSuper = false) public class ParameterEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ - @GeneratedValue(strategy = GenerationType.IDENTITY) @Id - @jakarta.persistence.Column(name = "id", nullable = false, insertable = true, updatable = true, length = 10, precision = 0) + @GeneratedValue(strategy = GenerationType.IDENTITY) @Basic + @Column(name = "id", nullable = false, insertable = true, updatable = true, length = 10, precision = 0) private Integer id; - @jakarta.persistence.Column(name = "belonging_unit_id", nullable = false, insertable = true, updatable = true, length = 10, precision = 0) + @Column(name = "belonging_unit_id", nullable = false, insertable = true, updatable = true, length = 10, precision = 0) @Basic private int belongingUnitId; - @jakarta.persistence.Column(name = "min_threshold", nullable = true, insertable = true, updatable = true, length = 12, precision = 0) + @Column(name = "min_threshold", nullable = true, insertable = true, updatable = true, length = 12, precision = 0) @Basic private Float minThreshold; - @jakarta.persistence.Column(name = "max_threshold", nullable = true, insertable = true, updatable = true, length = 12, precision = 0) + @Column(name = "max_threshold", nullable = true, insertable = true, updatable = true, length = 12, precision = 0) @Basic private Float maxThreshold; diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/PlagueStatusEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/PlagueStatusEntity.java index 0b6ef4ca..2219cf07 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/PlagueStatusEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/PlagueStatusEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -18,7 +18,7 @@ @Table(name = "plague_status", schema = "sabi") @Entity @Data -@EqualsAndHashCode(exclude = "localizedPlagueStatusEntities") +@EqualsAndHashCode(exclude = "localizedPlagueStatusEntities",callSuper = false) public class PlagueStatusEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/RemedyEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/RemedyEntity.java index c2da9cd7..038338d7 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/RemedyEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/RemedyEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -7,6 +7,7 @@ import jakarta.persistence.*; import lombok.Data; +import lombok.EqualsAndHashCode; /** * @@ -16,6 +17,7 @@ @Table(name = "remedy", schema = "sabi") @Entity @Data +@EqualsAndHashCode(callSuper=false) public class RemedyEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/TreatmentEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/TreatmentEntity.java index 883b43f7..089700eb 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/TreatmentEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/TreatmentEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -14,7 +14,7 @@ @Table(name = "treatment", schema = "sabi") @Entity @Data -@EqualsAndHashCode(exclude = "user") +@EqualsAndHashCode(exclude = "user",callSuper = false) public class TreatmentEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/UnitEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/UnitEntity.java index 72bd015d..ea19fc8b 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/UnitEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/UnitEntity.java @@ -7,6 +7,10 @@ import jakarta.persistence.*; import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.ArrayList; +import java.util.List; import java.util.ArrayList; import java.util.List; @@ -14,6 +18,7 @@ @Table(name = "unit", schema = "sabi") @Entity @Data +@EqualsAndHashCode(callSuper=false) public class UnitEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/UserMeasurementReminderEntity.java b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/UserMeasurementReminderEntity.java index 3bc42f37..cfbe7e7e 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/UserMeasurementReminderEntity.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/persistence/model/UserMeasurementReminderEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -13,7 +13,7 @@ @Table(name = "user_measurement_reminder", schema = "sabi") @Entity @Data -@EqualsAndHashCode(exclude = {"user"}) +@EqualsAndHashCode(exclude = {"user"},callSuper = false) public class UserMeasurementReminderEntity extends Auditable { // ------------------------------ FIELDS ------------------------------ diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/services/MeasurementServiceImpl.java b/sabi-server/src/main/java/de/bluewhale/sabi/services/MeasurementServiceImpl.java index 3264d580..661eabb4 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/services/MeasurementServiceImpl.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/services/MeasurementServiceImpl.java @@ -132,7 +132,6 @@ public ResultTo removeMeasurement(Long pMeasurementID, String pUs Message resultMsg; MeasurementTo usersMeasurementTo = new MeasurementTo(); - UserEntity user = userRepository.getByEmail(pUserEmail); if (user == null) { resultMsg = Message.error(TankMessageCodes.UNKNOWN_USER, pUserEmail); @@ -230,7 +229,7 @@ public ParameterTo fetchParameterInfoFor(Integer pUnitID, String pUsersLanguage) ParameterEntity parameterEntity = parameterRepository.findByBelongingUnitIdEquals(pUnitID); if (parameterEntity == null) { - log.warn("Requested parameter info for unitID «{}» is not availabe. Data maintenance recommended.", pUnitID); + log.warn("Requested parameter info for unitID «{}» is not available. Data maintenance recommended.", pUnitID); return null; } diff --git a/sabi-server/src/main/java/de/bluewhale/sabi/services/TankServiceImpl.java b/sabi-server/src/main/java/de/bluewhale/sabi/services/TankServiceImpl.java index 3c2e0701..6aa968a7 100644 --- a/sabi-server/src/main/java/de/bluewhale/sabi/services/TankServiceImpl.java +++ b/sabi-server/src/main/java/de/bluewhale/sabi/services/TankServiceImpl.java @@ -93,7 +93,7 @@ public List listTanks(final String pUserEmail) { UserEntity user = userRepository.getByEmail(pUserEmail); if (user == null) { - return Collections.emptyList(); + return Collections.emptyList(); } @NotNull List usersAquariums = aquariumRepository.findAllByUser_IdIs(user.getId()); @@ -104,21 +104,6 @@ public List listTanks(final String pUserEmail) { aquariumTos.add(aquariumTo); } - /* - // todo update: old comment before refactoring to repositories, needs to be rechecked - Bidirectional side does not work. Wrong JPA Setup? - - UserEntity userEntity = userDao.find(pUserId); - if (userEntity != null) { - List aquariumEntities = userEntity.getAquariums(); - for (AquariumEntity aquariumEntity : aquariumEntities) { - AquariumTo aquariumTo = new AquariumTo(); - mapAquariumEntity2To(aquariumEntity,aquariumTo); - tankList.add(aquariumTo); - } - } - - */ return aquariumTos; } @@ -148,7 +133,7 @@ public ResultTo updateTank(AquariumTo updatedAquariumTo, String pUse message = Message.error(TankMessageCodes.NOT_YOUR_TANK, updatedAquariumTo.getDescription()); } - ResultTo aquariumToResultTo = new ResultTo<>(updatedAquariumTo, message) ; + ResultTo aquariumToResultTo = new ResultTo<>(updatedAquariumTo, message); return aquariumToResultTo; } @@ -173,7 +158,7 @@ public AquariumTo getTank(Long aquariumId, String pUsersEmail) { public ResultTo removeTank(Long persistedTankId, String pUsersEmail) { ResultTo resultTo; UserEntity user = userRepository.getByEmail(pUsersEmail); - if (user !=null) { + if (user != null) { AquariumEntity usersAquarium = aquariumRepository.getAquariumEntityByIdAndUser_IdIs(persistedTankId, user.getId()); AquariumTo aquariumTo = new AquariumTo(); aquariumTo.setId(persistedTankId); @@ -186,7 +171,7 @@ public ResultTo removeTank(Long persistedTankId, String pUsersEmail) resultTo = new ResultTo<>(aquariumTo, Message.error(TankMessageCodes.NOT_YOUR_TANK)); } } else { - resultTo = new ResultTo<>(new AquariumTo(), Message.error(TankMessageCodes.UNKNOWN_USER)); + resultTo = new ResultTo<>(new AquariumTo(), Message.error(TankMessageCodes.UNKNOWN_USER)); } return resultTo; } @@ -219,7 +204,7 @@ public ResultTo generateAndAssignNewTemperatureApiKey(Long persisted if (usersTankEntity != null) { String apiKey = generateNewApiKey(); - // Ensure key is unique - and not already assigned + // Ensure key is unique - and not already assigned. It will generate a new one until it's unique while (aquariumRepository.getAquariumEntityByTemperatureApiKeyEquals(apiKey) != null) { apiKey = generateNewApiKey(); } diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/ArchitectureTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/ArchitectureTest.java index efa09b30..b09689aa 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/ArchitectureTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/ArchitectureTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -13,6 +13,7 @@ import com.tngtech.archunit.lang.ConditionEvents; import com.tngtech.archunit.lang.SimpleConditionEvent; import com.tngtech.archunit.library.Architectures; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -24,6 +25,7 @@ * @author Stefan Schubert */ @ExtendWith(SpringExtension.class) +@Tag("DeveloperTest") public class ArchitectureTest { private static final String PACKAGE_PREFIX = "de.bluewhale.sabi."; diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/BasicDataFactory.java b/sabi-server/src/test/java/de/bluewhale/sabi/BasicDataFactory.java deleted file mode 100644 index e6312bc0..00000000 --- a/sabi-server/src/test/java/de/bluewhale/sabi/BasicDataFactory.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). - * See project LICENSE file for the detailed terms and conditions. - */ - -package de.bluewhale.sabi; - -import de.bluewhale.sabi.model.SizeUnit; -import de.bluewhale.sabi.model.WaterType; -import de.bluewhale.sabi.persistence.model.*; -import de.bluewhale.sabi.persistence.repositories.*; -import org.junit.jupiter.api.BeforeEach; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jdbc.core.JdbcTemplate; - -import java.time.LocalDateTime; -import java.util.List; - -/** - * Derive your tests from this class to inject required minimum required basic data into H2. - * - * @author Stefan Schubert - */ -public class BasicDataFactory { - - @Autowired - UserRepository userRepository; - - @Autowired - ParameterRepository parameterRepository; - - @Autowired - UnitRepository unitRepository; - - @Autowired - LocalizedUnitRepository localizedUnitRepository; - - @Autowired - RemedyRepository remedyRepository; - - @Autowired - FishCatalogueRepository fishCatalogueRepository; - - @Autowired - AquariumRepository aquariumRepository; - - @Autowired - MeasurementRepository measurementRepository; - - @Autowired - private JdbcTemplate jdbcTemplate; - - private static boolean populatedBasicData = false; - - // pre existing test user - protected static final String P_USER1_EMAIL = "sabi@bluewhale.de"; - protected static final String P_USER2_EMAIL = "sabi_II@bluewhale.de"; - - @BeforeEach - public void initOnlyOnce() { - - if (populatedBasicData) return; - populatedBasicData = true; - - populateBasicData(); - - } - - public void populateBasicData() { - UserEntity testuser1 = new UserEntity(); - testuser1.setId(1l); // Don't rely on this ID it is replaced through H2 sequence on persistance when cerating a new entity - testuser1.setEmail(P_USER1_EMAIL); - testuser1.setPassword("098f6bcd4621d373cade4e832627b4f6"); - testuser1.setUsername("stefan"); - testuser1.setValidateToken("NO_IDEA"); - testuser1.setValidated(true); - testuser1.setLanguage("de"); - testuser1.setCountry("DE"); - - UserEntity testuser2 = new UserEntity(); - testuser2.setId(2l); // Don't rely on this ID it is replaced through H2 sequence on persistance when cerating a new entity - testuser2.setEmail(P_USER2_EMAIL); - testuser2.setPassword("098f6bcd4621d373cade4e832627b4f6"); - testuser2.setUsername("steven"); - testuser2.setValidateToken("NO_IDEA"); - testuser2.setValidated(true); - testuser2.setLanguage("en"); - testuser2.setCountry("US"); - - userRepository.saveAndFlush(testuser1); - userRepository.saveAndFlush(testuser2); - - UnitEntity unitEntity = new UnitEntity(); - unitEntity.setName("KH"); - unitEntity.setId(1); - - UnitEntity unitEntity2 = new UnitEntity(); - unitEntity2.setName("°C"); - unitEntity2.setId(2); - - UnitEntity savedUnitEntity = unitRepository.saveAndFlush(unitEntity); - - LocalizedUnitEntity localizedUnitEntity = new LocalizedUnitEntity(); - localizedUnitEntity.setDescription("Karbonathärte / Alkanität"); - localizedUnitEntity.setUnitId(savedUnitEntity.getId()); - localizedUnitRepository.saveAndFlush(localizedUnitEntity); - unitEntity.setLocalizedUnitEntities(List.of(localizedUnitEntity)); - - UnitEntity savedUnitEntity2 = unitRepository.saveAndFlush(unitEntity2); - - LocalizedUnitEntity localizedUnitEntity2 = new LocalizedUnitEntity(); - localizedUnitEntity2.setDescription("Grad Celsius"); - localizedUnitEntity2.setUnitId(savedUnitEntity2.getId()); - localizedUnitRepository.saveAndFlush(localizedUnitEntity2); - unitEntity2.setLocalizedUnitEntities(List.of(localizedUnitEntity2)); - - ParameterEntity parameterEntity = new ParameterEntity(); - parameterEntity.setBelongingUnitId(1); - parameterEntity.setMinThreshold(6.5f); - parameterEntity.setMaxThreshold(10f); - - ParameterEntity parameterEntity2 = new ParameterEntity(); - parameterEntity2.setBelongingUnitId(2); - parameterEntity2.setMinThreshold(24f); - parameterEntity2.setMaxThreshold(27f); - - ParameterEntity savedParameterEntity1 = parameterRepository.saveAndFlush(parameterEntity); - LocalizedParameterEntity localizedParameterEntity = new LocalizedParameterEntity(); - localizedParameterEntity.setDescription("Karbonathärte"); - localizedParameterEntity.setLanguage("de"); - localizedParameterEntity.setParameter_id(savedParameterEntity1.getId()); - savedParameterEntity1.setLocalizedParameterEntities(List.of(localizedParameterEntity)); - - ParameterEntity savedParameterEntity2 = parameterRepository.saveAndFlush(parameterEntity2); - LocalizedParameterEntity localizedParameterEntity2 = new LocalizedParameterEntity(); - localizedParameterEntity2.setDescription("Temperatur"); - localizedParameterEntity2.setLanguage("de"); - localizedParameterEntity2.setParameter_id(savedParameterEntity2.getId()); - savedParameterEntity2.setLocalizedParameterEntities(List.of(localizedParameterEntity2)); - - RemedyEntity remedyEntity = new RemedyEntity(); - remedyEntity.setProductname("KH+"); - remedyEntity.setVendor("Dupla"); - - remedyRepository.saveAndFlush(remedyEntity); - - FishCatalogueEntity fishCatalogueEntity = new FishCatalogueEntity(); - fishCatalogueEntity.setScientificName("Acreichthys tomentosus"); - fishCatalogueEntity.setDescription("Seegras Feilenfisch"); - fishCatalogueEntity.setMeerwasserwikiUrl("http://meerwasserwiki.de/w/index.php?title=Acreichthys_tomentosus"); - - fishCatalogueRepository.saveAndFlush(fishCatalogueEntity); - - AquariumEntity aquariumEntity = new AquariumEntity(); - aquariumEntity.setId(1l); - aquariumEntity.setSize(80); - aquariumEntity.setSizeUnit(SizeUnit.LITER); - aquariumEntity.setWaterType(WaterType.SEA_WATER); - aquariumEntity.setDescription("Nano-Reef_H2"); - aquariumEntity.setActive(Boolean.TRUE); - aquariumEntity.setUser(testuser1); - - AquariumEntity aquariumEntity2 = new AquariumEntity(); - aquariumEntity2.setId(2l); - aquariumEntity2.setSize(200); - aquariumEntity2.setSizeUnit(SizeUnit.LITER); - aquariumEntity2.setWaterType(WaterType.SEA_WATER); - aquariumEntity2.setDescription("Freshwater_H2"); - aquariumEntity2.setActive(Boolean.TRUE); - aquariumEntity2.setUser(testuser1); - - AquariumEntity aquariumEntity3 = new AquariumEntity(); - aquariumEntity3.setId(3l); - aquariumEntity3.setSize(150); - aquariumEntity3.setSizeUnit(SizeUnit.GALLONS); - aquariumEntity3.setWaterType(WaterType.SEA_WATER); - aquariumEntity3.setDescription("Exhibit"); - aquariumEntity3.setActive(Boolean.TRUE); - aquariumEntity3.setUser(testuser2); - - aquariumRepository.saveAndFlush(aquariumEntity); - aquariumRepository.saveAndFlush(aquariumEntity2); - aquariumRepository.saveAndFlush(aquariumEntity3); - - MeasurementEntity measurementEntity = new MeasurementEntity(); - measurementEntity.setId(1L); - measurementEntity.setMeasuredOn(LocalDateTime.now()); - measurementEntity.setMeasuredValue(27); - measurementEntity.setUnitId(unitEntity2.getId()); - measurementEntity.setAquarium(aquariumEntity); - measurementEntity.setUser(testuser1); - - MeasurementEntity measurementEntity2 = new MeasurementEntity(); - measurementEntity2.setId(2L); - measurementEntity2.setMeasuredOn(LocalDateTime.now()); - measurementEntity2.setMeasuredValue(20.5f); - measurementEntity2.setUnitId(unitEntity2.getId()); - measurementEntity2.setAquarium(aquariumEntity); - measurementEntity2.setUser(testuser1); - - MeasurementEntity measurementEntity3 = new MeasurementEntity(); - measurementEntity3.setId(3L); - measurementEntity3.setMeasuredOn(LocalDateTime.now()); - measurementEntity3.setMeasuredValue(0.892f); - measurementEntity3.setUnitId(unitEntity.getId()); - measurementEntity3.setAquarium(aquariumEntity2); - measurementEntity3.setUser(testuser1); - - measurementRepository.saveAndFlush(measurementEntity); - measurementRepository.saveAndFlush(measurementEntity2); - measurementRepository.saveAndFlush(measurementEntity3); - } - -// @After -// public void tearDown() Ø { -// JdbcTestUtils.deleteFromTables(jdbcTemplate, "measurement", "fish", -// "fish_catalogue", "remedy", "parameter", "aquarium", "unit", "users"); -// } - - -} diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/DeveloperTestSuite.java b/sabi-server/src/test/java/de/bluewhale/sabi/DeveloperTestSuite.java new file mode 100644 index 00000000..fa1484b0 --- /dev/null +++ b/sabi-server/src/test/java/de/bluewhale/sabi/DeveloperTestSuite.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). + * See project LICENSE file for the detailed terms and conditions. + */ + +package de.bluewhale.sabi; + +import org.junit.platform.suite.api.IncludeTags; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectPackages({"de.bluewhale.sabi", + "de.bluewhale.sabi.persistence", + "de.bluewhale.sabi.rest.controller", + "de.bluewhale.sabi.services", + "de.bluewhale.sabi.security"}) +@IncludeTags("DeveloperTest") +public class DeveloperTestSuite { + // Diese Klasse bleibt leer. Sie dient nur als Halter für die obigen Annotationen +} diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/IntegrationTestSuite.java b/sabi-server/src/test/java/de/bluewhale/sabi/IntegrationTestSuite.java new file mode 100644 index 00000000..d19d6950 --- /dev/null +++ b/sabi-server/src/test/java/de/bluewhale/sabi/IntegrationTestSuite.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). + * See project LICENSE file for the detailed terms and conditions. + */ + +package de.bluewhale.sabi; + +import org.junit.platform.suite.api.IncludeTags; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectPackages({"de.bluewhale.sabi.persistence","de.bluewhale.sabi.rest.controller"}) +@IncludeTags("IntegrationTest") +public class IntegrationTestSuite { + // Diese Klasse bleibt leer. Sie dient nur als Halter für die obigen Annotationen +} diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/ModuleTestSuite.java b/sabi-server/src/test/java/de/bluewhale/sabi/ModuleTestSuite.java new file mode 100644 index 00000000..5608f316 --- /dev/null +++ b/sabi-server/src/test/java/de/bluewhale/sabi/ModuleTestSuite.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). + * See project LICENSE file for the detailed terms and conditions. + */ + +package de.bluewhale.sabi; + +import org.junit.platform.suite.api.IncludeTags; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectPackages("de.bluewhale.sabi.rest.controller") +@IncludeTags("ModuleTest") +public class ModuleTestSuite { + // Diese Klasse bleibt leer. Sie dient nur als Halter für die obigen Annotationen +} diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/ServiceTestSuite.java b/sabi-server/src/test/java/de/bluewhale/sabi/ServiceTestSuite.java new file mode 100644 index 00000000..d7870a6e --- /dev/null +++ b/sabi-server/src/test/java/de/bluewhale/sabi/ServiceTestSuite.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). + * See project LICENSE file for the detailed terms and conditions. + */ + +package de.bluewhale.sabi; + +import org.junit.platform.suite.api.IncludeTags; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectPackages("de.bluewhale.sabi.services") +@IncludeTags("ServiceTest") +public class ServiceTestSuite { + // Diese Klasse bleibt leer. Sie dient nur als Halter für die obigen Annotationen +} diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/TestDataFactory.java b/sabi-server/src/test/java/de/bluewhale/sabi/TestDataFactory.java deleted file mode 100644 index 1c9bb7a7..00000000 --- a/sabi-server/src/test/java/de/bluewhale/sabi/TestDataFactory.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). - * See project LICENSE file for the detailed terms and conditions. - */ - -package de.bluewhale.sabi; - -import de.bluewhale.sabi.model.*; -import de.bluewhale.sabi.services.PlagueCenterService; -import de.bluewhale.sabi.services.TankService; -import de.bluewhale.sabi.services.UserService; - -import java.time.LocalDateTime; -import java.util.Locale; - -/** - * Small Util class which provide common test data. - * - * @author Stefan Schubert - */ -public class TestDataFactory { -// ------------------------------ FIELDS ------------------------------ - - public static final String TESTUSER_EMAIL1 = "testservice1@bluewhale.de"; - public static final String TESTUSER_EMAIL2 = "testservice2@bluewhale.de"; - - public static final String INVALID_PASSWORD = "quertz!1"; - public static final String VALID_PASSWORD = "All!Rules8-)Applied"; - - private static TestDataFactory instance; - - private TankService tankService; - private UserService userService; - - private PlagueCenterService plagueCenterService; - -// -------------------------- STATIC METHODS -------------------------- - - protected TestDataFactory() { - // defeat instantiation from outside - } - -// --------------------------- CONSTRUCTORS --------------------------- - - public static TestDataFactory getInstance() { - if (instance == null) { - instance = new TestDataFactory(); - } - return instance; - } - -// -------------------------- OTHER METHODS -------------------------- - - public AquariumTo getTestAquariumTo() { - final AquariumTo aquariumTo = new AquariumTo(); - aquariumTo.setDescription("Test Tank"); - aquariumTo.setActive(Boolean.TRUE); - aquariumTo.setSize(40); - aquariumTo.setSizeUnit(SizeUnit.LITER); - aquariumTo.setWaterType(WaterType.SEA_WATER); - return aquariumTo; - } - - public AquariumTo getTestAquariumFor(UserTo userTo) { - AquariumTo aquariumTo = getTestAquariumTo(); - aquariumTo.setId(1L); - aquariumTo.setUserId(userTo.getId()); - aquariumTo.setWaterType(WaterType.SEA_WATER); - return aquariumTo; - } - - public UserTo getRegisterNewTestUser(String eMail) { - String clearTextPassword = VALID_PASSWORD; - final NewRegistrationTO userTo = new NewRegistrationTO(eMail, eMail, clearTextPassword); - final ResultTo userToResultTo = userService.registerNewUser(userTo); - return userToResultTo.getValue(); - } - - public UserProfileTo getBasicUserProfileTo() { - return new UserProfileTo(Locale.ENGLISH.getLanguage(), Locale.UK.getCountry()); - } - - public UserProfileTo getUserProfileToWithMeasurementReminderFor(UserTo userTo) { - UserProfileTo userProfileTo = getBasicUserProfileTo(); - MeasurementReminderTo reminderTo = new MeasurementReminderTo(); - reminderTo.setUserId(userTo.getId().intValue()); - reminderTo.setPastDays(12); - reminderTo.setUnitId(1); - userProfileTo.getMeasurementReminderTos().add(reminderTo); - - return userProfileTo; - } - - - /** - * Links preexisting testdata for aquarium (user) and unit. - * - * @param pTankID - * @return - */ - public MeasurementTo getTestMeasurementTo(Long pTankID) { - final MeasurementTo measurementTo = new MeasurementTo(); - measurementTo.setAquariumId(pTankID); - measurementTo.setUnitId(1); - measurementTo.setId(4711l); - measurementTo.setMeasuredValue(1.15f); - measurementTo.setMeasuredOn(LocalDateTime.now()); - return measurementTo; - } - - public TestDataFactory withTankService(TankService service) { - tankService = service; - return this; - } - - public TestDataFactory withUserService(UserService service) { - userService = service; - return this; - } - - public TestDataFactory withPlagueCenterService(PlagueCenterService service) { - plagueCenterService = service; - return this; - } - - public ParameterTo getTestParameterTo() { - ParameterTo parameterTo = new ParameterTo(); - parameterTo.setBelongingUnitId(4711); - parameterTo.setMaxThreshold(10f); - parameterTo.setMinThreshold(20f); - parameterTo.setId(101); - parameterTo.setDescription("Junit"); - return parameterTo; - } - - public UnitTo getTestUnitTo() { - UnitTo unitTo = new UnitTo(); - unitTo.setId(4711); - unitTo.setUnitSign("SP"); - unitTo.setDescription("Scrum Story Points"); - return unitTo; - } -} diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/persistence/MeasurementRepositoryTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/persistence/MeasurementRepositoryTest.java index 08cc1841..83a0534d 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/persistence/MeasurementRepositoryTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/persistence/MeasurementRepositoryTest.java @@ -1,12 +1,10 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ package de.bluewhale.sabi.persistence; -import de.bluewhale.sabi.BasicDataFactory; -import de.bluewhale.sabi.TestDataFactory; import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.mapper.MeasurementMapper; import de.bluewhale.sabi.model.MeasurementTo; @@ -16,20 +14,28 @@ import de.bluewhale.sabi.persistence.repositories.AquariumRepository; import de.bluewhale.sabi.persistence.repositories.MeasurementRepository; import de.bluewhale.sabi.persistence.repositories.UserRepository; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import de.bluewhale.sabi.util.TestContainerVersions; +import de.bluewhale.sabi.util.TestDataFactory; +import org.flywaydb.core.Flyway; +import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ContextConfiguration; import org.springframework.transaction.annotation.Transactional; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.springframework.test.util.AssertionErrors.assertNotNull; import static org.springframework.test.util.AssertionErrors.assertTrue; @@ -41,13 +47,26 @@ * Date: 14.11.2015 */ @SpringBootTest +@Testcontainers @ContextConfiguration(classes = AppConfig.class) -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) -// @DataJpaTest todo does not work yet missing visible constructor in JPAConfig class - maybe not compatible with eclipse way? -public class MeasurementRepositoryTest extends BasicDataFactory { +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Tag("IntegrationTest") +@Transactional +@DirtiesContext +// DirtiesContext: Spring context is refreshed after the test class is executed, +// which includes reinitializing the HikariCP datasource (which is defined at spring level, while the testcontainer is not) +public class MeasurementRepositoryTest implements TestContainerVersions { static TestDataFactory testDataFactory; + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + + @Autowired + private Flyway flyway; + @Autowired MeasurementRepository measurementRepository; @@ -65,28 +84,32 @@ public static void initTestDataFactory() { testDataFactory = TestDataFactory.getInstance(); } - /** - * There seems to be a timing problem with H2, that causes, that the basic data is not available - * for some test classes, while for others it worked out. Until we know what's going wrong... - * we "double inject" by extending the BasicTestDataFactory and by calling it directly. - * The different behaviour can be observed by e.g. calling the master test suite and as comparising - * the measurement testsuite while this is method is deaktivated. - */ + @AfterAll + static void cleanup() { + mariaDBContainer.stop(); + } + @BeforeEach - public void ensureBasicDataAvailability() { + public void setUp() { + + // flyway.clean(); // Optional: Clean DB before each single test + // org.flywaydb.core.api.FlywayException: Unable to execute clean as it has been disabled with the 'flyway.cleanDisabled' property. + flyway.migrate(); + + } - UserEntity byEmail = userRepository.getByEmail(P_USER1_EMAIL); - if (byEmail == null) populateBasicData(); - UserEntity byEmail2 = userRepository.getByEmail(P_USER1_EMAIL); - assertNotNull("H2-Basicdata injection did not work!" ,byEmail2); + @Test + void connectionEstablished(){ + assertThat(mariaDBContainer.isCreated()); + assertThat(mariaDBContainer.isRunning()); } @Test - @Transactional + @Rollback public void testCreateMeasurement() throws Exception { // given a test measurement (linked aquarium already exists in database. - MeasurementTo measurementTo = testDataFactory.getTestMeasurementTo(1L); + MeasurementTo measurementTo = testDataFactory.getTestMeasurementTo(null); AquariumEntity aquariumEntity = aquariumRepository.getOne(measurementTo.getAquariumId()); MeasurementEntity measurementEntity = measurementMapper.mapMeasurementTo2EntityWithoutAquarium(measurementTo); @@ -101,15 +124,15 @@ public void testCreateMeasurement() throws Exception { MeasurementEntity foundMeasurementEntity = measurementRepository.findById(createdMeasurementEntity.getId()).get(); assertEquals(createdMeasurementEntity.getAquarium(), foundMeasurementEntity.getAquarium()); - assertEquals(createdMeasurementEntity.getAquarium().getId(), measurementTo.getAquariumId()); + assertEquals(createdMeasurementEntity.getAquarium().getId(), aquariumEntity.getId()); } @Test public void testFetchStoredTestUsersMeasurements() throws Exception { - // given some stored testdata for userID 1 - UserEntity userEntity = userRepository.getOne(1L); + // given some stored testdata for prestored user (via flyway) + UserEntity userEntity = userRepository.getByEmail("sabi@bluewhale.de"); // when List usersMeasurements = measurementRepository.findByUserOrderByMeasuredOnDesc(userEntity); // then @@ -120,7 +143,7 @@ public void testFetchStoredTestUsersMeasurements() throws Exception { public void testFetchStoredTestUsersMeasurementsWithResultLimit() throws Exception { // given some stored testdata for userID 1 (has two test measurements) - UserEntity userEntity = userRepository.getOne(1L); + UserEntity userEntity = userRepository.getByEmail("sabi@bluewhale.de"); // when Pageable page = PageRequest.of(0, 2, Sort.by(Sort.Direction.DESC, "measuredOn")); List usersMeasurements = measurementRepository.findByUserOrderByMeasuredOnDesc(userEntity,page); @@ -130,16 +153,22 @@ public void testFetchStoredTestUsersMeasurementsWithResultLimit() throws Excepti @Test + @Rollback public void testGetConcreteMeasurement() throws Exception { - // Given some prestored testdata for userID 1 - Long testAquariumId = 1L; - Long testUserId = 1L; - Long testMeasurementId = 1L; - UserEntity userEntity = userRepository.getOne(testUserId); + + // Given some pre-stored testdata for userID 1 + MeasurementTo measurementTo = testDataFactory.getTestMeasurementTo(null); + AquariumEntity aquariumEntity = aquariumRepository.getOne(measurementTo.getAquariumId()); + MeasurementEntity measurementEntity = measurementMapper.mapMeasurementTo2EntityWithoutAquarium(measurementTo); + measurementEntity.setAquarium(aquariumEntity); + measurementEntity.setUser(aquariumEntity.getUser()); + MeasurementEntity createdMeasurementEntity = measurementRepository.saveAndFlush(measurementEntity); + UserEntity userEntity = aquariumEntity.getUser(); + // When - MeasurementEntity measurement = measurementRepository.getByIdAndUser(testMeasurementId, userEntity); + MeasurementEntity measurement = measurementRepository.getByIdAndUser(createdMeasurementEntity.getId(), userEntity); // Then - assertTrue("Missing testdata for user 1L", measurement.getAquarium().getId()==testAquariumId); + assertTrue("Broken finder?", measurement.getAquarium().getId()==aquariumEntity.getId()); } diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/persistence/TankRepositoryTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/persistence/TankRepositoryTest.java index f598226c..0d3fbd0e 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/persistence/TankRepositoryTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/persistence/TankRepositoryTest.java @@ -1,28 +1,36 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ package de.bluewhale.sabi.persistence; -import de.bluewhale.sabi.BasicDataFactory; -import de.bluewhale.sabi.TestDataFactory; import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.persistence.model.AquariumEntity; import de.bluewhale.sabi.persistence.model.UserEntity; import de.bluewhale.sabi.persistence.repositories.AquariumRepository; import de.bluewhale.sabi.persistence.repositories.UserRepository; -import org.junit.jupiter.api.BeforeAll; +import de.bluewhale.sabi.util.TestContainerVersions; +import org.flywaydb.core.Flyway; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; +import org.springframework.transaction.annotation.Transactional; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import java.util.List; -import static org.springframework.test.util.AssertionErrors.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.util.AssertionErrors.assertTrue; /** @@ -31,12 +39,23 @@ * Date: 3.3.2021 */ @SpringBootTest +@Testcontainers @ContextConfiguration(classes = AppConfig.class) -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) -// @DataJpaTest todo does not work yet missing visible constructor in JPAConfig class - maybe not compatible with eclipse way? -public class TankRepositoryTest extends BasicDataFactory { +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Tag("IntegrationTest") +@Transactional +@DirtiesContext +// DirtiesContext: Spring context is refreshed after the test class is executed, +// which includes reinitializing the HikariCP datasource (which is defined at spring level, while the testcontainer is not) +public class TankRepositoryTest implements TestContainerVersions { + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); - static TestDataFactory testDataFactory; + @Autowired + private Flyway flyway; @Autowired AquariumRepository aquariumRepository; @@ -44,45 +63,40 @@ public class TankRepositoryTest extends BasicDataFactory { @Autowired UserRepository userRepository; - @BeforeAll - public static void initTestDataFactory() { - testDataFactory = TestDataFactory.getInstance(); - } - /** - * There seems to be a timing problem with H2, that causes, that the basic data is not available - * for some test classes, while for others it worked out. Until we know what's going wrong... - * we "double inject" by extending the BasicTestDataFactory and by calling it directly. - * The different behaviour can be observed by e.g. calling the master test suite and as comparising - * the measurement testsuite while this is method is deaktivated. - */ @BeforeEach - public void ensureBasicDataAvailability() { + public void setUp() { + + // flyway.clean(); // Optional: Clean DB before each single test + // org.flywaydb.core.api.FlywayException: Unable to execute clean as it has been disabled with the 'flyway.cleanDisabled' property. + flyway.migrate(); + + } + + @AfterAll + static void cleanup() { + mariaDBContainer.stop(); + } - UserEntity byEmail = userRepository.getByEmail(P_USER1_EMAIL); - if (byEmail == null) populateBasicData(); - UserEntity byEmail2 = userRepository.getByEmail(P_USER1_EMAIL); - assertNotNull("H2-Basicdata injection did not work!" ,byEmail2); + @Test + void connectionEstablished(){ + assertThat(mariaDBContainer.isCreated()); + assertThat(mariaDBContainer.isRunning()); } @Test public void testFindAllTanksOfSpecificUserById() throws Exception { - // given through BasicDataFactory - // User 1 has 2 tanks - // User 2 has 1 tank - UserEntity user1 = userRepository.getOne(1L); - UserEntity user2 = userRepository.getOne(2L); + // given through Flyway i.g. basic test data + String testUser = "sabi@bluewhale.de"; + UserEntity storedUser = userRepository.getByEmail(testUser); // when - List tanksOfUser1 = aquariumRepository.findAllByUser_IdIs(user1.getId()); - List tanksOfUser2 = aquariumRepository.findAllByUser_IdIs(user2.getId()); + List tanksOfUser1 = aquariumRepository.findAllByUser_IdIs(storedUser.getId()); // then - assertTrue("User1 one should have excatly 2 tanks", tanksOfUser1.size() == 2); - assertTrue("User2 one should have excatly 1 tank", tanksOfUser2.size() == 1); - assertFalse("Delivered Tank from other user!?", tanksOfUser1.contains(tanksOfUser2)); + assertTrue("Predefined BasicUser one should have exactly 2 tanks", tanksOfUser1.size() == 2); } diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/persistence/TestContainerSampleTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/persistence/TestContainerSampleTest.java new file mode 100644 index 00000000..47221bd8 --- /dev/null +++ b/sabi-server/src/test/java/de/bluewhale/sabi/persistence/TestContainerSampleTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). + * See project LICENSE file for the detailed terms and conditions. + */ + +package de.bluewhale.sabi.persistence; + +import de.bluewhale.sabi.persistence.model.UserEntity; +import de.bluewhale.sabi.persistence.repositories.UserRepository; +import de.bluewhale.sabi.util.TestContainerVersions; +import org.flywaydb.core.Flyway; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.transaction.annotation.Transactional; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; + + +/** + * Just a minimalisitc Blueprint Setup to demontrate the usage + * of TestContainer in Repository Tests + * + * To work with Testcontainers I added the following maven dependencies + *
+ *        <dependency>
+ *             <groupId>org.springframework.boot</groupId>
+ *             <artifactId>spring-boot-testcontainers</artifactId>
+ *             <scope>test</scope>
+ *         </dependency>
+ *
+ *         <dependency>
+ *             <groupId>org.testcontainers</groupId>
+ *             <artifactId>junit-jupiter</artifactId>
+ *             <version>${junit.testcontainer.version}</version>
+ *             <scope>test</scope>
+ *         </dependency>
+ *
+ *         <dependency>
+ *             <groupId>org.testcontainers</groupId>
+ *             <artifactId>mariadb</artifactId>
+ *             <version>${mariadb.testcontainer.version}</version>
+ *             <scope>test</scope>
+ *         </dependency>
+ *
+ */ +@Testcontainers +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Tag("IntegrationTest") +@DirtiesContext +@Transactional +// DirtiesContext: Spring context is refreshed after the test class is executed, +// which includes reinitializing the HikariCP datasource (which is defined at spring level, while the testcontainer is not) +public class TestContainerSampleTest implements TestContainerVersions { + + @Container + @ServiceConnection // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + + @Autowired + private Flyway flyway; + + @Autowired + UserRepository userRepository; + + @BeforeEach + public void setUp() { + // flyway.clean(); // Optional: Clean DB before each single test + // org.flywaydb.core.api.FlywayException: Unable to execute clean as it has been disabled with the 'flyway.cleanDisabled' property. + flyway.migrate(); + } + + @AfterAll + static void cleanup() { + mariaDBContainer.stop(); + } + + @Test + void connectionEstablished(){ + assertThat(mariaDBContainer.isCreated()); + assertThat(mariaDBContainer.isRunning()); + } + + @Test + void hasStoredUsers() throws Exception { + List userEntityList = userRepository.findAll(); + assertFalse(userEntityList.isEmpty()); + } + +} diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/persistence/UserRepositoryTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/persistence/UserRepositoryTest.java index ae0acb38..28502872 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/persistence/UserRepositoryTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/persistence/UserRepositoryTest.java @@ -1,25 +1,35 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ package de.bluewhale.sabi.persistence; -import de.bluewhale.sabi.BasicDataFactory; import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.persistence.model.UserEntity; import de.bluewhale.sabi.persistence.repositories.UserRepository; +import de.bluewhale.sabi.util.TestContainerVersions; +import org.flywaydb.core.Flyway; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ContextConfiguration; import org.springframework.transaction.annotation.Transactional; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import java.time.LocalDateTime; import java.util.Locale; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.springframework.test.util.AssertionErrors.assertNotEquals; import static org.springframework.test.util.AssertionErrors.assertNotNull; @@ -31,40 +41,77 @@ * Date: 14.11.2015 */ @SpringBootTest +@Testcontainers @ContextConfiguration(classes = AppConfig.class) -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) -public class UserRepositoryTest extends BasicDataFactory { +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Tag("IntegrationTest") +@Transactional +@DirtiesContext +// DirtiesContext: Spring context is refreshed after the test class is executed, +// which includes reinitializing the HikariCP datasource (which is defined at spring level, while the testcontainer is not) +public class UserRepositoryTest implements TestContainerVersions { + + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + + @Autowired + private Flyway flyway; @Autowired UserRepository userRepository; - /** - * There seems to be a timing problem with H2, that causes, that the basic data is not available - * for some test classes, while for others it worked out. Until we know what's going wrong... - * we "double inject" by extending the BasicTestDataFactory and by calling it directly. - * The different behaviour can be observed by e.g. calling the master test suite and as comparising - * the measurement testsuite while this is method is deaktivated. - */ @BeforeEach - public void ensureBasicDataAvailability() { - UserEntity byEmail = userRepository.getByEmail(P_USER1_EMAIL); - if (byEmail == null) populateBasicData(); - UserEntity byEmail2 = userRepository.getByEmail(P_USER1_EMAIL); - assertNotNull("H2-Basicdata injection did not work!" ,byEmail2); + public void setUp() { + + // flyway.clean(); // Optional: Clean DB before each single test + // org.flywaydb.core.api.FlywayException: Unable to execute clean as it has been disabled with the 'flyway.cleanDisabled' property. + flyway.migrate(); + + } + + @AfterAll + static void cleanup() { + mariaDBContainer.stop(); } @Test - @Transactional - public void testProbeTracebleAttributeMappingsOnTestData() throws Exception { + void connectionEstablished(){ + assertThat(mariaDBContainer.isCreated()); + assertThat(mariaDBContainer.isRunning()); + } - UserEntity userEntity = userRepository.getByEmail("sabi@bluewhale.de"); + + @Test + @Rollback + public void testProbeTracebleAttributeMappingsHasBeenSet() throws Exception { + + String email = "P_USER1_EMAIL@bluewhale.de"; + + // Given + UserEntity testuser1 = new UserEntity(); + testuser1.setEmail(email); + testuser1.setPassword("098f6bcd4621d373cade4e832627b4f6"); + testuser1.setUsername("Tim"); + testuser1.setValidateToken("NO_IDEA"); + testuser1.setValidated(true); + testuser1.setLanguage("de"); + testuser1.setCountry("DE"); + userRepository.save(testuser1); + + // When + UserEntity userEntity = userRepository.getByEmail(email); + + // Then assertNotNull("Missing Default Testdata", userEntity); assertNotNull("Temporal Column CreatedOn not mapped.", userEntity.getCreatedOn()); assertNotNull("Temporal Column LastmodOn not mapped.", userEntity.getLastmodOn()); } @Test - @Transactional + @Rollback public void testCreateUser() throws Exception { // given @@ -88,7 +135,7 @@ public void testCreateUser() throws Exception { } @Test - @Transactional + @Rollback public void testModifierAttributesViaGenericDAO() throws Exception { // given diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/AquariumIoTControllerTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/AquariumIoTControllerTest.java index 0bbf4f9c..7376e3b1 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/AquariumIoTControllerTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/AquariumIoTControllerTest.java @@ -1,27 +1,36 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ package de.bluewhale.sabi.rest.controller; import com.fasterxml.jackson.databind.ObjectMapper; +import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.model.IoTMeasurementTo; import de.bluewhale.sabi.persistence.repositories.UserRepository; import de.bluewhale.sabi.services.TankService; import de.bluewhale.sabi.util.RestHelper; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; import org.springframework.web.client.ResourceAccessException; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; @@ -31,6 +40,10 @@ * @author Stefan Schubert */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Testcontainers +@ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Tag("ModuleTest") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class AquariumIoTControllerTest { @@ -38,6 +51,22 @@ public class AquariumIoTControllerTest { private static final String SECRET_ASSUMED_AS_VALID = "allowed api key"; private static final String SECRET_ASSUMED_AS_INVALID = "invalid api key"; + + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + + @Autowired ObjectMapper objectMapper; // json mapper diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/MeasurementControllerTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/MeasurementControllerTest.java similarity index 90% rename from sabi-server/src/test/java/de/bluewhale/sabi/services/rest/MeasurementControllerTest.java rename to sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/MeasurementControllerTest.java index 77f753c9..57348fb8 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/MeasurementControllerTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/MeasurementControllerTest.java @@ -1,12 +1,12 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ -package de.bluewhale.sabi.services.rest; +package de.bluewhale.sabi.rest.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import de.bluewhale.sabi.TestDataFactory; +import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.mapper.AquariumMapper; import de.bluewhale.sabi.mapper.MeasurementMapper; import de.bluewhale.sabi.mapper.UserMapper; @@ -21,19 +21,28 @@ import de.bluewhale.sabi.persistence.repositories.UserRepository; import de.bluewhale.sabi.security.TokenAuthenticationService; import de.bluewhale.sabi.util.RestHelper; +import de.bluewhale.sabi.util.TestDataFactory; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.http.*; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.MatcherAssert.assertThat; @@ -44,24 +53,35 @@ /** * Demonstrate usage of the measurement REST API. * NOTICE: This test mocks the DAO persistent layer, as it was not meant to run as an integration test. - *

- * However notice the following drawbacks: - *

- * (1) It still requires the database, as without it we get a java.lang.IllegalStateException: - * Failed to load ApplicationContext, though this might be fixed by proper test configuration - * (2) Lines of code! The mocked variant outweighs the implementation by far. Which slows down development progress. - * I leave it to demonstrate the effect. For those cases it would be much better to leave this as real integration - * tests (however against an H2 in memory database, or by manually control your test data). - * + * * @author Stefan Schubert */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Testcontainers +@ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Tag("ModuleTest") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class MeasurementControllerTest { // ------------------------------ FIELDS ------------------------------ final static String MOCKED_USER = "testsabi@bluewhale.de"; + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + + @MockBean UserRepository userRepository; @@ -103,7 +123,7 @@ public void testListUsersMeasurements() throws Exception { AquariumTo aquariumTo = testDataFactory.getTestAquariumFor(userTo); AquariumEntity aquariumEntity = aquariumMapper.mapAquariumTo2Entity(aquariumTo); - MeasurementTo measurementTo = testDataFactory.getTestMeasurementTo(aquariumTo.getId()); + MeasurementTo measurementTo = testDataFactory.getTestMeasurementTo(aquariumTo); MeasurementEntity measurementEntity = measurementMapper.mapMeasurementTo2EntityWithoutAquarium(measurementTo); measurementEntity.setAquarium(aquariumEntity); @@ -151,8 +171,8 @@ public void testListUsersTankMeasurements() throws Exception { given(this.aquariumRepository.getAquariumEntityByIdAndUser_IdIs(aquariumTo.getId(), userTo.getId())).willReturn(aquariumEntity); given(this.aquariumRepository.getOne(aquariumTo.getId())).willReturn(aquariumEntity); - MeasurementTo measurementTo = testDataFactory.getTestMeasurementTo(aquariumTo.getId()); - MeasurementTo measurementTo2 = testDataFactory.getTestMeasurementTo(aquariumTo.getId()); + MeasurementTo measurementTo = testDataFactory.getTestMeasurementTo(aquariumTo); + MeasurementTo measurementTo2 = testDataFactory.getTestMeasurementTo(aquariumTo); MeasurementEntity measurementEntity = measurementMapper.mapMeasurementTo2EntityWithoutAquarium(measurementTo); MeasurementEntity measurementEntity2 = measurementMapper.mapMeasurementTo2EntityWithoutAquarium(measurementTo2); measurementEntity.setAquarium(aquariumEntity); @@ -203,7 +223,7 @@ public void testListUsersTankMeasurementsForSpecificMeasurement() throws Excepti given(this.aquariumRepository.getAquariumEntityByIdAndUser_IdIs(aquariumTo.getId(), userTo.getId())).willReturn(aquariumEntity); given(this.aquariumRepository.getOne(aquariumTo.getId())).willReturn(aquariumEntity); - MeasurementTo measurementTo = testDataFactory.getTestMeasurementTo(aquariumTo.getId()); + MeasurementTo measurementTo = testDataFactory.getTestMeasurementTo(aquariumTo); MeasurementEntity measurementEntity = measurementMapper.mapMeasurementTo2EntityWithoutAquarium(measurementTo); measurementEntity.setAquarium(aquariumEntity); @@ -279,7 +299,7 @@ public void testAddMeasurement() throws Exception { given(this.aquariumRepository.getAquariumEntityByIdAndUser_IdIs(aquariumTo.getId(), userTo.getId())).willReturn(aquariumEntity); given(this.aquariumRepository.findById(aquariumTo.getId())).willReturn(Optional.of(aquariumEntity)); - MeasurementTo measurementTo = testDataFactory.getTestMeasurementTo(aquariumTo.getId()); + MeasurementTo measurementTo = testDataFactory.getTestMeasurementTo(aquariumTo); MeasurementEntity measurementEntity = new MeasurementEntity(); measurementEntity.setId(88L); measurementEntity.setAquarium(aquariumEntity); @@ -321,7 +341,7 @@ public void testUpdateMeasurement() throws Exception { AquariumTo aquariumTo = testDataFactory.getTestAquariumFor(userTo); AquariumEntity aquariumEntity = aquariumMapper.mapAquariumTo2Entity(aquariumTo); - MeasurementTo updatableMeasurementTo = testDataFactory.getTestMeasurementTo(aquariumTo.getId()); + MeasurementTo updatableMeasurementTo = testDataFactory.getTestMeasurementTo(aquariumTo); updatableMeasurementTo.setId(88L); MeasurementEntity updatableMeasurementEntity = measurementMapper.mapMeasurementTo2EntityWithoutAquarium(updatableMeasurementTo); diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/MotdControllerTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/MotdControllerTest.java new file mode 100644 index 00000000..2e217fd5 --- /dev/null +++ b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/MotdControllerTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). + * See project LICENSE file for the detailed terms and conditions. + */ + +package de.bluewhale.sabi.rest.controller; + +import de.bluewhale.sabi.configs.AppConfig; +import de.bluewhale.sabi.services.AppService; +import org.junit.Assert; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.web.client.RestClient; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.reset; + + +/** + * Checks Motd Service + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Testcontainers +@ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +@Tag("ModuleTest") +public class MotdControllerTest { + + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + + @MockBean + AppService appService; + + @LocalServerPort + private int port; + + private RestClient restClient; + + @BeforeEach + public void initRestClient() { + if (restClient == null) { + String url = String.format("http://localhost:%d/sabi", port); + restClient = RestClient + .builder() + .baseUrl(url) // Dynamischer Port + .build(); + } + } + + @AfterEach + public void cleanUpMocks() { + reset(appService); + } + + /** + * Tests MOTD Rest API in case we have no content. + * + * @throws Exception + */ + @Test // REST-API + public void testModtRetrievalWithNoNews() throws Exception { + given(this.appService.fetchMotdFor("xx")).willReturn(null); + + restClient.get().uri("/api/app/motd/xx") + .retrieve() // führt den Request aus und ruft die Antwort ab + .onStatus( + status -> !status.isSameCodeAs(HttpStatus.NO_CONTENT), + (request, response) -> { + throw new RuntimeException("Retrieved wrong status code: " + response.getStatusCode()); + } + ) + .toEntity(String.class); + + } + + + /** + * Tests MOTD Rest API in case we have news. + * + * @throws Exception + */ + @Test // REST-API + public void testModtRetrieval() throws Exception { + + String motd = "Junit Modt"; + given(this.appService.fetchMotdFor("en")).willReturn(motd); + + ResponseEntity stringResponseEntity = restClient.get().uri("/api/app/motd/en") + .retrieve() + .onStatus(status -> status.value() != 200, (request, response) -> { + throw new RuntimeException("Retrieved wrong status code: " + response.getStatusCode()); + }).toEntity(String.class); + + Assert.assertTrue(stringResponseEntity.toString().contains(motd)); + } + +} diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/PlagueCenterControllerTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/PlagueCenterControllerTest.java similarity index 70% rename from sabi-server/src/test/java/de/bluewhale/sabi/services/rest/PlagueCenterControllerTest.java rename to sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/PlagueCenterControllerTest.java index 805edd37..30d59d02 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/PlagueCenterControllerTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/PlagueCenterControllerTest.java @@ -1,12 +1,12 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ -package de.bluewhale.sabi.services.rest; +package de.bluewhale.sabi.rest.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import de.bluewhale.sabi.TestDataFactory; +import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.mapper.UserMapper; import de.bluewhale.sabi.model.PlagueStatusTo; import de.bluewhale.sabi.model.UserTo; @@ -15,17 +15,26 @@ import de.bluewhale.sabi.security.TokenAuthenticationService; import de.bluewhale.sabi.services.PlagueCenterService; import de.bluewhale.sabi.util.RestHelper; +import de.bluewhale.sabi.util.TestDataFactory; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.http.*; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import java.util.ArrayList; import java.util.List; +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.BDDMockito.given; @@ -37,10 +46,29 @@ * @author Stefan Schubert */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Testcontainers +@ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Tag("ModuleTest") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class PlagueCenterControllerTest { // ------------------------------ FIELDS ------------------------------ + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + + final static String MOCKED_USER = "testsabi@bluewhale.de"; @MockBean @@ -54,7 +82,9 @@ public class PlagueCenterControllerTest { @Autowired ObjectMapper objectMapper; // json mapper + TestDataFactory testDataFactory = TestDataFactory.getInstance(); + @Autowired private TestRestTemplate restTemplate; diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/StatsControllerTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/StatsControllerTest.java similarity index 68% rename from sabi-server/src/test/java/de/bluewhale/sabi/services/rest/StatsControllerTest.java rename to sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/StatsControllerTest.java index 83160264..41abe565 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/StatsControllerTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/StatsControllerTest.java @@ -1,12 +1,12 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ -package de.bluewhale.sabi.services.rest; +package de.bluewhale.sabi.rest.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import de.bluewhale.sabi.TestDataFactory; +import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.mapper.UserMapper; import de.bluewhale.sabi.model.UserTo; import de.bluewhale.sabi.persistence.model.UserEntity; @@ -14,14 +14,23 @@ import de.bluewhale.sabi.persistence.repositories.UserRepository; import de.bluewhale.sabi.security.TokenAuthenticationService; import de.bluewhale.sabi.util.RestHelper; +import de.bluewhale.sabi.util.TestDataFactory; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.http.*; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; @@ -30,24 +39,35 @@ /** * Demonstrate usage of the unit REST API. * NOTICE: This test mocks the DAO persistent layer, as it was not meant to run as an integration test. - *

- * However notice the following drawbacks: - *

- * (1) It still requires the database, as without it we get a java.lang.IllegalStateException: - * Failed to load ApplicationContext, though this might be fixed by proper test configuration - * (2) Lines of code! The mocked variant outweighs the implementation by far. Which slows down development progress. - * I leave it to demonstrate the effect. For those cases it would be much better to leave this as real integration - * tests (however against an H2 in memory database, or by manually control your test data). * * @author Stefan Schubert */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Testcontainers +@ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Tag("ModuleTest") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class StatsControllerTest { // ------------------------------ FIELDS ------------------------------ final static String MOCKED_USER = "testsabi@bluewhale.de"; + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + + @MockBean MeasurementRepository measurementRepository; diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/TankControllerTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/TankControllerTest.java similarity index 89% rename from sabi-server/src/test/java/de/bluewhale/sabi/services/rest/TankControllerTest.java rename to sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/TankControllerTest.java index fc9ae2e6..83747856 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/TankControllerTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/TankControllerTest.java @@ -1,12 +1,12 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ -package de.bluewhale.sabi.services.rest; +package de.bluewhale.sabi.rest.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import de.bluewhale.sabi.TestDataFactory; +import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.mapper.AquariumMapper; import de.bluewhale.sabi.mapper.UserMapper; import de.bluewhale.sabi.model.AquariumTo; @@ -17,19 +17,28 @@ import de.bluewhale.sabi.persistence.repositories.UserRepository; import de.bluewhale.sabi.security.TokenAuthenticationService; import de.bluewhale.sabi.util.RestHelper; +import de.bluewhale.sabi.util.TestDataFactory; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.http.*; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import java.util.ArrayList; import java.util.List; import static de.bluewhale.sabi.api.HttpHeader.AUTH_TOKEN; import static de.bluewhale.sabi.api.HttpHeader.TOKEN_PREFIX; +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -43,12 +52,30 @@ * @author Stefan Schubert */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Testcontainers +@ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Tag("ModuleTest") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class TankControllerTest { // ------------------------------ FIELDS ------------------------------ final static String MOCKED_USER = "testsabi@bluewhale.de"; + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + @MockBean UserRepository userRepository; diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/UnitControllerTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/UnitControllerTest.java similarity index 81% rename from sabi-server/src/test/java/de/bluewhale/sabi/services/rest/UnitControllerTest.java rename to sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/UnitControllerTest.java index 9f2fd1d9..70f561bf 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/UnitControllerTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/UnitControllerTest.java @@ -3,11 +3,11 @@ * See project LICENSE file for the detailed terms and conditions. */ -package de.bluewhale.sabi.services.rest; +package de.bluewhale.sabi.rest.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import de.bluewhale.sabi.TestDataFactory; import de.bluewhale.sabi.api.Endpoint; +import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.mapper.ParameterMapper; import de.bluewhale.sabi.mapper.UnitMapper; import de.bluewhale.sabi.mapper.UserMapper; @@ -24,18 +24,27 @@ import de.bluewhale.sabi.persistence.repositories.UserRepository; import de.bluewhale.sabi.security.TokenAuthenticationService; import de.bluewhale.sabi.util.RestHelper; +import de.bluewhale.sabi.util.TestDataFactory; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.http.*; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.MatcherAssert.assertThat; @@ -44,23 +53,34 @@ /** * Demonstrate usage of the unit REST API. * NOTICE: This test mocks the DAO persistent layer, as it was not meant to run as an integration test. - *

- * However notice the following drawbacks: - *

- * (1) It still requires the database, as without it we get a java.lang.IllegalStateException: - * Failed to load ApplicationContext, though this might be fixed by proper test configuration - * (2) Lines of code! The mocked variant outweighs the implementation by far. Which slows down development progress. - * I leave it to demonstrate the effect. For those cases it would be much better to leave this as real integration - * tests (however against an H2 in memory database, or by manually control your test data). * * @author Stefan Schubert */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Testcontainers +@ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Tag("ModuleTest") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class UnitControllerTest { // ------------------------------ FIELDS ------------------------------ final static String MOCKED_USER = "testsabi@bluewhale.de"; + + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + @MockBean UnitRepository unitRepository; @MockBean diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/UserAuthControllerTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/UserAuthControllerTest.java similarity index 86% rename from sabi-server/src/test/java/de/bluewhale/sabi/services/rest/UserAuthControllerTest.java rename to sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/UserAuthControllerTest.java index cb95ce8e..79407ccb 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/UserAuthControllerTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/UserAuthControllerTest.java @@ -1,15 +1,15 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ -package de.bluewhale.sabi.services.rest; +package de.bluewhale.sabi.rest.controller; import com.dumbster.smtp.SimpleSmtpServer; import com.dumbster.smtp.SmtpMessage; import com.fasterxml.jackson.databind.ObjectMapper; -import de.bluewhale.sabi.TestDataFactory; import de.bluewhale.sabi.api.Endpoint; +import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.mapper.UserMapper; import de.bluewhale.sabi.model.AccountCredentialsTo; import de.bluewhale.sabi.model.UserTo; @@ -17,19 +17,28 @@ import de.bluewhale.sabi.persistence.repositories.UserRepository; import de.bluewhale.sabi.services.CaptchaAdapter; import de.bluewhale.sabi.util.RestHelper; +import de.bluewhale.sabi.util.TestDataFactory; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.http.*; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import javax.naming.NamingException; +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; @@ -43,9 +52,27 @@ * You may consult the test cases, while developing a specific client. */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Testcontainers +@ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Tag("ModuleTest") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class UserAuthControllerTest { + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + SimpleSmtpServer smtpServer; @@ -156,7 +183,7 @@ public void testSuccessfulNewUserRegistration() throws Exception { public void testWeakPasswordUserRegistration() throws Exception { // given a test user - UserTo userTo = new UserTo("test@bluewhale.de", "Tester",TestDataFactory.INVALID_PASSWORD); + UserTo userTo = new UserTo("test@bluewhale.de", "Tester", TestDataFactory.INVALID_PASSWORD); userTo.setCaptchaCode("test"); UserEntity userEntity = userMapper.mapUserTo2Entity(userTo); diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/UserProfileControllerTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/UserProfileControllerTest.java similarity index 83% rename from sabi-server/src/test/java/de/bluewhale/sabi/services/rest/UserProfileControllerTest.java rename to sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/UserProfileControllerTest.java index c8b29687..d004c767 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/UserProfileControllerTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/rest/controller/UserProfileControllerTest.java @@ -1,17 +1,17 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ -package de.bluewhale.sabi.services.rest; +package de.bluewhale.sabi.rest.controller; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.TokenExpiredException; import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.databind.ObjectMapper; -import de.bluewhale.sabi.TestDataFactory; import de.bluewhale.sabi.api.Endpoint; +import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.exception.CommonMessageCodes; import de.bluewhale.sabi.exception.Message; import de.bluewhale.sabi.model.ResultTo; @@ -20,17 +20,26 @@ import de.bluewhale.sabi.security.TokenAuthenticationService; import de.bluewhale.sabi.services.UserService; import de.bluewhale.sabi.util.RestHelper; +import de.bluewhale.sabi.util.TestDataFactory; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.http.*; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import java.util.Date; +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -41,9 +50,27 @@ * Checks UserProfile Rest API */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Testcontainers +@ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Tag("ModuleTest") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class UserProfileControllerTest { + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + @MockBean UserService userService; diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/security/PasswordPolicyTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/security/PasswordPolicyTest.java index 2075af6b..136d2a0d 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/security/PasswordPolicyTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/security/PasswordPolicyTest.java @@ -1,11 +1,12 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ package de.bluewhale.sabi.security; import org.eclipse.persistence.jpa.jpql.Assert; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -17,6 +18,7 @@ * @author Stefan Schubert */ @ExtendWith(SpringExtension.class) +@Tag("DeveloperTest") public class PasswordPolicyTest { @Test diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/CoralServiceTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/services/CoralServiceTest.java index 3e45cee7..98eac7b4 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/CoralServiceTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/services/CoralServiceTest.java @@ -1,25 +1,26 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ package de.bluewhale.sabi.services; -import de.bluewhale.sabi.TestDataFactory; import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.model.AquariumTo; -import de.bluewhale.sabi.model.ResultTo; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; +import de.bluewhale.sabi.util.TestDataFactory; +import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ContextConfiguration; -import org.springframework.transaction.annotation.Transactional; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; -import static de.bluewhale.sabi.TestDataFactory.TESTUSER_EMAIL1; +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; import static org.springframework.test.util.AssertionErrors.fail; @@ -29,17 +30,34 @@ * Date: 16.06.2017 */ @SpringBootTest +@Testcontainers @ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +@Tag("ServiceTest") public class CoralServiceTest { // ------------------------------ FIELDS ------------------------------ + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + @Autowired private TankService tankService; - @Autowired - private UserService userService; + static TestDataFactory testDataFactory = TestDataFactory.getInstance(); + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + // -------------------------- OTHER METHODS -------------------------- @@ -49,18 +67,15 @@ public class CoralServiceTest { * @throws Exception */ @Test - @Transactional + @Rollback @Disabled public void testAddCoral() throws Exception { // Given - TestDataFactory testDataFactory = TestDataFactory.getInstance().withUserService(userService); final AquariumTo aquariumTo = testDataFactory.getTestAquariumTo(); - final ResultTo aquariumToResultTo = tankService.registerNewTank(aquariumTo, TESTUSER_EMAIL1); // When fail("Implement me"); } - } diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/FishServiceTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/services/FishServiceTest.java index 5d733a92..fec5557b 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/FishServiceTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/services/FishServiceTest.java @@ -1,29 +1,46 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ package de.bluewhale.sabi.services; -import de.bluewhale.sabi.TestDataFactory; import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.exception.Message.CATEGORY; import de.bluewhale.sabi.model.AquariumTo; import de.bluewhale.sabi.model.FishTo; import de.bluewhale.sabi.model.ResultTo; import de.bluewhale.sabi.model.UserTo; +import de.bluewhale.sabi.persistence.model.AquariumEntity; +import de.bluewhale.sabi.persistence.model.FishEntity; +import de.bluewhale.sabi.persistence.model.UserEntity; +import de.bluewhale.sabi.persistence.repositories.AquariumRepository; +import de.bluewhale.sabi.persistence.repositories.FishRepository; +import de.bluewhale.sabi.persistence.repositories.UserRepository; +import de.bluewhale.sabi.util.TestDataFactory; import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ContextConfiguration; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDate; - -import static de.bluewhale.sabi.TestDataFactory.TESTUSER_EMAIL1; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; +import static de.bluewhale.sabi.util.TestDataFactory.TESTUSER_EMAIL1; +import static de.bluewhale.sabi.util.TestDataFactory.TEST_FISH_ID; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; import static org.springframework.test.util.AssertionErrors.*; @@ -33,18 +50,45 @@ * Date: 30.08.15 */ @SpringBootTest +@Testcontainers @ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Tag("ServiceTest") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class FishServiceTest { // ------------------------------ FIELDS ------------------------------ + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + @Autowired private TankService tankService; @Autowired private FishService fishService; - @Autowired - private UserService userService; + + static TestDataFactory testDataFactory = TestDataFactory.getInstance(); + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + + @MockBean + private UserRepository userRepository; + + @MockBean + private FishRepository fishRepository; + + @MockBean + private AquariumRepository aquariumRepository; + // -------------------------- OTHER METHODS -------------------------- @@ -54,25 +98,26 @@ public class FishServiceTest { * @throws Exception */ @Test - @Transactional + @Rollback public void testAddFish() throws Exception { // Given - TestDataFactory testDataFactory = TestDataFactory.getInstance().withUserService(userService); - final UserTo registeredUser = testDataFactory.getRegisterNewTestUser(TESTUSER_EMAIL1); + final UserTo testUser = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity testUserEntity = testDataFactory.getNewTestUserEntity(testUser); final AquariumTo aquariumTo = testDataFactory.getTestAquariumTo(); + AquariumEntity testAquariumEntity = testDataFactory.getTestAquariumEntity(aquariumTo, testUserEntity); + FishTo testFishTo = testDataFactory.getTestFishTo(aquariumTo); - final ResultTo aquariumToResultTo = tankService.registerNewTank(aquariumTo, registeredUser.getEmail()); - + given(userRepository.getOne(testUser.getId())).willReturn(testUserEntity); + given(aquariumRepository.getOne(testFishTo.getAquariumId())).willReturn(testAquariumEntity); + when(fishRepository.save(any(FishEntity.class))).thenAnswer(invocation -> { + FishEntity fishEntity = invocation.getArgument(0); + fishEntity.setId(TEST_FISH_ID); + return fishEntity; + }); // When - final FishTo fish = new FishTo(); - fish.setAddedOn(LocalDate.now()); - fish.setFishCatalogueId(1L); // existing default Data - fish.setAquariumId(aquariumToResultTo.getValue().getId()); - fish.setNickname("Green Latern"); - // The user is required to check that he or she really possesses the tank - final ResultTo fishResultTo = fishService.registerNewFish(fish,registeredUser); + final ResultTo fishResultTo = fishService.registerNewFish(testFishTo,testUser); // Then assertNotNull("ResultObject must not be empty",fishResultTo); @@ -85,25 +130,26 @@ public void testAddFish() throws Exception { // A User cannot register a fish for a tank that he or she does not own. @Test - @Transactional - public void testAddFishForWrongTank() throws Exception { + @Rollback + public void testAddFishForOtherUsersTank() throws Exception { // Given - TestDataFactory testDataFactory = TestDataFactory.getInstance().withUserService(userService); - final UserTo registeredUser = testDataFactory.getRegisterNewTestUser(TESTUSER_EMAIL1); - final UserTo fraudUser = testDataFactory.getRegisterNewTestUser("I_Intent@No.good"); - final AquariumTo aquariumTo = testDataFactory.getTestAquariumTo(); - final ResultTo aquariumToResultTo = tankService.registerNewTank(aquariumTo, registeredUser.getEmail()); + final UserTo testUser = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity testUserEntity = testDataFactory.getNewTestUserEntity(testUser); + AquariumTo testAquariumFor = testDataFactory.getTestAquariumFor(testUser); + AquariumEntity testAquariumEntity = testDataFactory.getTestAquariumEntity(testAquariumFor, testUserEntity); - // When - final FishTo fish = new FishTo(); - fish.setAddedOn(LocalDate.now()); - fish.setFishCatalogueId(1L); // existing default Data - fish.setAquariumId(aquariumToResultTo.getValue().getId()); - fish.setNickname("Green Latern"); + FishTo testFishTo = testDataFactory.getTestFishTo(testAquariumFor); + final UserTo fraudUser = testDataFactory.getNewTestUserTo("I_Intent@No.good"); + fraudUser.setId(88L); + UserEntity testFraudUserEntity = testDataFactory.getNewTestUserEntity(fraudUser); + + given(userRepository.getOne(fraudUser.getId())).willReturn(testFraudUserEntity); + given(aquariumRepository.getOne(testFishTo.getAquariumId())).willReturn(testAquariumEntity); // an Aquarium that is not owned by the fraud user + // When // The the fraud user tries to place something in a different tank - final ResultTo fishResultTo = fishService.registerNewFish(fish,fraudUser); + final ResultTo fishResultTo = fishService.registerNewFish(testFishTo, fraudUser); // then assertNull("ResultObject Value should be empty as creation was not permitted.",fishResultTo.getValue()); @@ -112,48 +158,31 @@ public void testAddFishForWrongTank() throws Exception { } -/* - @BeforeClass - public static void init() throws NamingException { - } -*/ - -/* @AfterClass - public static void tearDownClass() throws Exception { - } -*/ - - /** * Remode a fish and write it automatically to tanks history via log entry * * @throws Exception */ @Test - @Transactional + @Rollback public void testRemoveFish() throws Exception { // Given - TestDataFactory testDataFactory = TestDataFactory.getInstance().withUserService(userService); - final UserTo registeredUser = testDataFactory.getRegisterNewTestUser(TESTUSER_EMAIL1); + final UserTo testUser = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity testUserEntity = testDataFactory.getNewTestUserEntity(testUser); final AquariumTo aquariumTo = testDataFactory.getTestAquariumTo(); + AquariumEntity testAquariumEntity = testDataFactory.getTestAquariumEntity(aquariumTo, testUserEntity); + FishTo testFishTo = testDataFactory.getTestFishTo(aquariumTo); + FishEntity testFishEntity = testDataFactory.getTestFishEntity(testFishTo, testAquariumEntity.getId()); - final ResultTo aquariumToResultTo = tankService.registerNewTank(aquariumTo, registeredUser.getEmail()); - - final FishTo fish = new FishTo(); - fish.setAddedOn(LocalDate.now()); - fish.setFishCatalogueId(1L); // existing default Data - fish.setAquariumId(aquariumToResultTo.getValue().getId()); - fish.setNickname("Green Latern"); - - // The user is required to check that he your she really posses the tank - final ResultTo fishResultTo = fishService.registerNewFish(fish,registeredUser); + Long fishId = testFishTo.getId(); + given(fishRepository.findUsersFish(fishId, testUser.getId())).willReturn(testFishEntity).willReturn(null); + doNothing().when(fishRepository).delete(testFishEntity); // When - Long fishId = fishResultTo.getValue().getId(); - fishService.removeFish(fishId, registeredUser); + fishService.removeFish(fishId, testUser); // Then - FishTo removedFish = fishService.getUsersFish(fishId, registeredUser); + FishTo removedFish = fishService.getUsersFish(fishId, testUser); assertNull("Fish was not removed!", removedFish); } } diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/MeasurementServiceTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/services/MeasurementServiceTest.java index 1748ae79..b636ffe7 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/MeasurementServiceTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/services/MeasurementServiceTest.java @@ -5,26 +5,37 @@ package de.bluewhale.sabi.services; -import de.bluewhale.sabi.BasicDataFactory; -import de.bluewhale.sabi.TestDataFactory; import de.bluewhale.sabi.configs.AppConfig; import de.bluewhale.sabi.exception.Message; -import de.bluewhale.sabi.exception.Message.CATEGORY; import de.bluewhale.sabi.model.*; -import jakarta.transaction.Transactional; -import org.junit.jupiter.api.BeforeEach; +import de.bluewhale.sabi.persistence.model.*; +import de.bluewhale.sabi.persistence.repositories.*; +import de.bluewhale.sabi.util.TestDataFactory; import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ContextConfiguration; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; -import java.time.LocalDateTime; import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertNull; +import java.util.Optional; + +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; +import static de.bluewhale.sabi.util.TestDataFactory.TESTUSER_EMAIL1; +import static de.bluewhale.sabi.util.TestDataFactory.TEST_TANK_ID; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; import static org.springframework.test.util.AssertionErrors.*; @@ -34,241 +45,278 @@ * Date: 30.08.15 */ @SpringBootTest +@Testcontainers @ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) -public class MeasurementServiceTest extends BasicDataFactory { - +@Tag("ServiceTest") +@DirtiesContext +public class MeasurementServiceTest { + + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ // ------------------------------ FIELDS ------------------------------ - @Autowired - private TankService tankService; + static TestDataFactory testDataFactory = TestDataFactory.getInstance(); - @Autowired - private UserService userService; + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); @Autowired private MeasurementService measurementService; -// -------------------------- OTHER METHODS -------------------------- + @MockBean + private AquariumRepository aquariumRepository; - /** - * There seems to be a timing problem with H2, that causes, that the basic data is not available - * for some test classes, while for others it worked out. Or the Annotation is not being processed through - * inheritance. Until we know what's going wrong... - * we "double inject" by extending the BasicTestDataFactory and by calling it directly. - * The different behaviour can be observed by e.g. calling the master test suite and as comparising - * the measurement testsuite while this is method is deaktivated. - */ - @BeforeEach - public void ensureBasicDataAvailability() { - List list = measurementService.listMeasurements(P_USER1_EMAIL, 0); - if (list.isEmpty()) { - populateBasicData(); - list = measurementService.listMeasurements(P_USER1_EMAIL, 0); - } - assert(list.size() > 0); - - // PROBLEM here: We thought that stores User gets the ID wie set hard, but H2 Dirties context - // drops data but not sequence values, which is why repeated user creation leads to a different user ID. - // So any test which is relies on a certain user ID might run into trouble. - // UserEntity storedTestUser = userRepository.getByEmail(P_USER1_EMAIL); - // assertNotNull("Should not happen!","Precondition stored Test User failed!",storedTestUser); - // assertEquals("Stored Test User got wrong ID!? ", 1l, storedTestUser.getId().longValue()); - } + @MockBean + private UserRepository userRepository; + + @MockBean + private UnitRepository unitRepository; + + @MockBean + private MeasurementRepository measurementRepository; + + @MockBean + private LocalizedUnitRepository localizedUnitRepository; + + @MockBean + ParameterRepository parameterRepository; + +// -------------------------- OTHER METHODS -------------------------- @Test - @Transactional public void testListMeasurements() throws Exception { - // Given already stored testdata for measurements - AquariumTo aquariumTo = tankService.listTanks(P_USER1_EMAIL).get(0); - assertNotNull("Prepersisted Testdata is missing", aquariumTo); - Long tankID = aquariumTo.getId(); + + // Given: + UserTo testUserTo = TestDataFactory.getInstance().getNewTestUserTo(TestDataFactory.TESTUSER_EMAIL1); + UserEntity userEntity = TestDataFactory.getInstance().getNewTestUserEntity(testUserTo); + + AquariumTo testAquariumTo = testDataFactory.getTestAquariumTo(); + AquariumEntity aquariumEntity = testDataFactory.getTestAquariumEntity(testAquariumTo, userEntity); + + MeasurementTo testMeasurementTo = testDataFactory.getTestMeasurementTo(testAquariumTo); + MeasurementEntity measurementEntity = testDataFactory.getTestMeasurementEntity(testMeasurementTo, aquariumEntity); + + + given(aquariumRepository.getOne(TEST_TANK_ID)).willReturn(aquariumEntity); + given(measurementRepository.findByAquarium(aquariumEntity)).willReturn(List.of(measurementEntity)); + given(userRepository.getByEmail(TestDataFactory.TESTUSER_EMAIL1)).willReturn(userEntity); + given(measurementRepository.findByUserOrderByMeasuredOnDesc(userEntity)).willReturn(List.of(measurementEntity)); // When - List tank1Measurements = measurementService.listMeasurements(tankID); - List usersMeasurements = measurementService.listMeasurements(P_USER1_EMAIL, 0); + List tank1Measurements = measurementService.listMeasurements(TEST_TANK_ID); + List usersMeasurements = measurementService.listMeasurements(TestDataFactory.TESTUSER_EMAIL1, 0); // Then - assertNotNull("Should not happen!",tank1Measurements); - assertNotNull("Should not happen!",usersMeasurements); - assertTrue("Testdata gone?", tank1Measurements.size() >= 1); - assertTrue("Stored Testdata changed?", usersMeasurements.containsAll(tank1Measurements)); + assertNotNull("Should not happen!", tank1Measurements); + assertNotNull("Should not happen!", usersMeasurements); + assertTrue("Mocks didn't worked?", tank1Measurements.size() >= 1); + assertTrue("Relationships brocken?", usersMeasurements.containsAll(tank1Measurements)); } + @Test - @Transactional - public void testFindMeasurementParameter() throws Exception { - // Given already stored testdata for measurements + public void testFindLocalizedMeasurementParameter() throws Exception { + // Given parameter Entity with id 1 + LocalizedParameterEntity localizedParameterEntity = new LocalizedParameterEntity(); + localizedParameterEntity.setId(1L); + localizedParameterEntity.setLanguage("de"); + localizedParameterEntity.setDescription("Test Parameter"); + + ParameterEntity parameterEntity = new ParameterEntity(); + parameterEntity.setId(1); + parameterEntity.setLocalizedParameterEntities(List.of(localizedParameterEntity)); + + given(parameterRepository.findByBelongingUnitIdEquals(1)).willReturn(parameterEntity); // When ParameterTo parameterTo = measurementService.fetchParameterInfoFor(1, "de"); // Then - assertNotNull("Should not happen!",parameterTo); + assertNotNull("Should not happen!", parameterTo); } @Test - @Transactional public void testFindInvalidMeasurementParameter() throws Exception { - // Given already stored testdata for measurements + // Given Integer nonExistingUnit = Integer.MAX_VALUE; + given(parameterRepository.findByBelongingUnitIdEquals(nonExistingUnit)).willReturn(null); // When - ParameterTo parameterTo = measurementService.fetchParameterInfoFor(nonExistingUnit,"de"); + ParameterTo parameterTo = measurementService.fetchParameterInfoFor(nonExistingUnit, "de"); // Then - assertNull(parameterTo); + assertNull("According to API we should get null but got: ", parameterTo); } - @Test - @Transactional public void testListMeasurementsForSpecificTankAndUnit() throws Exception { - // Given already stored testdata for measurements - // for tank 2 only one measurement with unit id 1 + // Given + AquariumEntity aquariumEntity = new AquariumEntity(); + aquariumEntity.setId(2L); + + MeasurementEntity measurementEntity = new MeasurementEntity(); + measurementEntity.setAquarium(aquariumEntity); + measurementEntity.setUnitId(1); + + given(aquariumRepository.getOne(2L)).willReturn(aquariumEntity); // Tank + given(measurementRepository.findByAquariumAndUnitIdOrderByMeasuredOnAsc(aquariumEntity, 1)).willReturn(List.of(measurementEntity)); // When List measurements = measurementService.listMeasurementsFilteredBy(2L, 1); // Then - assertNotNull("Should not happen!",measurements); - assertTrue("Testdata gone or changed? Received more or less than expected on measurement.", measurements.size() == 1); + assertNotNull("Should not happen!", measurements); + assertTrue("Mocked Testdata gone or changed? Received more or less than expected on measurement.", measurements.size() == 1); } @Test - @Transactional public void testListMeasurementUnits() throws Exception { // Given already stored testdata for measurements + UnitEntity unitEntity = new UnitEntity(); + unitEntity.setName("Happyness"); + unitEntity.setId(1); + LocalizedUnitEntity localizedUnitEntity = new LocalizedUnitEntity(); + localizedUnitEntity.setLanguage("de"); + localizedUnitEntity.setDescription("Glück"); + + given(unitRepository.findAll()).willReturn(List.of(unitEntity)); + given(localizedUnitRepository.findByLanguageAndUnitId("de",unitEntity.getId())).willReturn(localizedUnitEntity); // When List measurementUnits = measurementService.listAllMeasurementUnits("de"); // Then - assertNotNull("Should not happen!",measurementUnits); + assertNotNull("Should not happen!", measurementUnits); assertTrue("Testdata gone?", measurementUnits.size() >= 1); + assertTrue("Translation failed?", measurementUnits.get(0).getDescription().equals("Glück")); } @Test - @Transactional + @Rollback public void testAddNewMeasurement() throws Exception { - // Given already store test data - AquariumTo aquariumTo = tankService.listTanks(P_USER1_EMAIL).get(0); - assertNotNull("Prepersisted Testdata is missing", aquariumTo); - Long tankID = aquariumTo.getId(); + // Given + UserTo testUserTo = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity userEntity = testDataFactory.getNewTestUserEntity(testUserTo); + AquariumTo testAquariumTo = testDataFactory.getTestAquariumTo(); + AquariumEntity aquariumEntity = testDataFactory.getTestAquariumEntity(testAquariumTo, userEntity); + MeasurementTo testMeasurementTo = testDataFactory.getTestMeasurementTo(testAquariumTo); - // When adding a new Measurment - TestDataFactory testDataFactory = TestDataFactory.getInstance(); - MeasurementTo testMeasurementTo = testDataFactory.getTestMeasurementTo(tankID); - ResultTo measurementToResultTo = measurementService.addMeasurement(testMeasurementTo, P_USER1_EMAIL); + given(userRepository.getByEmail(TestDataFactory.TESTUSER_EMAIL1)).willReturn(userEntity); + given(aquariumRepository.getAquariumEntityByIdAndUser_IdIs(TEST_TANK_ID,userEntity.getId())).willReturn(aquariumEntity); + given(measurementRepository.saveAndFlush(any())).willReturn(new MeasurementEntity()); + + // When adding a new Measurement + ResultTo measurementToResultTo = measurementService.addMeasurement(testMeasurementTo, TestDataFactory.TESTUSER_EMAIL1); // Then assertNotNull("Should not happen!",measurementToResultTo); - assertNotNull("Should not happen!",measurementToResultTo.getValue()); - assertEquals("Creating measurement failed? " + measurementToResultTo.getMessage().getCode(), CATEGORY.INFO, measurementToResultTo.getMessage().getType()); + assertNotNull("Should not happen! Empty ResultTo",measurementToResultTo.getValue()); + assertEquals("Creating measurement failed? " + measurementToResultTo.getMessage().getCode(), Message.CATEGORY.INFO, measurementToResultTo.getMessage().getType()); } @Test - @Transactional - public void testGetLastetMeasurementEntryDateTime() throws Exception { - - // Given already store test data - TestDataFactory testDataFactory = TestDataFactory.getInstance(); - AquariumTo aquariumTo = tankService.listTanks(P_USER1_EMAIL).get(0); - assertNotNull("Prepersisted Testdata missing", aquariumTo); - Long tankID = aquariumTo.getId(); + @Rollback + public void testRemoveMeasurement() throws Exception { + // Given - // Stored Measurement A - MeasurementTo testMeasurementATo = testDataFactory.getTestMeasurementTo(tankID); - testMeasurementATo.setMeasuredOn(LocalDateTime.now().minusYears(2)); - ResultTo measurementAToResultTo = measurementService.addMeasurement(testMeasurementATo, P_USER1_EMAIL); - assertEquals("Failure storing test data A?: " + measurementAToResultTo.getMessage().getCode(), CATEGORY.INFO, measurementAToResultTo.getMessage().getType()); + UserEntity userEntity = new UserEntity(); + userEntity.setEmail(TestDataFactory.TESTUSER_EMAIL1); + userEntity.setId(99L); + MeasurementEntity measurementEntity = new MeasurementEntity(); + measurementEntity.setId(42L); - // And Afterwards created Measurement B - MeasurementTo testMeasurementBTo = testDataFactory.getTestMeasurementTo(tankID); - testMeasurementBTo.setId(testMeasurementATo.getId() + 1l); - testMeasurementBTo.setMeasuredValue(99f); - ResultTo measurementBToResultTo = measurementService.addMeasurement(testMeasurementBTo, P_USER1_EMAIL); - assertEquals("Failure storing test data B?: " + measurementBToResultTo.getMessage().getCode(), CATEGORY.INFO, measurementBToResultTo.getMessage().getType()); + given(userRepository.getByEmail(TestDataFactory.TESTUSER_EMAIL1)).willReturn(userEntity); + given(measurementRepository.getByIdAndUser(42L, userEntity)).willReturn(measurementEntity); + doNothing().when(measurementRepository).delete(measurementEntity); // When - LocalDateTime lastRecordedTime = measurementService.getLastTimeOfMeasurementTakenFilteredBy(measurementAToResultTo.getValue().getAquariumId(), testMeasurementATo.getUnitId()); - - // Then - assertNotNull("Looked like stored measurments have not been flushed.", lastRecordedTime); - assertEquals("Did not retrieved the latest measurement date", LocalDateTime.now().getYear(), lastRecordedTime.getYear()); - } - - - @Test - @Transactional - public void testRemoveMeasurement() throws Exception { - // Given a stored measurement for a tank and user - TestDataFactory testDataFactory = TestDataFactory.getInstance(); - testDataFactory.withUserService(userService); - testDataFactory.withTankService(tankService); - String newTestUserMail = "junit@sabi.de"; - UserTo persistedTestUserTo = testDataFactory.getRegisterNewTestUser(newTestUserMail); - AquariumTo testAquariumTo = testDataFactory.getTestAquariumFor(persistedTestUserTo); - testAquariumTo.setId(57654L); - ResultTo newTankResultTo = tankService.registerNewTank(testAquariumTo, newTestUserMail); - - MeasurementTo testMeasurementTo = testDataFactory.getTestMeasurementTo(newTankResultTo.getValue().getId()); - testMeasurementTo.setId(889911L); // will be ignored ID will be overwritten, i.e. provided by addMeasurement - ResultTo measurementToResultTo1 = measurementService.addMeasurement(testMeasurementTo, newTestUserMail); - - // When - ResultTo measurementToResultTo = measurementService.removeMeasurement(measurementToResultTo1.getValue().getId(), newTestUserMail); + ResultTo measurementToResultTo = measurementService.removeMeasurement(42L, userEntity.getEmail()); // Then assertNotNull("Should not happen!",measurementToResultTo); - assertNotNull("Should not happen!",measurementToResultTo.getValue()); - CATEGORY messageType = measurementToResultTo.getMessage().getType(); + assertNotNull("Should not happen! ResultTo contains no Value!",measurementToResultTo.getValue()); + Message.CATEGORY messageType = measurementToResultTo.getMessage().getType(); assertTrue("Removal of measurement failed?", messageType.equals(Message.CATEGORY.INFO)); } @Test - @Transactional + @Rollback public void testUpdateMeasurement() throws Exception { - // Given already stored measurements - List measurementToList = measurementService.listMeasurements(P_USER1_EMAIL, 0); - MeasurementTo prestoresMeasurementTo = measurementToList.get(0); - float oldValue = prestoresMeasurementTo.getMeasuredValue(); + // Given + float oldValue = 1.0f; + float newValue = 2.0f; + + UserTo testUserTo = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity userEntity = testDataFactory.getNewTestUserEntity(testUserTo); + + AquariumTo testAquariumTo = testDataFactory.getTestAquariumFor(testUserTo); + + MeasurementEntity measurementEntity = testDataFactory.getTestMeasurementEntityWithDefaults(); + measurementEntity.setMeasuredValue(oldValue); + measurementEntity.setId(42L); + + MeasurementEntity updatedMeasurementEntity = testDataFactory.getTestMeasurementEntityWithDefaults(); + updatedMeasurementEntity.setMeasuredValue(newValue); + updatedMeasurementEntity.setId(42L); + + given(userRepository.getByEmail(TestDataFactory.TESTUSER_EMAIL1)).willReturn(userEntity); + given(measurementRepository.getByIdAndUser(42L, userEntity)).willReturn(measurementEntity); + given(measurementRepository.save(any())).willReturn(updatedMeasurementEntity); // When we update the measurement - float newValue = oldValue + 1.5f; - prestoresMeasurementTo.setMeasuredValue(newValue); - ResultTo measurementToResultTo = measurementService.updateMeasurement(prestoresMeasurementTo, P_USER1_EMAIL); + MeasurementTo testMeasurementTo = testDataFactory.getTestMeasurementTo(testAquariumTo); + testMeasurementTo.setMeasuredValue(newValue); + testMeasurementTo.setId(measurementEntity.getId()); + + ResultTo measurementToResultTo = measurementService.updateMeasurement(testMeasurementTo, TestDataFactory.TESTUSER_EMAIL1); // Then assertNotNull("Should not happen!",measurementToResultTo); assertEquals("Update failed?",newValue, measurementToResultTo.getValue().getMeasuredValue()); - CATEGORY messageType = measurementToResultTo.getMessage().getType(); + Message.CATEGORY messageType = measurementToResultTo.getMessage().getType(); assertTrue("Update of measurement failed?", messageType.equals(Message.CATEGORY.INFO)); } @Test - @Transactional + @Rollback public void testAddIotAuthorizedMeasurement() { - // Given already store test data - AquariumTo aquariumTo = tankService.listTanks(P_USER1_EMAIL).get(0); - assertNotNull("Prepersisted Testdata is missing", aquariumTo); - Long tankID = aquariumTo.getId(); + // Given + + UserTo testUserTo = testDataFactory.getNewTestUserTo(testDataFactory.TESTUSER_EMAIL1); + UserEntity userEntity = testDataFactory.getNewTestUserEntity(testUserTo); + + AquariumTo testAquariumTo = testDataFactory.getTestAquariumFor(testUserTo); + AquariumEntity testAquariumEntity = testDataFactory.getTestAquariumEntity(testAquariumTo, userEntity); + + MeasurementTo testMeasurementTo = testDataFactory.getTestMeasurementTo(testAquariumTo); + MeasurementEntity createdMeasurementEntity = testDataFactory.getTestMeasurementEntity(testMeasurementTo, testAquariumEntity); + + given(aquariumRepository.findById(TEST_TANK_ID)).willReturn(Optional.of(testAquariumEntity)); + given(measurementRepository.saveAndFlush(any())).willReturn(createdMeasurementEntity); // When - TestDataFactory testDataFactory = TestDataFactory.getInstance(); - MeasurementTo testMeasurementTo = testDataFactory.getTestMeasurementTo(tankID); ResultTo measurementToResultTo = measurementService.addIotAuthorizedMeasurement(testMeasurementTo); // Then assertNotNull("Should not happen!",measurementToResultTo); - assertNotNull("Should not happen!",measurementToResultTo.getValue()); - CATEGORY messageType = measurementToResultTo.getMessage().getType(); + assertNotNull("Should not happen! ResultTO contains no value",measurementToResultTo.getValue()); + Message.CATEGORY messageType = measurementToResultTo.getMessage().getType(); assertTrue("Creating measurement failed?", messageType.equals(Message.CATEGORY.INFO)); } + } diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/NotificationServiceTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/services/NotificationServiceTest.java index 16a5f5a2..171c22cc 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/NotificationServiceTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/services/NotificationServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -8,12 +8,19 @@ import de.bluewhale.sabi.configs.AppConfig; import jakarta.mail.MessagingException; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; import static org.springframework.test.util.AssertionErrors.fail; @@ -23,13 +30,31 @@ * @author Stefan Schubert */ @SpringBootTest +@Testcontainers +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @ContextConfiguration(classes = AppConfig.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +@Tag("ServiceTest") public class NotificationServiceTest { + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + @Autowired NotificationService notificationService; + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + + @Test @Disabled // To avoid mail spam, this test with real mail server settings was just to see diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/PlagueCenterServiceTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/services/PlagueCenterServiceTest.java index 7149af75..eb46e811 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/PlagueCenterServiceTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/services/PlagueCenterServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -10,19 +10,27 @@ import de.bluewhale.sabi.persistence.model.LocalizedPlagueStatusEntity; import de.bluewhale.sabi.persistence.model.PlagueStatusEntity; import de.bluewhale.sabi.persistence.repositories.PlagueStatusRepository; +import de.bluewhale.sabi.util.TestDataFactory; import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.transaction.annotation.Transactional; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import java.util.ArrayList; import java.util.List; +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.BDDMockito.given; import static org.springframework.test.util.AssertionErrors.assertEquals; @@ -33,12 +41,24 @@ * Business-Layer tests for PlagueCenterService. */ @SpringBootTest +@Testcontainers @ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +@Tag("ServiceTest") public class PlagueCenterServiceTest { // ------------------------------ FIELDS ------------------------------ + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + @Autowired private PlagueCenterService plagueCenterService; @@ -48,6 +68,14 @@ public class PlagueCenterServiceTest { @Autowired private UserService userService; + static TestDataFactory testDataFactory = TestDataFactory.getInstance(); + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + + // -------------------------- OTHER METHODS -------------------------- @@ -55,26 +83,8 @@ public class PlagueCenterServiceTest { @Transactional public void testListTranslatedPlagueStatus() throws Exception { // Given - PlagueStatusEntity plagueStatusEntity = new PlagueStatusEntity(); - plagueStatusEntity.setId(1l); - - ArrayList localizedPlagueStatusEntities = new ArrayList<>(); - - LocalizedPlagueStatusEntity localizedPlagueStatus1 = new LocalizedPlagueStatusEntity(); - localizedPlagueStatus1.setPlague_status_id(1); - localizedPlagueStatus1.setId(99l); - localizedPlagueStatus1.setDescription("Spreading"); - localizedPlagueStatus1.setLanguage("en"); - - LocalizedPlagueStatusEntity localizedPlagueStatus2 = new LocalizedPlagueStatusEntity(); - localizedPlagueStatus2.setPlague_status_id(1); - localizedPlagueStatus2.setId(88l); - localizedPlagueStatus2.setDescription("Ausweitend"); - localizedPlagueStatus2.setLanguage("de"); - - localizedPlagueStatusEntities.add(localizedPlagueStatus1); - localizedPlagueStatusEntities.add(localizedPlagueStatus2); - + PlagueStatusEntity plagueStatusEntity = testDataFactory.getTestPlagueStatusEntity(); + List localizedPlagueStatusEntities = testDataFactory.getTestLocalizedPlagueStatusEntities(); plagueStatusEntity.setLocalizedPlagueStatusEntities(localizedPlagueStatusEntities); ArrayList plagueStatusEntities = new ArrayList(); diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/TankServiceTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/services/TankServiceTest.java index 842552d1..8fe175ee 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/TankServiceTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/services/TankServiceTest.java @@ -1,27 +1,45 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ package de.bluewhale.sabi.services; -import de.bluewhale.sabi.TestDataFactory; import de.bluewhale.sabi.configs.AppConfig; -import de.bluewhale.sabi.exception.Message.CATEGORY; -import de.bluewhale.sabi.model.*; +import de.bluewhale.sabi.exception.Message; +import de.bluewhale.sabi.model.AquariumTo; +import de.bluewhale.sabi.model.ResultTo; +import de.bluewhale.sabi.model.UserTo; +import de.bluewhale.sabi.persistence.model.AquariumEntity; +import de.bluewhale.sabi.persistence.model.UserEntity; +import de.bluewhale.sabi.persistence.repositories.AquariumRepository; +import de.bluewhale.sabi.persistence.repositories.UserRepository; +import de.bluewhale.sabi.util.TestDataFactory; import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ContextConfiguration; -import org.springframework.transaction.annotation.Transactional; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import java.util.List; -import static de.bluewhale.sabi.TestDataFactory.TESTUSER_EMAIL1; +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; +import static de.bluewhale.sabi.util.TestDataFactory.TESTUSER_EMAIL1; +import static de.bluewhale.sabi.util.TestDataFactory.TEST_TANK_ID; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; import static org.springframework.test.util.AssertionErrors.*; @@ -31,161 +49,188 @@ * Date: 30.08.15 */ @SpringBootTest +@Testcontainers @ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Tag("ServiceTest") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class TankServiceTest { - private static final String JABA_DABA_DOOOOO = "JabaDabaDooooo"; - // ------------------------------ FIELDS ------------------------------ - @Autowired - private TankService tankService; + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + private static final String JABA_DABA_DOOOOO = "JabaDabaDooooo"; - @Autowired - private UserService userService; -// -------------------------- OTHER METHODS -------------------------- - - /** - * Tank properties are something like name, description, size. - * Excluded are inhabitants etc... they are linked to a tank - * - * @throws Exception - */ - @Test - @Transactional - public void testAlterTankProperties() throws Exception { - // Given - TestDataFactory testDataFactory = TestDataFactory.getInstance().withUserService(userService); - final UserTo registeredUser = testDataFactory.getRegisterNewTestUser(TESTUSER_EMAIL1); - - AquariumTo aquariumTo = testDataFactory.getTestAquariumTo(); - ResultTo aquariumToResultTo = tankService.registerNewTank(aquariumTo, registeredUser.getEmail()); - - // When - aquariumTo = aquariumToResultTo.getValue(); - aquariumTo.setDescription(JABA_DABA_DOOOOO); - - // Then - aquariumToResultTo = tankService.updateTank(aquariumTo, TESTUSER_EMAIL1); - - aquariumTo = tankService.getTank(aquariumToResultTo.getValue().getId(), TESTUSER_EMAIL1); - - // Then - assertEquals("Tank was not updated", aquariumTo.getDescription(), JABA_DABA_DOOOOO); - } - - @Test - @Transactional - public void testListUsersTanks() throws Exception { - // Given - TestDataFactory testDataFactory = TestDataFactory.getInstance().withTankService(tankService).withUserService(userService); - UserTo registeredUser = testDataFactory.getRegisterNewTestUser(TESTUSER_EMAIL1); - - AquariumTo aquariumTo1 = new AquariumTo(); - aquariumTo1.setDescription("Small Test Tank"); - aquariumTo1.setSize(40); - aquariumTo1.setSizeUnit(SizeUnit.LITER); - aquariumTo1.setWaterType(WaterType.SEA_WATER); - - AquariumTo aquariumTo2 = new AquariumTo(); - aquariumTo2.setDescription("Big Test Tank"); - aquariumTo2.setSize(120); - aquariumTo2.setSizeUnit(SizeUnit.LITER); - aquariumTo2.setWaterType(WaterType.SEA_WATER); - - ResultTo aquariumToResultTo = tankService.registerNewTank(aquariumTo1, registeredUser.getEmail()); - ResultTo aquariumToResultTo1 = tankService.registerNewTank(aquariumTo2, registeredUser.getEmail()); - - // When - List usersAquariums = tankService.listTanks(TESTUSER_EMAIL1); - - // Then - assertNotNull("Oops",usersAquariums); - assertEquals("Persisted Testdata?",2, usersAquariums.size()); - assertTrue(usersAquariums.get(0).getDescription() == aquariumTo1.getDescription()); - assertTrue(usersAquariums.get(1).getDescription() == aquariumTo2.getDescription()); - } - -/* - @BeforeClass - public static void init() throws NamingException { - } -*/ - -/* @AfterClass - public static void tearDownClass() throws Exception { - } -*/ - - - @Test - @Transactional - public void testRegisterNewTank() throws Exception { - // Given - TestDataFactory testDataFactory = TestDataFactory.getInstance().withUserService(userService); - final UserTo registeredUser = testDataFactory.getRegisterNewTestUser(TESTUSER_EMAIL1); - - final AquariumTo aquariumTo = testDataFactory.getTestAquariumTo(); - - // When - final ResultTo aquariumToResultTo = tankService.registerNewTank(aquariumTo, TESTUSER_EMAIL1); + static TestDataFactory testDataFactory = TestDataFactory.getInstance(); - // Then - assertNotNull("ResultObject must not be empty",aquariumToResultTo); - final AquariumTo aquarium = aquariumToResultTo.getValue(); - assertNotNull("ResultObject had no Aquarium inside!",aquarium); - assertNotNull("Tank ID was not provided!",aquarium.getId()); - assertEquals("User Assignment missing.",registeredUser.getId(), aquarium.getUserId()); - assertEquals("Wrong message type.", CATEGORY.INFO, aquariumToResultTo.getMessage().getType()); - } + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); - @Test - @Transactional - public void testCreateTemperatureAPIKeyForTankAndRetrieveTankByAPIKey() throws Exception { - // Given - prepersisted Testdata - TestDataFactory testDataFactory = TestDataFactory.getInstance().withUserService(userService); - final AquariumTo aquariumTo = testDataFactory.getTestAquariumTo(); - UserTo testUser = testDataFactory.getRegisterNewTestUser(TESTUSER_EMAIL1); - final ResultTo aquariumToResultTo = tankService.registerNewTank(aquariumTo, TESTUSER_EMAIL1); + // ------------------------------ FIELDS ------------------------------ - // When - ResultTo resultOfAPIKeyGeneration = tankService.generateAndAssignNewTemperatureApiKey(aquariumToResultTo.getValue().getId(), TESTUSER_EMAIL1); - AquariumTo retrievedTankByAPIKey = tankService.getTankForTemperatureApiKey(resultOfAPIKeyGeneration.getValue().getTemperatueApiKey()); + @Autowired + private TankService tankService; - // Then + @Autowired + private UserService userService; - assertNotNull("ResultObject for generate an API Key must not be empty",resultOfAPIKeyGeneration); + @MockBean + private UserRepository userRepository; + @MockBean + private AquariumRepository aquariumRepository; - final AquariumTo aquariumWithJustAddedAPIKey = resultOfAPIKeyGeneration.getValue(); - assertNotNull("ResultObject for generate an API Key had no Aquarium inside!",aquariumWithJustAddedAPIKey); - assertNotNull("API Key has not been stored!",aquariumWithJustAddedAPIKey.getTemperatueApiKey()); - assertEquals("Wrong message type.", CATEGORY.INFO, resultOfAPIKeyGeneration.getMessage().getType()); - - assertNotNull("Did not retrieved a tank by API Key",retrievedTankByAPIKey); - assertEquals("Ouch - returned different Tank! This is a MAJOR Bug", aquariumWithJustAddedAPIKey.getId(), retrievedTankByAPIKey.getId()); - } - - - @Test - @Transactional - public void testRemoveTank() throws Exception { - - // Given - TestDataFactory testDataFactory = TestDataFactory.getInstance().withUserService(userService); - final UserTo registeredUser = testDataFactory.getRegisterNewTestUser(TESTUSER_EMAIL1); - final AquariumTo aquariumTo = testDataFactory.getTestAquariumTo(); - final ResultTo aquariumToResultTo = tankService.registerNewTank(aquariumTo, registeredUser.getEmail()); - - Long persistedTankId = aquariumToResultTo.getValue().getId(); - tankService.removeTank(persistedTankId, TESTUSER_EMAIL1); - - // When - AquariumTo tankAfterDeletion = tankService.getTank(persistedTankId, TESTUSER_EMAIL1); - - // Then - assertNull("Users tank was supposed to be removed!", tankAfterDeletion); - - } +// -------------------------- OTHER METHODS -------------------------- + /** + * Tank properties are something like name, description, size. + * Excluded are inhabitants etc... they are linked to a tank + * + * @throws Exception + */ + @Test + @Rollback + public void testAlterTankProperties() throws Exception { + // Given + final UserTo testUser = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity userEntity = testDataFactory.getNewTestUserEntity(testUser); + + AquariumTo aquariumTo = testDataFactory.getTestAquariumTo(); + aquariumTo.setDescription(JABA_DABA_DOOOOO); // Change the description + AquariumEntity aquariumEntity = testDataFactory.getTestAquariumEntity(aquariumTo, userEntity); + + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(userEntity); + given(aquariumRepository.getAquariumEntityByIdAndUser_IdIs(TEST_TANK_ID, userEntity.getId())).willReturn(aquariumEntity); + given(aquariumRepository.saveAndFlush(any())).willReturn(aquariumEntity); + + // When + ResultTo aquariumToResultTo = tankService.updateTank(aquariumTo, TESTUSER_EMAIL1); + + // Then + aquariumTo = tankService.getTank(aquariumToResultTo.getValue().getId(), TESTUSER_EMAIL1); + + // Then + assertEquals("Tank was not updated", aquariumTo.getDescription(), JABA_DABA_DOOOOO); + } + + + @Test + public void testListUsersTanks() throws Exception { + // Given + UserTo testUser = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity userEntity = testDataFactory.getNewTestUserEntity(testUser); + + AquariumEntity aquarium1 = testDataFactory.getTestAquariumEntity(testDataFactory.getTestAquariumTo(), userEntity); + AquariumEntity aquarium2 = testDataFactory.getTestAquariumEntity(testDataFactory.getTestAquariumTo(), userEntity); + aquarium2.setDescription("Second Tank"); + + // Mocking + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(userEntity); + given(aquariumRepository.findAllByUser_IdIs(userEntity.getId())).willReturn(List.of(aquarium1, aquarium2)); + + // When + List usersAquariums = tankService.listTanks(TESTUSER_EMAIL1); + + // Then + assertNotNull("Oops", usersAquariums); + assertEquals("Lost Testdata?", 2, usersAquariums.size()); + assertTrue(usersAquariums.get(0).getDescription() == aquarium1.getDescription()); + assertTrue(usersAquariums.get(1).getDescription() == aquarium2.getDescription()); + } + + + @Test + @Rollback + public void testRegisterNewTank() throws Exception { + // Given + final UserTo testUser = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity userEntity = testDataFactory.getNewTestUserEntity(testUser); + + final AquariumTo aquariumTo = testDataFactory.getTestAquariumTo(); + AquariumEntity aquarium1 = testDataFactory.getTestAquariumEntity(aquariumTo, userEntity); + + // Mocking + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(userEntity); + given(aquariumRepository.existsById(TEST_TANK_ID)).willReturn(false); + given(aquariumRepository.saveAndFlush(any())).willReturn(aquarium1); + + // When + final ResultTo aquariumToResultTo = tankService.registerNewTank(aquariumTo, TESTUSER_EMAIL1); + + // Then + assertNotNull("ResultObject must not be empty", aquariumToResultTo); + final AquariumTo aquarium = aquariumToResultTo.getValue(); + assertNotNull("ResultObject had no Aquarium inside!", aquarium); + assertNotNull("Tank ID was not provided!", aquarium.getId()); + assertEquals("User Assignment missing.", testUser.getId(), aquarium.getUserId()); + assertEquals("Wrong message type.", Message.CATEGORY.INFO, aquariumToResultTo.getMessage().getType()); + } + + @Test + @Rollback + public void testCreateTemperatureAPIKeyForTankAndRetrieveTankByAPIKey() throws Exception { + // Given + UserTo testUserTo = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity testUserEntity = testDataFactory.getNewTestUserEntity(testUserTo); + AquariumTo aquariumTo = testDataFactory.getTestAquariumFor(testUserTo); + AquariumEntity testAquariumEntity = testDataFactory.getTestAquariumEntity(aquariumTo, testUserEntity); + AquariumEntity testAquariumEntityWithAPIKey = testDataFactory.getTestAquariumEntity(aquariumTo, testUserEntity); + testAquariumEntityWithAPIKey.setTemperatureApiKey("1234567890"); + + // Mocking + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(testUserEntity); + given(aquariumRepository.getAquariumEntityByIdAndUser_IdIs(TEST_TANK_ID, testUserTo.getId())).willReturn(testAquariumEntity); + given(aquariumRepository.getAquariumEntityByTemperatureApiKeyEquals(any())) + .willReturn(null) + .willReturn(testAquariumEntityWithAPIKey); + + // When + ResultTo resultOfAPIKeyGeneration = tankService.generateAndAssignNewTemperatureApiKey(aquariumTo.getId(), TESTUSER_EMAIL1); + AquariumTo retrievedTankByAPIKey = tankService.getTankForTemperatureApiKey(resultOfAPIKeyGeneration.getValue().getTemperatureApiKey()); + + // Then + + assertNotNull("ResultObject for generate an API Key must not be empty", resultOfAPIKeyGeneration); + + final AquariumTo aquariumWithJustAddedAPIKey = resultOfAPIKeyGeneration.getValue(); + assertNotNull("ResultObject for generate an API Key had no Aquarium inside!", aquariumWithJustAddedAPIKey); + assertNotNull("API Key has not been stored!", aquariumWithJustAddedAPIKey.getTemperatureApiKey()); + assertEquals("Wrong message type.", Message.CATEGORY.INFO, resultOfAPIKeyGeneration.getMessage().getType()); + + assertNotNull("Did not retrieved a tank by API Key", retrievedTankByAPIKey); + assertEquals("Ouch - returned different Tank! This is a MAJOR Bug", aquariumWithJustAddedAPIKey.getId(), retrievedTankByAPIKey.getId()); + } + + @Test + @Rollback + public void testRemoveTank() throws Exception { + + // Given + final UserTo testUserTo = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity testUserEntity = testDataFactory.getNewTestUserEntity(testUserTo); + final AquariumTo aquariumTo = testDataFactory.getTestAquariumTo(); + AquariumEntity testAquariumEntity = testDataFactory.getTestAquariumEntity(aquariumTo, testUserEntity); + + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(testUserEntity); + given(aquariumRepository.getAquariumEntityByIdAndUser_IdIs(aquariumTo.getId(), testUserTo.getId())) + .willReturn(testAquariumEntity) + .willReturn(null); + doNothing().when(aquariumRepository).delete(testAquariumEntity); + + // When + tankService.removeTank(aquariumTo.getId(), TESTUSER_EMAIL1); + + // Then + AquariumTo tankAfterDeletion = tankService.getTank(aquariumTo.getId(), TESTUSER_EMAIL1); + assertNull("Users tank was supposed to be removed!", tankAfterDeletion); + + } } diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/UserServiceTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/services/UserServiceTest.java index 74feec66..deb0be89 100644 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/UserServiceTest.java +++ b/sabi-server/src/test/java/de/bluewhale/sabi/services/UserServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -13,18 +13,29 @@ import de.bluewhale.sabi.model.UserTo; import de.bluewhale.sabi.persistence.model.UserEntity; import de.bluewhale.sabi.persistence.repositories.UserRepository; +import de.bluewhale.sabi.util.TestDataFactory; import jakarta.validation.constraints.NotNull; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ContextConfiguration; -import org.springframework.transaction.annotation.Transactional; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; -import java.util.Locale; - -import static de.bluewhale.sabi.TestDataFactory.*; +import static de.bluewhale.sabi.util.TestContainerVersions.MARIADB_11_3_2; +import static de.bluewhale.sabi.util.TestDataFactory.TESTUSER_EMAIL1; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; /** @@ -33,319 +44,332 @@ * Date: 30.08.15 */ @SpringBootTest +@Testcontainers @ContextConfiguration(classes = AppConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Tag("ServiceTest") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class UserServiceTest { + + /* + NOTICE This Testclass initializes a Testcontainer to satisfy the + Spring Boot Context Initialization of JPAConfig. In fact we don't rely here on the + Database level, as for test layer isolation we completely mock the repositories here. + The Testcontainer is just needed to satisfy the Spring Boot Context Initialization. + In future this Testclass might be refactored to be able to run without spring context, + but for now we keep it as it is. + */ + + static TestDataFactory testDataFactory = TestDataFactory.getInstance(); + + @Container + @ServiceConnection + // This does the trick. Spring Autoconfigures itself to connect against this container data requests- + static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_11_3_2); + // ------------------------------ FIELDS ------------------------------ - @Autowired - UserService userService; + @Autowired + UserService userService; + + @Autowired + PasswordEncoder passwordEncoder; - @Autowired - UserRepository userRepository; + @MockBean + UserRepository userRepository; // -------------------------- OTHER METHODS -------------------------- -/* - @BeforeClass - public static void init() throws NamingException { - } -*/ - -/* @AfterClass - public static void tearDownClass() throws Exception { - } -*/ - - - /** - * This test was written to demonstrate that @NotNull Annotations won't do anything if confronted with null values. - * The reason for this is, that this is only half of the part of correct bean validation. - * As soon as we start to introduce constraint checking through a JSR-303 Validator class, this test might fail. - * - * @throws Exception - */ - @Test - public void testNullValidation() throws Exception { - // Given - // Nothing - // When - userService.validateUser(null, null); - // Then - // No Exception occurs - } - - - @Test - @Transactional - public void testRegisterExistingUserWithSameEmailAdress() throws Exception { - // Given - NewRegistrationTO userTo1 = new NewRegistrationTO(TESTUSER_EMAIL1, "User1", VALID_PASSWORD); - final ResultTo firstUserResultTo = userService.registerNewUser(userTo1); - - // When - NewRegistrationTO userTo2 = new NewRegistrationTO(TESTUSER_EMAIL1, "User2", VALID_PASSWORD); - final ResultTo userResultTo = userService.registerNewUser(userTo2); - - // Then - assertNotNull(userResultTo); - assertNull(userResultTo.getValue()); - final Message message = userResultTo.getMessage(); - assertNotNull(message); - assertNotNull(message.getCode()); - assertEquals(message.getCode().getExceptionCode(), - AuthExceptionCodes.USER_REGISTRATION_FAILED); - assertEquals(message.getCode(), AuthMessageCodes.USER_ALREADY_EXISTS_WITH_THIS_EMAIL); - } - - @Test - @Transactional - public void testUserProfileUpdate() throws Exception { - // Given - NewRegistrationTO userTo1 = new NewRegistrationTO(TESTUSER_EMAIL1, "User1", VALID_PASSWORD); - userTo1.setLanguage(Locale.GERMAN.getLanguage()); - userTo1.setCountry(Locale.GERMAN.getCountry()); - final ResultTo firstUserResultTo = userService.registerNewUser(userTo1); - - // When - UserProfileTo userProfileTo = new UserProfileTo( - Locale.ENGLISH.getLanguage(), - Locale.ENGLISH.getCountry()); - - ResultTo userProfileResultTo = userService.updateProfile(userProfileTo, TESTUSER_EMAIL1); - UserEntity userEntity = userRepository.getByEmail(TESTUSER_EMAIL1); - - // Then - final Message message = userProfileResultTo.getMessage(); - assertNotNull(message); - assertEquals(message.getCode(), CommonMessageCodes.UPDATE_SUCCEEDED); - - assertEquals(userEntity.getLanguage(), Locale.ENGLISH.getLanguage()); - assertEquals(userEntity.getCountry(), Locale.ENGLISH.getCountry()); - } - - @Test - @Transactional - public void testGetUserProfile() throws Exception { - // Given - NewRegistrationTO userTo1 = new NewRegistrationTO(TESTUSER_EMAIL1, "User1", VALID_PASSWORD); - userTo1.setLanguage(Locale.GERMAN.getLanguage()); - userTo1.setCountry(Locale.GERMAN.getCountry()); - final ResultTo firstUserResultTo = userService.registerNewUser(userTo1); - - // When - ResultTo userProfileToResultTo = userService.getUserProfile(TESTUSER_EMAIL1); - - // Then - final Message message = userProfileToResultTo.getMessage(); - assertNotNull(userProfileToResultTo); - assertNotNull(message); - assertEquals(message.getCode(), CommonMessageCodes.OK); - - UserProfileTo userProfileTo = userProfileToResultTo.getValue(); - assertNotNull(userProfileTo); - assertEquals(userProfileTo.getLanguage(), userTo1.getLanguage()); - assertEquals(userProfileTo.getCountry(), userTo1.getCountry()); - - } - - @Test - @Transactional - public void testFraudUserProfileUpdate() throws Exception { - assertThrows(BusinessException.class, () -> { - - // Given - NewRegistrationTO userTo1 = new NewRegistrationTO(TESTUSER_EMAIL1, "User1", VALID_PASSWORD); - userTo1.setLanguage(Locale.GERMAN.getLanguage()); - userTo1.setCountry(Locale.GERMAN.getCountry()); - final ResultTo firstUserResultTo = userService.registerNewUser(userTo1); - - // When - UserProfileTo userProfileTo = new UserProfileTo( - Locale.ENGLISH.getLanguage(), - Locale.ENGLISH.getCountry()); - - ResultTo userProfileResultTo = userService.updateProfile(userProfileTo, "DOES@NOT.MATCH"); - - }); - } - - @Test - @Transactional - public void testRegisterExistingUserWithSameUsername() throws Exception { - // Given - NewRegistrationTO userTo1 = new NewRegistrationTO(TESTUSER_EMAIL1, "User1", VALID_PASSWORD); - final ResultTo firstUserResultTo = userService.registerNewUser(userTo1); - - // When - NewRegistrationTO userTo2 = new NewRegistrationTO(TESTUSER_EMAIL2, "User1", VALID_PASSWORD); - final ResultTo userResultTo = userService.registerNewUser(userTo2); - - // Then - assertNotNull(userResultTo); - assertNull(userResultTo.getValue()); - final Message message = userResultTo.getMessage(); - assertNotNull(message); - assertNotNull(message.getCode()); - assertEquals(message.getCode().getExceptionCode(), - AuthExceptionCodes.USER_REGISTRATION_FAILED); - assertEquals(message.getCode(), AuthMessageCodes.USER_ALREADY_EXISTS_WITH_THIS_USERNAME); - } - - @Test - @Transactional - public void testRegisterUser() throws Exception { - // Given - NewRegistrationTO userTo = new NewRegistrationTO(TESTUSER_EMAIL1, "Tester", VALID_PASSWORD); - - // When - final ResultTo userToResultTo = userService.registerNewUser(userTo); - - // Then - assertNotNull(userToResultTo); - assertNotNull(userToResultTo.getValue()); - assertNotNull(userToResultTo.getValue().getId()); - assertNotNull(userToResultTo.getValue().getValidationToken()); - } - - - /** - * During register process the user gets an email with an token. This test the service to validate the token for the user. - * - * @throws Exception - */ - @Test - @Transactional - public void testUserValidation() throws Exception { - // Given - NewRegistrationTO userTo = new NewRegistrationTO(TESTUSER_EMAIL1, "Tester", VALID_PASSWORD); - final ResultTo userToResultTo = userService.registerNewUser(userTo); - final String token = userToResultTo.getValue().getValidationToken(); - - // When - boolean isValidated = userService.validateUser(userTo.getEmail(), token); - boolean brokenValidation = userService.validateUser(userTo.getEmail(), "otherToken"); - - // Then - assertTrue(isValidated); - assertFalse(brokenValidation); - } - - - /** - * For users safety we are using a one way password encryption. So that the DB admin is not ensnared to misuse the data. - * - * @throws Exception - */ - @Test - @Transactional - public void testStoreEncryptedPasswords() throws Exception { - // Given - - final String clearTextPassword = VALID_PASSWORD; - NewRegistrationTO userTo = new NewRegistrationTO(TESTUSER_EMAIL1, "Tester", clearTextPassword); - - // When - final ResultTo userToResultTo = userService.registerNewUser(userTo); - - // Then - final UserTo storedUser = userToResultTo.getValue(); - final String encryptedPassword = storedUser.getPassword(); - assertNotEquals("Stored passwords must be encrypted!", clearTextPassword, encryptedPassword); - } - - - @Test - @Transactional - public void testSignIn() throws Exception { - // Given - final String clearTextPassword = VALID_PASSWORD; - NewRegistrationTO userTo = new NewRegistrationTO(TESTUSER_EMAIL1, "Tester", clearTextPassword); - ResultTo resultTo = userService.registerNewUser(userTo); - userService.validateUser(TESTUSER_EMAIL1, resultTo.getValue().getValidationToken()); - - // When - @NotNull ResultTo signInResult = userService.signIn(TESTUSER_EMAIL1, clearTextPassword); - - // Then - assertNotNull("Contract Broken? Expected Session Token", signInResult.getValue()); - - final Message message = signInResult.getMessage(); - assertNotNull(message); - assertNotNull(message.getCode()); - assertEquals(AuthMessageCodes.SIGNIN_SUCCEEDED, message.getCode()); - } - - - @Test - @Transactional - public void testSignInWithUnknownUserName() throws Exception { - // Given - // Nothing - - // When - @NotNull ResultTo signInResult = userService.signIn("I Don't care@anything", "abc"); - - // Then - assertNotNull(signInResult); - final Message message = signInResult.getMessage(); - assertNotNull(message); - assertNotNull(message.getCode()); - assertEquals(message.getCode().getExceptionCode(), - AuthExceptionCodes.AUTHENTICATION_FAILED); - assertEquals(message.getCode(), AuthMessageCodes.UNKNOWN_USERNAME); - } - - - @Test - @Transactional - public void testSignInWithUnknownOrWrongPassword() throws Exception { - // Given a user which registered and validated his email address. - final String clearTextPassword = VALID_PASSWORD; - NewRegistrationTO userTo = new NewRegistrationTO(TESTUSER_EMAIL1, "Tester", clearTextPassword); - ResultTo userToResultTo = userService.registerNewUser(userTo); - UserTo createdUserTo = userToResultTo.getValue(); - userService.validateUser(TESTUSER_EMAIL1, createdUserTo.getValidationToken()); - - - // When - @NotNull ResultTo signInResult = userService.signIn(TESTUSER_EMAIL1, "abc"); - - // Then - assertNotNull(signInResult); - final Message message = signInResult.getMessage(); - assertNotNull(message); - assertNotNull(message.getCode()); - assertEquals(message.getCode().getExceptionCode(), - AuthExceptionCodes.AUTHENTICATION_FAILED); - assertEquals(message.getCode(), AuthMessageCodes.WRONG_PASSWORD); - } - - - @Test - @Transactional - public void testSignInWithUnvalidatedEmailFails() throws Exception { - - // For being able to login we need a clearly validated user account, - // for which we use the verified email address - - // Given - final String clearTextPassword = VALID_PASSWORD; - NewRegistrationTO userTo = new NewRegistrationTO(TESTUSER_EMAIL1, "Tester", clearTextPassword); - userService.registerNewUser(userTo); - - // When - @NotNull ResultTo signInResult = userService.signIn(TESTUSER_EMAIL1, "abc"); - - // Then - assertNotNull(signInResult); - final Message message = signInResult.getMessage(); - assertNotNull(message); - assertNotNull(message.getCode()); - assertEquals(message.getCode().getExceptionCode(), - AuthExceptionCodes.AUTHENTICATION_FAILED); - assertEquals(message.getCode(), AuthMessageCodes.INCOMPLETE_REGISTRATION_PROCESS); - } + + /** + * This test was written to demonstrate that @NotNull Annotations won't do anything if confronted with null values. + * The reason for this is, that this is only half of the part of correct bean validation. + * As soon as we start to introduce constraint checking through a JSR-303 Validator class, this test might fail. + * + * @throws Exception + */ + @Test + public void testNullValidation() throws Exception { + // Given + // Nothing + // When + userService.validateUser(null, null); + // Then + // No Exception occurs + } + + + @Test + @Rollback + public void testRegisterExistingUserWithSameEmailAddress() throws Exception { + // Given + + NewRegistrationTO newRegistrationTO = testDataFactory.getNewRegistrationTO(TESTUSER_EMAIL1); + + UserTo testUserTo = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity allreadyExistingUser = testDataFactory.getNewTestUserEntity(testUserTo); + + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(allreadyExistingUser); + given(userRepository.getByUsername(testUserTo.getUsername())).willReturn(allreadyExistingUser); + + // When + final ResultTo userResultTo = userService.registerNewUser(newRegistrationTO); + + // Then + assertNotNull(userResultTo); + assertNull(userResultTo.getValue()); + final Message message = userResultTo.getMessage(); + assertNotNull(message); + assertNotNull(message.getCode()); + assertEquals(message.getCode().getExceptionCode(), + AuthExceptionCodes.USER_REGISTRATION_FAILED); + assertEquals(message.getCode(), AuthMessageCodes.USER_ALREADY_EXISTS_WITH_THIS_EMAIL); + } + + @Test + @Rollback + public void testUserProfileUpdate() throws Exception { + // Given + UserProfileTo userProfileTo = testDataFactory.getBasicUserProfileTo(); + UserTo testUserTo = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity testUserEntity = testDataFactory.getNewTestUserEntity(testUserTo); + + // Mocking the UserRepository + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(testUserEntity); + + // When + ResultTo userProfileResultTo = userService.updateProfile(userProfileTo, TESTUSER_EMAIL1); + + // Then + final Message message = userProfileResultTo.getMessage(); + assertNotNull(message); + assertEquals(message.getCode(), CommonMessageCodes.UPDATE_SUCCEEDED); + } + + @Test + public void testGetUserProfile() throws Exception { + // Given + UserTo testUserTo = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity newTestUserEntity = testDataFactory.getNewTestUserEntity(testUserTo); + + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(newTestUserEntity); + + // When + ResultTo userProfileToResultTo = userService.getUserProfile(TESTUSER_EMAIL1); + + // Then + final Message message = userProfileToResultTo.getMessage(); + assertNotNull(userProfileToResultTo); + assertNotNull(message); + assertEquals(message.getCode(), CommonMessageCodes.OK); + + UserProfileTo userProfileTo = userProfileToResultTo.getValue(); + assertNotNull(userProfileTo); + } + + @Test + @Rollback + public void testFraudUserProfileUpdate() throws Exception { + assertThrows(BusinessException.class, () -> { + + // Given + UserProfileTo basicUserProfileTo = testDataFactory.getBasicUserProfileTo(); + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(null); + + // When (result in an exception) + ResultTo userProfileResultTo = userService.updateProfile(basicUserProfileTo, "DOES@NOT.MATCH"); + + }); + } + + @Test + @Rollback + public void testRegisterExistingUserWithSameUsername() throws Exception { + // Given + UserTo testUserTo = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity newTestUserEntity = testDataFactory.getNewTestUserEntity(testUserTo); + NewRegistrationTO newRegistrationTO = testDataFactory.getNewRegistrationTO(TESTUSER_EMAIL1); + + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(null); + given(userRepository.getByUsername(any())).willReturn(newTestUserEntity); + + + // When + final ResultTo userResultTo = userService.registerNewUser(newRegistrationTO); + + // Then + assertNotNull(userResultTo); + assertNull(userResultTo.getValue()); + final Message message = userResultTo.getMessage(); + assertNotNull(message); + assertNotNull(message.getCode()); + assertEquals(message.getCode().getExceptionCode(), + AuthExceptionCodes.USER_REGISTRATION_FAILED); + assertEquals(message.getCode(), AuthMessageCodes.USER_ALREADY_EXISTS_WITH_THIS_USERNAME); + } + + @Test + @Rollback + public void testRegisterUser() throws Exception { + // Given + NewRegistrationTO newRegistrationTO = testDataFactory.getNewRegistrationTO(TESTUSER_EMAIL1); + UserTo testUserTo = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity newTestUserEntity = testDataFactory.getNewTestUserEntity(testUserTo); + newTestUserEntity.setValidateToken("123456"); + + + // Mocking the UserRepository + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(null); + given(userRepository.getByUsername(any())).willReturn(null); + given(userRepository.saveAndFlush(any())).willReturn(newTestUserEntity); + + // When + final ResultTo userToResultTo = userService.registerNewUser(newRegistrationTO); + + // Then + assertNotNull(userToResultTo); + assertNotNull(userToResultTo.getValue()); + assertNotNull(userToResultTo.getValue().getId()); + assertNotNull(userToResultTo.getValue().getValidationToken()); + } + + + /** + * During register process the user gets an email with an token. This test the service to validate the token for the user. + * + * @throws Exception + */ + @Test + public void testUserValidation() throws Exception { + // Given + UserTo testUserTo = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity newTestUserEntity = testDataFactory.getNewTestUserEntity(testUserTo); + + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(newTestUserEntity); + + // When + boolean isValidated = userService.validateUser(testUserTo.getEmail(), newTestUserEntity.getValidateToken()); + boolean brokenValidation = userService.validateUser(testUserTo.getEmail(), "otherToken"); + + // Then + assertTrue(isValidated); + assertFalse(brokenValidation); + } + + + /** + * For users safety we are using a one way password encryption. So that the DB admin is not ensnared to misuse the data. + * + * @throws Exception + */ + @Test + public void testStoreEncryptedPasswords() throws Exception { + // Given + NewRegistrationTO newRegistrationTO = testDataFactory.getNewRegistrationTO(TESTUSER_EMAIL1); + + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(null); + given(userRepository.getByUsername(any())).willReturn(null); + when(userRepository.saveAndFlush(any(UserEntity.class))).thenAnswer(invocation -> invocation.getArgument(0)); + + // When + final ResultTo userToResultTo = userService.registerNewUser(newRegistrationTO); + + // Then + final UserTo storedUser = userToResultTo.getValue(); + final String encryptedPassword = storedUser.getPassword(); + assertNotEquals("Stored passwords must be encrypted!", newRegistrationTO.getPassword(), encryptedPassword); + } + + + @Test + public void testSignIn() throws Exception { + // Given + UserTo testUserTo = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity newTestUserEntity = testDataFactory.getNewTestUserEntity(testUserTo); + newTestUserEntity.setPassword(passwordEncoder.encode(testUserTo.getPassword())); + + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(newTestUserEntity); + + // When + @NotNull ResultTo signInResult = userService.signIn(TESTUSER_EMAIL1, testUserTo.getPassword()); + + // Then + assertNotNull("Contract Broken? Expected Session Token", signInResult.getValue()); + + final Message message = signInResult.getMessage(); + assertNotNull(message); + assertNotNull(message.getCode()); + assertEquals(AuthMessageCodes.SIGNIN_SUCCEEDED, message.getCode()); + } + + + @Test + public void testSignInWithUnknownUserName() throws Exception { + // Given + given(userRepository.getByEmail(any())).willReturn(null); + given(userRepository.getByUsername(any())).willReturn(null); + + // When + @NotNull ResultTo signInResult = userService.signIn("I Don't care@anything", "abc"); + + // Then + assertNotNull(signInResult); + final Message message = signInResult.getMessage(); + assertNotNull(message); + assertNotNull(message.getCode()); + assertEquals(message.getCode().getExceptionCode(), + AuthExceptionCodes.AUTHENTICATION_FAILED); + assertEquals(message.getCode(), AuthMessageCodes.UNKNOWN_USERNAME); + } + + + @Test + public void testSignInWithUnknownOrWrongPassword() throws Exception { + // Given a user which registered and validated his email address. + UserTo testUserTo = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity newTestUserEntity = testDataFactory.getNewTestUserEntity(testUserTo); + + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(newTestUserEntity); + + // When + @NotNull ResultTo signInResult = userService.signIn(TESTUSER_EMAIL1, "abc"); + + // Then + assertNotNull(signInResult); + final Message message = signInResult.getMessage(); + assertNotNull(message); + assertNotNull(message.getCode()); + assertEquals(message.getCode().getExceptionCode(), + AuthExceptionCodes.AUTHENTICATION_FAILED); + assertEquals(message.getCode(), AuthMessageCodes.WRONG_PASSWORD); + } + + + @Test + public void testSignInWithUnvalidatedEmailFails() throws Exception { + + // For being able to login we need a clearly validated user account, + // for which we use the verified email address + + // Given + UserTo testUserTo = testDataFactory.getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity newTestUserEntity = testDataFactory.getNewTestUserEntity(testUserTo); + newTestUserEntity.setValidated(false); + + given(userRepository.getByEmail(TESTUSER_EMAIL1)).willReturn(newTestUserEntity); + + // When + @NotNull ResultTo signInResult = userService.signIn(TESTUSER_EMAIL1, "abc"); + + // Then + assertNotNull(signInResult); + final Message message = signInResult.getMessage(); + assertNotNull(message); + assertNotNull(message.getCode()); + assertEquals(message.getCode().getExceptionCode(), + AuthExceptionCodes.AUTHENTICATION_FAILED); + assertEquals(message.getCode(), AuthMessageCodes.INCOMPLETE_REGISTRATION_PROCESS); + } } diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/MotdControllerTest.java b/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/MotdControllerTest.java deleted file mode 100644 index 8715f7af..00000000 --- a/sabi-server/src/test/java/de/bluewhale/sabi/services/rest/MotdControllerTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2023 by Stefan Schubert under the MIT License (MIT). - * See project LICENSE file for the detailed terms and conditions. - */ - -package de.bluewhale.sabi.services.rest; - -import de.bluewhale.sabi.model.MotdTo; -import de.bluewhale.sabi.services.AppService; -import de.bluewhale.sabi.util.RestHelper; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.reset; - - -/** - * Checks Motd Service - */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) -public class MotdControllerTest { - - @MockBean - AppService appService; - - @Autowired - private TestRestTemplate restTemplate; - - - @AfterEach - public void cleanUpMocks() { - reset(appService); - } - - /** - * Tests MOTD Rest API in case we have no content. - * - * @throws Exception - */ - @Test // REST-API - public void testModtRetrievalWithNoNews() throws Exception { - - // given a motd - MotdTo motdTo = new MotdTo("junit modt"); - - HttpHeaders headers = RestHelper.buildHttpHeader(); - ResponseEntity responseEntity = restTemplate.exchange("/api/app/motd/xx", HttpMethod.GET, null, String.class); - - // then we should get a 204 as result. - assertThat(responseEntity.getStatusCode(), equalTo(HttpStatus.NO_CONTENT)); - } - - /** - * Tests MOTD Rest API in case we news. - * - * @throws Exception - */ - @Test // REST-API - public void testModtRetrieval() throws Exception { - - // given a motd - String motd = "Junit Modt"; - given(this.appService.fetchMotdFor("en")).willReturn(motd); - - // when - HttpHeaders headers = RestHelper.buildHttpHeader(); - ResponseEntity responseEntity = restTemplate.exchange("/api/app/motd/en", HttpMethod.GET, null, String.class); - - // then we should get a 200 as result. - assertThat(responseEntity.getStatusCode(), equalTo(HttpStatus.OK)); - } -} diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/util/TestContainerVersions.java b/sabi-server/src/test/java/de/bluewhale/sabi/util/TestContainerVersions.java new file mode 100644 index 00000000..0ba95a43 --- /dev/null +++ b/sabi-server/src/test/java/de/bluewhale/sabi/util/TestContainerVersions.java @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). + * See project LICENSE file for the detailed terms and conditions. + */ + +package de.bluewhale.sabi.util; + +public interface TestContainerVersions { + String MARIADB_11_3_2 = "mariadb:11.3.2"; +} diff --git a/sabi-server/src/test/java/de/bluewhale/sabi/util/TestDataFactory.java b/sabi-server/src/test/java/de/bluewhale/sabi/util/TestDataFactory.java new file mode 100644 index 00000000..d96771cf --- /dev/null +++ b/sabi-server/src/test/java/de/bluewhale/sabi/util/TestDataFactory.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). + * See project LICENSE file for the detailed terms and conditions. + */ + +package de.bluewhale.sabi.util; + +import de.bluewhale.sabi.model.*; +import de.bluewhale.sabi.persistence.model.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * Small Util class which provide common test DTOs. + * + * @author Stefan Schubert + */ +public class TestDataFactory { +// ------------------------------ FIELDS ------------------------------ + + public static final String TESTUSER_EMAIL1 = "testservice1@bluewhale.de"; + public static final String TESTUSER_EMAIL2 = "testservice2@bluewhale.de"; + + public static final String INVALID_PASSWORD = "quertz!1"; + public static final String VALID_PASSWORD = "All!Rules8-)Applied"; + + public static final Long TEST_TANK_ID = 1L; // do not change - integration tests rely on this + public static final Long TEST_USER_ID = 1L; // do not change - integration tests rely on this + public static final Long TEST_FISH_ID = 1L; // do not change - integration tests rely on this + public static final Long TEST_PLAGUE_ID = 1L; // do not change - integration tests rely on this + + private static TestDataFactory instance; + + +// -------------------------- STATIC METHODS -------------------------- + + protected TestDataFactory() { + // defeat instantiation from outside + } + +// --------------------------- CONSTRUCTORS --------------------------- + + public static TestDataFactory getInstance() { + if (instance == null) { + instance = new TestDataFactory(); + } + return instance; + } + +// -------------------------- OTHER METHODS -------------------------- + + public AquariumTo getTestAquariumTo() { + final AquariumTo aquariumTo = new AquariumTo(); + aquariumTo.setDescription("Test Tank"); + aquariumTo.setActive(Boolean.TRUE); + aquariumTo.setSize(40); + aquariumTo.setSizeUnit(SizeUnit.LITER); + aquariumTo.setWaterType(WaterType.SEA_WATER); + aquariumTo.setId(TEST_TANK_ID); + return aquariumTo; + } + + public AquariumTo getTestAquariumFor(UserTo userTo) { + AquariumTo aquariumTo = getTestAquariumTo(); + aquariumTo.setUserId(userTo.getId()); + return aquariumTo; + } + + public AquariumEntity getTestAquariumEntity(AquariumTo aquariumTo, UserEntity userEntity) { + AquariumEntity aquariumEntity = new AquariumEntity(); + aquariumEntity.setId(aquariumTo.getId()); + aquariumEntity.setDescription(aquariumTo.getDescription()); + aquariumEntity.setSize(aquariumTo.getSize()); + aquariumEntity.setSizeUnit(aquariumTo.getSizeUnit()); + aquariumEntity.setWaterType(aquariumTo.getWaterType()); + aquariumEntity.setUser(userEntity); + return aquariumEntity; + } + + public FishTo getTestFishTo(AquariumTo aquariumTo) { + FishTo fishTo = new FishTo(); + fishTo.setAquariumId(aquariumTo.getId()); + fishTo.setFishCatalogueId(1L); + fishTo.setNickname("Green Latern"); + return fishTo; + } + + public FishEntity getTestFishEntity(FishTo fishTo, Long aquariumId) { + FishEntity fishEntity = new FishEntity(); + fishEntity.setAquariumId(aquariumId); + fishEntity.setFishCatalogueId(fishTo.getFishCatalogueId()); + fishEntity.setNickname(fishTo.getNickname()); + fishEntity.setId(TEST_FISH_ID); + return fishEntity; + } + + + public NewRegistrationTO getNewRegistrationTO(String eMail) { + NewRegistrationTO newRegistrationTO = new NewRegistrationTO(); + newRegistrationTO.setEmail(eMail); + newRegistrationTO.setPassword(VALID_PASSWORD); + newRegistrationTO.setLanguage(Locale.ENGLISH.getLanguage()); + newRegistrationTO.setCountry(Locale.UK.getCountry()); + return newRegistrationTO; + } + + public UserTo getNewTestUserTo(String eMail) { + UserTo userTo1 = new UserTo(eMail, eMail, VALID_PASSWORD); + userTo1.setUsername("TestUser"); + userTo1.setLanguage(Locale.ENGLISH.getLanguage()); + userTo1.setCountry(Locale.UK.getCountry()); + userTo1.setValidated(true); + userTo1.setId(TEST_USER_ID); + return userTo1; + } + + public UserEntity getNewTestUserEntity(UserTo userTo) { + UserEntity userEntity = new UserEntity(); + userEntity.setEmail(userTo.getEmail()); + userEntity.setId(userTo.getId()); + userEntity.setLanguage(userTo.getLanguage()); + userEntity.setCountry(userTo.getCountry()); + userEntity.setValidateToken("1234567890"); + userEntity.setValidated(userTo.isValidated()); + return userEntity; + } + + public UserProfileTo getBasicUserProfileTo() { + return new UserProfileTo(Locale.ENGLISH.getLanguage(), Locale.UK.getCountry()); + } + + public UserProfileTo getUserProfileToWithMeasurementReminderFor(UserTo userTo) { + UserProfileTo userProfileTo = getBasicUserProfileTo(); + MeasurementReminderTo reminderTo = new MeasurementReminderTo(); + reminderTo.setUserId(userTo.getId().intValue()); + reminderTo.setPastDays(12); + reminderTo.setUnitId(1); + userProfileTo.getMeasurementReminderTos().add(reminderTo); + + return userProfileTo; + } + + + /** + * Links preexisting testdata for aquarium (user) and unit. + * + * @param aquariumTo, can be null in which case we assign a default tank id + * @return + */ + public MeasurementTo getTestMeasurementTo(AquariumTo aquariumTo) { + final MeasurementTo measurementTo = new MeasurementTo(); + measurementTo.setAquariumId(aquariumTo == null ? TEST_TANK_ID:aquariumTo.getId()); + measurementTo.setUnitId(1); + measurementTo.setId(4711l); + measurementTo.setMeasuredValue(1.15f); + measurementTo.setMeasuredOn(LocalDateTime.now()); + return measurementTo; + } + + public MeasurementEntity getTestMeasurementEntity(MeasurementTo measurementTo, AquariumEntity aquariumEntity) { + MeasurementEntity measurementEntity = new MeasurementEntity(); + measurementEntity.setId(measurementTo.getId()); + measurementEntity.setUnitId(measurementTo.getUnitId()); + measurementEntity.setAquarium(aquariumEntity); + measurementEntity.setMeasuredOn(measurementTo.getMeasuredOn()); + measurementEntity.setMeasuredValue(measurementTo.getMeasuredValue()); + return measurementEntity; + } + + /** + * Will assign Default testuser and test-aquarium & + * @return a valid MeasurementEntity + */ + public MeasurementEntity getTestMeasurementEntityWithDefaults() { + UserTo testUserTo = getNewTestUserTo(TESTUSER_EMAIL1); + UserEntity newTestUserEntity = getNewTestUserEntity(testUserTo); + AquariumTo testAquariumTo = getTestAquariumTo(); + AquariumEntity testAquariumEntity = getTestAquariumEntity(testAquariumTo, newTestUserEntity); + MeasurementTo testMeasurementTo = getTestMeasurementTo(testAquariumTo); + return getTestMeasurementEntity(testMeasurementTo, testAquariumEntity); + } + + + public ParameterTo getTestParameterTo() { + ParameterTo parameterTo = new ParameterTo(); + parameterTo.setBelongingUnitId(4711); + parameterTo.setMaxThreshold(10f); + parameterTo.setMinThreshold(20f); + parameterTo.setId(101); + parameterTo.setDescription("Junit"); + return parameterTo; + } + + public UnitTo getTestUnitTo() { + UnitTo unitTo = new UnitTo(); + unitTo.setId(4711); + unitTo.setUnitSign("SP"); + unitTo.setDescription("Scrum Story Points"); + return unitTo; + } + + public PlagueStatusEntity getTestPlagueStatusEntity() { + PlagueStatusEntity plagueStatusEntity = new PlagueStatusEntity(); + plagueStatusEntity.setId(TEST_PLAGUE_ID); + plagueStatusEntity.setLocalizedPlagueStatusEntities(null); + return plagueStatusEntity; + } + + public List getTestLocalizedPlagueStatusEntities() { + ArrayList localizedPlagueStatusEntities = new ArrayList<>(); + + LocalizedPlagueStatusEntity localizedPlagueStatus1 = new LocalizedPlagueStatusEntity(); + localizedPlagueStatus1.setPlague_status_id(1); + localizedPlagueStatus1.setId(99l); + localizedPlagueStatus1.setDescription("Spreading"); + localizedPlagueStatus1.setLanguage("en"); + + LocalizedPlagueStatusEntity localizedPlagueStatus2 = new LocalizedPlagueStatusEntity(); + localizedPlagueStatus2.setPlague_status_id(1); + localizedPlagueStatus2.setId(88l); + localizedPlagueStatus2.setDescription("Ausweitend"); + localizedPlagueStatus2.setLanguage("de"); + + localizedPlagueStatusEntities.add(localizedPlagueStatus1); + localizedPlagueStatusEntities.add(localizedPlagueStatus2); + + return localizedPlagueStatusEntities; + + } + +} diff --git a/sabi-server/src/test/resources/application.yml b/sabi-server/src/test/resources/application.yml index 2c967bef..1818d794 100644 --- a/sabi-server/src/test/resources/application.yml +++ b/sabi-server/src/test/resources/application.yml @@ -9,9 +9,6 @@ eclipselink: logging: level: INFO target-database: Auto -h2: - test: - mode: true logging: level: org: @@ -48,10 +45,10 @@ spring: datasource: hikari: connection-timeout: 3000 - maximum-pool-size: 5 + maximum-pool-size: 20 minimum-idle: 2 - type: com.zaxxer.hikari.HikariDataSource - url: jdbc:h2:mem:test;MODE=LEGACY;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_ON_EXIT=true;DB_CLOSE_DELAY=-1 + # Hikari will be configured through Testcontainer Setup in integration tests. + # url: jdbc:h2:mem:test;MODE=LEGACY;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_ON_EXIT=true;DB_CLOSE_DELAY=-1 mail: host: localhost password: YouWillNeverKnow @@ -64,4 +61,4 @@ spring: enable: true username: sabi@bluewhale.de main: - allow-bean-definition-overriding: true + allow-bean-definition-overriding: true \ No newline at end of file diff --git a/sabi-server/src/test/resources/banner.txt b/sabi-server/src/test/resources/banner.txt new file mode 100644 index 00000000..acb5d360 --- /dev/null +++ b/sabi-server/src/test/resources/banner.txt @@ -0,0 +1,8 @@ + ____ _ _ _____ _ _ +/ ___| __ _| |__ (_) |_ _|__ ___| |_(_)_ __ __ _ +\___ \ / _` | '_ \| | | |/ _ \/ __| __| | '_ \ / _` | + ___) | (_| | |_) | | | | __/\__ \ |_| | | | | (_| |_ _ _ +|____/ \__,_|_.__/|_| |_|\___||___/\__|_|_| |_|\__, (_|_|_) + |___/ +using Spring-Boot: ${spring-boot.formatted-version} 🐋 + diff --git a/sabi-webclient/pom.xml b/sabi-webclient/pom.xml index 801eecd1..7ebefe61 100644 --- a/sabi-webclient/pom.xml +++ b/sabi-webclient/pom.xml @@ -10,7 +10,7 @@ de.bluewhale sabi-webclient - 1.2.7 + 1.2.9 jar sabi-webclient A JSF based webclient for sabi. @@ -47,23 +47,23 @@ org.springframework.boot spring-boot-starter-parent - 3.2.2 + 3.3.3 UTF-8 UTF-8 - 21 + 22 2.20.0 - 4.0.1 - 1.12.3 + 4.1.0 + 1.13.5 5.2.2 - 1.2.6 - 1.6.4 - 9.0.9 - 1.18.30 - 2.16.2 + 1.2.9 + 1.6.5 + 10.0.3 + 1.18.34 + 2.17.1 @@ -255,28 +255,6 @@ - - - org.owasp - dependency-check-maven - ${owasp.plugin.version} - - - ${nvd.api.key} - - - - verify - - check - - - - @@ -297,4 +275,44 @@ + + + + owasp-check + + + + + nvd.api.key + !@{nvd.api.key} + + + + + + org.owasp + dependency-check-maven + ${owasp.plugin.version} + + + ${nvd.api.key} + + + + verify + + check + + + + + + + + + diff --git a/sabi-webclient/src/main/java/de/bluewhale/sabi/webclient/apigateway/TankServiceImpl.java b/sabi-webclient/src/main/java/de/bluewhale/sabi/webclient/apigateway/TankServiceImpl.java index 40d9bf1f..34836617 100644 --- a/sabi-webclient/src/main/java/de/bluewhale/sabi/webclient/apigateway/TankServiceImpl.java +++ b/sabi-webclient/src/main/java/de/bluewhale/sabi/webclient/apigateway/TankServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 by Stefan Schubert under the MIT License (MIT). + * Copyright (c) 2024 by Stefan Schubert under the MIT License (MIT). * See project LICENSE file for the detailed terms and conditions. */ @@ -100,7 +100,7 @@ public String reCreateTemperatureAPIKey(Long tankID, String pJWTBackendAuthtoken throw new BusinessException(CommonExceptionCodes.INTERNAL_ERROR); } - return myTankWithAPIKey.getTemperatueApiKey(); + return myTankWithAPIKey.getTemperatureApiKey(); } @Override diff --git a/sabi-webclient/src/main/java/de/bluewhale/sabi/webclient/controller/TankListView.java b/sabi-webclient/src/main/java/de/bluewhale/sabi/webclient/controller/TankListView.java index 93d777b4..d62cc598 100644 --- a/sabi-webclient/src/main/java/de/bluewhale/sabi/webclient/controller/TankListView.java +++ b/sabi-webclient/src/main/java/de/bluewhale/sabi/webclient/controller/TankListView.java @@ -101,7 +101,7 @@ public void generateTemperatureApiKey(AquariumTo tank) { selectedTank = tank; try { String apiKey = tankService.reCreateTemperatureAPIKey(tank.getId(), userSession.getSabiBackendToken()); - tank.setTemperatueApiKey(apiKey); + tank.setTemperatureApiKey(apiKey); } catch (BusinessException e) { log.error(e.getLocalizedMessage()); MessageUtil.warn("messages","common.error.internal_server_problem.t",userSession.getLocale()); diff --git a/sabi-webclient/src/main/resources/META-INF/resources/secured/tankView.xhtml b/sabi-webclient/src/main/resources/META-INF/resources/secured/tankView.xhtml index f8a38932..5c8d8285 100644 --- a/sabi-webclient/src/main/resources/META-INF/resources/secured/tankView.xhtml +++ b/sabi-webclient/src/main/resources/META-INF/resources/secured/tankView.xhtml @@ -1,6 +1,6 @@ - - +