diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c41cc9e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..175e6bb --- /dev/null +++ b/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + com.pfefferminzia + pet-insurance + 1.0-SNAPSHOT + + + 25 + UTF-8 + + 5.11.4 + 3.13.0 + 3.5.2 + + + + + org.junit.jupiter + junit-jupiter-api + ${junit.jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.jupiter.version} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.plugin.version} + + ${maven.compiler.release} + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven.surefire.plugin.version} + + + + \ No newline at end of file diff --git a/src/test/java/com/pfefferminzia/petinsurance/calculations/CalculationsTest.java b/src/test/java/com/pfefferminzia/petinsurance/calculations/CalculationsTest.java new file mode 100644 index 0000000..52fd31a --- /dev/null +++ b/src/test/java/com/pfefferminzia/petinsurance/calculations/CalculationsTest.java @@ -0,0 +1,175 @@ +package com.pfefferminzia.petinsurance.calculations; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import com.pfefferminzia.petinsurance.animals.Animal; +import com.pfefferminzia.petinsurance.animals.Cat; +import com.pfefferminzia.petinsurance.animals.Dog; +import com.pfefferminzia.petinsurance.animals.SmallAnimal; +import com.pfefferminzia.petinsurance.exception.InvalidInsuranceContractException; +import com.pfefferminzia.petinsurance.tariffs.TariffConfiguration; +import com.pfefferminzia.petinsurance.tariffs.TariffLoader; + +class CalculationsTest { + private static Calculations calculationsEngine; + + @BeforeAll + static void setUp() { + TariffConfiguration tariffConfig = TariffLoader.loadFromFile(); + calculationsEngine = new Calculations(tariffConfig); + } + + static Stream dogBreedPremiumCases() { + return Stream.of(Arguments.of("Husky", new BigDecimal("3500"), new BigDecimal("980")), + Arguments.of("Dackel", new BigDecimal("3500"), new BigDecimal("882")), + Arguments.of("Bernhardiner", new BigDecimal("3500"), new BigDecimal("1078"))); + } + + @ParameterizedTest + @MethodSource("dogBreedPremiumCases") + void shouldCalculateCorrectlyForDogSevenYearsAndPremiumTariff(String breed, + BigDecimal expectedSum, BigDecimal expectedPremium) { + + Animal dog = new Dog(LocalDate.of(2019, 8, 1), breed); + InsuranceContract contract = + new InsuranceContract(dog, LocalDate.of(2026, 8, 1), "PREMIUM"); + + CalculationResult result = + calculationsEngine.calculateInsuranceSumAndYearlyPremium(contract); + + assertBigDecimalEquals(expectedSum, result.insuranceSum()); + assertBigDecimalEquals(expectedPremium, result.yearlyPremium()); + } + + @Test + void shouldCalculateCorrectlyForCatThreeYearsAndOptimalTariff() { + Animal cat = new Cat(LocalDate.of(2023, 8, 1)); + InsuranceContract contract = + new InsuranceContract(cat, LocalDate.of(2026, 8, 1), "OPTIMAL"); + + CalculationResult result = + calculationsEngine.calculateInsuranceSumAndYearlyPremium(contract); + + assertBigDecimalEquals(new BigDecimal("2400"), result.insuranceSum()); + assertBigDecimalEquals(new BigDecimal("600"), result.yearlyPremium()); + } + + @Test + void shouldCalculateCorrectlyForSmallAnimalOneYearAndKompaktTariff() { + Animal smallAnimal = new SmallAnimal(LocalDate.of(2025, 8, 1)); + InsuranceContract contract = + new InsuranceContract(smallAnimal, LocalDate.of(2026, 8, 1), "KOMPAKT"); + + CalculationResult result = + calculationsEngine.calculateInsuranceSumAndYearlyPremium(contract); + + assertBigDecimalEquals(new BigDecimal("1000"), result.insuranceSum()); + assertBigDecimalEquals(new BigDecimal("200"), result.yearlyPremium()); + } + + static Stream ageBracketCases() { + return Stream.of( + Arguments.of(2, new BigDecimal("200")), // exactly at ≤2 boundary → factor 0.20 + Arguments.of(3, new BigDecimal("250")), // one above ≤2 boundary → factor 0.25 + Arguments.of(5, new BigDecimal("250")), // exactly at ≤5 boundary → factor 0.25 + Arguments.of(6, new BigDecimal("280")), // one above ≤5 boundary → factor 0.28 + Arguments.of(7, new BigDecimal("280")), // exactly at ≤7 boundary → factor 0.28 + Arguments.of(8, new BigDecimal("300")) // one above ≤7 boundary → factor 0.30 + ); + } + + @ParameterizedTest(name = "age {0} → premium {1}") + @MethodSource("ageBracketCases") + void shouldApplyCorrectAgeFactorAtEachBracketBoundary(int age, BigDecimal expectedPremium) { + LocalDate startDate = LocalDate.of(2026, 8, 1); + Animal animal = new SmallAnimal(startDate.minusYears(age)); + InsuranceContract contract = new InsuranceContract(animal, startDate, "KOMPAKT"); + + CalculationResult result = + calculationsEngine.calculateInsuranceSumAndYearlyPremium(contract); + + assertBigDecimalEquals(expectedPremium, result.yearlyPremium()); + } + + @Test + void shouldThrowExceptionWhenPremiumTariffIsSelectedBeforeJulyFirst() { + Animal dog = new Dog(LocalDate.of(2019, 8, 1), "Schäferhund"); + InsuranceContract contract = + new InsuranceContract(dog, LocalDate.of(2026, 2, 1), "PREMIUM"); + + assertThrows(IllegalStateException.class, + () -> calculationsEngine.calculateInsuranceSumAndYearlyPremium(contract)); + } + + @Test + void shouldReportViolationWhenAnimalIsNull() { + LocalDate insuranceStart = LocalDate.of(2026, 8, 1); + + InvalidInsuranceContractException ex = assertThrows(InvalidInsuranceContractException.class, + () -> new InsuranceContract(null, insuranceStart, "KOMPAKT")); + + assertEquals(List.of("Animal darf nicht leer sein"), ex.getViolations()); + } + + @Test + void shouldReportViolationWhenTariffNameIsNull() { + Animal animal = new Cat(LocalDate.of(2023, 8, 1)); + LocalDate insuranceStart = LocalDate.of(2026, 8, 1); + + + InvalidInsuranceContractException ex = assertThrows(InvalidInsuranceContractException.class, + () -> new InsuranceContract(animal, insuranceStart, null)); + + assertEquals(List.of("Tarif darf nicht leer sein"), ex.getViolations()); + } + + @Test + void shouldReportViolationWhenInsuranceStartDateIsNull() { + Animal animal = new Cat(LocalDate.of(2023, 8, 1)); + + InvalidInsuranceContractException ex = assertThrows(InvalidInsuranceContractException.class, + () -> new InsuranceContract(animal, null, "KOMPAKT")); + + assertEquals(List.of("Versicherungsstart darf nicht leer sein"), ex.getViolations()); + } + + @Test + void shouldReportViolationWhenBirthdayIsAfterContractStart() { + Animal animal = new Cat(LocalDate.of(2026, 8, 1)); + LocalDate insuranceStart = LocalDate.of(2026, 2, 1); + + InvalidInsuranceContractException ex = assertThrows(InvalidInsuranceContractException.class, + () -> new InsuranceContract(animal, insuranceStart, "KOMPAKT")); + + assertEquals(List.of("Start der Versicherung kann nicht vor dem Geburtstag sein."), + ex.getViolations()); + } + + @Test + void shouldCollectAllViolationsWhenMultipleFieldsAreInvalid() { + InvalidInsuranceContractException ex = assertThrows(InvalidInsuranceContractException.class, + () -> new InsuranceContract(null, null, null)); + + assertEquals( + List.of("Animal darf nicht leer sein", "Tarif darf nicht leer sein", + "Versicherungsstart darf nicht leer sein"), + ex.getViolations()); + } + + private void assertBigDecimalEquals(BigDecimal expected, BigDecimal actual) { + if (expected.compareTo(actual) != 0) { + fail(String.format("Expected: <%s> but is: <%s>", expected.toPlainString(), + actual.toPlainString())); + } + } +} diff --git a/src/test/resources/tariff.properties b/src/test/resources/tariff.properties new file mode 100644 index 0000000..74dd508 --- /dev/null +++ b/src/test/resources/tariff.properties @@ -0,0 +1,20 @@ + +# Animal base insurance sums: species=baseSum +baseSum.DOG=2500.00 +baseSum.CAT=2000.00 +baseSum.SMALLANIMAL=1000.00 + +# Tariffs: name=factor,availableFromDate +tariffs.KOMPAKT=1.0,1970-01-01 +tariffs.OPTIMAL=1.2,1970-01-01 +tariffs.PREMIUM=1.4,2026-07-01 + +# Age tariffs factor: age=factor +age.2=0.20 +age.5=0.25 +age.7=0.28 +elderlyFactor=0.30 + +# Dog breed premium factor: breed=factor +breed.DACKEL=-0.1 +breed.BERNHARDINER=0.1 \ No newline at end of file