Skip to content

test(dotnet): scenario parity runner — C# server runs the shared conformance corpus - #64

Merged
brentrager merged 1 commit into
mainfrom
parity-cs
Jun 24, 2026
Merged

test(dotnet): scenario parity runner — C# server runs the shared conformance corpus#64
brentrager merged 1 commit into
mainfrom
parity-cs

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

What

Ports the scenario parity runner (python/server/tests/test_scenario_parity.py) into the C# server's integration test suite, so the C# server runs the shared spec/conformance/scenarios/*.json corpus — the same corpus the Python server already runs. When all five native servers (Rust · C# · Python · TypeScript · Go) run this corpus green, they are at protocol parity.

ScenarioParityTests (dotnet/server/integration-tests/ScenarioParityTests.cs) discovers every scenario via [Theory]/[MemberData] and, for each, drives the in-process ASP.NET Core WebSocket host (reusing the existing MockChatClient + WS host from WebSocketProtocolIntegrationTests):

  • seeds MockChatClient from mockLlmScript ({kind:"text"}PushText, {kind:"toolCall"}PushToolCall),
  • per step substitutes {{vars}}, sends the frame, then matches the expect sequence — type, status/statusGte, assert dot-paths, capture vars, repeat runs, accumulate+assertAccumulated — skipping keepalive/pong.

Divergences found + fixed

Running the corpus surfaced two real C#-server divergences from the shared spec/events/error.schema.json (the Python reference server already honors both):

  1. Missing envelope-level error object — the schema requires the {code, message} descriptor at both the top level (error) and nested under data.error (the top-level copy is "kept for clients that pattern-match on the envelope-level error field"). The C# server emitted only data.error. ProtocolEvents.Error now emits both (plus data.requestId), mirroring Python.
  2. Wrong error code — the unknown-session error used "NOT_FOUND"; the spec and Python server use "SESSION_NOT_FOUND". FrameDispatcher now emits SESSION_NOT_FOUND for both no-such-session paths (get_session + send_message).

These are server-conformance fixes (not test or scenario changes); the seed scenarios pass unchanged.

Note for maintainers: the Go server (#61) appears to carry the same unknown-session NOT_FOUND divergence — worth checking when the Go parity runner lands.

Verify

dotnet test dotnet/server/integration-tests/ -c Release13 passed (both parity scenarios + 11 existing integration tests). No regressions across the other dotnet projects: server 50, host 2, postgres 33, core 27.

🤖 Generated with Claude Code

…ormance corpus

Ports python/server/tests/test_scenario_parity.py into the C# server's integration
test suite. ScenarioParityTests discovers spec/conformance/scenarios/*.json and, for
each scenario ([Theory]/[MemberData]), drives the in-process ASP.NET Core WebSocket
host through it: seeds MockChatClient from mockLlmScript, then per step substitutes
{{vars}}, sends the frame, and matches the expect sequence (type, status/statusGte,
assert dot-paths, capture vars, repeat runs, accumulate+assertAccumulated), skipping
keepalive/pong. When all five servers run this corpus green, they are at protocol parity.

Running the corpus surfaced two real C#-server divergences from the shared
spec/events/error.schema.json (which the Python reference server already honors):

- The error frame omitted the envelope-level `error` object. The schema requires the
  {code, message} descriptor at BOTH the top level and nested under `data.error` (the
  top-level copy "kept for clients that pattern-match on the envelope-level `error`
  field"). ProtocolEvents.Error now emits both (and data.requestId), mirroring Python.
- The unknown-session error used code "NOT_FOUND"; the spec/Python use
  "SESSION_NOT_FOUND". FrameDispatcher now emits SESSION_NOT_FOUND for both the
  get_session and send_message no-such-session paths.

Both seed scenarios (basic-streaming-turn, unknown-session-error) pass, and all
existing dotnet tests stay green (integration 13, server 50, host 2, postgres 33, core 27).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U7Mn93HpqhSgEmX6tRdPAv
@changeset-bot

changeset-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 6ea9dba

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@brentrager
brentrager merged commit b33a34c into main Jun 24, 2026
1 check passed
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