This commit is contained in:
Ralph Soika 2025-11-13 19:45:59 +01:00
parent 278880a6b0
commit e76a8dc668
2 changed files with 117 additions and 1 deletions

View file

@ -2,7 +2,6 @@ package com.alexanderlogistics.ksef;
public class Params { public class Params {
public static final String TOKEN = "20251113-EC-2751AC3000-5C5466924B-62|nip-9552521552|fc0c95d020dc45d4af491fb0aa755f3f3f674b33ecbf4250a88ea6e36fa8340b"; public static final String TOKEN = "20251113-EC-2751AC3000-5C5466924B-62|nip-9552521552|fc0c95d020dc45d4af491fb0aa755f3f3f674b33ecbf4250a88ea6e36fa8340b";
public static final String TOKENx = "20251113-EC-2751AC3000-5C5466924B-62.nip-9552521552.fc0c95d020dc45d4af491fb0aa755f3f3f674b33ecbf4250a88ea6e36fa8340b";
public static final String NIP = "9552521552"; public static final String NIP = "9552521552";
public static String getEncodedToken() { public static String getEncodedToken() {
@ -10,4 +9,5 @@ public class Params {
return result; return result;
} }
} }

View file

@ -1,14 +1,27 @@
package com.alexanderlogistics.ksef; package com.alexanderlogistics.ksef;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import java.net.URI; import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import org.imixs.workflow.exceptions.ModelException; import org.imixs.workflow.exceptions.ModelException;
import org.imixs.workflow.exceptions.PluginException; import org.imixs.workflow.exceptions.PluginException;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
@ -44,6 +57,9 @@ public class TestKSeF {
private final ObjectMapper objectMapper = new ObjectMapper(); private final ObjectMapper objectMapper = new ObjectMapper();
private String refNumber = null;
private String authToken = null;
@BeforeAll @BeforeAll
public static void setupClass() { public static void setupClass() {
logger.info("=== KSeF 2.0 Client Test ==="); logger.info("=== KSeF 2.0 Client Test ===");
@ -119,6 +135,106 @@ public class TestKSeF {
// Prüfen, dass wir eine API-Antwort bekommen // Prüfen, dass wir eine API-Antwort bekommen
assertTrue(response.statusCode() < 500, "API sollte antworten, Status < 500"); assertTrue(response.statusCode() < 500, "API sollte antworten, Status < 500");
assertFalse(response.body().contains("<html>"), "Response sollte keine HTML-Seite sein"); assertFalse(response.body().contains("<html>"), "Response sollte keine HTML-Seite sein");
// JSON parsen und Werte speichern
if (response.statusCode() == 200 || response.statusCode() == 202) {
JsonNode jsonNode = objectMapper.readTree(response.body());
// Reference Number speichern
refNumber = jsonNode.get("referenceNumber").asText();
logger.info("Extracted referenceNumber: " + refNumber);
// Authentication Token speichern
authToken = jsonNode.get("authenticationToken").get("token").asText();
logger.info("Extracted authenticationToken: " + authToken);
}
}
@Test
@Order(3)
@DisplayName("KSeF Public Key Zertifikate abrufen")
public void testFetchKsefPublicKeys() throws Exception {
assertNotNull(Params.TOKEN, "KSEF_TOKEN environment variable nicht gesetzt");
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(KSEF_TEST_URL + "/security/public-key-certificates"))
.header("Accept", "application/json")
.header("Authorization", "Bearer " + Params.TOKEN) // Access Token nutzen
.GET()
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println("Response: " + response.body());
// Assertions
assertEquals(200, response.statusCode(), "Request sollte 200 OK zurückgeben");
assertTrue(response.body().contains("certificate"), "Response sollte ein Feld 'certificate' enthalten");
}
@Test
@Order(4)
@DisplayName("Rechnung übermitteln an KSeF 2.0 API")
public void testSendInvoice() throws Exception {
assertNotNull(refNumber, "ReferenceNumber nicht gesetzt");
// 1. Rechnung laden
Path invoicePath = Paths.get("test/resources/test-invoice.xml");
byte[] invoiceBytes = Files.readAllBytes(invoicePath);
int invoiceSize = invoiceBytes.length;
// 2. SHA-256 Hash der Originalrechnung
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] invoiceHashBytes = sha256.digest(invoiceBytes);
String invoiceHash = Base64.getEncoder().encodeToString(invoiceHashBytes);
// 3. AES-256 Schlüssel + IV generieren
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey aesKey = keyGen.generateKey();
byte[] ivBytes = new byte[16];
new SecureRandom().nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
// 4. Rechnung verschlüsseln (AES-256-CBC + PKCS7Padding)
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, aesKey, iv);
byte[] encryptedInvoiceBytes = cipher.doFinal(invoiceBytes);
int encryptedInvoiceSize = encryptedInvoiceBytes.length;
byte[] encryptedInvoiceHashBytes = sha256.digest(encryptedInvoiceBytes);
String encryptedInvoiceHash = Base64.getEncoder().encodeToString(encryptedInvoiceHashBytes);
// 5. Verschlüsselten Inhalt Base64 codieren
String encryptedInvoiceContent = Base64.getEncoder().encodeToString(encryptedInvoiceBytes);
// 6. JSON-Payload erstellen
String jsonPayload = "{"
+ "\"invoiceHash\":\"" + invoiceHash + "\","
+ "\"invoiceSize\":" + invoiceSize + ","
+ "\"encryptedInvoiceHash\":\"" + encryptedInvoiceHash + "\","
+ "\"encryptedInvoiceSize\":" + encryptedInvoiceSize + ","
+ "\"encryptedInvoiceContent\":\"" + encryptedInvoiceContent + "\","
+ "\"offlineMode\":false"
+ "}";
// 7. HTTP POST Request an die API
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(KSEF_TEST_URL + "/sessions/online/" + refNumber + "/invoices"))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", "Bearer " + authToken) // Token aus Authentifizierung
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println("Response: " + response.body());
assertEquals(200, response.statusCode(), "Invoice Upload sollte Status 200 zurückgeben");
assertTrue(response.body().contains("invoiceId"), "Antwort sollte field invoiceId enthalten");
} }
} }