feat(product): Testabdeckung und Validierungen gemaess Pflichtenheft vervollstaendigt
parent
42a01b3c50
commit
16ef33a1c9
|
|
@ -1 +1,8 @@
|
||||||
[ ]
|
[ {
|
||||||
|
"productId" : "P-0003",
|
||||||
|
"bezeichnung" : "Bohrmaschine",
|
||||||
|
"einzelpreisNetto" : 89.99,
|
||||||
|
"mwstSatz" : "NORMAL",
|
||||||
|
"beschreibung" : "Schlagbohrer",
|
||||||
|
"kategorie" : "Elektrowerkzeuge"
|
||||||
|
} ]
|
||||||
|
|
@ -4,45 +4,53 @@ import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import de.hsmannheim.faktura.dokumente.repository.DokumentRepository;
|
||||||
import de.hsmannheim.faktura.product.Product;
|
import de.hsmannheim.faktura.product.Product;
|
||||||
import de.hsmannheim.faktura.product.repository.ProductRepository;
|
import de.hsmannheim.faktura.product.repository.ProductRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service-Klasse zur Bereitstellung der Geschäftslogik für die Produktverwaltung (Gruppe G)[cite: 84].
|
* Service-Klasse zur Bereitstellung der Geschäftslogik für die Produktverwaltung (Gruppe G).
|
||||||
* Dient als zentrale programminterne API für die Schnittstellen zur Programmoberfläche (Gruppe E)
|
* Dient als zentrale programminterne API für die Schnittstellen zur Programmoberfläche (Gruppe E)
|
||||||
* und den Dokumentenprozess (Gruppe F)[cite: 10, 58, 59, 60].
|
* und den Dokumentenprozess (Gruppe F).
|
||||||
* Folgt der Vorgabe NF-MAIN-02 für die Quellcodedokumentation[cite: 51, 377].
|
* Folgt der Vorgabe NF-MAIN-02 für die Quellcodedokumentation.
|
||||||
*/
|
*/
|
||||||
public class ProductService {
|
public class ProductService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Referenz auf das Datenhaltungs-Repository zur JSON-Persistierung[cite: 71, 85].
|
* Referenz auf das Datenhaltungs-Repository zur JSON-Persistierung.
|
||||||
*/
|
*/
|
||||||
private final ProductRepository productRepository;
|
private final ProductRepository productRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Referenz auf das Dokumenten-Repository zur Durchführung der Referenzprüfung.
|
||||||
|
*/
|
||||||
|
private final DokumentRepository dokumentRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Konstruktor zur Erstellung des ProductService mittels Dependency Injection.
|
* Konstruktor zur Erstellung des ProductService mittels Dependency Injection.
|
||||||
*
|
*
|
||||||
* @param productRepository Das zu verwendende Repository für Datenzugriffe[cite: 85].
|
* @param productRepository Das zu verwendende Repository für Datenzugriffe
|
||||||
|
* @param dokumentRepository Das zu verwendende Repository für Dokumentendaten
|
||||||
*/
|
*/
|
||||||
public ProductService(ProductRepository productRepository) {
|
public ProductService(ProductRepository productRepository, DokumentRepository dokumentRepository) {
|
||||||
this.productRepository = productRepository;
|
this.productRepository = productRepository;
|
||||||
|
this.dokumentRepository = dokumentRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt eine tabellarische Liste aller im System hinterlegten Produkte zurück[cite: 36, 58, 59, 60].
|
* Gibt eine tabellarische Liste aller im System hinterlegten Produkte zurück.
|
||||||
* Unterstützt die Initialisierung der Übersichtsliste in der Benutzeroberfläche[cite: 36, 70].
|
* Unterstützt die Initialisierung der Übersichtsliste in der Benutzeroberfläche.
|
||||||
*
|
*
|
||||||
* @return Eine Liste von {@link Product}-Objekten[cite: 87].
|
* @return Eine Liste von {@link Product}-Objekten.
|
||||||
*/
|
*/
|
||||||
public List<Product> getAllProducts() {
|
public List<Product> getAllProducts() {
|
||||||
return productRepository.findAll();
|
return productRepository.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sucht nach einem spezifischen Produkt anhand seiner eindeutigen ID[cite: 88].
|
* Sucht nach einem spezifischen Produkt anhand seiner eindeutigen ID.
|
||||||
*
|
*
|
||||||
* @param productId Die eindeutige Kennung des gesuchten Produkts [cite: 88]
|
* @param productId Die eindeutige Kennung des gesuchten Produkts
|
||||||
* @return Ein {@link Optional}, das das gefundene Produkt enthält, oder leer ist
|
* @return Ein {@link Optional}, das das gefundene Produkt enthält, oder leer ist
|
||||||
*/
|
*/
|
||||||
public Optional<Product> getProductById(String productId) {
|
public Optional<Product> getProductById(String productId) {
|
||||||
|
|
@ -50,28 +58,31 @@ public class ProductService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Speichert ein neues Produkt oder aktualisiert einen bestehenden Datensatz[cite: 28, 29, 58, 59, 60].
|
* Speichert ein neues Produkt oder aktualisiert einen bestehenden Datensatz.
|
||||||
* <p>
|
* * @param product Das zu speichernde oder zu aktualisierende {@link Product}-Objekt
|
||||||
* <b>Validierungen gemäß Pflichtenheft:</b>
|
* @throws IllegalArgumentException Wenn Validierungsregeln aus Kapitel 6.1.1 verletzt werden
|
||||||
* <ul>
|
|
||||||
* <li>Überprüfung auf Null-Objekte.</li>
|
|
||||||
* <li><b>Anforderung F-SH-PV-06:</b> Der Netto-Einzelpreis darf nicht negativ (< 0.00) sein[cite: 38].</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @param product Das zu speichernde {@link Product}-Objekt [cite: 89]
|
|
||||||
* @throws IllegalArgumentException Wenn das Produkt null ist oder der Netto-Einzelpreis negativ ist [cite: 38, 145]
|
|
||||||
*/
|
*/
|
||||||
public void saveProduct(Product product) {
|
public void saveProduct(Product product) {
|
||||||
if (product == null) {
|
if (product == null) {
|
||||||
throw new IllegalArgumentException("Das Produkt darf nicht null sein.");
|
throw new IllegalArgumentException("Das Produkt darf nicht null sein.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validierung gemäß Anforderung F-SH-PV-06 (Preisprüfung)
|
if (product.getBezeichnung() == null || product.getBezeichnung().trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Anforderung F-SH-PV-01 verletzt: Die Bezeichnung ist ein Pflichtfeld.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (product.getBezeichnung().length() > 100) {
|
||||||
|
throw new IllegalArgumentException("Anforderung F-SH-PV-01 verletzt: Die Bezeichnung darf maximal 100 Zeichen lang sein.");
|
||||||
|
}
|
||||||
|
|
||||||
if (product.getEinzelpreisNetto() == null || product.getEinzelpreisNetto().compareTo(BigDecimal.ZERO) < 0) {
|
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.");
|
throw new IllegalArgumentException("Anforderung F-SH-PV-06 verletzt: Der Einzelpreis Netto darf nicht negativ sein.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatische ID-Generierung, falls ein neues Produkt noch keinen Identifikator besitzt
|
if (product.getBeschreibung() != null && product.getBeschreibung().length() > 500) {
|
||||||
|
throw new IllegalArgumentException("Anforderung F-PV-07 verletzt: Die Beschreibung darf maximal 500 Zeichen lang sein.");
|
||||||
|
}
|
||||||
|
|
||||||
if (product.getProductId() == null || product.getProductId().trim().isEmpty()) {
|
if (product.getProductId() == null || product.getProductId().trim().isEmpty()) {
|
||||||
product.setProductId("PROD-" + java.util.UUID.randomUUID().toString().substring(0, 8));
|
product.setProductId("PROD-" + java.util.UUID.randomUUID().toString().substring(0, 8));
|
||||||
}
|
}
|
||||||
|
|
@ -80,34 +91,37 @@ public class ProductService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Löscht ein Produkt permanent aus dem Datenbestand anhand seiner ID[cite: 58, 59, 60, 90].
|
* Löscht ein Produkt permanent aus dem Datenbestand anhand seiner ID.
|
||||||
* <p>
|
* * @param productId Die eindeutige ID des zu löschenden Produkts
|
||||||
* <b>Geschäftsregel GR-05 (Stammdatenschutz):</b>
|
|
||||||
* Vor dem eigentlichen Löschen muss zwingend über den Dokumentenprozess (Gruppe F) geprüft werden,
|
|
||||||
* ob das Produkt in aktiven Belegen (Angebote, Rechnungen etc.) referenziert wird[cite: 33, 34, 115, 431].
|
|
||||||
* Bei einer bestehenden Referenz wird der Löschvorgang blockiert[cite: 35, 115, 432].
|
|
||||||
*
|
|
||||||
* @param productId Die eindeutige ID des zu löschenden Produkts [cite: 90]
|
|
||||||
* @return {@code true}, wenn das Produkt erfolgreich gelöscht wurde; {@code false}, wenn die ID leer war
|
* @return {@code true}, wenn das Produkt erfolgreich gelöscht wurde; {@code false}, wenn die ID leer war
|
||||||
* oder der Löschvorgang blockiert wurde[cite: 35, 90].
|
* oder der Löschvorgang blockiert wurde
|
||||||
*/
|
*/
|
||||||
public boolean deleteProduct(String productId) {
|
public boolean deleteProduct(String productId) {
|
||||||
if (productId == null || productId.trim().isEmpty()) {
|
if (productId == null || productId.trim().isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String cleanId = productId.replace("PROD-", "").replace("P-", "").trim();
|
||||||
|
long idAsLong = Long.parseLong(cleanId);
|
||||||
|
|
||||||
|
boolean isReferenced = dokumentRepository.existsReferencedProdukt(idAsLong);
|
||||||
|
|
||||||
|
if (isReferenced) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
System.err.println("ID-Format ungültig für Referenzprüfung: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return productRepository.deleteById(productId);
|
return productRepository.deleteById(productId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filtert die Produktliste in Echtzeit basierend auf einem Suchbegriff[cite: 37, 360, 361].
|
* Filtert die Produktliste in Echtzeit basierend auf einem Suchbegriff.
|
||||||
* <p>
|
* * @param query Der eingegebene Suchbegriff für die Live-Filterung
|
||||||
* <b>Anforderung F-SH-PV-05:</b>
|
* @return Eine Liste der gefilterten Produkte, die dem Suchkriterium entsprechen
|
||||||
* Es werden alle Produkte zurückgegeben, bei denen der Suchbegriff eine Teilmenge der
|
|
||||||
* Bezeichnung bildet, wobei Groß- und Kleinschreibung vollständig ignoriert wird[cite: 37, 153].
|
|
||||||
*
|
|
||||||
* @param query Der eingegebene Suchbegriff für die Live-Filterung [cite: 37, 91]
|
|
||||||
* @return Eine Liste der gefilterten Produkte, die dem Suchkriterium entsprechen [cite: 37, 91]
|
|
||||||
*/
|
*/
|
||||||
public List<Product> searchProducts(String query) {
|
public List<Product> searchProducts(String query) {
|
||||||
if (query == null || query.trim().isEmpty()) {
|
if (query == null || query.trim().isEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package de.hsmannheim.faktura.product.repository;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import de.hsmannheim.faktura.product.MwstSatz;
|
||||||
|
import de.hsmannheim.faktura.product.Product;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testklasse für das ProductRepository (Gruppe G).
|
||||||
|
* Überprüft die korrekte Tabelleninitialisierung und die verlustfreie
|
||||||
|
* dateibasierte Persistenz der Stammdaten (NF-SH-ARCH-01).
|
||||||
|
*/
|
||||||
|
public class ProductRepositoryTest {
|
||||||
|
|
||||||
|
private ProductRepository productRepository;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
productRepository = new ProductRepository();
|
||||||
|
|
||||||
|
for (Product p : productRepository.findAll()) {
|
||||||
|
productRepository.deleteById(p.getProductId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testet die tabellarische Bereitstellung aller Produkte für die Übersichtsliste.
|
||||||
|
* Abdeckung von Anforderung: F-SH-PV-04.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGetAllAndSorting() {
|
||||||
|
Product p1 = new Product("P-0001", "A-Schraube", new BigDecimal("1.50"), MwstSatz.NORMAL, "", "");
|
||||||
|
Product p2 = new Product("P-0002", "B-Mutter", new BigDecimal("0.80"), MwstSatz.NORMAL, "", "");
|
||||||
|
|
||||||
|
productRepository.save(p1);
|
||||||
|
productRepository.save(p2);
|
||||||
|
|
||||||
|
List<Product> allProducts = productRepository.findAll();
|
||||||
|
assertEquals(2, allProducts.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testet die verlustfreie Wiederherstellung der Produktdaten aus dem JSON-Speicher nach einem Systemneustart.
|
||||||
|
* Abdeckung von Anforderung: NF-SH-ARCH-01.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPersistenceOnRestart() {
|
||||||
|
Product product = new Product("P-1000", "Persistenz-Test", new BigDecimal("49.99"), MwstSatz.NORMAL, "Sicherung auf Dateiebene", "Test");
|
||||||
|
productRepository.save(product);
|
||||||
|
|
||||||
|
// Simulation des Anwendungs-Neustarts durch Neuinitialisierung des Repositories
|
||||||
|
ProductRepository newRepositoryInstance = new ProductRepository();
|
||||||
|
|
||||||
|
assertTrue(newRepositoryInstance.findById("P-1000").isPresent());
|
||||||
|
assertEquals("Persistenz-Test", newRepositoryInstance.findById("P-1000").get().getBezeichnung());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package de.hsmannheim.faktura.product.service;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import de.hsmannheim.faktura.dokumente.repository.DokumentRepository;
|
||||||
|
import de.hsmannheim.faktura.product.MwstSatz;
|
||||||
|
import de.hsmannheim.faktura.product.Product;
|
||||||
|
import de.hsmannheim.faktura.product.repository.ProductRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performancetest-Klasse für die Produktverwaltung (Gruppe G).
|
||||||
|
* Verifiziert die Zeitschranken beim Laden und Filtern von großen Datenmengen (NF-SH-PERF-01).
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public class ProductPerformanceTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Überprüft, ob das Durchsuchen und Echtzeit-Filtern bei einem Datenbestand von 1.000
|
||||||
|
* Produkten in weniger als 2 Sekunden vollständig abgeschlossen wird.
|
||||||
|
* Abdeckung von Anforderung: NF-SH-PERF-01.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSearchPerformance() {
|
||||||
|
ProductRepository repo = new ProductRepository();
|
||||||
|
DokumentRepository dummyDocRepo = new de.hsmannheim.faktura.dokumente.repository.DokumentRepository() {
|
||||||
|
@Override public boolean existsReferencedProdukt(long id) { return false; }
|
||||||
|
@Override public boolean existsReferencedKunde(long id) { return false; }
|
||||||
|
@Override public int findMaxLaufendeNummer(String p, int j) { return 0; }
|
||||||
|
@Override public void save(de.hsmannheim.faktura.dokumente.domain.Dokument d) {}
|
||||||
|
@Override public java.util.Optional<de.hsmannheim.faktura.dokumente.domain.Dokument> findByNummer(String n) { return java.util.Optional.empty(); }
|
||||||
|
@Override public List<de.hsmannheim.faktura.dokumente.domain.Dokument> findByKunde(long id) { return java.util.List.of(); }
|
||||||
|
@Override public boolean existsFolgedokument(String q) { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductService service = new ProductService(repo, dummyDocRepo);
|
||||||
|
|
||||||
|
// Bereinigen und künstliches Befüllen mit exakt 1.000 Produkten
|
||||||
|
for (Product p : repo.findAll()) {
|
||||||
|
repo.deleteById(p.getProductId());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i <= 1000; i++) {
|
||||||
|
repo.save(new Product("P-" + i, "Produkt Nummer " + i, new BigDecimal("10.00"), MwstSatz.NORMAL, "", "Kategorie"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zeitmessung starten
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
List<Product> results = service.searchProducts("Nummer 999");
|
||||||
|
|
||||||
|
long endTime = System.currentTimeMillis();
|
||||||
|
long duration = endTime - startTime;
|
||||||
|
|
||||||
|
// Überprüfung der Zeitschranke (2000 Millisekunden = 2 Sekunden)
|
||||||
|
assertTrue(duration < 2000, "Der Lasttest wurde nicht in der geforderten Zeit von 2 Sekunden erfüllt. Benötigt: " + duration + "ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,14 +2,17 @@ package de.hsmannheim.faktura.product.service;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import de.hsmannheim.faktura.dokumente.domain.Dokument;
|
||||||
import de.hsmannheim.faktura.product.MwstSatz;
|
import de.hsmannheim.faktura.product.MwstSatz;
|
||||||
import de.hsmannheim.faktura.product.Product;
|
import de.hsmannheim.faktura.product.Product;
|
||||||
import de.hsmannheim.faktura.product.repository.ProductRepository;
|
import de.hsmannheim.faktura.product.repository.ProductRepository;
|
||||||
|
|
@ -17,24 +20,58 @@ import de.hsmannheim.faktura.product.repository.ProductRepository;
|
||||||
/**
|
/**
|
||||||
* Komponententest-Klasse für den ProductService (Gruppe G).
|
* Komponententest-Klasse für den ProductService (Gruppe G).
|
||||||
* Überprüft die Einhaltung der funktionalen Anforderungen und Geschäftsregeln
|
* Überprüft die Einhaltung der funktionalen Anforderungen und Geschäftsregeln
|
||||||
* aus dem Pflichtenheft vollständig ohne GUI-Abhängigkeiten (NF-TEST-02)[cite: 51, 379].
|
* aus dem Pflichtenheft vollständig ohne GUI-Abhängigkeiten (NF-TEST-02).
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("all")
|
||||||
public class ProductServiceTest {
|
public class ProductServiceTest {
|
||||||
|
|
||||||
private ProductService productService;
|
private ProductService productService;
|
||||||
private ProductRepository productRepository;
|
private ProductRepository productRepository;
|
||||||
|
private de.hsmannheim.faktura.dokumente.repository.DokumentRepository dokumentRepository;
|
||||||
|
|
||||||
/**
|
|
||||||
* Setzt vor jedem einzelnen Testlauf eine frische Testumgebung auf
|
|
||||||
* und bereinigt den lokalen JSON-Cache, um Seiteneffekte zu verhindern[cite: 16, 143].
|
|
||||||
*/
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
productRepository = new ProductRepository();
|
productRepository = new ProductRepository();
|
||||||
productService = new ProductService(productRepository);
|
|
||||||
|
|
||||||
// Cache leeren, damit alte JSON-Einträge die Testergebnisse nicht verfälschen
|
dokumentRepository = new de.hsmannheim.faktura.dokumente.repository.DokumentRepository() {
|
||||||
|
@Override
|
||||||
|
public boolean existsReferencedProdukt(long produktId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean existsReferencedKunde(long kundenId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int findMaxLaufendeNummer(String praefix, int jahr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(Dokument dokument) {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'save'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Dokument> findByNummer(String nummer) {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'findByNummer'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Dokument> findByKunde(long kundenId) {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'findByKunde'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean existsFolgedokument(String quellDokumentNummer) {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'existsFolgedokument'");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
productService = new ProductService(productRepository, dokumentRepository);
|
||||||
|
|
||||||
for (Product p : productRepository.findAll()) {
|
for (Product p : productRepository.findAll()) {
|
||||||
productRepository.deleteById(p.getProductId());
|
productRepository.deleteById(p.getProductId());
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +79,7 @@ public class ProductServiceTest {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Testet das erfolgreiche Anlegen eines validen Produkts mit allen Pflichtattributen.
|
* Testet das erfolgreiche Anlegen eines validen Produkts mit allen Pflichtattributen.
|
||||||
* Abdeckung von Anforderung: F-SH-PV-01 (Testfall: ProductServiceTest#testSaveValidProduct).
|
* Abdeckung von Anforderung: F-SH-PV-01.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testSaveValidProduct() {
|
public void testSaveValidProduct() {
|
||||||
|
|
@ -52,13 +89,31 @@ public class ProductServiceTest {
|
||||||
assertTrue(productService.getProductById("P-0001").isPresent());
|
assertTrue(productService.getProductById("P-0001").isPresent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testet das erfolgreiche Modifizieren und Überschreiben bestehender Produktdatensätze.
|
||||||
|
* Abdeckung von Anforderung: F-SH-PV-02.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUpdateProductAttributes() {
|
||||||
|
Product product = new Product("P-0001", "Alte Bezeichnung", new BigDecimal("5.00"), MwstSatz.NORMAL, "Alt", "Alt");
|
||||||
|
productService.saveProduct(product);
|
||||||
|
|
||||||
|
Product updatedProduct = new Product("P-0001", "Neue Bezeichnung", new BigDecimal("12.50"), MwstSatz.ERMAESSIGT, "Neu", "Neu");
|
||||||
|
productService.saveProduct(updatedProduct);
|
||||||
|
|
||||||
|
Optional<Product> result = productService.getProductById("P-0001");
|
||||||
|
assertTrue(result.isPresent());
|
||||||
|
assertEquals("Neue Bezeichnung", result.get().getBezeichnung());
|
||||||
|
assertEquals(new BigDecimal("12.50"), result.get().getEinzelpreisNetto());
|
||||||
|
assertEquals(MwstSatz.ERMAESSIGT, result.get().getMwstSatz());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft, ob Preise kleiner als 0.00 wie gefordert blockiert und mit einer Exception abgewiesen werden.
|
* Prüft, ob Preise kleiner als 0.00 wie gefordert blockiert und mit einer Exception abgewiesen werden.
|
||||||
* Abdeckung von Anforderung: F-SH-PV-06 / AC-SH-PV-01 (Testfall: ProductServiceTest#testNegativePriceRejection)[cite: 142, 145, 159].
|
* Abdeckung von Anforderung: F-SH-PV-06 / AC-SH-PV-01.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testNegativePriceRejection() {
|
public void testNegativePriceRejection() {
|
||||||
// AC-SH-PV-01: Preis kleiner als 0.00 muss abgelehnt werden
|
|
||||||
Product invalidProduct = new Product("P-9999", "Testprodukt", new BigDecimal("-1.00"), MwstSatz.NORMAL, "Test", "Test");
|
Product invalidProduct = new Product("P-9999", "Testprodukt", new BigDecimal("-1.00"), MwstSatz.NORMAL, "Test", "Test");
|
||||||
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> {
|
assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
|
@ -66,10 +121,42 @@ public class ProductServiceTest {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Überprüft, ob Beschreibungen mit mehr als 500 Zeichen blockiert und mit einer Exception abgewiesen werden.
|
||||||
|
* Abdeckung von Anforderung: F-PV-07.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDescriptionLengthBounds() {
|
||||||
|
StringBuilder longDescription = new StringBuilder();
|
||||||
|
for (int i = 0; i < 501; i++) {
|
||||||
|
longDescription.append("A");
|
||||||
|
}
|
||||||
|
|
||||||
|
Product invalidProduct = new Product("P-0002", "Produkt mit langer Beschreibung", new BigDecimal("10.00"), MwstSatz.NORMAL, longDescription.toString(), "Test");
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
productService.saveProduct(invalidProduct);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob die optionale Kategoriezuweisung korrekt verarbeitet und persistent im Objekt hinterlegt wird.
|
||||||
|
* Abdeckung von Anforderung: F-PV-08.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testCategoryAssignment() {
|
||||||
|
Product product = new Product("P-0003", "Bohrmaschine", new BigDecimal("89.99"), MwstSatz.NORMAL, "Schlagbohrer", "Elektrowerkzeuge");
|
||||||
|
productService.saveProduct(product);
|
||||||
|
|
||||||
|
Optional<Product> result = productService.getProductById("P-0003");
|
||||||
|
assertTrue(result.isPresent());
|
||||||
|
assertEquals("Elektrowerkzeuge", result.get().getKategorie());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Überprüft die Echtzeit-Filterung der Produktliste anhand einer Teilmenge des Namens,
|
* Überprüft die Echtzeit-Filterung der Produktliste anhand einer Teilmenge des Namens,
|
||||||
* wobei Groß- und Kleinschreibung ignoriert werden muss.
|
* wobei Groß- und Kleinschreibung ignoriert werden muss.
|
||||||
* Abdeckung von Anforderung: F-SH-PV-05 / AC-SH-PV-03 (Testfall: ProductServiceTest#testSearchCaseInsensitive)[cite: 150, 153, 159].
|
* Abdeckung von Anforderung: F-SH-PV-05 / AC-SH-PV-03.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testSearchCaseInsensitive() {
|
public void testSearchCaseInsensitive() {
|
||||||
|
|
@ -79,8 +166,52 @@ public class ProductServiceTest {
|
||||||
productService.saveProduct(p1);
|
productService.saveProduct(p1);
|
||||||
productService.saveProduct(p2);
|
productService.saveProduct(p2);
|
||||||
|
|
||||||
// Suche nach "schraube" muss im Ergebnis exakt beide Objekte liefern
|
|
||||||
List<Product> results = productService.searchProducts("schraube");
|
List<Product> results = productService.searchProducts("schraube");
|
||||||
assertEquals(2, results.size());
|
assertEquals(2, results.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Überprüft die Blockierung des Löschvorgangs, falls das Produkt in Belegen referenziert wird.
|
||||||
|
* Abdeckung von Geschäftsregel: GR-05 / AC-SH-PV-02 / F-SH-PV-03.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDeleteReferencedProduct() {
|
||||||
|
de.hsmannheim.faktura.dokumente.repository.DokumentRepository mockRepo = new de.hsmannheim.faktura.dokumente.repository.DokumentRepository() {
|
||||||
|
@Override
|
||||||
|
public boolean existsReferencedProdukt(long produktId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean existsReferencedKunde(long kundenId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int findMaxLaufendeNummer(String praefix, int jahr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void save(Dokument dokument) {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'save'");
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Optional<Dokument> findByNummer(String nummer) {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'findByNummer'");
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public List<Dokument> findByKunde(long kundenId) {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'findByKunde'");
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean existsFolgedokument(String quellDokumentNummer) {
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'existsFolgedokument'");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductService serviceWithSperre = new ProductService(productRepository, mockRepo);
|
||||||
|
Product product = new Product("P-0001", "Gesperrtes Produkt", new BigDecimal("10.00"), MwstSatz.NORMAL, "", "");
|
||||||
|
productRepository.save(product);
|
||||||
|
|
||||||
|
boolean result = serviceWithSperre.deleteProduct("P-0001");
|
||||||
|
assertFalse(result, "Das Löschen hätte blockiert werden müssen, da das Produkt referenziert wird.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue