Skip to content

feat(tests): protocol dispatch test against canonical fixtures#12

Open
Taure wants to merge 1 commit into
mainfrom
feat/dispatch-test
Open

feat(tests): protocol dispatch test against canonical fixtures#12
Taure wants to merge 1 commit into
mainfrom
feat/dispatch-test

Conversation

@Taure
Copy link
Copy Markdown
Contributor

@Taure Taure commented May 1, 2026

Summary

Adds a per-SDK protocol dispatch unit test that feeds every canonical server-emitted message envelope through AsobiRealtime.HandleMessage and asserts the matching SDK event fires. Catches doc-vs-server drift (silent missed callbacks) before any user reports it.

The 32-fixture canonical corpus is vendored from asobi/priv/protocol/fixtures into Tests/Runtime/Resources/Fixtures/ and loaded at test time via Resources.LoadAll<TextAsset>.

Dispatch audit findings

Six dispatch cases were silently falling through to the generic OnMatchEvent / OnWorldEvent buckets, plus session.heartbeat had no surfaced event. Added explicit handlers and dedicated events:

Wire type New event
match.finished OnMatchFinished
match.matchmaker_expired OnMatchmakerExpired
match.matchmaker_failed OnMatchmakerFailed
world.finished OnWorldFinished
world.list OnWorldList
world.phase_changed OnWorldPhaseChanged
session.heartbeat OnHeartbeat

Decision: matchmaker.matched

Kept as a fall-through alias to OnMatchmakerMatched with a // TODO deprecate note (server only ever emits match.matched). Justification: someone got bitten by drift before and added it defensively; consumers may have wired up to it. Removing without a deprecation cycle is breaking. A dedicated test (MatchmakerMatchedAliasesMatchMatched) pins the alias so it is removed deliberately, not by accident.

Test design

  • One [Test] per fixture via [TestCaseSource] (32/32 fixtures dispatched)
  • One test per direction of the bijection: every fixture has an Expected mapping; every Expected entry has a fixture (catches stale entries on either side)
  • One test pinning the matchmaker.matched alias

HandleMessage made internal and a parameterless test-only constructor added; visibility wired via Runtime/AssemblyInfo.cs -> [InternalsVisibleTo("Asobi.Tests")]. The test does NOT depend on MonoBehaviour and uses [Test] (not [UnityTest]), so it runs in EditMode-style isolation inside the existing PlayMode test asmdef.

CI status

CI will not pass on this PR until UNITY_LICENSE / UNITY_EMAIL / UNITY_PASSWORD secrets are added (#11). The dispatch test is correctly wired into the existing .github/workflows/smoke.yml workflow (it picks up everything under Tests/Runtime automatically, and testMode: playmode covers [Test] attributes in the runtime-test asmdef). It will run as soon as secrets are present.

Note: the pre-existing SmokeTest.cs has compile errors (it accesses .match_id / .players on string event payloads, calls SendMatchInput / Disconnect which do not exist) that predate this PR. They will surface once CI compiles tests; they are out of scope here per the "no SDK structural refactor" constraint and warrant a separate fix.

Test plan

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.
@Taure
Copy link
Copy Markdown
Contributor Author

Taure commented May 2, 2026

Heads up: #13 extracts the dispatch logic into a pure-C# AsobiDispatcher base class and runs the same 32-fixture suite from a standalone dotnet test project on stock ubuntu-24.04 (no Unity license needed). That makes #13's dispatch job the new required check for protocol regressions.

This PR (#12) stays useful — it documents the Unity-side intent and will run the PlayMode coverage as soon as the secrets in #11 are provisioned. Both can land in either order; git handles the duplicate commit cleanly when one rebases over the other.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant