Skip to content

Feat(test)/unity test framework#276

Open
JoshhSandhu wants to merge 3 commits intomagicblock-labs:mainfrom
JoshhSandhu:feat(test)/unity-test-framework
Open

Feat(test)/unity test framework#276
JoshhSandhu wants to merge 3 commits intomagicblock-labs:mainfrom
JoshhSandhu:feat(test)/unity-test-framework

Conversation

@JoshhSandhu
Copy link
Copy Markdown
Contributor

@JoshhSandhu JoshhSandhu commented Apr 9, 2026

Status Type ⚠️ Core Change Issue
Ready Tooling No Requested by @jonasXchen in MagicBlock Builders Telegram

NOTE: This PR adds the first automated test infrastructure to the Solana Unity SDK.

SolanaMobileWalletAdapter lifecycle methods are intentionally not covered in this PR because the current constructor throws on non-Android platforms. That work needs a follow-up refactor before those paths can be unit tested cleanly.

Related PRs: #269, #271

Problem

The Solana Unity SDK did not have any automated test infrastructure before this PR.

That meant core Mobile Wallet Adapter logic had no repeatable regression coverage, including crypto helpers, session state behavior, JSON-RPC request construction, and response model convenience properties. It also made it harder to validate changes requested during recent MWA work without relying entirely on manual testing.

Solution

This PR adds the first Unity Test Framework scaffold to the SDK and introduces 31 EditMode unit tests across 4 test classes.

The new tests focus on logic that can be exercised in pure EditMode without requiring Android runtime dependencies:

  • EcdsaSignatures crypto helpers
  • MobileWalletAdapterSession
  • MobileWalletAdapterClient JSON-RPC request building
  • Response<T> computed properties

A MockMessageSender test double was added for IMessageSender so request payloads can be asserted without a real transport.

This PR is intentionally scoped to the parts of the MWA stack that are already testable in Editor. SolanaMobileWalletAdapter lifecycle and reconnect flows are not included yet because they currently depend on constructor behavior that throws on non-Android platforms.

Before & After Screenshots

Insert screenshots of example code output

BEFORE:
N/A. No automated tests existed in the SDK before this PR.

AFTER:
image

Other changes (e.g. bug fixes, small refactors)

  • Added the initial Tests/ structure for Unity Test Framework coverage
  • Added the EditMode test assembly definition and supporting test folder layout
  • Added a mock transport helper for request-level unit tests
  • Added coverage around MWA crypto and request-building logic that was previously untested
  • Added coverage for response model success/failure convenience properties

Deploy Notes

This PR primarily adds test infrastructure and EditMode unit coverage. It does not introduce new package dependencies or deployment steps.

New scripts:

  • Tests/EditMode/Crypto/EcdsaSignaturesTests.cs : EditMode coverage for DER/P1363 conversion and P-256 key encode/decode
  • Tests/EditMode/Crypto/MobileWalletAdapterSessionTests.cs : EditMode coverage for session token and pre-ECDH error paths
  • Tests/EditMode/JsonRpc/ResponseModelTests.cs : EditMode coverage for Response<T> success/failure properties
  • Tests/EditMode/MwaClient/MobileWalletAdapterClientTests.cs : EditMode coverage for JSON-RPC request building
  • Tests/EditMode/Mocks/MockMessageSender.cs : Test double for IMessageSender

New dependencies:

  • None

Summary by CodeRabbit

  • Tests

    • Added comprehensive edit-mode test suites for ECDSA signature conversions and key encoding, mobile wallet session behavior and tokens, JSON‑RPC response models, and client request building; included mocking utilities.
  • Bug Fixes

    • Relaxed an internal assertion related to signature handling and fixed an incorrect decryption error message.
  • Chores

    • Added test assembly definition and Unity metadata files to support the new tests.

Relax assertion in ECDSASignatures.cs: p1363Offset check changed from > 0 to >= 0 to allow a zero offset when packing P-256 P1363 signature components. Prevents erroneous assertion failures when a valid zero offset is used.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3d0e1b85-4f84-46c8-8992-4ee6f19bd61a

📥 Commits

Reviewing files that changed from the base of the PR and between dbcbbf8 and 21f4181.

📒 Files selected for processing (4)
  • Runtime/codebase/SolanaMobileStack/MobileWalletAdapterSession.cs
  • Tests/EditMode/Crypto/MobileWalletAdapterSessionTests.cs
  • Tests/EditMode/EditMode.asmdef
  • Tests/EditMode/Mocks/MockMessageSender.cs

Walkthrough

Adds a large set of Unity edit-mode tests (cryptography, JSON‑RPC, Mobile Wallet Adapter client/session), supporting assembly and metadata, a test mock, and a one-line change relaxing an assertion in ECDSA signature packing and a corrected decryption error message.

Changes

Cohort / File(s) Summary
Core crypto tweaks
Runtime/codebase/SolanaMobileStack/Crypto/ECDSASignatures.cs, Runtime/codebase/SolanaMobileStack/MobileWalletAdapterSession.cs
Relaxed p1363Offset assertion from > 0 to >= 0 in ECDSA packing; corrected decrypt-path exception/log message to reference decryption.
Top-level tests folder metadata
Tests.meta, Tests/EditMode.meta, Tests/EditMode/Crypto.meta, Tests/EditMode/JsonRpc.meta, Tests/EditMode/Mocks.meta, Tests/EditMode/MwaClient.meta, Tests/EditMode/EditMode.asmdef.meta
Added Unity .meta files for new test folders and assets.
Test assembly definition
Tests/EditMode/EditMode.asmdef
Added edit-mode test assembly definition referencing SDK and test runner assemblies, with precompiled refs (NUnit, BouncyCastle, Newtonsoft).
Crypto tests
Tests/EditMode/Crypto/EcdsaSignaturesTests.cs, Tests/EditMode/Crypto/EcdsaSignaturesTests.cs.meta
New tests for DER↔P1363 signature conversions, DER error cases, and P‑256 public key encode/decode validation.
Session crypto tests
Tests/EditMode/Crypto/MobileWalletAdapterSessionTests.cs, Tests/EditMode/Crypto/MobileWalletAdapterSessionTests.cs.meta
New tests for MobileWalletAdapterSession covering association token format/derivation, encryption/decryption precondition errors, and public key formatting.
JSON‑RPC tests
Tests/EditMode/JsonRpc/ResponseModelTests.cs, Tests/EditMode/JsonRpc/ResponseModelTests.cs.meta
New tests for Response<T> success/failure semantics, error field behavior, and generic result preservation.
Mobile Wallet Adapter client tests
Tests/EditMode/MwaClient/MobileWalletAdapterClientTests.cs, Tests/EditMode/MwaClient/MobileWalletAdapterClientTests.cs.meta
New tests validating JSON‑RPC request construction for Authorize/Reauthorize, ID incrementing, parameter values, and URI argument validation.
Test mocks
Tests/EditMode/Mocks/MockMessageSender.cs, Tests/EditMode/Mocks/MockMessageSender.cs.meta
Added MockMessageSender implementing IMessageSender that records sent payloads and exposes last message (cloned).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feat(test)/unity test framework' is partially related to the changeset as it refers to test infrastructure, but it is vague and does not clearly summarize the main change—specifically adding 31 EditMode unit tests across four test classes. Revise the title to be more specific and descriptive, such as 'Add EditMode unit tests for Solana Mobile Wallet Adapter' or 'Add test infrastructure with 31 EditMode unit tests.'
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description comprehensively addresses all required template sections with clear problem statement, detailed solution, deployment notes, and contextual information about test coverage scope and limitations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Tests/EditMode/Crypto/MobileWalletAdapterSessionTests.cs`:
- Line 99: The test DecryptSessionPayload_* is asserting the wrong log message —
it expects an encrypt-related error ("Cannot encrypt, no session key has been
established") in a decrypt test; update the LogAssert.Expect call in the test
method (the assertion on the log message) to assert the corresponding decrypt
message (e.g., "Cannot decrypt, no session key has been established") or
otherwise match the actual error produced by the DecryptSessionPayload method so
the test validates the correct behavior.

In `@Tests/EditMode/EditMode.asmdef`:
- Line 13: Update the Tests/EditMode assembly definition by setting the
"allowUnsafeCode" property to false (change the "allowUnsafeCode" entry
currently true in EditMode.asmdef) so the assembly disables unsafe code; ensure
the JSON key remains present and set to false to maintain tighter security
defaults.

In `@Tests/EditMode/Mocks/MockMessageSender.cs`:
- Around line 15-25: The mock currently stores and returns references to
caller-provided byte[] buffers which can be mutated later; update
MockMessageSender to defensive-copy buffers: in Send(byte[] message) create and
store a new byte[] copy into SentMessages (rather than the original reference),
and change the LastMessage getter to return a copy of the stored buffer (or
null) so callers cannot mutate internal state; reference the Send method, the
SentMessages collection, and the LastMessage property when making these changes.

In `@Tests/EditMode/MwaClient/MobileWalletAdapterClientTests.cs`:
- Line 53: Change the request-construction tests in
MobileWalletAdapterClientTests from void to async Task and await the
asynchronous calls instead of discarding them; specifically update each test
that currently does `_ = _client.Authorize(...)` (and other similar `_ =
_client.*` calls at the listed locations) to `await _client.Authorize(...)`,
adjust the test method signatures to `async Task` so exceptions surface and the
Task is awaited, and apply this change consistently across all instances
referenced (lines for the Authorize call and the other similar calls in the same
test class).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ed9d59b4-718f-478f-a2c8-bd2bc21d45e0

📥 Commits

Reviewing files that changed from the base of the PR and between 054503e and dbcbbf8.

📒 Files selected for processing (19)
  • Runtime/codebase/SolanaMobileStack/Crypto/ECDSASignatures.cs
  • Tests.meta
  • Tests/EditMode.meta
  • Tests/EditMode/Crypto.meta
  • Tests/EditMode/Crypto/EcdsaSignaturesTests.cs
  • Tests/EditMode/Crypto/EcdsaSignaturesTests.cs.meta
  • Tests/EditMode/Crypto/MobileWalletAdapterSessionTests.cs
  • Tests/EditMode/Crypto/MobileWalletAdapterSessionTests.cs.meta
  • Tests/EditMode/EditMode.asmdef
  • Tests/EditMode/EditMode.asmdef.meta
  • Tests/EditMode/JsonRpc.meta
  • Tests/EditMode/JsonRpc/ResponseModelTests.cs
  • Tests/EditMode/JsonRpc/ResponseModelTests.cs.meta
  • Tests/EditMode/Mocks.meta
  • Tests/EditMode/Mocks/MockMessageSender.cs
  • Tests/EditMode/Mocks/MockMessageSender.cs.meta
  • Tests/EditMode/MwaClient.meta
  • Tests/EditMode/MwaClient/MobileWalletAdapterClientTests.cs
  • Tests/EditMode/MwaClient/MobileWalletAdapterClientTests.cs.meta

const string cluster = "mainnet-beta";

// Act
_ = _client.Authorize(identityUri, iconUri, identityName, cluster);
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Confirm client API returns Task"
rg -nP --type=cs -C2 '\bTask<[^>]+>\s+Authorize\s*\(|\bTask<[^>]+>\s+Reauthorize\s*\(' Runtime/codebase/SolanaMobileStack/MobileWalletAdapterClient.cs

echo "2) Show discarded-task call sites in this test file"
rg -nP --type=cs -C1 '_\s*=\s*_client\.(Authorize|Reauthorize)\s*\(' Tests/EditMode/MwaClient/MobileWalletAdapterClientTests.cs

echo "3) Inspect mock sender completion behavior"
rg -nP --type=cs -C4 'class\s+MockMessageSender|Task\.CompletedTask|Task\.FromResult|Send\s*\(' Tests/EditMode

Repository: magicblock-labs/Solana.Unity-SDK

Length of output: 2971


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Full Authorize implementation ==="
sed -n '22,32p' Runtime/codebase/SolanaMobileStack/MobileWalletAdapterClient.cs

echo -e "\n=== Full Reauthorize implementation ==="
sed -n '34,45p' Runtime/codebase/SolanaMobileStack/MobileWalletAdapterClient.cs

echo -e "\n=== Check Send signature ==="
rg -nP --type=cs 'Task\s+Send\s*\(|void\s+Send\s*\(' Runtime/codebase/SolanaMobileStack

echo -e "\n=== Check IMessageSender interface ==="
rg -nP --type=cs -A5 'interface\s+IMessageSender' Runtime/codebase/SolanaMobileStack

Repository: magicblock-labs/Solana.Unity-SDK

Length of output: 1607


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find SendRequest implementation ==="
rg -nP --type=cs -A10 'Task<.*>\s+SendRequest\s*<' Runtime/codebase/SolanaMobileStack/MobileWalletAdapterClient.cs

echo -e "\n=== Check if there's async waiting after Send() ==="
rg -nP --type=cs -B5 -A10 'SendRequest' Runtime/codebase/SolanaMobileStack/MobileWalletAdapterClient.cs | head -60

Repository: magicblock-labs/Solana.Unity-SDK

Length of output: 115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for SendRequest definition ==="
rg -nP --type=cs 'Task<.*>\s+SendRequest' Runtime/codebase/SolanaMobileStack/

echo -e "\n=== Check if MobileWalletAdapterClient has a base class ==="
rg -nP --type=cs 'class MobileWalletAdapterClient' Runtime/codebase/SolanaMobileStack/

echo -e "\n=== Look for SendRequest in broader context ==="
rg -nP --type=cs 'SendRequest' Runtime/codebase/SolanaMobileStack/ | head -20

Repository: magicblock-labs/Solana.Unity-SDK

Length of output: 1120


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== SendRequest implementation in JsonRpc20Client ==="
sed -n '25,50p' Runtime/codebase/SolanaMobileStack/JsonRpc20Client.cs

echo -e "\n=== Check full JsonRpc20Client context ==="
wc -l Runtime/codebase/SolanaMobileStack/JsonRpc20Client.cs

echo -e "\n=== Full JsonRpc20Client file (if small enough) ==="
cat -n Runtime/codebase/SolanaMobileStack/JsonRpc20Client.cs

Repository: magicblock-labs/Solana.Unity-SDK

Length of output: 4291


Convert request-construction tests to async Task and await call results.

These methods return Task<AuthorizationResult>, but discarding them with _ = swallows potential exceptions and masks async failures. Convert to async Task test methods and await the calls.

Proposed pattern (apply across similar tests)
+using System.Threading.Tasks;
...
-[Test]
-public void Authorize_SendsJsonRpc_WithCorrectMethod()
+[Test]
+public async Task Authorize_SendsJsonRpc_WithCorrectMethod()
 {
     // Arrange
     var identityUri = new Uri("https://example.com");
     var iconUri = new Uri("/icon.png", UriKind.Relative);
     const string identityName = "TestApp";
     const string cluster = "mainnet-beta";

     // Act
-    _ = _client.Authorize(identityUri, iconUri, identityName, cluster);
+    await _client.Authorize(identityUri, iconUri, identityName, cluster);

     // Assert
     var request = DecodeLastRequest();
     Assert.AreEqual("authorize", request.Method,
         "Method must be 'authorize'");
 }

Also applies to: 68-68, 83-83, 98-98, 114-114, 129-129, 133-133, 161-161, 186-186, 202-202

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/EditMode/MwaClient/MobileWalletAdapterClientTests.cs` at line 53,
Change the request-construction tests in MobileWalletAdapterClientTests from
void to async Task and await the asynchronous calls instead of discarding them;
specifically update each test that currently does `_ = _client.Authorize(...)`
(and other similar `_ = _client.*` calls at the listed locations) to `await
_client.Authorize(...)`, adjust the test method signatures to `async Task` so
exceptions surface and the Task is awaited, and apply this change consistently
across all instances referenced (lines for the Authorize call and the other
similar calls in the same test class).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The discard is intentional. Authorize() / Reauthorize() call SendRequest<T>() which sends the payload to IMessageSender synchronously and then returns a TaskCompletionSource.Task that only completes when a wallet response arrives via Receive(). In these tests no response ever arrives because MockMessageSender is a one-way capture, so awaiting the task would deadlock every test.

The tests only need to inspect the outgoing request payload, which is already captured in the mock by the time the next line runs. The fire-and-forget is the correct pattern here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description image shows the Unity Test Runner with all 31 EditMode tests passing (green checkmarks), including the 11 MobileWalletAdapterClientTests.

@JoshhSandhu, that's a clear and correct explanation — thank you. The TaskCompletionSource-backed response tracking means await without a paired Receive() would block indefinitely, making the discard the only safe option for outbound-payload-only tests. I'm withdrawing the suggestion.

(ᓀ‸ᓂ) withdrawing suggestion


✏️ Learnings added
Learnt from: JoshhSandhu
Repo: magicblock-labs/Solana.Unity-SDK PR: 276
File: Tests/EditMode/MwaClient/MobileWalletAdapterClientTests.cs:53-53
Timestamp: 2026-04-09T00:49:25.699Z
Learning: In `magicblock-labs/Solana.Unity-SDK`, `MobileWalletAdapterClient.Authorize()` and `Reauthorize()` call `SendRequest<T>()`, which delivers the payload to `IMessageSender` **synchronously** and then returns a `TaskCompletionSource<T>.Task` that only completes when a wallet response arrives via `Receive()`. In EditMode unit tests that use `MockMessageSender` (a one-way payload capture with no response path), awaiting these tasks would deadlock. The correct pattern is `_ = _client.Authorize(...)` — discard the task after the synchronous send so the test can immediately inspect the captured payload. Do not flag this as a missing `await`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: JoshhSandhu
Repo: magicblock-labs/Solana.Unity-SDK PR: 269
File: Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs:291-294
Timestamp: 2026-03-29T01:23:40.762Z
Learning: In `magicblock-labs/Solana.Unity-SDK`, in `Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs`, the `SignMessage()` method deliberately uses `cachedPk` (captured from `Account?.PublicKey` or `PlayerPrefs.GetString("pk")` before the `LocalAssociationScenario`) rather than `authorization.PublicKey` returned by the first lambda. This is intentional: under the current `async void Action<IAdapterOperations>` pattern, mutations made to closed-over variables in the first lambda are not guaranteed to be visible in the second lambda, so cross-lambda variable sharing is unsafe. The fix (using `authorization.PublicKey` for signing) requires migrating to `Func<IAdapterOperations, Task>`, which is deferred to PR `#260`.

Learnt from: JoshhSandhu
Repo: magicblock-labs/Solana.Unity-SDK PR: 269
File: Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs:63-93
Timestamp: 2026-03-29T00:04:25.932Z
Learning: In `magicblock-labs/Solana.Unity-SDK`, the `async client => { ... }` lambdas passed to `LocalAssociationScenario.StartAndExecute()` use `Action<IAdapterOperations>` (effectively async void), which can swallow exceptions. This is a known architectural issue intentionally left unchanged to avoid conflicting with PR `#260`, which is rewriting `LocalAssociationScenario.ExecuteNextAction()` and is expected to migrate the delegate type to `Func<IAdapterOperations, Task>`.

fix decrypt error string, defensive copy in mock, disable unsafe code in test assembly
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