From 67127f533bb0fad1a6ea135d95dd2fc3f30c07c7 Mon Sep 17 00:00:00 2001 From: Ben Papillon Date: Thu, 16 Jul 2026 09:42:19 -0700 Subject: [PATCH] chore(tests): add packaged-artifact E2E variant for wasm packaging smoke test --- .fernignore | 2 + .gitignore | 3 ++ scripts/e2e-pack.sh | 61 ++++++++++++++++++++++ testapp-packaged/README.md | 66 ++++++++++++++++++++++++ testapp-packaged/Testapp.Packaged.csproj | 38 ++++++++++++++ testapp-packaged/nuget.config | 11 ++++ 6 files changed, 181 insertions(+) create mode 100755 scripts/e2e-pack.sh create mode 100644 testapp-packaged/README.md create mode 100644 testapp-packaged/Testapp.Packaged.csproj create mode 100644 testapp-packaged/nuget.config diff --git a/.fernignore b/.fernignore index 02123399..6fdb516f 100644 --- a/.fernignore +++ b/.fernignore @@ -6,11 +6,13 @@ .gitignore WASM_VERSION scripts/download-wasm.sh +scripts/e2e-pack.sh CLAUDE.md LICENSE README.md examples/ testapp/ +testapp-packaged/ src/SchematicHQ.Client.Test/Cache/ src/SchematicHQ.Client.Test/Datastream/ src/SchematicHQ.Client.Test/Integration/ diff --git a/.gitignore b/.gitignore index f83e9c99..c802751e 100644 --- a/.gitignore +++ b/.gitignore @@ -486,3 +486,6 @@ $RECYCLE.BIN/ # Rules engine WASM binary (downloaded at build time from schematic-api GitHub Releases) src/SchematicHQ.Client/RulesEngine/Wasm/rulesengine.wasm src/SchematicHQ.Client/RulesEngine/Wasm/.wasm_version + +# Local NuGet feed produced by scripts/e2e-pack.sh for the packaged-artifact E2E variant +artifacts/ diff --git a/scripts/e2e-pack.sh b/scripts/e2e-pack.sh new file mode 100755 index 00000000..b11295af --- /dev/null +++ b/scripts/e2e-pack.sh @@ -0,0 +1,61 @@ +#!/bin/bash +set -euo pipefail + +# Packs the SchematicHQ.Client SDK into a local NuGet feed for the packaged-artifact +# E2E variant (testapp-packaged/). This validates the real distribution artifact: +# the embedded rulesengine.wasm and the transitive Wasmtime native library must +# survive pack -> restore -> run, which a plain build never +# exercises. +# +# Produces: artifacts/local-feed/SchematicHQ.Client..nupkg +# Consumed by: testapp-packaged/Testapp.Packaged.csproj (via testapp-packaged/nuget.config) + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +FEED_DIR="$REPO_ROOT/artifacts/local-feed" +PKG_VERSION="${SCHEMATIC_E2E_PKG_VERSION:-0.0.0-e2e}" + +# Ensure the rules engine WASM is present before packing so it gets embedded. +"$SCRIPT_DIR/download-wasm.sh" + +echo "Packing SchematicHQ.Client v${PKG_VERSION} -> ${FEED_DIR}" +rm -rf "$FEED_DIR" +mkdir -p "$FEED_DIR" + +dotnet pack "$REPO_ROOT/src/SchematicHQ.Client/SchematicHQ.Client.csproj" \ + -c Release \ + -p:Version="$PKG_VERSION" \ + -o "$FEED_DIR" + +NUPKG="$FEED_DIR/SchematicHQ.Client.${PKG_VERSION}.nupkg" +if [ ! -f "$NUPKG" ]; then + echo "ERROR: expected package not produced: $NUPKG" >&2 + exit 1 +fi + +# Guard: fail loudly if the embedded WASM did not make it into the packaged DLL. +# The wasm (~672 KB) is an embedded manifest resource inside SchematicHQ.Client.dll, +# not a loose package entry, so we verify by DLL size — the exact packaging +# regression this variant exists to catch (an empty/stripped resource ships a +# DLL hundreds of KB smaller). +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT +unzip -o -q "$NUPKG" -d "$TMP" + +DLL="$TMP/lib/net8.0/SchematicHQ.Client.dll" +if [ ! -f "$DLL" ]; then + echo "ERROR: lib/net8.0/SchematicHQ.Client.dll missing from package" >&2 + exit 1 +fi + +# Cross-platform file size (macOS: stat -f%z, Linux: stat -c%s). +DLL_SIZE=$(stat -f%z "$DLL" 2>/dev/null || stat -c%s "$DLL") +MIN_SIZE=600000 # rulesengine.wasm is ~672 KB; a DLL without it is far smaller. +if [ "$DLL_SIZE" -lt "$MIN_SIZE" ]; then + echo "ERROR: packaged DLL is ${DLL_SIZE} bytes (< ${MIN_SIZE})." >&2 + echo " rulesengine.wasm appears NOT embedded — the package would silently" >&2 + echo " fall back to flag defaults at runtime." >&2 + exit 1 +fi + +echo "Packed OK: $NUPKG (embedded DLL ${DLL_SIZE} bytes)" diff --git a/testapp-packaged/README.md b/testapp-packaged/README.md new file mode 100644 index 00000000..7e3b9202 --- /dev/null +++ b/testapp-packaged/README.md @@ -0,0 +1,66 @@ +# testapp-packaged — packaged-artifact E2E variant + +This is a second copy of the SDK E2E test app that consumes `SchematicHQ.Client` +as a **NuGet package** instead of a ``. It exists to catch +packaging/install failures that the source-built `../testapp` cannot. + +## Why + +The rules engine now runs inside a WebAssembly binary (`rulesengine.wasm`) that is +**embedded in the SDK DLL** and executed by the **Wasmtime native library** +(`libwasmtime`), which ships as a transitive, per-RID native asset of the `Wasmtime` +NuGet package. There are several links in the chain where that can break between +`dotnet build` on a dev machine and a real end-user install: + +- `dotnet pack` producing a DLL that is missing the embedded `.wasm` +- the NuGet package omitting or mis-declaring the `Wasmtime` dependency +- `libwasmtime` failing to resolve for the consumer's RID at restore/publish time + (e.g. `linux-musl-x64` / Alpine, `linux-arm`, `win-x86` are **not** shipped by + Wasmtime) + +`../testapp` builds the SDK from local source, so it exercises none of these — its +green run only proves the working tree compiles. This variant restores the built +`.nupkg` from a local feed, so a datastream-mode flag check here runs through the +**packaged** wasm + Wasmtime exactly as an end user would. + +## How it works + +1. `scripts/e2e-pack.sh` packs the SDK (`-p:Version=0.0.0-e2e`) into + `artifacts/local-feed/` and asserts the embedded wasm survived (DLL size guard). +2. `nuget.config` here adds that folder as a package source. +3. `Testapp.Packaged.csproj` links `../testapp/Program.cs` (identical app logic) and + references `SchematicHQ.Client` `0.0.0-e2e` from the local feed. + +## Run locally + +```bash +# from repo root — needs gh auth to the private schematic-api repo for the wasm +./scripts/e2e-pack.sh +dotnet build testapp-packaged/Testapp.Packaged.csproj -c Release +dotnet run --project testapp-packaged/Testapp.Packaged.csproj --no-build -c Release +# then, in another shell, drive it with actions/sdk-e2e/run-local.sh (datastream mode): +# E2E_API_KEY=api_xxx ./run-local.sh '{"useDataStream": true}' +``` + +## Wire into the shared suite + +Add a matrix entry alongside the existing `csharp` one in +`schematic-api/.github/workflows/sdk_e2e.yml` so the packaged artifact is smoke-tested +in a wasm-backed (datastream) mode on every run: + +```yaml + csharp-packaged: + repository: SchematicHQ/schematic-csharp + dotnet-version: "8.0" + setup: ./scripts/e2e-pack.sh && dotnet build testapp-packaged/Testapp.Packaged.csproj -c Release + start: dotnet run --project testapp-packaged/Testapp.Packaged.csproj --no-build -c Release +``` + +> **RID caveat:** GitHub's `ubuntu-latest` runner is `linux-x64` (glibc) — the RID +> Wasmtime *does* ship. This variant therefore proves the pack/restore/embed chain but +> **not** the platforms most likely to be missing a `libwasmtime` (Alpine/musl, arm, +> Windows, macOS). To cover those, run the packaged testapp inside the target base +> image (e.g. an `mcr.microsoft.com/dotnet/aspnet:8.0-alpine` container) and assert a +> non-default flag result. Because `DatastreamClient` degrades to flag defaults when the +> engine fails to initialize, a missing native lib is silent — the assertion must check +> a rule-derived value, not merely that the call returned. diff --git a/testapp-packaged/Testapp.Packaged.csproj b/testapp-packaged/Testapp.Packaged.csproj new file mode 100644 index 00000000..668fa357 --- /dev/null +++ b/testapp-packaged/Testapp.Packaged.csproj @@ -0,0 +1,38 @@ + + + + + net8.0 + enable + enable + SchematicHQ.Testapp + Testapp + + 0.0.0-e2e + + + + + + + + + + + + diff --git a/testapp-packaged/nuget.config b/testapp-packaged/nuget.config new file mode 100644 index 00000000..0684e0c6 --- /dev/null +++ b/testapp-packaged/nuget.config @@ -0,0 +1,11 @@ + + + + + + + + + +