swt23w23/src/test/java/catering/inventory/InventoryControllerIntegrationTests.java
2024-01-19 19:00:51 +01:00

756 lines
28 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-FileCopyrightText: 2023-2024 swt23w23
package catering.inventory;
import static catering.inventory.InventoryControllerIntegrationTests.PermissionResult.FORBIDDEN;
import static catering.inventory.InventoryControllerIntegrationTests.PermissionResult.LOGIN;
import static catering.inventory.InventoryControllerIntegrationTests.PermissionResult.OK;
import static catering.inventory.InventoryControllerIntegrationTests.PermissionResult.OVERVIEW;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.not;
import static org.salespointframework.core.Currencies.EURO;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.math.BigDecimal;
import java.util.Optional;
import java.util.Set;
import catering.orderCatalog.CustomCatalogEntryRepository;
import org.javamoney.moneta.Money;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.salespointframework.catalog.Product;
import org.salespointframework.catalog.Product.ProductIdentifier;
import org.salespointframework.inventory.UniqueInventory;
import org.salespointframework.inventory.UniqueInventoryItem;
import org.salespointframework.quantity.Metric;
import org.salespointframework.quantity.Quantity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import catering.catalog.CateringCatalog;
import catering.catalog.Consumable;
import catering.catalog.Rentable;
import catering.order.OrderType;
@AutoConfigureMockMvc
@SpringBootTest
class InventoryControllerIntegrationTests {
@Autowired
MockMvc mvc;
@Autowired
UniqueInventory<UniqueInventoryItem> inventory;
@Autowired
CateringCatalog catalog;
@Autowired
private CustomCatalogEntryRepository catalogEntryRepository;
UniqueInventoryItem anyConsumableItem;
UniqueInventoryItem anyRentableItem;
Consumable anyConsumable;
Rentable anyRentable;
ProductIdentifier anyConsumableId;
ProductIdentifier anyRentableId;
UniqueInventoryItem anyItemOf(Class<? extends Product> type) {
switch (type.getSimpleName()) {
case "Consumable":
return anyConsumableItem;
case "Rentable":
return anyRentableItem;
default:
throw new IllegalArgumentException("Invalid product type!");
}
}
private long countItems() {
return inventory.findAll().stream().count();
}
/**
* Functional interface for comparing two {@link BigDecimal}s.
*
* It can be used for jUnit tests with
* {@code .withEqualsForType(InventoryControllerIntegrationTests::compareBigDecimal, BigDecimal.class)}.
*
* This is required when comparing {@link Quantity}s with a {@link Metric} of
* {@link Metric#LITER}.
*
* @param a {@link BigDecimal}
* @param b {@link BigDecimal}
* @return {@literal true} if they are equal, {@literal false} if not
*/
private static boolean compareBigDecimal(BigDecimal a, BigDecimal b) {
return a.compareTo(b) == 0;
}
@BeforeEach
void populateAnyInventoryItem() {
catalogEntryRepository.deleteAll();
inventory.deleteAll();
catalog.deleteAll();
anyConsumableItem = inventory.save(new UniqueInventoryItem(
catalog.save(new Consumable("Any Consumable", Money.of(1, EURO), Money.of(0.5, EURO),
Optional.of(Money.of(0.75, EURO)), Set.of(OrderType.EVENT_CATERING, OrderType.PARTY_SERVICE),
Metric.UNIT)),
Quantity.of(1)));
anyRentableItem = inventory.save(new UniqueInventoryItem(
catalog.save(new Rentable("Any Rentable", Money.of(1, EURO), Money.of(0.5, EURO),
Set.of(OrderType.EVENT_CATERING, OrderType.PARTY_SERVICE), Metric.UNIT)),
Quantity.of(1)));
anyConsumable = (Consumable) anyConsumableItem.getProduct();
anyRentable = (Rentable) anyRentableItem.getProduct();
anyConsumableId = anyConsumable.getId();
anyRentableId = anyRentable.getId();
}
void updateAnies() {
anyConsumableItem = inventory.findById(anyConsumableItem.getId()).get();
anyRentableItem = inventory.findById(anyRentableItem.getId()).get();
anyConsumable = (Consumable) anyConsumableItem.getProduct();
anyRentable = (Rentable) anyRentableItem.getProduct();
}
/*
* Tests for listing
*/
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanList() throws Exception {
mvc.perform(get("/inventory"))
.andExpect(status().isOk())
.andExpect(content().string(containsString(anyConsumableItem.getProduct().getName())));
}
/*
* Tests for adding
*/
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanReachAddConsumablePage() throws Exception {
mvc.perform(get("/inventory/add")
.queryParam("type", Consumable.class.getSimpleName()))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Produkt anlegen")));
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanReachAddRentablePage() throws Exception {
mvc.perform(get("/inventory/add")
.queryParam("type", Rentable.class.getSimpleName()))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Produkt anlegen")));
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCantReachAddInvalidPage() throws Exception {
mvc.perform(get("/inventory/add")
.queryParam("type", "INVALID"))
.andExpect(status().is3xxRedirection()); // not good error handling
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanAddConsumableWithoutPromotion() throws Exception {
long itemCountBefore = countItems();
mvc.perform(post("/inventory/add")
.queryParam("type", Consumable.class.getSimpleName())
.param("name", "Added Consumable")
.param("quantity", "100")
.param("orderTypes", "EVENT_CATERING", "MOBILE_BREAKFAST", "PARTY_SERVICE")
.param("wholesalePrice", "3.00")
.param("retailPrice", "7.50")
.param("metric", "UNIT"))
.andExpect(redirectedUrl("/inventory"));
assertThat(countItems()).isEqualTo(itemCountBefore + 1);
assertThat(inventory.findAll().stream()
.filter(ie -> ie.getProduct().getName().equals("Added Consumable")).findAny())
.get()
.usingRecursiveComparison()
.ignoringFields("inventoryItemIdentifier.inventoryItemId", "isNew", "product.id.productId",
"product.isNew")
.isEqualTo(
new UniqueInventoryItem(new Consumable("Added Consumable",
Money.of(7.5, EURO),
Money.of(3, EURO),
Optional.empty(),
Set.of(OrderType.EVENT_CATERING, OrderType.MOBILE_BREAKFAST, OrderType.PARTY_SERVICE),
Metric.UNIT),
Quantity.of(100)));
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanAddConsumableWithPromotion() throws Exception {
long itemCountBefore = countItems();
mvc.perform(post("/inventory/add")
.queryParam("type", Consumable.class.getSimpleName())
.param("name", "Added Consumable")
.param("quantity", "99")
.param("orderTypes", "PARTY_SERVICE")
.param("wholesalePrice", "4.00")
.param("retailPrice", "7.00")
.param("promotionPrice", "5.00")
.param("metric", "LITER"))
.andExpect(redirectedUrl("/inventory"));
assertThat(countItems()).isEqualTo(itemCountBefore + 1);
assertThat(inventory.findAll().stream()
.filter(ie -> ie.getProduct().getName().equals("Added Consumable")).findAny())
.get()
.usingRecursiveComparison()
.withEqualsForType(InventoryControllerIntegrationTests::compareBigDecimal, BigDecimal.class)
.ignoringFields("inventoryItemIdentifier.inventoryItemId", "isNew", "product.id.productId",
"product.isNew")
.isEqualTo(
new UniqueInventoryItem(new Consumable("Added Consumable",
Money.of(7, EURO),
Money.of(4, EURO),
Optional.of(Money.of(5, EURO)),
Set.of(OrderType.PARTY_SERVICE),
Metric.LITER),
Quantity.of(99, Metric.LITER)));
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanAddRentable() throws Exception {
long itemCountBefore = countItems();
mvc.perform(post("/inventory/add")
.queryParam("type", Rentable.class.getSimpleName())
.param("name", "Added Rentable")
.param("quantity", "8")
.param("orderTypes", "MOBILE_BREAKFAST")
.param("wholesalePrice", "1000.00")
.param("retailPrice", "10.25")
.param("metric", "UNIT"))
.andExpect(redirectedUrl("/inventory"));
assertThat(countItems()).isEqualTo(itemCountBefore + 1);
assertThat(inventory.findAll().stream()
.filter(ie -> ie.getProduct().getName().equals("Added Rentable")).findAny())
.get()
.usingRecursiveComparison()
.ignoringFields("inventoryItemIdentifier.inventoryItemId", "isNew", "product.id.productId",
"product.isNew")
.isEqualTo(
new UniqueInventoryItem(new Rentable("Added Rentable",
Money.of(10.25, EURO),
Money.of(1000.00, EURO),
Set.of(OrderType.MOBILE_BREAKFAST),
Metric.UNIT),
Quantity.of(8)));
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCantAddConsumableWithEmptyFields() throws Exception {
long itemCountBefore = countItems();
mvc.perform(post("/inventory/add")
.queryParam("type", Consumable.class.getSimpleName())
.param("name", "")
.param("quantity", "")
.param("orderTypes", "")
.param("wholesalePrice", "")
.param("retailPrice", "")
.param("promotionPrice", "")
.param("metric", ""))
.andExpect(content().string(containsString("Ungültiger Name")))
.andExpect(content().string(containsString("Ungültige Menge")))
.andExpect(content().string(not(containsString("Ungültiger Buchungstyp")))) // can be empty set
.andExpect(content().string(containsString("Ungültiger Einkaufspreis")))
.andExpect(content().string(containsString("Ungültiger Verkaufspreis")))
.andExpect(content().string(not(containsString("Ungültiger Aktionspreis")))) // optional
.andExpect(content().string(containsString("Ungültige Einheit")));
assertThat(countItems()).isEqualTo(itemCountBefore);
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCantAddRentableWithEmptyFields() throws Exception {
long itemCountBefore = countItems();
mvc.perform(post("/inventory/add")
.queryParam("type", Rentable.class.getSimpleName())
.param("name", "")
.param("quantity", "")
.param("orderTypes", "")
.param("wholesalePrice", "")
.param("retailPrice", "")
.param("metric", ""))
.andExpect(content().string(containsString("Ungültiger Name")))
.andExpect(content().string(containsString("Ungültige Menge")))
.andExpect(content().string(not(containsString("Ungültiger Buchungstyp")))) // can be empty set
.andExpect(content().string(containsString("Ungültiger Einkaufspreis")))
.andExpect(content().string(containsString("Ungültiger Verkaufspreis")))
.andExpect(content().string(containsString("Ungültige Einheit")));
assertThat(countItems()).isEqualTo(itemCountBefore);
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCantAddConsumableWithMissingFields() throws Exception {
long itemCountBefore = countItems();
mvc.perform(post("/inventory/add")
.queryParam("type", Consumable.class.getSimpleName()))
.andExpect(content().string(containsString("Ungültiger Name")))
.andExpect(content().string(containsString("Ungültige Menge")))
.andExpect(content().string(containsString("Ungültiger Buchungstyp")))
.andExpect(content().string(containsString("Ungültiger Einkaufspreis")))
.andExpect(content().string(containsString("Ungültiger Verkaufspreis")))
.andExpect(content().string(not(containsString("Ungültiger Aktionspreis")))) // optional
.andExpect(content().string(not(containsString("Ungültige Einheit")))); // "" can be cast to Metric.UNIT
assertThat(countItems()).isEqualTo(itemCountBefore);
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCantAddRentableWithMissingFields() throws Exception {
long itemCountBefore = countItems();
mvc.perform(post("/inventory/add")
.queryParam("type", Rentable.class.getSimpleName()))
.andExpect(content().string(containsString("Ungültiger Name")))
.andExpect(content().string(containsString("Ungültige Menge")))
.andExpect(content().string(containsString("Ungültiger Buchungstyp")))
.andExpect(content().string(containsString("Ungültiger Einkaufspreis")))
.andExpect(content().string(containsString("Ungültiger Verkaufspreis")))
.andExpect(content().string(not(containsString("Ungültige Einheit")))); // "" can be cast to Metric.UNIT
assertThat(countItems()).isEqualTo(itemCountBefore);
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCantAddConsumableWithNegativeNumbers() throws Exception {
long itemCountBefore = countItems();
mvc.perform(post("/inventory/add")
.queryParam("type", Consumable.class.getSimpleName())
.param("name", "Added Consumable")
.param("quantity", "-12")
.param("orderTypes", "EVENT_CATERING")
.param("wholesalePrice", "-4.20")
.param("retailPrice", "-2.31")
.param("promotionPrice", "-7.14")
.param("metric", "LITER"))
.andExpect(content().string(containsString("Ungültige Menge")))
.andExpect(content().string(containsString("Ungültiger Einkaufspreis")))
.andExpect(content().string(containsString("Ungültiger Verkaufspreis")))
.andExpect(content().string(containsString("Ungültiger Aktionspreis")));
assertThat(countItems()).isEqualTo(itemCountBefore);
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCantAddRentableWithNegativeNumbers() throws Exception {
long itemCountBefore = countItems();
mvc.perform(post("/inventory/add")
.queryParam("type", Rentable.class.getSimpleName())
.param("name", "Added Rentable")
.param("quantity", "-5")
.param("orderTypes", "MOBILE_BREAKFAST")
.param("wholesalePrice", "-589.12")
.param("retailPrice", "-12.40")
.param("metric", "UNIT"))
.andExpect(content().string(containsString("Ungültige Menge")))
.andExpect(content().string(containsString("Ungültiger Einkaufspreis")))
.andExpect(content().string(containsString("Ungültiger Verkaufspreis")));
assertThat(countItems()).isEqualTo(itemCountBefore);
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanAddConsumableWithZeroes() throws Exception {
long itemCountBefore = countItems();
mvc.perform(post("/inventory/add")
.queryParam("type", Consumable.class.getSimpleName())
.param("name", "Added Consumable")
.param("quantity", "0")
.param("orderTypes", "EVENT_CATERING")
.param("wholesalePrice", "0")
.param("retailPrice", "0")
.param("promotionPrice", "0")
.param("metric", "UNIT"))
.andExpect(content().string(not(containsString("Ungültige Menge"))))
.andExpect(content().string(not(containsString("Ungültiger Einkaufspreis"))))
.andExpect(content().string(not(containsString("Ungültiger Verkaufspreis"))))
.andExpect(content().string(not(containsString("Ungültiger Aktionspreis"))));
assertThat(countItems()).isEqualTo(itemCountBefore + 1);
assertThat(inventory.findAll().stream().filter(i -> i.getProduct() instanceof Consumable))
.extracting("product.name", "quantity", "product.wholesalePrice", "product.retailPrice",
"product.promotionPrice")
.contains(tuple("Added Consumable", Quantity.of(0), Money.of(0, EURO), Money.of(0, EURO),
Optional.of(Money.of(0, EURO))));
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanAddRentableWithZeroes() throws Exception {
long itemCountBefore = countItems();
mvc.perform(post("/inventory/add")
.queryParam("type", Rentable.class.getSimpleName())
.param("name", "Added Rentable")
.param("quantity", "0")
.param("orderTypes", "MOBILE_BREAKFAST")
.param("wholesalePrice", "0")
.param("retailPrice", "0")
.param("metric", "UNIT"))
.andExpect(content().string(not(containsString("Ungültige Menge"))))
.andExpect(content().string(not(containsString("Ungültiger Einkaufspreis"))))
.andExpect(content().string(not(containsString("Ungültiger Verkaufspreis"))));
assertThat(countItems()).isEqualTo(itemCountBefore + 1);
assertThat(inventory.findAll().stream())
.extracting("product.name", "quantity", "product.wholesalePrice", "product.retailPrice")
.contains(tuple("Added Rentable", Quantity.of(0), Money.of(0, EURO), Money.of(0, EURO)));
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void invalidAddReturnsNiceError() throws Exception {
mvc.perform(post("/inventory/add")
.queryParam("type", Consumable.class.getSimpleName())
.param("name", "")
.param("quantity", "10")
.param("wholesalePrice", "1.00")
.param("retailPrice", "2.00"))
.andExpect(content().string(containsString("Ungültiger Name")));
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void missingRetailPriceIsNoError() throws Exception {
mvc.perform(post("/inventory/add")
.queryParam("type", Consumable.class.getSimpleName())
.param("name", "MOCK")
.param("quantity", "10")
.param("wholesalePrice", "1.00")
.param("retailPrice", "2.00"))
.andExpect(content().string(not(containsString("Ungültiger Angebotspreis"))));
}
/*
* Tests for editing
*/
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanReachEditConsumablePage() throws Exception {
mvc.perform(get("/inventory/edit/" + anyConsumableId)
.queryParam("type", Consumable.class.getSimpleName()))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Produkt bearbeiten")))
.andExpect(content().string(containsString(anyConsumable.getName())));
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanReachEditRentablePage() throws Exception {
mvc.perform(get("/inventory/edit/" + anyRentableId)
.queryParam("type", Rentable.class.getSimpleName()))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Produkt bearbeiten")))
.andExpect(content().string(containsString(anyRentable.getName())));
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanEditConsumable() throws Exception {
mvc.perform(post("/inventory/edit/" + anyConsumableId)
.queryParam("type", Consumable.class.getSimpleName())
.param("name", "Edited Consumable")
.param("orderTypes", "PARTY_SERVICE", "MOBILE_BREAKFAST")
.param("quantity", "95")
.param("wholesalePrice", "10")
.param("retailPrice", "20")
.param("promotionPrice", "17"))
.andExpect(redirectedUrl("/inventory"));
updateAnies();
assertThat(anyConsumableItem.getQuantity()).isEqualTo(Quantity.of(95));
assertThat(anyConsumable)
.extracting("name", "wholesalePrice", "retailPrice", "promotionPrice")
.containsExactly("Edited Consumable", Money.of(10, EURO), Money.of(20, EURO),
Optional.of(Money.of(17, EURO)));
assertThat(anyConsumable.getCategories()).containsExactlyInAnyOrder("PARTY_SERVICE", "MOBILE_BREAKFAST");
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanEditRentable() throws Exception {
mvc.perform(post("/inventory/edit/" + anyConsumableId)
.queryParam("type", Consumable.class.getSimpleName())
.param("name", "Edited Rentable")
.param("orderTypes", "PARTY_SERVICE", "MOBILE_BREAKFAST")
.param("quantity", "95")
.param("wholesalePrice", "10")
.param("retailPrice", "20"))
.andExpect(redirectedUrl("/inventory"));
updateAnies();
assertThat(anyConsumableItem.getQuantity()).isEqualTo(Quantity.of(95));
assertThat(anyConsumable)
.extracting("name", "wholesalePrice", "retailPrice")
.containsExactly("Edited Rentable", Money.of(10, EURO), Money.of(20, EURO));
assertThat(anyConsumable.getCategories()).containsExactlyInAnyOrder("PARTY_SERVICE", "MOBILE_BREAKFAST");
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanEditConsumablePromo() throws Exception {
mvc.perform(post("/inventory/edit/" + anyConsumableId)
.queryParam("type", Consumable.class.getSimpleName())
.param("name", anyConsumable.getName())
.param("orderTypes", anyConsumable.getCategories().stream().toList().toArray(new String[0]))
.param("quantity", anyConsumableItem.getQuantity().getAmount().toString())
.param("wholesalePrice", anyConsumable.getWholesalePrice().getNumber().toString())
.param("retailPrice", anyConsumable.getRetailPrice().getNumber().toString())
.param("promotionPrice", ""))
.andExpect(redirectedUrl("/inventory"));
updateAnies();
assertThat(anyConsumable.getPromotionPrice()).isEmpty();
mvc.perform(post("/inventory/edit/" + anyConsumableId)
.queryParam("type", Consumable.class.getSimpleName())
.param("name", anyConsumable.getName())
.param("orderTypes", anyConsumable.getCategories().stream().toList().toArray(new String[0]))
.param("quantity", anyConsumableItem.getQuantity().getAmount().toString())
.param("wholesalePrice", anyConsumable.getWholesalePrice().getNumber().toString())
.param("retailPrice", anyConsumable.getRetailPrice().getNumber().toString())
.param("promotionPrice", "7"))
.andExpect(redirectedUrl("/inventory"));
updateAnies();
assertThat(anyConsumable.getPromotionPrice()).get().isEqualTo(Money.of(7, EURO));
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanEditConsumableWithMissingFields() throws Exception {
long itemCountBefore = countItems();
mvc.perform(post("/inventory/edit/" + anyConsumableId)
.queryParam("type", Consumable.class.getSimpleName()))
.andExpect(content().string(containsString("Ungültiger Name")))
.andExpect(content().string(containsString("Ungültige Menge")))
.andExpect(content().string(containsString("Ungültiger Buchungstyp")))
.andExpect(content().string(containsString("Ungültiger Einkaufspreis")))
.andExpect(content().string(containsString("Ungültiger Verkaufspreis")))
.andExpect(content().string(not(containsString("Ungültiger Aktionspreis")))) // optional
.andExpect(content().string(not(containsString("Ungültige Einheit")))); // "" can be cast to Metric.UNIT
assertThat(countItems()).isEqualTo(itemCountBefore);
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanEditRentableWithMissingFields() throws Exception {
long itemCountBefore = countItems();
mvc.perform(post("/inventory/edit/" + anyRentableId)
.queryParam("type", Rentable.class.getSimpleName()))
.andExpect(content().string(containsString("Ungültiger Name")))
.andExpect(content().string(containsString("Ungültige Menge")))
.andExpect(content().string(containsString("Ungültiger Buchungstyp")))
.andExpect(content().string(containsString("Ungültiger Einkaufspreis")))
.andExpect(content().string(containsString("Ungültiger Verkaufspreis")))
.andExpect(content().string(not(containsString("Ungültige Einheit")))); // "" can be cast to Metric.UNIT
assertThat(countItems()).isEqualTo(itemCountBefore);
}
// tests for editing
// with all fields empty,
// all prices negative
// and all prices zero
// are omitted for brevity,
// as they are essentially the same as with adding
/*
* Tests for deleting
*/
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void adminCanDelete() throws Exception {
long itemCountBefore = countItems();
mvc.perform(get("/inventory/delete/" + anyConsumableId))
.andExpect(redirectedUrl("/inventory"));
assertThat(countItems()).isEqualTo(itemCountBefore - 1);
assertThat(inventory.findAll().stream())
.extracting("product.name", "quantity")
.doesNotContain(tuple(anyConsumable.getName(), anyConsumableItem.getQuantity()));
}
/*
* Tests for authorization
*/
static enum PermissionResult {
/**
* The result is an HTTP 403 (Forbidden) error code
*/
FORBIDDEN,
/**
* The result is a redirect to the login page
*/
LOGIN,
/**
* The result is an HTTP 200 (Ok) status code
*/
OK,
/**
* The result is a redirect to the inventory overview page
*/
OVERVIEW;
@FunctionalInterface
public interface PermissionChecker {
ResultActions check(ResultActions resultActions) throws Exception;
}
PermissionChecker toChecker() {
return switch (this) {
case FORBIDDEN -> ra -> ra.andExpect(status().isForbidden());
case LOGIN -> InventoryControllerIntegrationTests::assertRedirectsToLogin;
case OK -> ra -> ra.andExpect(status().isOk());
case OVERVIEW -> ra -> ra.andExpect(redirectedUrl("/inventory"));
};
}
}
/**
* Helper function for asserting that protected endpoints are not accessible by
* unauthentificated users.
*
* @param resultActions the result of
* {@link org.springframework.test.web.servlet.MockMvc#perform}
* @return the {@link ResultActions} given as a parameter to perform more
* matching on
*/
static ResultActions assertRedirectsToLogin(ResultActions resultActions) throws Exception {
// Spring security uses the full URL to redirect to login,
// so we cant use redirectedUrl("/login") in this case.
return resultActions
.andExpect(status().is3xxRedirection()) // concrete redirection type is implementation detail
.andExpect(header().string(HttpHeaders.LOCATION, endsWith("/login")));
}
ResultActions requestAsAnonymous(MockHttpServletRequestBuilder requestBuilder) throws Exception {
return mvc.perform(requestBuilder.with(anonymous()));
}
ResultActions requestAsCustomer(MockHttpServletRequestBuilder requestBuilder) throws Exception {
return mvc.perform(requestBuilder.with(user("unprivileged").roles("CUSTOMER")));
}
ResultActions requestAsAdministrator(MockHttpServletRequestBuilder requestBuilder) throws Exception {
return mvc.perform(requestBuilder.with(user("admin").roles("ADMIN")));
}
void checkPermissions(MockHttpServletRequestBuilder requestBuilder, PermissionResult anonymous,
PermissionResult customer, PermissionResult administrator) throws Exception {
anonymous.toChecker().check(requestAsAnonymous(requestBuilder));
customer.toChecker().check(requestAsCustomer(requestBuilder));
administrator.toChecker().check(requestAsAdministrator(requestBuilder));
}
@Test
void checkPermissionsForList() throws Exception {
checkPermissions(get("/inventory"), LOGIN, FORBIDDEN, OK);
}
@Test
void checkPermissionsForEditPage() throws Exception {
for (Class<? extends Product> type : Set.of(Consumable.class, Rentable.class)) {
checkPermissions(
get("/inventory/edit/" + anyItemOf(type).getProduct().getId())
.queryParam("type", type.getSimpleName()),
LOGIN, FORBIDDEN, OK);
}
}
@Test
void checkPermissionsForEdit() throws Exception {
for (Class<? extends Product> type : Set.of(Consumable.class, Rentable.class)) {
checkPermissions(
post("/inventory/edit/" + anyItemOf(type).getProduct().getId())
.queryParam("type", type.getSimpleName()),
LOGIN, FORBIDDEN, OK);
}
}
@Test
void checkPermissionsForAddPage() throws Exception {
checkPermissions(get("/inventory/add").queryParam("type", Consumable.class.getSimpleName()), LOGIN, FORBIDDEN,
OK);
}
@Test
void checkPermissionsForAdd() throws Exception {
checkPermissions(post("/inventory/add").queryParam("type", Consumable.class.getSimpleName()), LOGIN, FORBIDDEN,
OK);
}
@Test
void checkPermissionsForDelete() throws Exception {
checkPermissions(get("/inventory/delete/" + anyConsumableId), LOGIN, FORBIDDEN, OVERVIEW);
}
}