diff --git a/.github/workflows/release-smoke.yml b/.github/workflows/release-smoke.yml new file mode 100644 index 00000000..9f18aa62 --- /dev/null +++ b/.github/workflows/release-smoke.yml @@ -0,0 +1,41 @@ +name: Release Smoke (consumer verification) + +# Manually verifies that a PUBLISHED GraphCompose release resolves and works for +# real external consumers straight from Maven Central (scripts/release-smoke/). +# Not tied to a tag or push — dispatch it after a publish (allowing for Central +# indexing delay), or any time, to re-check a shipped version. It never touches a +# -SNAPSHOT: release smoke tests published artifacts only. + +on: + workflow_dispatch: + inputs: + version: + description: 'Published GraphCompose version to smoke-test from Maven Central' + required: true + type: string + default: '2.0.0' + +permissions: + contents: read + +jobs: + smoke: + name: Smoke ${{ inputs.version }} from Maven Central + runs-on: ubuntu-latest + env: + JAVA_TOOL_OPTIONS: -Djava.awt.headless=true + steps: + - name: Check out repository + uses: actions/checkout@v7 + + - name: Set up Temurin JDK 17 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '17' + + - name: Run release smoke against Maven Central + # The harness forces Central-only resolution (its settings.xml) and evicts + # the GraphCompose artifacts before each scenario, so this genuinely tests + # the published ${{ inputs.version }} coordinates end to end. + run: bash scripts/release-smoke/run.sh --version "${{ inputs.version }}" diff --git a/scripts/release-smoke/README.md b/scripts/release-smoke/README.md new file mode 100644 index 00000000..5d052a05 --- /dev/null +++ b/scripts/release-smoke/README.md @@ -0,0 +1,71 @@ +# Release smoke — external consumer projects + +Standalone Maven projects that consume the **published** GraphCompose +coordinates from Maven Central, proving the modular 2.0 release resolves and +behaves as documented *outside* this repository — no reactor, no local +`mvn install` of GraphCompose beforehand. + +These projects are **not** part of the root reactor (the root `pom.xml` +`` does not list them and each pom has no ``), so a normal +build never touches them. Run them explicitly with the harness below. + +## Scenarios + +| Dir | Coordinate(s) under test | Proves | +|---|---|---| +| `s1-graph-compose` | `graph-compose` | the drop-in wrapper renders a PDF out of the box | +| `s2-core-only` | `graph-compose-core` | a lean core throws `MissingBackendException` naming `graph-compose-render-pdf`, and its dependency tree pulls no PDFBox / POI / ZXing / templates / fonts / emoji (enforced by `maven-enforcer` bannedDependencies) | +| `s3-core-render-pdf` | `graph-compose-core` + `graph-compose-render-pdf` | the explicit lean + backend combination renders a PDF | +| `s4-templates` | `graph-compose-templates` | a built-in template composes and renders through the PDF stack | +| `s5-testing` | `graph-compose` + `graph-compose-testing` | the consumer testing helper (`LayoutSnapshotAssertions`) resolves and round-trips a layout snapshot | +| `s6-bundle` | `graph-compose-bundle` | the batteries-included aggregate renders a templated document, exposes the bundled fonts (`DefaultFonts.bundledFontNames()`), and makes the colour-emoji set resolvable (`GraphComposeEmoji.isAvailable()`) | + +The "must pull core + render-pdf" (wrapper) and "must pull the documented +aggregate" (bundle) assertions are proven positively: `s1` / `s6` can only +render because the backend and companions were pulled transitively. + +## Running + +```bash +# Evict the GraphCompose artifacts before each scenario (Central-only, isolated): +./scripts/release-smoke/run.sh + +# Smoke-test a different published version: +./scripts/release-smoke/run.sh --version 2.0.1 + +# Fast dev iteration — keep everything cached: +./scripts/release-smoke/run.sh --warm +``` + +```powershell +pwsh ./scripts/release-smoke/run.ps1 # isolated +pwsh ./scripts/release-smoke/run.ps1 -Version 2.0.1 # a different published version +pwsh ./scripts/release-smoke/run.ps1 -Warm # warm +``` + +Or dispatch the **Release Smoke (consumer verification)** GitHub Actions workflow +(`.github/workflows/release-smoke.yml`) with a `version` input — handy after a +publish, once Central has indexed the release. + +The harness passes an isolated `settings.xml` (`-s`) whose `mirrorOf=*` mirror +forces **every** artifact and plugin request through Maven Central, and uses one +dedicated local repository under `target/` that never receives an `mvn install` +of GraphCompose. In the default (isolated) mode it **evicts the GraphCompose +artifacts** (`io/github/demchaav/**`) before each scenario — hard-failing if the +eviction does not take — so every scenario must re-resolve `graph-compose-*` from +Central, proving the release resolves with no local reactor build behind it. +Maven's own plugins and third-party libraries (PDFBox, JUnit, …) stay cached, +because re-downloading Maven's core plugins onto an empty repository is heavy, +flaky, and tests Maven rather than this release. Classpath isolation between +scenarios (e.g. the lean core never seeing the PDF backend) comes from each +project's declared dependencies and the `s2` enforcer rule, not from the +repository state. + +The harness prints one `RESULT PASS|FAIL` line per project and ends +with a machine-readable `SUMMARY {"version":"…","passed":N,"failed":M,"total":T}` +line. Exit code is non-zero if any scenario fails. + +The version under test defaults to the current published release (`2.0.0`) and is +overridable with `--version` / `-Version` (or the workflow's `version` input); it +is passed to Maven as `-Dgc.version`, overriding the `gc.version` property in each +pom. Release smoke always tests **published** artifacts — never a `-SNAPSHOT`. diff --git a/scripts/release-smoke/run.ps1 b/scripts/release-smoke/run.ps1 new file mode 100644 index 00000000..07cb51fc --- /dev/null +++ b/scripts/release-smoke/run.ps1 @@ -0,0 +1,68 @@ +<# + Release smoke harness (PowerShell) — runs every external consumer project + against the published GraphCompose coordinates on Maven Central. See README.md. + + Isolation model: a dedicated local repository under target/ that never receives + an `mvn install` of GraphCompose, plus an isolated settings.xml whose mirror + forces ALL resolution through Maven Central. Before each scenario the GraphCompose + artifacts (io\github\demchaav\**) are EVICTED — and the harness hard-fails if the + eviction does not take — so every scenario must re-resolve graph-compose-* from + Central. Maven's own plugins and third-party libraries stay cached: re-downloading + Maven's core plugins on an empty repo is heavy, flaky, and tests Maven, not this + release. + + Usage: + pwsh ./scripts/release-smoke/run.ps1 # isolated, tests 2.0.0 + pwsh ./scripts/release-smoke/run.ps1 -Version 2.0.1 # test a different published version + pwsh ./scripts/release-smoke/run.ps1 -Warm # keep everything cached (fast dev iteration) +#> +param( + [switch]$Warm, + # Default version under test: the currently published release. Release smoke + # must test PUBLISHED artifacts — never a -SNAPSHOT. + [string]$Version = '2.0.0' +) + +$ErrorActionPreference = 'Continue' +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$repoRoot = (Resolve-Path (Join-Path $here '..\..')).Path +$mvnw = Join-Path $repoRoot 'mvnw.cmd' +$settings = Join-Path $here 'settings.xml' + +$scenarios = @('s1-graph-compose', 's2-core-only', 's3-core-render-pdf', 's4-templates', 's5-testing', 's6-bundle') +$repo = Join-Path $repoRoot 'target\release-smoke-m2\repo' +New-Item -ItemType Directory -Force -Path $repo | Out-Null + +$pass = 0 +$fail = 0 +$results = @() + +foreach ($s in $scenarios) { + if (-not $Warm) { + # Evict only the GraphCompose coordinates, then hard-fail if it did not take. + $gc = Join-Path $repo 'io\github\demchaav' + if (Test-Path $gc) { Remove-Item -Recurse -Force $gc } + if (Test-Path $gc) { + Write-Error "FATAL: could not remove the GraphCompose cache directory: $gc" + exit 3 + } + } + Write-Host "" + Write-Host "==================================================================" + Write-Host "=== SMOKE $s (version=$Version, repo=$repo, evicted=$(if ($Warm) { 'no' } else { 'yes' }))" + Write-Host "==================================================================" + & $mvnw -B -ntp -s $settings "-Dgc.version=$Version" -f (Join-Path $here "$s\pom.xml") "-Dmaven.repo.local=$repo" clean verify + if ($LASTEXITCODE -eq 0) { + $results += "$s PASS"; $pass++ + } else { + $results += "$s FAIL"; $fail++ + } +} + +Write-Host "" +Write-Host "===================== RELEASE SMOKE SUMMARY =====================" +Write-Host "version-under-test: $Version" +foreach ($r in $results) { Write-Host "RESULT $r" } +Write-Host ("SUMMARY {""version"":""$Version"",""passed"":$pass,""failed"":$fail,""total"":$($pass + $fail)}") + +if ($fail -ne 0) { exit 1 } else { exit 0 } diff --git a/scripts/release-smoke/run.sh b/scripts/release-smoke/run.sh new file mode 100755 index 00000000..96f472a8 --- /dev/null +++ b/scripts/release-smoke/run.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# +# Release smoke harness — runs every external consumer project against the +# published GraphCompose coordinates on Maven Central. See README.md. +# +# Isolation model: a dedicated local repository under target/ that never receives +# an `mvn install` of GraphCompose, plus an isolated settings.xml whose mirror +# forces ALL resolution through Maven Central. Before each scenario the GraphCompose +# artifacts (io/github/demchaav/**) are EVICTED — and the harness hard-fails if the +# eviction does not take — so every scenario must re-resolve graph-compose-* from +# Central. Maven's own plugins and third-party libraries stay cached: re-downloading +# Maven's core plugins on an empty repo is heavy, flaky, and tests Maven, not this +# release. +# +# Usage: +# ./scripts/release-smoke/run.sh # isolated, tests gc.version=2.0.0 +# ./scripts/release-smoke/run.sh --version 2.0.1 # test a different published version +# ./scripts/release-smoke/run.sh --warm # keep everything cached (fast dev iteration) +# +set -u + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$HERE/../.." && pwd)" +MVNW="$REPO_ROOT/mvnw" +SETTINGS="$HERE/settings.xml" + +SCENARIOS=(s1-graph-compose s2-core-only s3-core-render-pdf s4-templates s5-testing s6-bundle) +REPO="$REPO_ROOT/target/release-smoke-m2/repo" + +# Default version under test: the currently published release. Release smoke must +# test PUBLISHED artifacts — never a -SNAPSHOT. +GC_VERSION="2.0.0" +WARM=0 +while [ $# -gt 0 ]; do + case "$1" in + --warm) WARM=1; shift ;; + --version) GC_VERSION="${2:?--version needs a value}"; shift 2 ;; + --version=*) GC_VERSION="${1#*=}"; shift ;; + *) echo "unknown argument: $1" >&2; exit 2 ;; + esac +done +mkdir -p "$REPO" + +pass=0 +fail=0 +declare -a results + +for s in "${SCENARIOS[@]}"; do + if [ "$WARM" = "0" ]; then + # Evict only the GraphCompose coordinates so they must come from Central, + # then hard-fail if the eviction did not take (a stale cache would mask a + # broken publish). + gc_dir="$REPO/io/github/demchaav" + rm -rf "$gc_dir" + if [ -d "$gc_dir" ]; then + echo "FATAL: could not remove the GraphCompose cache directory: $gc_dir" >&2 + exit 3 + fi + fi + echo "" + echo "==================================================================" + echo "=== SMOKE $s (version=$GC_VERSION, repo=$REPO, evicted=$([ "$WARM" = "0" ] && echo yes || echo no))" + echo "==================================================================" + "$MVNW" -B -ntp -s "$SETTINGS" -Dgc.version="$GC_VERSION" \ + -f "$HERE/$s/pom.xml" -Dmaven.repo.local="$REPO" clean verify + if [ $? -eq 0 ]; then + results+=("$s PASS") + pass=$((pass + 1)) + else + results+=("$s FAIL") + fail=$((fail + 1)) + fi +done + +echo "" +echo "===================== RELEASE SMOKE SUMMARY =====================" +echo "version-under-test: $GC_VERSION" +for r in "${results[@]}"; do + echo "RESULT $r" +done +echo "SUMMARY {\"version\":\"$GC_VERSION\",\"passed\":$pass,\"failed\":$fail,\"total\":$((pass + fail))}" + +[ "$fail" = "0" ] diff --git a/scripts/release-smoke/s1-graph-compose/pom.xml b/scripts/release-smoke/s1-graph-compose/pom.xml new file mode 100644 index 00000000..ce855022 --- /dev/null +++ b/scripts/release-smoke/s1-graph-compose/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + + com.demcha.smoke + graph-compose-smoke-graph-compose + 1.0.0 + + + UTF-8 + 17 + 2.0.0 + 5.11.4 + 3.27.3 + 3.5.2 + + + + + io.github.demchaav + graph-compose + ${gc.version} + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.assertj + assertj-core + ${assertj.version} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.version} + + + + diff --git a/scripts/release-smoke/s1-graph-compose/src/test/java/com/demcha/smoke/WrapperRendersPdfTest.java b/scripts/release-smoke/s1-graph-compose/src/test/java/com/demcha/smoke/WrapperRendersPdfTest.java new file mode 100644 index 00000000..22dc01b3 --- /dev/null +++ b/scripts/release-smoke/s1-graph-compose/src/test/java/com/demcha/smoke/WrapperRendersPdfTest.java @@ -0,0 +1,39 @@ +package com.demcha.smoke; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Scenario 1 — the drop-in {@code graph-compose} wrapper renders a PDF out of + * the box, exactly as a 1.x caller expects after upgrading to 2.0. The wrapper + * pulls {@code graph-compose-core} + {@code graph-compose-render-pdf} + * transitively, so a successful render is positive proof both were resolved. + */ +class WrapperRendersPdfTest { + + @Test + void graphComposeWrapperRendersPdfOutOfTheBox() throws Exception { + Path out = Files.createTempFile("gc-smoke-wrapper", ".pdf"); + try (DocumentSession document = GraphCompose.document(out) + .pageSize(DocumentPageSize.A4) + .margin(36f, 36f, 36f, 36f) + .create()) { + document.add(document.dsl().paragraph() + .text("graph-compose 2.0.0 renders PDF out of the box.") + .build()); + document.buildPdf(); + } + + assertThat(Files.size(out)).isGreaterThan(0L); + byte[] head = Arrays.copyOf(Files.readAllBytes(out), 5); + assertThat(new String(head)).isEqualTo("%PDF-"); + } +} diff --git a/scripts/release-smoke/s2-core-only/pom.xml b/scripts/release-smoke/s2-core-only/pom.xml new file mode 100644 index 00000000..d1d90e2e --- /dev/null +++ b/scripts/release-smoke/s2-core-only/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + + com.demcha.smoke + graph-compose-smoke-core-only + 1.0.0 + + + UTF-8 + 17 + 2.0.0 + 5.11.4 + 3.27.3 + 3.5.2 + 3.5.0 + + + + + io.github.demchaav + graph-compose-core + ${gc.version} + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.assertj + assertj-core + ${assertj.version} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.version} + + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${enforcer.version} + + + core-stays-lean + + enforce + + + + + true + + org.apache.pdfbox + org.apache.poi + com.google.zxing + io.github.demchaav:graph-compose-render-pdf + io.github.demchaav:graph-compose-render-docx + io.github.demchaav:graph-compose-render-pptx + io.github.demchaav:graph-compose-templates + io.github.demchaav:graph-compose-fonts + io.github.demchaav:graph-compose-emoji + + + + true + + + + + + + diff --git a/scripts/release-smoke/s2-core-only/src/test/java/com/demcha/smoke/CoreOnlyMissingBackendTest.java b/scripts/release-smoke/s2-core-only/src/test/java/com/demcha/smoke/CoreOnlyMissingBackendTest.java new file mode 100644 index 00000000..2753c706 --- /dev/null +++ b/scripts/release-smoke/s2-core-only/src/test/java/com/demcha/smoke/CoreOnlyMissingBackendTest.java @@ -0,0 +1,41 @@ +package com.demcha.smoke; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.exceptions.MissingBackendException; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Scenario 2 — a lean {@code graph-compose-core} consumer with no render + * backend on the classpath. Asking it to build a PDF must fail with a + * {@link MissingBackendException} whose message names the artifact to add + * ({@code graph-compose-render-pdf}), rather than an opaque NPE or ISE. The + * dependency-tree leanness is asserted separately by the enforcer rule in the + * pom. + */ +class CoreOnlyMissingBackendTest { + + @Test + void coreOnlyRenderThrowsMissingBackendNamingRenderPdf() throws Exception { + Path out = Files.createTempFile("gc-smoke-core-only", ".pdf"); + assertThatThrownBy(() -> { + try (DocumentSession document = GraphCompose.document(out) + .pageSize(DocumentPageSize.A4) + .margin(36f, 36f, 36f, 36f) + .create()) { + document.add(document.dsl().paragraph() + .text("no backend on the classpath") + .build()); + document.buildPdf(); + } + }) + .isInstanceOf(MissingBackendException.class) + .hasMessageContaining("graph-compose-render-pdf"); + } +} diff --git a/scripts/release-smoke/s3-core-render-pdf/pom.xml b/scripts/release-smoke/s3-core-render-pdf/pom.xml new file mode 100644 index 00000000..8e104cce --- /dev/null +++ b/scripts/release-smoke/s3-core-render-pdf/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + + com.demcha.smoke + graph-compose-smoke-core-render-pdf + 1.0.0 + + + UTF-8 + 17 + 2.0.0 + 5.11.4 + 3.27.3 + 3.5.2 + + + + + io.github.demchaav + graph-compose-core + ${gc.version} + + + io.github.demchaav + graph-compose-render-pdf + ${gc.version} + runtime + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.assertj + assertj-core + ${assertj.version} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.version} + + + + diff --git a/scripts/release-smoke/s3-core-render-pdf/src/test/java/com/demcha/smoke/CoreRenderPdfTest.java b/scripts/release-smoke/s3-core-render-pdf/src/test/java/com/demcha/smoke/CoreRenderPdfTest.java new file mode 100644 index 00000000..930fa2da --- /dev/null +++ b/scripts/release-smoke/s3-core-render-pdf/src/test/java/com/demcha/smoke/CoreRenderPdfTest.java @@ -0,0 +1,40 @@ +package com.demcha.smoke; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Scenario 3 — the explicit bring-your-own-backend combination + * ({@code graph-compose-core} + {@code graph-compose-render-pdf}) renders a PDF. + * The PDF backend registers its {@code FixedLayoutBackendProvider} / + * {@code FontMetricsProvider} via {@code META-INF/services}, so the core + * discovers it at runtime through the ServiceLoader SPI. + */ +class CoreRenderPdfTest { + + @Test + void coreWithRenderPdfRendersPdf() throws Exception { + Path out = Files.createTempFile("gc-smoke-core-render-pdf", ".pdf"); + try (DocumentSession document = GraphCompose.document(out) + .pageSize(DocumentPageSize.A4) + .margin(36f, 36f, 36f, 36f) + .create()) { + document.add(document.dsl().paragraph() + .text("graph-compose-core + graph-compose-render-pdf renders via the SPI.") + .build()); + document.buildPdf(); + } + + assertThat(Files.size(out)).isGreaterThan(0L); + byte[] head = Arrays.copyOf(Files.readAllBytes(out), 5); + assertThat(new String(head)).isEqualTo("%PDF-"); + } +} diff --git a/scripts/release-smoke/s4-templates/pom.xml b/scripts/release-smoke/s4-templates/pom.xml new file mode 100644 index 00000000..259b95b5 --- /dev/null +++ b/scripts/release-smoke/s4-templates/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + + + com.demcha.smoke + graph-compose-smoke-templates + 1.0.0 + + + UTF-8 + 17 + 2.0.0 + 5.11.4 + 3.27.3 + 3.5.2 + + + + + io.github.demchaav + graph-compose + ${gc.version} + + + io.github.demchaav + graph-compose-templates + ${gc.version} + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.assertj + assertj-core + ${assertj.version} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.version} + + + + diff --git a/scripts/release-smoke/s4-templates/src/test/java/com/demcha/smoke/TemplatesRenderTest.java b/scripts/release-smoke/s4-templates/src/test/java/com/demcha/smoke/TemplatesRenderTest.java new file mode 100644 index 00000000..9a6708cf --- /dev/null +++ b/scripts/release-smoke/s4-templates/src/test/java/com/demcha/smoke/TemplatesRenderTest.java @@ -0,0 +1,53 @@ +package com.demcha.smoke; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec; +import com.demcha.compose.document.templates.invoice.presets.ModernInvoice; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Scenario 4 — a built-in template ({@code ModernInvoice}) from the opt-in + * {@code graph-compose-templates} module composes over the canonical DSL and + * renders through the PDF stack. + */ +class TemplatesRenderTest { + + @Test + void modernInvoiceTemplateComposesAndRenders() throws Exception { + InvoiceDocumentSpec spec = InvoiceDocumentSpec.builder() + .title("Smoke Invoice") + .invoiceNumber("SMOKE-001") + .issueDate("2026-07-13") + .dueDate("2026-08-13") + .status("DUE") + .fromParty(p -> p.name("Acme Studio")) + .billToParty(p -> p.name("Client Ltd")) + .lineItem("Consulting", "Release smoke test", "1", "$100.00", "$100.00") + .totalRow("Total", "$100.00") + .build(); + + DocumentTemplate template = ModernInvoice.create(); + + Path out = Files.createTempFile("gc-smoke-templates", ".pdf"); + try (DocumentSession document = GraphCompose.document(out) + .pageSize(DocumentPageSize.A4) + .margin(36f, 36f, 36f, 36f) + .create()) { + template.compose(document, spec); + document.buildPdf(); + } + + assertThat(Files.size(out)).isGreaterThan(0L); + byte[] head = Arrays.copyOf(Files.readAllBytes(out), 5); + assertThat(new String(head)).isEqualTo("%PDF-"); + } +} diff --git a/scripts/release-smoke/s5-testing/pom.xml b/scripts/release-smoke/s5-testing/pom.xml new file mode 100644 index 00000000..2ae6e839 --- /dev/null +++ b/scripts/release-smoke/s5-testing/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + + com.demcha.smoke + graph-compose-smoke-testing + 1.0.0 + + + UTF-8 + 17 + 2.0.0 + 5.11.4 + 3.27.3 + 3.5.2 + + + + + io.github.demchaav + graph-compose + ${gc.version} + + + io.github.demchaav + graph-compose-testing + ${gc.version} + test + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.assertj + assertj-core + ${assertj.version} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.version} + + + + diff --git a/scripts/release-smoke/s5-testing/src/test/java/com/demcha/smoke/TestingHelperTest.java b/scripts/release-smoke/s5-testing/src/test/java/com/demcha/smoke/TestingHelperTest.java new file mode 100644 index 00000000..0e4c2bd0 --- /dev/null +++ b/scripts/release-smoke/s5-testing/src/test/java/com/demcha/smoke/TestingHelperTest.java @@ -0,0 +1,50 @@ +package com.demcha.smoke; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.testing.layout.LayoutSnapshotAssertions; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Scenario 5 — the consumer testing support {@code graph-compose-testing} + * resolves and its documented helper ({@code LayoutSnapshotAssertions}) works + * end to end: extract a layout snapshot, write a baseline in update mode, then + * assert the freshly rendered layout matches it. Baselines are written under + * {@code target/} so nothing is left in the source tree. + */ +class TestingHelperTest { + + @Test + void layoutSnapshotAssertionsRoundTrips() throws Exception { + Path expected = Path.of("target", "smoke-snapshots", "expected"); + Path actual = Path.of("target", "smoke-snapshots", "actual"); + Path out = Files.createTempFile("gc-smoke-testing", ".pdf"); + + try (DocumentSession document = GraphCompose.document(out) + .pageSize(DocumentPageSize.A4) + .margin(36f, 36f, 36f, 36f) + .create()) { + document.add(document.dsl().paragraph() + .text("graph-compose-testing layout snapshot smoke.") + .build()); + + // 1) update mode: extract + serialize the snapshot to the baseline root. + System.setProperty(LayoutSnapshotAssertions.UPDATE_PROPERTY, "true"); + LayoutSnapshotAssertions.assertMatches(document, expected, actual, "release-smoke"); + + // 2) compare mode: the freshly rendered layout must match the baseline. + System.setProperty(LayoutSnapshotAssertions.UPDATE_PROPERTY, "false"); + LayoutSnapshotAssertions.assertMatches(document, expected, actual, "release-smoke"); + } finally { + System.clearProperty(LayoutSnapshotAssertions.UPDATE_PROPERTY); + } + + assertThat(Files.exists(expected.resolve("release-smoke.json"))).isTrue(); + } +} diff --git a/scripts/release-smoke/s6-bundle/pom.xml b/scripts/release-smoke/s6-bundle/pom.xml new file mode 100644 index 00000000..6ca5a9f9 --- /dev/null +++ b/scripts/release-smoke/s6-bundle/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + + com.demcha.smoke + graph-compose-smoke-bundle + 1.0.0 + + + UTF-8 + 17 + 2.0.0 + 5.11.4 + 3.27.3 + 3.5.2 + + + + + io.github.demchaav + graph-compose-bundle + ${gc.version} + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.assertj + assertj-core + ${assertj.version} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.version} + + + + diff --git a/scripts/release-smoke/s6-bundle/src/test/java/com/demcha/smoke/BundleRendersTest.java b/scripts/release-smoke/s6-bundle/src/test/java/com/demcha/smoke/BundleRendersTest.java new file mode 100644 index 00000000..05e4cc01 --- /dev/null +++ b/scripts/release-smoke/s6-bundle/src/test/java/com/demcha/smoke/BundleRendersTest.java @@ -0,0 +1,63 @@ +package com.demcha.smoke; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.templates.api.DocumentTemplate; +import com.demcha.compose.document.templates.data.invoice.InvoiceDocumentSpec; +import com.demcha.compose.document.templates.invoice.presets.ModernInvoice; +import com.demcha.compose.emoji.GraphComposeEmoji; +import com.demcha.compose.font.DefaultFonts; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Scenario 6 — the batteries-included {@code graph-compose-bundle} aggregate. + * A single dependency renders a templated document through the PDF stack, + * exposes the bundled font catalogue, and makes the colour-emoji set + * resolvable — proving the bundle pulled the documented aggregate set + * (wrapper + templates + fonts + emoji) transitively. + */ +class BundleRendersTest { + + @Test + void bundleRendersTemplateAndExposesFontsAndEmoji() throws Exception { + InvoiceDocumentSpec spec = InvoiceDocumentSpec.builder() + .title("Bundle Smoke Invoice") + .invoiceNumber("SMOKE-BUNDLE-001") + .issueDate("2026-07-13") + .dueDate("2026-08-13") + .status("DUE") + .fromParty(p -> p.name("Acme Studio")) + .billToParty(p -> p.name("Client Ltd")) + .lineItem("Consulting", "Bundle smoke test", "1", "$100.00", "$100.00") + .totalRow("Total", "$100.00") + .build(); + + DocumentTemplate template = ModernInvoice.create(); + + Path out = Files.createTempFile("gc-smoke-bundle", ".pdf"); + try (DocumentSession document = GraphCompose.document(out) + .pageSize(DocumentPageSize.A4) + .margin(36f, 36f, 36f, 36f) + .create()) { + template.compose(document, spec); + document.buildPdf(); + } + + assertThat(Files.size(out)).isGreaterThan(0L); + byte[] head = Arrays.copyOf(Files.readAllBytes(out), 5); + assertThat(new String(head)).isEqualTo("%PDF-"); + + // Bundled font catalogue is on the classpath (graph-compose-fonts). + assertThat(DefaultFonts.bundledFontNames()).isNotEmpty(); + + // Colour-emoji set is resolvable (graph-compose-emoji). + assertThat(GraphComposeEmoji.isAvailable()).isTrue(); + } +} diff --git a/scripts/release-smoke/settings.xml b/scripts/release-smoke/settings.xml new file mode 100644 index 00000000..834bf4a3 --- /dev/null +++ b/scripts/release-smoke/settings.xml @@ -0,0 +1,21 @@ + + + + + + central-only + Maven Central only + https://repo.maven.apache.org/maven2 + * + + +