feat(tests): standalone .NET dispatch test (closes #11)#13
Open
Taure wants to merge 4 commits into
Open
Conversation
Per-SDK protocol dispatch unit test: feeds every canonical server-emitted message envelope through AsobiRealtime.HandleMessage and asserts the matching SDK event fires. Vendors the 32-fixture corpus from asobi/priv/protocol/fixtures into Tests/Runtime/Resources/Fixtures. Audit findings against the canonical corpus turned up six dispatch cases that were silently falling through to the generic OnMatchEvent / OnWorldEvent buckets, plus session.heartbeat with no surfaced event. Added explicit handlers and events: match.finished -> OnMatchFinished match.matchmaker_expired -> OnMatchmakerExpired match.matchmaker_failed -> OnMatchmakerFailed world.finished -> OnWorldFinished world.list -> OnWorldList world.phase_changed -> OnWorldPhaseChanged session.heartbeat -> OnHeartbeat The defensive matchmaker.matched alias is kept as a fall-through to OnMatchmakerMatched with a TODO-deprecate note; removing without a deprecation cycle would break consumers that listened for it during the historical drift period. The alias is pinned by a dedicated test. Made HandleMessage internal and added a parameterless test-only ctor to allow exercising dispatch without a live WebSocket. Asobi.Tests already references Asobi via its asmdef; InternalsVisibleTo is wired through a new Runtime/AssemblyInfo.cs. The test also asserts the bidirectional invariant: every fixture has an Expected mapping and every Expected entry has a fixture, so future drift in either direction fails CI.
Pulls all events + the HandleMessage switch out of AsobiRealtime into a new pure-C# AsobiDispatcher base class (no UnityEngine deps). Adds a zero-dep envelope parser (ProtocolEnvelope) so dispatch no longer needs JsonUtility. AsobiRealtime now inherits AsobiDispatcher and only retains the WebSocket + JsonUtility serialization concerns. Cid resolution stays on the subclass via an OnPendingResponse override hook. Enables a standalone .NET dispatch test that doesn't need Unity.
Adds Tests/AsobiCore.NET/ — a stock dotnet 8 NUnit project that compiles the shared Runtime/WebSocket/AsobiDispatcher.cs source (via csproj <Compile Include=...> with no copies) and runs the same 32-fixture dispatch suite + the matchmaker.matched alias test from the existing Unity-side DispatchTests. Fixtures live under Tests/Runtime/Resources/Fixtures/ and are referenced via <None Include> with CopyToOutputDirectory so the test reads them from AppContext.BaseDirectory at run time. Same source of truth as the Unity asmdef. Adds .github/workflows/dispatch.yml running 'dotnet test' on stock ubuntu-24.04 with no Unity license required. The existing Smoke workflow stays so the PlayMode test still runs once UNITY_LICENSE secrets land (#11). Unity sees Tests/AsobiCore.NET/ via an asmdef gated on a never-set defineConstraint so the .NET test code is never picked up by Unity's predefined assembly.
The PlayMode SmokeTest predates the typed-event work. It assumed: - OnMatchmakerMatched / OnMatchState carry typed payload structs (they carry raw JSON strings) - SendMatchInput / Disconnect exist (the actual API is SendMatchInputAsync / DisconnectAsync) Switches the handlers to read fields via JsonHelper.ExtractJsonField (internal, visible to the test asmdef via the existing IVT) and uses the actual async API names so the file compiles.
4 tasks
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.
Why
smoke.ymlusesgame-ci/unity-test-runner@v4which needsUNITY_LICENSE/UNITY_EMAIL/UNITY_PASSWORDsecrets to compile + run anything (#11). Those secrets aren't provisioned, so neither the existing PlayMode smoke nor the 32-fixture protocol dispatch test added in #12 can act as a required check today.This is the same problem the Unreal SDK had. We solved it there by extracting the engine-agnostic C++ into a CMake static lib + doctest exe (
Source/AsobiCore/) that runs on stockubuntu-24.04. This PR is the C# equivalent for asobi-unity.What
A standalone
dotnet testproject that exercises the same dispatch logic Unity runs, with zero Unity dependencies.Refactor (decision: extract pure protocol code)
Runtime/WebSocket/AsobiDispatcher.cs— new pure-C# base class that owns:On*event declarations (35 events)HandleMessage(raw)— the dispatch switchProtocolEnvelope.Parse(raw)— a tiny zero-dep parser that pulls justtypeandcidfrom the envelope (replaces the Unity-onlyJsonUtility.FromJson<WsMessage>in the dispatch path)AsobiRealtimenow: AsobiDispatcher— only retains the Unity-aware concerns (ClientWebSocket,JsonUtilitypayload serialization, async send/receive). Cid resolution stays on the subclass via anOnPendingResponsevirtual hook.No public API changes — the events stay where consumers see them on
AsobiRealtime(inherited).Standalone .NET test project
Tests/AsobiCore.NET/:Asobi.Core.Tests.csproj— net8.0, NUnit 4 (matches the existing Unity test asmdef which uses NUnit attributes)<Compile Include="..\..\Runtime\WebSocket\AsobiDispatcher.cs" Link="..."/>— shared source, no copies<None Include="..\..\Tests\Runtime\Resources\Fixtures\*.json" CopyToOutputDirectory="PreserveNewest" />— same 32 fixtures Unity loads viaResources.LoadAll<TextAsset>, here loaded fromAppContext.BaseDirectory/Fixtures/DispatcherTests.cs— port ofTests/Runtime/DispatchTests.cs(from feat(tests): protocol dispatch test against canonical fixtures #12), targetingAsobiDispatcherdirectlyTests/AsobiCore.NET/ also has an asmdef gated on a never-set
defineConstraintsso Unity's predefined assembly never picks upDispatcherTests.cs.What it tests
[Test]per fixture via[TestCaseSource])EveryFixtureHasExpectedMapping/EveryExpectedHasFixture— pin the bijection between fixture set and dispatch switchMatchmakerMatchedAliasesMatchMatched— pins thematchmaker.matched->match.matchedhistorical aliasCI
.github/workflows/dispatch.yml— new job onubuntu-24.04, justsetup-dotnet+dotnet test. No secrets. This is the new required check for protocol regressions. The existingsmoke.ymlstays so the PlayMode test runs once #11's secrets land.SmokeTest.cs fixes
The file shipped with #8 had compile errors (called
.match_idon string event payloads, used non-existentSendMatchInput/DisconnectAPIs) that didn't show up because the Unity job never compiled them. Fixed in a separate commit:match_idviaJsonHelper.ExtractJsonFieldon the raw event JSONSendMatchInput->SendMatchInputAsyncDisconnect->DisconnectAsyncCoordination with #12
Per repo convention, kept #12's Unity-side test alongside this one (option (a) in the brief). This PR cherry-picks #12's commit so the dispatcher refactor can build on the events #12 introduced (
OnHeartbeat,OnMatchFinished, etc.). When either PR merges first, the other rebases cleanly — git detects the identical commit. The .NET test in this PR becomes the required check; the PlayMode test stays for when secrets land.Test plan
dotnet test Tests/AsobiCore.NET/Asobi.Core.Tests.csproj— 35/35 green locallyCloses #11.
Do not merge — leave for morning review.