Step 8 — Testing Strategy
8.1 Unit test — pakai in-memory stream
StreamJsonRpc punya helper FullDuplexStream.CreatePair() untuk test tanpa pipe nyata:
HermesServices.Tests/HermesRpcTests.cs
using HermesIpc.Contracts;
using HermesServiceEngine.Rpc;
using Nerdbank.Streams;
using StreamJsonRpc;
using Xunit;
public class HermesRpcTests
{
[Fact]
public async Task StartXdr_ReturnsSuccess()
{
var (clientStream, serverStream) = FullDuplexStream.CreatePair();
// Server
var notifier = new TestNotifier();
var serverRpc = JsonRpc.Attach(serverStream, new HermesRpcServer(notifier));
// Client
var proxy = JsonRpc.Attach<IHermesRpc>(clientStream);
// Act
var result = await proxy.StartXdrAsync("test-config", default);
// Assert
Assert.True(result.Success);
Assert.True(notifier.XdrStatusEvents.Count > 0);
serverRpc.Dispose();
}
}
class TestNotifier : IClientNotifier
{
public List<bool> XdrStatusEvents { get; } = new();
public Task OnXdrStatusChangedAsync(bool running)
{
XdrStatusEvents.Add(running);
return Task.CompletedTask;
}
public Task OnSaseStatusChangedAsync(SaseStatus s) => Task.CompletedTask;
public Task OnKcptunLogAsync(string s) => Task.CompletedTask;
}
8.2 Integration test — real pipe
HermesServices.Tests/IntegrationTests.cs
[Fact]
[Trait("Category", "Integration")]
public async Task EndToEnd_RoundTrip()
{
using var host = new HermesRpcHost();
await host.StartAsync();
await using var client = HermesRpcClient.Instance;
await client.StartAsync();
var health = await client.Proxy.GetHealthAsync(default);
Assert.True(health.ServiceRunning);
}
Jalankan di Windows host (CI: GitHub-hosted windows-latest).
8.3 Cross-platform smoke test
Tambah CI workflow yang test build & test pada Windows + macOS + Linux:
.github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- run: dotnet test --filter "Category!=Integration"
8.4 Backwards-compat test (selama feature flag aktif)
Selama kode lama masih ada, jalankan dual mode test:
[Theory]
[InlineData(true)] // StreamJsonRpc
[InlineData(false)] // Legacy
public async Task XdrStart_BothImplementations(bool useRpc)
{
FeatureFlags.UseStreamJsonRpc = useRpc;
// ... act + assert ...
}
Pastikan output identik di kedua mode sebelum hapus legacy code.
8.5 Stress test
[Fact]
public async Task ConcurrentCalls_NoRaceCondition()
{
var tasks = Enumerable.Range(0, 100)
.Select(_ => client.Proxy.GetHealthAsync(default))
.ToArray();
await Task.WhenAll(tasks);
Assert.All(tasks, t => Assert.True(t.Result.ServiceRunning));
}
100 paralel — pipe lama akan hang karena single-message. StreamJsonRpc lewat tanpa masalah.
8.6 Manual test checklist (sebelum hapus legacy code)
- App start, RPC connect dalam < 2 detik
- Start/Stop XDR → status berubah di UI
- Install/Activate/Start/Stop RMM → semua sukses
- Start SASE dengan config WG valid → tunnel up, event
OnSaseStatusChanged(Connected)muncul - Stop SASE → tunnel down, event
Disconnected - Service restart saat UI running → UI auto-reconnect dalam < 5 detik
- Kill UI process saat RPC call in-flight → server tidak crash
- Multiple rapid Start/Stop SASE → tidak ada
WgRenamerace - Network namespace di Linux container (opsional, future macOS)
- Log file tidak mengandung private key
Checklist
- Unit test pakai
FullDuplexStream.CreatePair() - Integration test di Windows CI
- Cross-platform smoke test matrix
- Stress test concurrent calls
- Manual test checklist 100% green