// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-FileCopyrightText: 2024 swt23w23 package catering.inventory; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.salespointframework.core.Currencies.EURO; import java.util.Optional; import java.util.Set; import javax.money.MonetaryAmount; import javax.money.NumberValue; import org.javamoney.moneta.Money; import org.junit.jupiter.api.Test; import org.salespointframework.catalog.Product; import org.salespointframework.inventory.UniqueInventoryItem; import org.salespointframework.quantity.Metric; import org.salespointframework.quantity.Quantity; import catering.catalog.Consumable; import catering.catalog.Rentable; import catering.order.OrderType; import net.jqwik.api.Arbitraries; import net.jqwik.api.Arbitrary; import net.jqwik.api.Assume; import net.jqwik.api.Combinators; import net.jqwik.api.ForAll; import net.jqwik.api.Property; import net.jqwik.api.Provide; import net.jqwik.api.constraints.NotBlank; public class InventoryMutateFormUnitTests { /* * Providers for jqwik */ @Provide Arbitrary euroMonetaryAmount() { return Arbitraries.doubles() .filter(amount -> amount >= 0) .map(amount -> Money.of(amount, EURO)); } @Provide Arbitrary> optionalEuroMonetaryAmount() { return euroMonetaryAmount().optional(); } @Provide Arbitrary positiveOrZero() { return Arbitraries.longs().filter(x -> x >= 0); } @Provide Arbitrary consumable() { return Combinators .combine(Arbitraries.strings().filter(s -> !s.isBlank()), euroMonetaryAmount(), euroMonetaryAmount(), optionalEuroMonetaryAmount(), Arbitraries.subsetOf(OrderType.values()), Arbitraries.of(Metric.class)) .as((name, retailPrice, wholesalePrice, promotionPrice, orderTypes, metric) -> new Consumable(name, retailPrice, wholesalePrice, promotionPrice, orderTypes, metric)); } @Provide Arbitrary rentable() { return Combinators .combine(Arbitraries.strings().filter(s -> !s.isBlank()), euroMonetaryAmount(), euroMonetaryAmount(), Arbitraries.subsetOf(OrderType.values()), Arbitraries.of(Metric.class)) .as((name, retailPrice, wholesalePrice, orderTypes, metric) -> new Rentable(name, retailPrice, wholesalePrice, orderTypes, metric)); } Arbitrary item(Arbitrary product) { return Combinators.combine(product, positiveOrZero()) .as((prod, qty) -> new UniqueInventoryItem(prod, prod.createQuantity(qty))); } @Provide Arbitrary consumableItem() { return item(consumable()); } @Provide Arbitrary rentableItem() { return item(rentable()); } /* * Property-based tests with jqwik */ @Property void property_formToConsumableToFormIsIdentity(@ForAll @NotBlank String name, @ForAll("positiveOrZero") long qty, @ForAll Metric metric, @ForAll("euroMonetaryAmount") MonetaryAmount retailPrice, @ForAll("euroMonetaryAmount") MonetaryAmount wholesalePrice, @ForAll("optionalEuroMonetaryAmount") Optional promotionPrice, @ForAll Set orderTypes) { ConsumableMutateForm form = new ConsumableMutateForm(); Assume.that(form.supportedMetrics().contains(metric)); form.setName(name); form.setQuantity(qty); form.setMetric(metric); form.setRetailPrice(retailPrice.getNumber().doubleValueExact()); form.setWholesalePrice(retailPrice.getNumber().doubleValueExact()); form.setPromotionPrice(promotionPrice.map(MonetaryAmount::getNumber).map(NumberValue::doubleValueExact)); form.setOrderTypes(Set.of(OrderType.PARTY_SERVICE)); Product product = form.toProduct(); UniqueInventoryItem item = new UniqueInventoryItem(product, product.createQuantity(qty)); assertThat(ConsumableMutateForm.of(item)) .usingRecursiveComparison() .isEqualTo(form); } @Property void property_formToRentableToFormIsIdentity(@ForAll @NotBlank String name, @ForAll("positiveOrZero") long qty, @ForAll Metric metric, @ForAll("euroMonetaryAmount") MonetaryAmount retailPrice, @ForAll("euroMonetaryAmount") MonetaryAmount wholesalePrice, @ForAll Set orderTypes) { RentableMutateForm form = new RentableMutateForm(); Assume.that(form.supportedMetrics().contains(metric)); form.setName(name); form.setQuantity(qty); form.setMetric(metric); form.setRetailPrice(retailPrice.getNumber().doubleValueExact()); form.setWholesalePrice(retailPrice.getNumber().doubleValueExact()); form.setOrderTypes(Set.of(OrderType.PARTY_SERVICE)); Product product = form.toProduct(); UniqueInventoryItem item = new UniqueInventoryItem(product, product.createQuantity(qty)); assertThat(RentableMutateForm.of(item)) .usingRecursiveComparison() .isEqualTo(form); } @Property void property_consumableToFormToConsumableIsIdentity(@ForAll("consumableItem") UniqueInventoryItem item) { InventoryMutateForm form = InventoryMutateForm.of(item); assertThat(form).isInstanceOf(ConsumableMutateForm.class); assertThat(form.toProduct()) .usingRecursiveComparison() .ignoringFields("id") .isEqualTo(item.getProduct()); assertThat(form.getQuantity()).isEqualTo(item.getQuantity().getAmount().longValueExact()); } @Property void property_rentableToFormToRentableIsIdentity(@ForAll("rentableItem") UniqueInventoryItem item) { InventoryMutateForm form = InventoryMutateForm.of(item); assertThat(form).isInstanceOf(RentableMutateForm.class); assertThat(form.toProduct()) .usingRecursiveComparison() .ignoringFields("id") .isEqualTo(item.getProduct()); assertThat(form.getQuantity()).isEqualTo(item.getQuantity().getAmount().longValueExact()); } @Property void property_consumableFormModifiesExistingConsumable(@ForAll("consumableItem") UniqueInventoryItem item, @ForAll("consumable") Consumable product) { InventoryMutateForm.of(item).modifyProduct(product); assertThat(product) .usingRecursiveComparison() .ignoringFields("id") .ignoringFields("metric") // metric can’t be modified (salespoint limitation) .isEqualTo(item.getProduct()); } @Property void property_rentableFormModifiesExistingConsumable(@ForAll("rentableItem") UniqueInventoryItem item, @ForAll("rentable") Rentable product) { InventoryMutateForm.of(item).modifyProduct(product); assertThat(product) .usingRecursiveComparison() .ignoringFields("id") .ignoringFields("metric") // metric can’t be modified (salespoint limitation) .isEqualTo(item.getProduct()); } /* * Edge cases with jqwik */ @Property void property_ofIllegal(@ForAll("consumableItem") UniqueInventoryItem consumableItem, @ForAll("rentableItem") UniqueInventoryItem rentableItem) { assertThatThrownBy(() -> InventoryMutateForm .of(new UniqueInventoryItem(new Product("no subclass", Money.of(0.0, EURO)), Quantity.of(1)))) .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> ConsumableMutateForm.of(rentableItem)) .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> RentableMutateForm.of(consumableItem)) .isInstanceOf(IllegalArgumentException.class); } @Property void property_modifyIllegalProduct(@ForAll("consumableItem") UniqueInventoryItem consumableItem, @ForAll("rentableItem") UniqueInventoryItem rentableItem, @ForAll("consumable") Consumable consumable, @ForAll("rentable") Rentable rentable) { assertThatThrownBy(() -> InventoryMutateForm.of(rentableItem).modifyProduct(consumable)) .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> InventoryMutateForm.of(consumableItem).modifyProduct(rentable)) .isInstanceOf(IllegalArgumentException.class); } /* * JUnit Unit tests */ @Test void toConsumable() { ConsumableMutateForm form = new ConsumableMutateForm(); form.setName("Weißwurst (vegan)"); form.setQuantity(10L); form.setMetric(Metric.UNIT); form.setRetailPrice(3.46); form.setWholesalePrice(1.50); form.setPromotionPrice(Optional.of(3.33)); form.setOrderTypes(Set.of(OrderType.PARTY_SERVICE)); assertThat(form.getQuantity()).isEqualTo(10L); assertThat(form.toProduct()) .usingRecursiveComparison() .ignoringFields("id") .isEqualTo(new Consumable("Weißwurst (vegan)", Money.of(3.46, EURO), Money.of(1.50, EURO), Optional.of(Money.of(3.33, EURO)), Set.of(OrderType.PARTY_SERVICE), Metric.UNIT)); } @Test void toRentable() { RentableMutateForm form = new RentableMutateForm(); form.setName("Kebab-Drehspieß „Der Gerät“ Alkadur"); form.setQuantity(3L); form.setMetric(Metric.UNIT); form.setRetailPrice(20.0); form.setWholesalePrice(10000.0); form.setOrderTypes(Set.of(OrderType.EVENT_CATERING)); assertThat(form.getQuantity()).isEqualTo(3L); assertThat(form.toProduct()) .usingRecursiveComparison() .ignoringFields("id") .isEqualTo(new Rentable("Kebab-Drehspieß „Der Gerät“ Alkadur", Money.of(20.0, EURO), Money.of(10000.0, EURO), Set.of(OrderType.EVENT_CATERING), Metric.UNIT)); } @Test void forProductType() { assertThat(InventoryMutateForm.forProductType(Consumable.class)).isInstanceOf(ConsumableMutateForm.class); assertThat(InventoryMutateForm.forProductType(Rentable.class)).isInstanceOf(RentableMutateForm.class); assertThatThrownBy(() -> InventoryMutateForm.forProductType(Product.class)).isInstanceOf(IllegalArgumentException.class); } }