docs: document expanded test coverage and the fake ElectrumX server

README's "Running tests" section now lists what each test area covers and
how to measure coverage; CLAUDE.md/AGENTS.md point future work at extending
the fake ElectrumX server instead of mocking ElectrumClient (it isn't an
interface, by design).
This commit is contained in:
2026-07-02 23:21:50 +02:00
parent 56ce1d4259
commit 31aabd0856
3 changed files with 33 additions and 5 deletions
+6 -3
View File
@@ -29,6 +29,7 @@ src/App/ shared Avalonia UI library (App, Views, ViewModels, Loc, Asset
src/App.Desktop/ desktop head (WinExe): Program.cs, app.manifest, .ico -> runnable
src/App.Android/ Android head (net10.0-android): MainApplication/MainActivity -> apk
src/Cli/ CLI on the same Core tests/ xUnit
docker/ reproducible release builds (build.sh + pinned Dockerfiles) -> dist/
```
The Avalonia UI lives **once** in `src/App` (a library); the two heads only carry the
@@ -43,11 +44,12 @@ by `MainWindow` on desktop and as the single-view root on Android.
`export PATH="$HOME/.dotnet10:$PATH" DOTNET_ROOT="$HOME/.dotnet10"`.
- Build: `dotnet build`
- Tests (headless, the primary verification layer): `dotnet test` -- single: `dotnet test --filter "FullyQualifiedName~TestName"`; property-based tests (CsCheck, `PropertyTests.cs`) run in the same command and take ~30 s
- Tests (headless, the primary verification layer): `dotnet test` -- single: `dotnet test --filter "FullyQualifiedName~TestName"`; property-based tests (CsCheck, `PropertyTests.cs`) run in the same command and take ~30 s; coverage: `dotnet test tests/PalladiumWallet.Tests --collect:"XPlat Code Coverage"` (coverlet, Cobertura XML). Network/SPV code (`ElectrumClient`, `WalletSynchronizer`, `TransactionInspector`, TOFU pinning) is tested against the in-process fake ElectrumX server in `tests/PalladiumWallet.Tests/Net/FakeElectrumServer.cs` (loopback TCP + optional TLS, per-method handlers, call counters): extend that, don't mock the client -- it isn't an interface, by design.
- GUI hot reload: `dotnet watch --project src/App.Desktop` (on WSL2/WSLg the window shows on the Windows desktop, no graphics dependencies to install)
- CLI: `dotnet run --project src/Cli -- <command>` (no args -> usage)
- Windows publish: `dotnet publish src/App.Desktop -r win-x64 -p:PublishSingleFile=true --self-contained`
- Linux publish: `dotnet publish src/App.Desktop -r linux-x64 --self-contained` (then AppImage via PupNet Deploy)
- **Release binaries (all 3 targets): `./docker/build.sh [windows|linux|android|all]`** -- reproducible builds in Docker (toolchain pinned in `docker/Dockerfile.*`, no SDK needed on host), artifacts in `dist/`, version taken from the App csproj. See `docker/README.md`. Gotchas already encoded there: single-file desktop publishes need `-p:IncludeNativeLibrariesForSelfExtract=true` (without it Avalonia's native libs -- Skia/HarfBuzz/ANGLE -- stay outside the exe, which then silently fails to start); the android workload dictates the SDK API level (error XA5207 -> bump `ANDROID_SDK_PLATFORM` in `Dockerfile.android`). Android release builds need a persistent signing keystore, generated once with `docker/keystore/generate-keystore.sh` (never committed -- see `docker/keystore/README.md`): without it every build gets a different random signature and users must uninstall the old app to receive an update instead of updating in place.
- Manual Windows publish: `dotnet publish src/App.Desktop -r win-x64 -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true --self-contained`
- Manual Linux publish: same with `-r linux-x64` (single-file binary; AppImage via PupNet Deploy is a future step, no pupnet.conf yet)
**Android (apk).** Needs the `android` workload (`dotnet workload install android`), a JDK
(`JAVA_HOME`), and the Android SDK. To provision the SDK once:
@@ -82,3 +84,4 @@ Note: a plain `dotnet build` at the solution level needs the Android SDK path fo
- **Cross-implementation tests:** compare addresses, txids, and PSBTs against a reference wallet (golden vectors). A different address or txid is a blocking bug.
- **Security:** seed and private keys never in plaintext on disk/logs/network; every server response validated with Merkle + checkpoints; watch-only truly read-only.
- **Releases:** whenever a new version tag is created (bumping `<Version>` in `src/App/PalladiumWallet.App.csproj`), update `CHANGELOG.md` with an entry for that version before/with the tag -- it's the technical record of what shipped, not optional bookkeeping.
+1 -1
View File
@@ -44,7 +44,7 @@ by `MainWindow` on desktop and as the single-view root on Android.
`export PATH="$HOME/.dotnet10:$PATH" DOTNET_ROOT="$HOME/.dotnet10"`.
- Build: `dotnet build`
- Tests (headless, the primary verification layer): `dotnet test` — single: `dotnet test --filter "FullyQualifiedName~TestName"`; property-based tests (CsCheck, `PropertyTests.cs`) run in the same command and take ~30 s
- Tests (headless, the primary verification layer): `dotnet test` — single: `dotnet test --filter "FullyQualifiedName~TestName"`; property-based tests (CsCheck, `PropertyTests.cs`) run in the same command and take ~30 s; coverage: `dotnet test tests/PalladiumWallet.Tests --collect:"XPlat Code Coverage"` (coverlet, Cobertura XML). Network/SPV code (`ElectrumClient`, `WalletSynchronizer`, `TransactionInspector`, TOFU pinning) is tested against the in-process fake ElectrumX server in `tests/PalladiumWallet.Tests/Net/FakeElectrumServer.cs` (loopback TCP + optional TLS, per-method handlers, call counters): extend that, don't mock the client — it isn't an interface, by design.
- GUI hot reload: `dotnet watch --project src/App.Desktop` (on WSL2/WSLg the window shows on the Windows desktop, no graphics dependencies to install)
- CLI: `dotnet run --project src/Cli -- <command>` (no args → usage)
- **Release binaries (all 3 targets): `./docker/build.sh [windows|linux|android|all]`** — reproducible builds in Docker (toolchain pinned in `docker/Dockerfile.*`, no SDK needed on host), artifacts in `dist/`, version taken from the App csproj. See `docker/README.md`. Gotchas already encoded there: single-file desktop publishes need `-p:IncludeNativeLibrariesForSelfExtract=true` (without it Avalonia's native libs — Skia/HarfBuzz/ANGLE — stay outside the exe, which then silently fails to start); the android workload dictates the SDK API level (error XA5207 → bump `ANDROID_SDK_PLATFORM` in `Dockerfile.android`). Android release builds need a persistent signing keystore, generated once with `docker/keystore/generate-keystore.sh` (never committed — see `docker/keystore/README.md`): without it every build gets a different random signature and users must uninstall the old app to receive an update instead of updating in place.
+26 -1
View File
@@ -139,9 +139,34 @@ Run only the tests in one project:
dotnet test tests/PalladiumWallet.Tests
```
Measure code coverage (the [coverlet](https://github.com/coverlet-coverage/coverlet) collector is already referenced; the report is a Cobertura XML under `TestResults/`):
```bash
dotnet test tests/PalladiumWallet.Tests --collect:"XPlat Code Coverage"
```
> Cross-implementation tests compare addresses, txids and PSBTs against reference golden vectors: a different address or txid is a blocking bug.
The suite includes **property-based tests** ([CsCheck](https://github.com/AnthonyLloyd/CsCheck)) in `tests/PalladiumWallet.Tests/PropertyTests.cs`. These generate hundreds of random inputs per test and verify invariants that must hold universally — no crash on arbitrary strings, encrypt/decrypt roundtrip for any plaintext and password, every leaf in a randomly-built Merkle tree verifies against its root. They run automatically with `dotnet test` and take ~30 s.
### What the suite covers
The tests mirror the `Core` layout (`tests/PalladiumWallet.Tests/<area>/`):
| Area | What is verified |
|---|---|
| `Chain/` | Network profiles (prefixes, ports, coin type 746, SLIP-132 headers), NBitcoin network registration |
| `Crypto/` | BIP39 (official Trezor vectors, NFKD normalisation), BIP32/44/49/84/86 derivation against public golden vectors, SLIP-132 encode/decode, HD and imported-key accounts, watch-only isolation |
| `Spv/` | Scripthash (vectors computed independently in Python), Merkle proofs (Bitcoin block 100000 + random trees), header parsing, and the full **`WalletSynchronizer`**: gap-limit scanning, UTXO/history reconstruction, unconfirmed/immature balances, busy-retry, disk-cache reuse — including the path where a lying server fails Merkle verification and the sync must abort |
| `Net/` | JSON-RPC transport (pipelining, error mapping, notifications, disconnection, cancellation), typed protocol wrappers, peer discovery/persistence, TLS trust-on-first-use pinning end-to-end (pin, match, mismatch, reset), release-tag parsing |
| `Storage/` | AES-GCM encryption (roundtrip, tampering, fresh salt/nonce), wallet document schema/versioning, atomic saves, single-instance lock, data paths |
| `Wallet/` | Transaction building and signing (spendability rules, coinbase maturity, dust change, multi-UTXO selection, all standard destination types, watch-only PSBT flow, a golden txid for the PSBT signing path), transaction detail assembly (fees, mine/theirs attribution, RBF, coinbase), amount parsing/formatting |
Network-facing code is tested against an **in-process fake ElectrumX server**
(`tests/PalladiumWallet.Tests/Net/FakeElectrumServer.cs`): a real loopback TCP
socket speaking newline-delimited JSON-RPC (optionally TLS with a self-signed
certificate), with per-method handlers and call counters. Client, synchroniser
and inspector therefore exercise the same code paths used in production —
framing, retries, TLS pinning included — without any external dependency.
The suite also includes **property-based tests** ([CsCheck](https://github.com/AnthonyLloyd/CsCheck)) in `tests/PalladiumWallet.Tests/PropertyTests.cs`. These generate hundreds of random inputs per test and verify invariants that must hold universally — no crash on arbitrary strings, encrypt/decrypt roundtrip for any plaintext and password, SLIP-132 key roundtrip for every script kind and network, every leaf in a randomly-built Merkle tree verifies against its root. They run automatically with `dotnet test` and take ~30 s.
---