From fe232c2fc98ebe5d0636f7c603d464ad1697b5b8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 13 May 2026 17:48:25 +0000 Subject: [PATCH 1/2] ci(workflow): Diagnostik bei Backend-Failure + Keycloak als docker-run-Step Run 1 ist im Backend-Job mit "exit code 1" gefallen ohne sichtbare Maven-Logs (Issue-Annotation enthielt nur den Step-Namen). Damit der naechste Lauf eine Diagnose liefert: - ./mvnw -e -ntp --fail-at-end statt -T 1C: volle Stacktraces und alle Module durchlaufen, statt beim ersten Fehler abzubrechen. Parallele Modul-Ausfuehrung bleibt vorerst aus, bis die Tests stabil sind. - Neue Step "Upload Surefire-Reports on failure": laedt alle target/{surefire,failsafe}-reports/ als Artefakt hoch, damit das konkrete Test-XML eingesehen werden kann. Im E2E-Job konnte Keycloak nicht als services:-Container starten (siehe auch parallel laufende explore-Pipeline mit gleichem Muster): das offizielle Image braucht `start-dev` als CMD und GHA-Services unterstuetzen weder `command` noch CMD-Override. Daher Keycloak jetzt per `docker run -d ... start-dev` als eigenen Step starten, inklusive Keycloak-Log-Sammeln und Hochladen auf Failure. https://claude.ai/code/session_014NNjRKw5bDqRffqCUGe8ud --- .github/workflows/ci.yml | 53 +++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69811d8..b5155a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,21 @@ jobs: cache: maven - name: Maven Verify (Build + Tests + JaCoCo) - run: ./mvnw -B -T 1C -ntp verify + # -e fuer volle Stacktraces, --fail-at-end damit alle Module + # durchlaufen statt beim ersten Fehler abzubrechen. Kein -T 1C, + # bis die Backend-Tests stabil sind (parallele Modul-Ausfuehrung + # macht Surefire-Output beim Debuggen unleserlich). + run: ./mvnw -B -e -ntp --fail-at-end verify + + - name: Upload Surefire-Reports on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: surefire-reports-${{ github.run_number }} + path: | + **/target/surefire-reports/** + **/target/failsafe-reports/** + retention-days: 14 - name: Upload Backend-JAR (fuer E2E) uses: actions/upload-artifact@v4 @@ -177,19 +191,10 @@ jobs: --health-timeout=5s --health-retries=15 - keycloak: - image: quay.io/keycloak/keycloak:24.0 - env: - KEYCLOAK_ADMIN: admin - KEYCLOAK_ADMIN_PASSWORD: admin - KC_HEALTH_ENABLED: 'true' - ports: - - 8080:8080 - options: >- - --health-cmd="curl -fsS http://localhost:8080/health/ready || exit 1" - --health-interval=10s - --health-timeout=5s - --health-retries=30 + # Keycloak laeuft NICHT als services-Container: das Image braucht + # `start-dev` als Argument, und GHA-Services unterstuetzen weder + # `command` noch CMD-Override. Stattdessen `docker run` als Step + # (siehe "Start Keycloak"). env: CVM_SERVER_PORT: '8081' @@ -227,6 +232,16 @@ jobs: name: frontend-dist path: cvm-frontend/dist/cvm-frontend/browser + - name: Start Keycloak (start-dev) + run: | + set -euo pipefail + docker run -d --name keycloak \ + -p 8080:8080 \ + -e KEYCLOAK_ADMIN=admin \ + -e KEYCLOAK_ADMIN_PASSWORD=admin \ + -e KC_HEALTH_ENABLED=true \ + quay.io/keycloak/keycloak:24.0 start-dev + - name: Import Keycloak-Realm run: | set -euo pipefail @@ -237,6 +252,11 @@ jobs: break fi sleep 2 + if [ "$i" = "60" ]; then + echo "Keycloak nicht ready. Container-Log:" + docker logs keycloak | tail -100 + exit 1 + fi done TOKEN=$(curl -fsS -X POST \ -d "grant_type=password" \ @@ -301,6 +321,10 @@ jobs: working-directory: cvm-frontend run: npx playwright test + - name: Sammele Keycloak-Log + if: failure() + run: docker logs keycloak > keycloak.log 2>&1 || true + - name: Upload Playwright-Report if: failure() uses: actions/upload-artifact@v4 @@ -311,4 +335,5 @@ jobs: cvm-frontend/test-results/ backend.log frontend.log + keycloak.log retention-days: 14 From 4e67f33e9ed9c81d3bcb4dab147eeb102854c6c0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 13 May 2026 17:54:30 +0000 Subject: [PATCH 2/2] test(persistence): tenantId in Test-Fixtures setzen (DEFAULT_TENANT_ID) Run 2 hat ueber das hochgeladene Surefire-Artefakt (DataIntegrityViolationException) gezeigt, dass vier Tests in cvm-persistence Produkt/-Version/Environment/Scan/Occurrence/ Finding/Assessment ohne tenant_id speichern - das Feld ist seit V0035 (Iteration 62A, CVM-62) NOT NULL. Lokal skippen die Tests, weil das Sandbox kein Docker hat; in CI laufen sie und decken den vorbestehenden Fixture-Bug auf. Fix mechanisch, kein Produktionscode-Eingriff: - AbstractPersistenceIntegrationsTest: protected DEFAULT_TENANT_ID (00000000-0000-0000-0000-000000000001), entspricht dem is_default-Mandanten aus dem Flyway-Seed V0023. - ProductRepositoryIntegrationsTest: tenantId an Product + ProductVersion. - AssessmentImmutableTest: tenantId an Product, ProductVersion, Environment, Scan, ComponentOccurrence, Finding, Assessment. Component + Cve sind mandantenuebergreifend und bleiben unveraendert. CVM-62 https://claude.ai/code/session_014NNjRKw5bDqRffqCUGe8ud --- .../AbstractPersistenceIntegrationsTest.java | 10 +++ .../assessment/AssessmentImmutableTest.java | 66 +++++++++++-------- .../ProductRepositoryIntegrationsTest.java | 9 ++- 3 files changed, 54 insertions(+), 31 deletions(-) diff --git a/cvm-persistence/src/test/java/com/ahs/cvm/persistence/AbstractPersistenceIntegrationsTest.java b/cvm-persistence/src/test/java/com/ahs/cvm/persistence/AbstractPersistenceIntegrationsTest.java index c6a53e8..080394e 100644 --- a/cvm-persistence/src/test/java/com/ahs/cvm/persistence/AbstractPersistenceIntegrationsTest.java +++ b/cvm-persistence/src/test/java/com/ahs/cvm/persistence/AbstractPersistenceIntegrationsTest.java @@ -1,6 +1,7 @@ package com.ahs.cvm.persistence; import com.ahs.cvm.persistence.support.DockerAvailability; +import java.util.UUID; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.context.DynamicPropertyRegistry; @@ -17,6 +18,15 @@ @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) public abstract class AbstractPersistenceIntegrationsTest { + /** + * Default-Mandant, der von Flyway-Migration V0023 als + * {@code is_default = TRUE} angelegt wird. Persistenz-Tests haben + * keine Aspect-Infrastruktur, die tenant_id automatisch fuellt - + * Fixtures setzen die Mandanten-Zuordnung deshalb explizit hierueber. + */ + protected static final UUID DEFAULT_TENANT_ID = + UUID.fromString("00000000-0000-0000-0000-000000000001"); + @SuppressWarnings("resource") static final PostgreSQLContainer POSTGRES = new PostgreSQLContainer<>( DockerImageName.parse("pgvector/pgvector:pg16") diff --git a/cvm-persistence/src/test/java/com/ahs/cvm/persistence/assessment/AssessmentImmutableTest.java b/cvm-persistence/src/test/java/com/ahs/cvm/persistence/assessment/AssessmentImmutableTest.java index efb5417..a0add6d 100644 --- a/cvm-persistence/src/test/java/com/ahs/cvm/persistence/assessment/AssessmentImmutableTest.java +++ b/cvm-persistence/src/test/java/com/ahs/cvm/persistence/assessment/AssessmentImmutableTest.java @@ -75,45 +75,53 @@ void assessmentSupersededErlaubt() { } private Assessment bereiteAssessmentVor() { - Product produkt = productRepository.save( - Product.builder().name("PortalCore-Test").key("pc-imm").build()); - ProductVersion pv = productVersionRepository.save( - ProductVersion.builder() - .product(produkt) - .version("1.0.0-imm") - .build()); - Environment env = environmentRepository.save( - Environment.builder() - .key("REF-IMM") - .name("Referenz Immutable") - .stage(EnvironmentStage.REF) - .build()); - Scan scan = scanRepository.save( - Scan.builder() - .productVersion(pv) - .sbomFormat("CycloneDX") - .sbomChecksum("sha256:imm") - .scannedAt(Instant.now()) - .build()); + Product produkt = productRepository.save(Product.builder() + .tenantId(DEFAULT_TENANT_ID) + .name("PortalCore-Test") + .key("pc-imm") + .build()); + ProductVersion pv = productVersionRepository.save(ProductVersion.builder() + .tenantId(DEFAULT_TENANT_ID) + .product(produkt) + .version("1.0.0-imm") + .build()); + Environment env = environmentRepository.save(Environment.builder() + .tenantId(DEFAULT_TENANT_ID) + .key("REF-IMM") + .name("Referenz Immutable") + .stage(EnvironmentStage.REF) + .build()); + Scan scan = scanRepository.save(Scan.builder() + .tenantId(DEFAULT_TENANT_ID) + .productVersion(pv) + .sbomFormat("CycloneDX") + .sbomChecksum("sha256:imm") + .scannedAt(Instant.now()) + .build()); Component comp = componentRepository.save( Component.builder() .purl("pkg:maven/org.test/imm@1.0") .name("imm") .version("1.0") .build()); - ComponentOccurrence occ = occurrenceRepository.save( - ComponentOccurrence.builder().scan(scan).component(comp).direct(true).build()); + ComponentOccurrence occ = occurrenceRepository.save(ComponentOccurrence.builder() + .tenantId(DEFAULT_TENANT_ID) + .scan(scan) + .component(comp) + .direct(true) + .build()); Cve cve = cveRepository.save( Cve.builder().cveId("CVE-2026-22610").source("NVD").build()); - Finding finding = findingRepository.save( - Finding.builder() - .scan(scan) - .componentOccurrence(occ) - .cve(cve) - .detectedAt(Instant.now()) - .build()); + Finding finding = findingRepository.save(Finding.builder() + .tenantId(DEFAULT_TENANT_ID) + .scan(scan) + .componentOccurrence(occ) + .cve(cve) + .detectedAt(Instant.now()) + .build()); Assessment assessment = Assessment.builder() + .tenantId(DEFAULT_TENANT_ID) .finding(finding) .productVersion(pv) .environment(env) diff --git a/cvm-persistence/src/test/java/com/ahs/cvm/persistence/product/ProductRepositoryIntegrationsTest.java b/cvm-persistence/src/test/java/com/ahs/cvm/persistence/product/ProductRepositoryIntegrationsTest.java index 4382c80..3c9a570 100644 --- a/cvm-persistence/src/test/java/com/ahs/cvm/persistence/product/ProductRepositoryIntegrationsTest.java +++ b/cvm-persistence/src/test/java/com/ahs/cvm/persistence/product/ProductRepositoryIntegrationsTest.java @@ -24,6 +24,7 @@ class ProductRepositoryIntegrationsTest extends AbstractPersistenceIntegrationsT @DisplayName("Product: save setzt id und createdAt via @PrePersist") void produktSpeichernSetztTechnischeFelder() { Product portalcore = Product.builder() + .tenantId(DEFAULT_TENANT_ID) .name("PortalCore-Test") .key("portalcore-test") .description("Referenz-Produkt") @@ -39,11 +40,15 @@ void produktSpeichernSetztTechnischeFelder() { @Test @DisplayName("ProductVersion: findByProductIdAndVersion liefert eindeutigen Treffer") void produktVersionLookup() { - Product produkt = productRepository.save( - Product.builder().name("SmileKH-Test").key("smile-test").build()); + Product produkt = productRepository.save(Product.builder() + .tenantId(DEFAULT_TENANT_ID) + .name("SmileKH-Test") + .key("smile-test") + .build()); ProductVersion version = productVersionRepository.save( ProductVersion.builder() + .tenantId(DEFAULT_TENANT_ID) .product(produkt) .version("1.14.2-test") .gitCommit("a3f9beef")