Produktverwaltung: Unit-Tests für Service erfolgreich hinzugefügt

pull/3/head
Lulia 2026-06-27 21:25:50 +02:00
parent 57028fba23
commit a29c99d9e8
6 changed files with 330 additions and 0 deletions

View File

@ -0,0 +1,8 @@
[ {
"produktId" : "P-0001",
"bezeichnung" : "M6 Schraube",
"einzelpreisNetto" : 10.50,
"mwstSatz" : "NORMAL",
"beschreibung" : "Beschreibung",
"kategorie" : "Werkzeug"
} ]

View File

@ -0,0 +1,8 @@
package de.hsmannheim.faktura.produkt;
public enum MwstSatz {
NORMAL,
ERMAESSIGT
}

View File

@ -0,0 +1,91 @@
package de.hsmannheim.faktura.produkt;
import java.math.BigDecimal;
public class Produkt {
private String produktId;
private String bezeichnung;
private BigDecimal einzelpreisNetto;
private MwstSatz mwstSatz;
private String beschreibung;
private String kategorie;
public Produkt() {
}
public Produkt(String produktId, String bezeichnung, BigDecimal einzelpreisNetto,
MwstSatz mwstSatz, String beschreibung, String kategorie) {
this.produktId = produktId;
this.bezeichnung = bezeichnung;
this.einzelpreisNetto = einzelpreisNetto;
this.mwstSatz = mwstSatz;
this.beschreibung = beschreibung;
this.kategorie = kategorie;
}
// ==========================================
// Getter und Setter
// ==========================================
public String getProduktId() {
return produktId;
}
public void setProduktId(String produktId) {
this.produktId = produktId;
}
public String getBezeichnung() {
return bezeichnung;
}
public void setBezeichnung(String bezeichnung) {
this.bezeichnung = bezeichnung;
}
public BigDecimal getEinzelpreisNetto() {
return einzelpreisNetto;
}
public void setEinzelpreisNetto(BigDecimal einzelpreisNetto) {
this.einzelpreisNetto = einzelpreisNetto;
}
public MwstSatz getMwstSatz() {
return mwstSatz;
}
public void setMwstSatz(MwstSatz mwstSatz) {
this.mwstSatz = mwstSatz;
}
public String getBeschreibung() {
return beschreibung;
}
public void setBeschreibung(String beschreibung) {
this.beschreibung = beschreibung;
}
public String getKategorie() {
return kategorie;
}
public void setKategorie(String kategorie) {
this.kategorie = kategorie;
}
@Override
public String toString() {
return "Produkt{" +
"produktId='" + produktId + '\'' +
", bezeichnung='" + bezeichnung + '\'' +
", einzelpreisNetto=" + einzelpreisNetto +
", mwstSatz=" + mwstSatz +
", kategorie='" + kategorie + '\'' +
'}';
}
}

View File

@ -0,0 +1,101 @@
package de.hsmannheim.faktura.produkt.repository;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import de.hsmannheim.faktura.produkt.Produkt;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class ProduktRepository {
private static final String FILE_PATH = "./data/products.json";
private final ObjectMapper objectMapper;
private List<Produkt> cache;
public ProduktRepository() {
this.objectMapper = new ObjectMapper();
this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
this.cache = new ArrayList<>();
loadData();
}
public List<Produkt> findAll() {
return new ArrayList<>(cache);
}
public Optional<Produkt> findById(String produktId) {
return cache.stream()
.filter(p -> p.getProduktId().equals(produktId))
.findFirst();
}
public void save(Produkt product) {
Optional<Produkt> existingProduct = findById(product.getProduktId());
if (existingProduct.isPresent()) {
int index = cache.indexOf(existingProduct.get());
cache.set(index, product);
} else {
cache.add(product);
}
saveData();
}
public boolean deleteById(String produktId) {
boolean removed = cache.removeIf(p -> p.getProduktId().equals(produktId));
if (removed) {
saveData();
}
return removed;
}
private void loadData() {
File file = new File(FILE_PATH);
if (!file.exists()) {
this.cache = new ArrayList<>();
return;
}
try {
this.cache = objectMapper.readValue(file, new TypeReference<List<Produkt>>() {});
} catch (IOException e) {
System.err.println("Fehler beim Laden der Produktdaten: " + e.getMessage());
this.cache = new ArrayList<>();
}
}
private void saveData() {
File file = new File(FILE_PATH);
File directory = file.getParentFile();
if (directory != null && !directory.exists()) {
directory.mkdirs();
}
try {
objectMapper.writeValue(file, cache);
} catch (IOException e) {
System.err.println("Fehler beim Speichern der Produktdaten: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,64 @@
package de.hsmannheim.faktura.produkt.service;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import de.hsmannheim.faktura.produkt.Produkt;
import de.hsmannheim.faktura.produkt.repository.ProduktRepository;
public class ProduktService {
private final ProduktRepository produktRepository;
public ProduktService(ProduktRepository produktRepository) {
this.produktRepository = produktRepository;
}
public List<Produkt> getAllProducts() {
return produktRepository.findAll();
}
public Optional<Produkt> getProductById(String produktId) {
return produktRepository.findById(produktId);
}
public void saveProduct(Produkt product) {
if (product == null) {
throw new IllegalArgumentException("Das Produkt darf nicht null sein.");
}
if (product.getEinzelpreisNetto() == null || product.getEinzelpreisNetto().compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Anforderung F-SH-PV-06 verletzt: Der Einzelpreis Netto darf nicht negativ sein.");
}
if (product.getProduktId() == null || product.getProduktId().trim().isEmpty()) {
product.setProduktId("PROD-" + java.util.UUID.randomUUID().toString().substring(0, 8));
}
produktRepository.save(product);
}
public boolean deleteProduct(String produktId) {
if (produktId == null || produktId.trim().isEmpty()) {
return false;
}
return produktRepository.deleteById(produktId);
}
public List<Produkt> searchProducts(String query) {
if (query == null || query.trim().isEmpty()) {
return getAllProducts();
}
String lowerQuery = query.toLowerCase();
return getAllProducts().stream()
.filter(p -> p.getBezeichnung() != null && p.getBezeichnung().toLowerCase().contains(lowerQuery))
.collect(java.util.stream.Collectors.toList());
}
}

View File

@ -0,0 +1,58 @@
package de.hsmannheim.faktura.produkt.service;
import de.hsmannheim.faktura.produkt.Produkt;
import de.hsmannheim.faktura.produkt.MwstSatz;
import de.hsmannheim.faktura.produkt.repository.ProduktRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class ProduktServiceTest {
private ProduktService produktService;
private ProduktRepository produktRepository;
@BeforeEach
public void setUp() {
produktRepository = new ProduktRepository();
produktService = new ProduktService(produktRepository);
// Cache leeren, damit alte JSON-Einträge die Tests nicht verfälschen
for (Produkt p : produktRepository.findAll()) {
produktRepository.deleteById(p.getProduktId());
}
}
@Test
public void testSaveValidProduct() {
Produkt valid = new Produkt("P-0001", "M6 Schraube", new BigDecimal("10.50"), MwstSatz.NORMAL, "Beschreibung", "Werkzeug");
assertDoesNotThrow(() -> produktService.saveProduct(valid));
assertTrue(produktService.getProductById("P-0001").isPresent());
}
@Test
public void testNegativePriceRejection() {
// AC-SH-PV-01: Preis kleiner als 0.00 muss abgelehnt werden
Produkt invalidProduct = new Produkt("P-9999", "Testprodukt", new BigDecimal("-1.00"), MwstSatz.NORMAL, "Test", "Test");
assertThrows(IllegalArgumentException.class, () -> produktService.saveProduct(invalidProduct));
}
@Test
public void testSearchCaseInsensitive() {
// AC-SH-PV-03: Suchtest für Groß-/Kleinschreibung
Produkt p1 = new Produkt("P-0001", "M6 Schraube", new BigDecimal("1.00"), MwstSatz.NORMAL, "", "");
Produkt p2 = new Produkt("P-0002", "M8 SCHRAUBE", new BigDecimal("2.00"), MwstSatz.NORMAL, "", "");
produktService.saveProduct(p1);
produktService.saveProduct(p2);
List<Produkt> results = produktService.searchProducts("schraube");
assertEquals(2, results.size());
}
}