Add comprehensive unit, integration, and E2E test suites - #221
Merged
Conversation
Replace the bash smoke test with 421 xUnit v3 tests across three projects, all runnable via plain `dotnet test` and wired into CI: - WaveBox.Core.Tests (185, parallel): extensions, User crypto, Utility, SQLiteConnectionPool, and reflection tests asserting every ORM model and Subsonic DTO is rooted in ModelTypeRegistry/SubsonicDtoRegistry, catching NativeAOT rooting drift at test time. - WaveBox.Server.Tests (217): unit layer (UriWrapper, SubsonicMapper, XML serializer, transcoders, image resize) plus a serialized integration collection against a real temp SQLite database: schema migration idempotency, repositories, playlist logic, a real TagLib folder scan of a generated MP3 fixture, ServerSettings JSONC round-trips, and SubsonicAuth error codes. - WaveBox.E2E.Tests (19): full port of Scripts/smoke-test.sh. A fixture boots the real server binary on a random free port with an isolated root; WAVEBOX_E2E_BINARY selects the NativeAOT publish output in CI, falling back to the local build for development. - WaveBox.TestFixtures: shared MP3 generator (C# port of make_fixture.py, dropping the python3 dependency), temp-root, free-port, and conf-seeding helpers. Testability changes: WAVEBOX_ROOT/WAVEBOX_TEMP env overrides for ServerUtility.RootPath()/ServerInfo.TempFolder (also enables Windows E2E isolation), InternalsVisibleTo for the test projects, and three private pure helpers widened to internal. Bug fixes, each with a regression test: - IntExtensions.ToTimeString dropped the minutes segment at H>0, M=0 (3600s rendered "1:00" instead of "1:00:00") - AbstractTranscoder.GetHashCode inverted null ternary NRE'd exactly when Item was null - SQLiteConnectionPool.CloseAllConnections never re-enabled pooling after a backup - Utility.RandomString used a shared non-thread-safe Random to generate session IDs; now Random.Shared - Config "port" was Int16, so values above 32767 failed JSON parsing and silently fell back to 6500; now int end to end - StringExtensions.RemoveByteOrderMark used culture-sensitive StartsWith, stripping the first character of every string under ICU; now ordinal CI: fix the stale push trigger (master -> main), add a fast 3-OS test job gating the AOT publish matrix, add NuGet caching, and replace the smoke-test step with the E2E suite against the published binary, now including win-x64. Scripts/smoke-test.sh and tests/fixtures/make_fixture.py are retired. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F6dsiZvEPAKYYMKBpGsypr
The Windows CI leg caught a real production bug: FlushSettings rendered mediaFolders/folderArtNames with a plain quoted CSV, so backslashes in Windows paths were written unescaped into wavebox.conf. The next reload (or server restart) failed to parse the file and silently reset every setting to defaults. Lists are now rendered with JsonEncodedText so flushed values re-parse as valid JSON strings. Adds a regression test using a folder name containing a backslash and a quote, which exercises the escaping on every OS rather than only on Windows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F6dsiZvEPAKYYMKBpGsypr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the bash smoke test with 421 xUnit v3 tests across three projects, runnable locally with plain
dotnet testand wired into CI on PRs and pushes tomain.tests/WaveBox.Core.TestsUtility,SQLiteConnectionPool, plus reflection tests asserting every ORM model / Subsonic DTO is rooted inModelTypeRegistry/SubsonicDtoRegistry(catches NativeAOT rooting drift at test time)tests/WaveBox.Server.TestsUriWrapper,SubsonicMapper, XML serializer, transcoders, image resize) + serialized integration tests against a real temp SQLite DB: schema migration idempotency, repositories, playlist logic, a real TagLib folder scan of a generated MP3 fixture,ServerSettingsJSONC round-trips,SubsonicAutherror codestests/WaveBox.E2E.TestsScripts/smoke-test.sh(all 18 sections): boots the real server binary on a random free port with an isolated root and drives/api+/restover HTTP.WAVEBOX_E2E_BINARYselects the NativeAOT publish output (CI) or falls back to the local build (dev). Transcode test self-skips without ffmpegtests/WaveBox.TestFixturesprovides the shared MP3 generator (C# port ofmake_fixture.py— python3 dependency dropped), temp-root, free-port, and conf-seeding helpers.Scripts/smoke-test.shandtests/fixtures/make_fixture.pyare retired.Testability changes
WAVEBOX_ROOT/WAVEBOX_TEMPenv overrides inServerUtility.RootPath()/ServerInfo.TempFolder— enables isolation on Windows (the oldHOMEtrick couldn't), and keeps test runs away from real user dataInternalsVisibleTofor the test projects; three private pure helpers widened tointernalBug fixes (each with a regression test)
IntExtensions.ToTimeStringdropped the minutes segment when hours > 0 and minutes == 0 (3600s→"1:00"instead of"1:00:00")AbstractTranscoder.GetHashCodehad an inverted null ternary that NRE'd exactly whenItemwas nullSQLiteConnectionPool.CloseAllConnectionsnever re-enabled pooling after a backupUtility.RandomStringgenerated session IDs from a shared non-thread-safeRandom; nowRandom.SharedportwasInt16, so any port above 32767 failed JSON parsing and silently fell back to 6500; nowintend to endStringExtensions.RemoveByteOrderMarkused culture-sensitiveStartsWith— under ICU, U+FEFF is zero-weight, so it stripped the first character of every string; now ordinalA few oddities are deliberately pinned (not fixed) with commented characterization tests, notably
Playlist.IndexOfMediaItemmatching by ItemType only.CI
push: branches: [master]trigger →maintestjob (ubuntu/macos/windows) gates the six-RID AOTpublishmatrix, so broken PRs don't burn six AOT buildsactions/cacheWAVEBOX_ROOToverride)Test plan
dotnet test(whole solution): 421 passed, 0 failed, stable across rerunsdotnet publish -c Release -r osx-arm64+ E2E against the NativeAOT binary (exactly the CI path): 19/19 passedtestjob on 3 OSes and E2E on 4 RIDs🤖 Generated with Claude Code
https://claude.ai/code/session_01F6dsiZvEPAKYYMKBpGsypr