Dockerfile, API-Versionierung (/api/v1) und Tests für die REST-Schicht
- Dockerfile (Temurin 21, Zeitzone Berlin, MS Core Fonts für PDF-Renderer) - API-Pfade auf /api/v1/invoices versioniert (Controller, Javadoc, README) - Web-Schicht-Tests für InvoiceSigningController (MockMvc, gemockte Services) - End-to-End-Integrationstest: sign -> ZIP -> erneute Validierung - .env in .gitignore aufgenommen Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,3 +5,6 @@ target/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# Lokale Umgebungsvariablen
|
||||||
|
.env
|
||||||
|
|||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
FROM eclipse-temurin:21-jre
|
||||||
|
|
||||||
|
ARG JAR_FILE=target/*.jar
|
||||||
|
|
||||||
|
# Zeitzone auf Berlin setzen und 24h-Format konfigurieren
|
||||||
|
ENV TZ=Europe/Berlin
|
||||||
|
ENV LC_TIME=de_DE.UTF-8
|
||||||
|
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
|
# Schriften für den PDF-Renderer: Microsoft Core Fonts (Arial, Times New Roman,
|
||||||
|
# Courier New, Verdana) sowie DejaVu als Fallback. Die EULA der MS-Fonts wird
|
||||||
|
# vorab bestätigt; der Installer lädt die Fonts beim Image-Build herunter.
|
||||||
|
RUN echo "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true" | debconf-set-selections \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends fontconfig fonts-dejavu-core ttf-mscorefonts-installer \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY ${JAR_FILE} app.jar
|
||||||
|
EXPOSE 8084
|
||||||
|
ENTRYPOINT ["java", "-jar", "/app.jar", "--spring.profiles.active=production"]
|
||||||
@@ -20,7 +20,7 @@ Der Service läuft standardmäßig auf Port `8084` (überschreibbar per `SERVER_
|
|||||||
### PDF in ZUGFeRD-Rechnung umwandeln
|
### PDF in ZUGFeRD-Rechnung umwandeln
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:8084/api/invoices/sign \
|
curl -X POST http://localhost:8084/api/v1/invoices/sign \
|
||||||
-F "file=@rechnung.pdf" \
|
-F "file=@rechnung.pdf" \
|
||||||
-F "metadata=@metadata.json;type=application/json" \
|
-F "metadata=@metadata.json;type=application/json" \
|
||||||
-o rechnung-zugferd.zip
|
-o rechnung-zugferd.zip
|
||||||
@@ -33,7 +33,7 @@ der Header `X-Zugferd-Valid` enthält das Prüfergebnis.
|
|||||||
### ZUGFeRD-Rechnung validieren
|
### ZUGFeRD-Rechnung validieren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:8084/api/invoices/validate \
|
curl -X POST http://localhost:8084/api/v1/invoices/validate \
|
||||||
-F "file=@rechnung-zugferd.pdf"
|
-F "file=@rechnung-zugferd.pdf"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -29,17 +29,17 @@ import java.util.stream.Collectors;
|
|||||||
* REST-API zur Erzeugung und Prüfung rechtskonformer ZUGFeRD-E-Rechnungen.
|
* REST-API zur Erzeugung und Prüfung rechtskonformer ZUGFeRD-E-Rechnungen.
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* curl -X POST http://localhost:8083/api/invoices/sign \
|
* curl -X POST http://localhost:8084/api/v1/invoices/sign \
|
||||||
* -F "file=@rechnung.pdf" \
|
* -F "file=@rechnung.pdf" \
|
||||||
* -F "metadata=@metadata.json;type=application/json" \
|
* -F "metadata=@metadata.json;type=application/json" \
|
||||||
* -o rechnung-zugferd.zip
|
* -o rechnung-zugferd.zip
|
||||||
*
|
*
|
||||||
* curl -X POST http://localhost:8083/api/invoices/validate \
|
* curl -X POST http://localhost:8084/api/v1/invoices/validate \
|
||||||
* -F "file=@rechnung-zugferd.pdf"
|
* -F "file=@rechnung-zugferd.pdf"
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/invoices")
|
@RequestMapping("/api/v1/invoices")
|
||||||
public class InvoiceSigningController {
|
public class InvoiceSigningController {
|
||||||
|
|
||||||
/** Response-Header mit dem Ergebnis der Mustang-Validierung (true/false). */
|
/** Response-Header mit dem Ergebnis der Mustang-Validierung (true/false). */
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package de.assecutor.pdfservice.api;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
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.MediaType;
|
||||||
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End-to-End-Test über die echte Service-Kette (kein Mocking): ein Rechnungs-PDF
|
||||||
|
* wird über /sign in eine ZUGFeRD-Rechnung umgewandelt und das erzeugte PDF
|
||||||
|
* anschließend über /validate erneut geprüft.
|
||||||
|
*/
|
||||||
|
@SpringBootTest
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
class InvoiceSigningApiIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
private static final String METADATA = """
|
||||||
|
{
|
||||||
|
"invoiceNumber": "RE-2026-4711",
|
||||||
|
"issueDate": "2026-07-08",
|
||||||
|
"deliveryDate": "2026-07-01",
|
||||||
|
"dueDate": "2026-07-22",
|
||||||
|
"currency": "EUR",
|
||||||
|
"paymentTerms": "Zahlbar innerhalb von 14 Tagen ohne Abzug.",
|
||||||
|
"buyerReference": "KR-2026-042",
|
||||||
|
"iban": "DE75512108001245126199",
|
||||||
|
"sender": {
|
||||||
|
"name": "Assecutor Data Service GmbH",
|
||||||
|
"street": "Gerhart-Hauptmann-Weg 14",
|
||||||
|
"zip": "21502",
|
||||||
|
"city": "Geesthacht",
|
||||||
|
"countryCode": "DE",
|
||||||
|
"vatId": "DE261094748",
|
||||||
|
"email": "rechnung@example.com",
|
||||||
|
"phone": "+49 40 18 123 771 0"
|
||||||
|
},
|
||||||
|
"recipient": {
|
||||||
|
"name": "Kunde AG",
|
||||||
|
"street": "Beispielweg 2",
|
||||||
|
"zip": "10115",
|
||||||
|
"city": "Berlin",
|
||||||
|
"countryCode": "DE",
|
||||||
|
"email": "einkauf@example.com"
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{"description": "Beratungsleistung", "quantity": 1, "unitPriceNet": 1500.00, "vatPercent": 19}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void signsAndRevalidatesInvoiceEndToEnd() throws Exception {
|
||||||
|
MockMultipartFile pdf = new MockMultipartFile("file", "rechnung.pdf",
|
||||||
|
MediaType.APPLICATION_PDF_VALUE, createSamplePdf());
|
||||||
|
MockMultipartFile metadata = new MockMultipartFile("metadata", "metadata.json",
|
||||||
|
MediaType.APPLICATION_JSON_VALUE, METADATA.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
// 1. PDF + Metadaten -> ZUGFeRD-ZIP
|
||||||
|
MvcResult signResult = mockMvc.perform(multipart("/api/v1/invoices/sign").file(pdf).file(metadata))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(header().string(InvoiceSigningController.VALIDATION_HEADER, "true"))
|
||||||
|
.andExpect(header().string("Content-Type", "application/zip"))
|
||||||
|
.andReturn();
|
||||||
|
|
||||||
|
Map<String, byte[]> entries = readZip(signResult.getResponse().getContentAsByteArray());
|
||||||
|
assertEquals(3, entries.size(), "ZIP muss PDF, Factur-X-XML und Prüfbericht enthalten: " + entries.keySet());
|
||||||
|
byte[] zugferdPdf = entries.get("RE-2026-4711-zugferd.pdf");
|
||||||
|
assertNotNull(zugferdPdf, "ZUGFeRD-PDF fehlt im ZIP: " + entries.keySet());
|
||||||
|
assertNotNull(entries.get("factur-x.xml"), "Factur-X-XML fehlt im ZIP");
|
||||||
|
assertNotNull(entries.get("validation-report.xml"), "Prüfbericht fehlt im ZIP");
|
||||||
|
|
||||||
|
String facturX = new String(entries.get("factur-x.xml"), StandardCharsets.UTF_8);
|
||||||
|
assertTrue(facturX.contains("RE-2026-4711"), "Rechnungsnummer fehlt im Factur-X-XML");
|
||||||
|
|
||||||
|
// 2. Das erzeugte ZUGFeRD-PDF muss die Validierung erneut bestehen
|
||||||
|
MockMultipartFile signedPdf = new MockMultipartFile("file", "RE-2026-4711-zugferd.pdf",
|
||||||
|
MediaType.APPLICATION_PDF_VALUE, zugferdPdf);
|
||||||
|
mockMvc.perform(multipart("/api/v1/invoices/validate").file(signedPdf))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(header().string(InvoiceSigningController.VALIDATION_HEADER, "true"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateRejectsPlainPdfWithoutZugferdXml() throws Exception {
|
||||||
|
MockMultipartFile pdf = new MockMultipartFile("file", "rechnung.pdf",
|
||||||
|
MediaType.APPLICATION_PDF_VALUE, createSamplePdf());
|
||||||
|
|
||||||
|
mockMvc.perform(multipart("/api/v1/invoices/validate").file(pdf))
|
||||||
|
.andExpect(status().isUnprocessableEntity())
|
||||||
|
.andExpect(header().string(InvoiceSigningController.VALIDATION_HEADER, "false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] createSamplePdf() throws Exception {
|
||||||
|
try (PDDocument doc = new PDDocument(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||||
|
PDPage page = new PDPage();
|
||||||
|
doc.addPage(page);
|
||||||
|
try (PDPageContentStream cs = new PDPageContentStream(doc, page)) {
|
||||||
|
// PDF/A verlangt eingebettete Schriften; LiberationSans liegt pdfbox bei
|
||||||
|
PDType0Font font = PDType0Font.load(doc, PDDocument.class.getResourceAsStream(
|
||||||
|
"/org/apache/pdfbox/resources/ttf/LiberationSans-Regular.ttf"), true);
|
||||||
|
cs.beginText();
|
||||||
|
cs.setFont(font, 12);
|
||||||
|
cs.newLineAtOffset(50, 700);
|
||||||
|
cs.showText("Rechnung RE-2026-4711");
|
||||||
|
cs.endText();
|
||||||
|
}
|
||||||
|
doc.save(out);
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, byte[]> readZip(byte[] zip) throws Exception {
|
||||||
|
Map<String, byte[]> entries = new HashMap<>();
|
||||||
|
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zip))) {
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
|
entries.put(entry.getName(), zis.readAllBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package de.assecutor.pdfservice.api;
|
||||||
|
|
||||||
|
import de.assecutor.pdfservice.zugferd.ZugferdConversionException;
|
||||||
|
import de.assecutor.pdfservice.zugferd.ZugferdResult;
|
||||||
|
import de.assecutor.pdfservice.zugferd.ZugferdService;
|
||||||
|
import de.assecutor.pdfservice.zugferd.ZugferdValidationService;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
|
||||||
|
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.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web-Schicht-Tests für {@link InvoiceSigningController}: HTTP-Statuscodes,
|
||||||
|
* Header und Fehlerbehandlung — die ZUGFeRD-Verarbeitung selbst ist gemockt
|
||||||
|
* (fachliche Tests dazu in ZugferdServiceTest).
|
||||||
|
*/
|
||||||
|
@WebMvcTest(InvoiceSigningController.class)
|
||||||
|
class InvoiceSigningControllerTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@MockitoBean
|
||||||
|
private ZugferdService zugferdService;
|
||||||
|
|
||||||
|
@MockitoBean
|
||||||
|
private ZugferdValidationService validationService;
|
||||||
|
|
||||||
|
private static final String VALID_METADATA = """
|
||||||
|
{
|
||||||
|
"invoiceNumber": "RE-2026-0815",
|
||||||
|
"issueDate": "2026-07-08",
|
||||||
|
"sender": {
|
||||||
|
"name": "Assecutor Data Service GmbH",
|
||||||
|
"street": "Gerhart-Hauptmann-Weg 14",
|
||||||
|
"zip": "21502",
|
||||||
|
"city": "Geesthacht",
|
||||||
|
"countryCode": "DE",
|
||||||
|
"vatId": "DE261094748",
|
||||||
|
"email": "rechnung@example.com",
|
||||||
|
"phone": "+49 40 18 123 771 0"
|
||||||
|
},
|
||||||
|
"recipient": {
|
||||||
|
"name": "Kunde AG",
|
||||||
|
"street": "Beispielweg 2",
|
||||||
|
"zip": "10115",
|
||||||
|
"city": "Berlin",
|
||||||
|
"countryCode": "DE",
|
||||||
|
"email": "einkauf@example.com"
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{"description": "Beratungsleistung", "quantity": 1, "unitPriceNet": 1500.00, "vatPercent": 19}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
private static MockMultipartFile pdfPart() {
|
||||||
|
return new MockMultipartFile("file", "rechnung.pdf", MediaType.APPLICATION_PDF_VALUE,
|
||||||
|
"%PDF-1.4 dummy".getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MockMultipartFile metadataPart(String json) {
|
||||||
|
return new MockMultipartFile("metadata", "metadata.json", MediaType.APPLICATION_JSON_VALUE,
|
||||||
|
json.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void signReturnsZipWithHeadersWhenInvoiceIsValid() throws Exception {
|
||||||
|
byte[] zip = {0x50, 0x4B, 0x03, 0x04};
|
||||||
|
when(zugferdService.createZugferdZip(any(), any()))
|
||||||
|
.thenReturn(new ZugferdResult(zip, "RE-2026-0815.zip", true, "<report/>"));
|
||||||
|
|
||||||
|
mockMvc.perform(multipart("/api/v1/invoices/sign").file(pdfPart()).file(metadataPart(VALID_METADATA)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(content().contentType("application/zip"))
|
||||||
|
.andExpect(header().string(InvoiceSigningController.VALIDATION_HEADER, "true"))
|
||||||
|
.andExpect(header().string("Content-Disposition", containsString("RE-2026-0815.zip")))
|
||||||
|
.andExpect(content().bytes(zip));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void signReturns422WhenInvoiceIsInvalidButStillDeliversZip() throws Exception {
|
||||||
|
byte[] zip = {0x50, 0x4B, 0x03, 0x04};
|
||||||
|
when(zugferdService.createZugferdZip(any(), any()))
|
||||||
|
.thenReturn(new ZugferdResult(zip, "RE-2026-0815.zip", false, "<report/>"));
|
||||||
|
|
||||||
|
mockMvc.perform(multipart("/api/v1/invoices/sign").file(pdfPart()).file(metadataPart(VALID_METADATA)))
|
||||||
|
.andExpect(status().isUnprocessableEntity())
|
||||||
|
.andExpect(header().string(InvoiceSigningController.VALIDATION_HEADER, "false"))
|
||||||
|
.andExpect(content().bytes(zip));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void signReturns400ForMalformedMetadataJson() throws Exception {
|
||||||
|
mockMvc.perform(multipart("/api/v1/invoices/sign").file(pdfPart()).file(metadataPart("kein json {")))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(jsonPath("$.error", containsString("kein gültiges JSON")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void signReturns400ForIncompleteMetadata() throws Exception {
|
||||||
|
String withoutInvoiceNumber = VALID_METADATA.replace("\"invoiceNumber\": \"RE-2026-0815\",", "");
|
||||||
|
|
||||||
|
mockMvc.perform(multipart("/api/v1/invoices/sign").file(pdfPart()).file(metadataPart(withoutInvoiceNumber)))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(jsonPath("$.error", containsString("Metadaten unvollständig")))
|
||||||
|
.andExpect(jsonPath("$.error", containsString("invoiceNumber")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void signReturns400WhenConversionFails() throws Exception {
|
||||||
|
when(zugferdService.createZugferdZip(any(), any()))
|
||||||
|
.thenThrow(new ZugferdConversionException("Die hochgeladene Datei ist kein gültiges PDF."));
|
||||||
|
|
||||||
|
mockMvc.perform(multipart("/api/v1/invoices/sign").file(pdfPart()).file(metadataPart(VALID_METADATA)))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(jsonPath("$.error", containsString("kein gültiges PDF")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void signReturns400WhenMetadataPartIsMissing() throws Exception {
|
||||||
|
mockMvc.perform(multipart("/api/v1/invoices/sign").file(pdfPart()))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateReturnsReportXmlWithHeaderForValidInvoice() throws Exception {
|
||||||
|
when(validationService.validate(any(), eq("rechnung.pdf")))
|
||||||
|
.thenReturn(new ZugferdValidationService.ValidationResult(true, "<report>ok</report>"));
|
||||||
|
|
||||||
|
mockMvc.perform(multipart("/api/v1/invoices/validate").file(pdfPart()))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(content().contentType(MediaType.APPLICATION_XML))
|
||||||
|
.andExpect(header().string(InvoiceSigningController.VALIDATION_HEADER, "true"))
|
||||||
|
.andExpect(content().string("<report>ok</report>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateReturns422ForInvalidInvoice() throws Exception {
|
||||||
|
when(validationService.validate(any(), any()))
|
||||||
|
.thenReturn(new ZugferdValidationService.ValidationResult(false, "<report>fehler</report>"));
|
||||||
|
|
||||||
|
mockMvc.perform(multipart("/api/v1/invoices/validate").file(pdfPart()))
|
||||||
|
.andExpect(status().isUnprocessableEntity())
|
||||||
|
.andExpect(header().string(InvoiceSigningController.VALIDATION_HEADER, "false"))
|
||||||
|
.andExpect(content().string("<report>fehler</report>"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user