From 7c69a2bea51a59567eaeccd7bb3f318afa63b257 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 01:16:41 +0000 Subject: [PATCH] test: add terminal auth-failure coverage for PR #206 fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tests for the auth-failure backoff and terminal error detection added in PR #206 (fix: stop aggressive retry on auth failures): - HandleRequestError_TerminalAuthError_SetsAuthFailedFlag (theory): token mismatch / origin not allowed / too many failed → _authFailed set - HandleRequestError_TerminalAuthError_RaisesAuthenticationFailedEvent: AuthenticationFailed event fired with message text - HandleRequestError_TerminalAuthError_RaisesErrorStatus: ConnectionStatus.Error raised on terminal auth error - HandleRequestError_TerminalAuthError_OnNonConnectMethod_DoesNotSetAuthFailed: terminal-auth guard is scoped to 'connect' method only - HandleHelloOk_AfterAuthFailed_ClearsAuthFailedFlag: hello-ok response resets the flag so reconnect is re-enabled - HandleRequestError_AllDeviceSignatureModesExhausted_SetsAuthFailed: cycling through all 4 signature modes fires AuthenticationFailed once 660 Shared pass (+8), 20 skipped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../OpenClawGatewayClientTests.cs | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/tests/OpenClaw.Shared.Tests/OpenClawGatewayClientTests.cs b/tests/OpenClaw.Shared.Tests/OpenClawGatewayClientTests.cs index 3906e408..3713ca00 100644 --- a/tests/OpenClaw.Shared.Tests/OpenClawGatewayClientTests.cs +++ b/tests/OpenClaw.Shared.Tests/OpenClawGatewayClientTests.cs @@ -288,6 +288,16 @@ public List CaptureStatusChanges() _client.StatusChanged += (_, s) => changes.Add(s); return changes; } + + public bool GetAuthFailedFlag() => + GetPrivateField("_authFailed"); + + public List CaptureAuthenticationFailedEvents() + { + var events = new List(); + _client.AuthenticationFailed += (_, msg) => events.Add(msg); + return events; + } } private class TestLogger : IOpenClawLogger @@ -1626,4 +1636,142 @@ public void HandleRequestError_UnknownMethod_NodeList_SetsUnsupportedFlag() var flags = helper.GetUnsupportedMethodFlags(); Assert.True(flags.NodeList); } + + // --- HandleRequestError: terminal auth errors (PR #206 fix) --- + + [Theory] + [InlineData("token mismatch")] + [InlineData("origin not allowed")] + [InlineData("too many failed attempts")] + public void HandleRequestError_TerminalAuthError_SetsAuthFailedFlag(string errorMessage) + { + var helper = new GatewayClientTestHelper(); + helper.TrackPendingRequest("req-auth-1", "connect"); + + helper.ProcessRawMessage($$""" + { + "type": "res", + "id": "req-auth-1", + "ok": false, + "error": "{{errorMessage}}" + } + """); + + Assert.True(helper.GetAuthFailedFlag()); + } + + [Fact] + public void HandleRequestError_TerminalAuthError_RaisesAuthenticationFailedEvent() + { + var helper = new GatewayClientTestHelper(); + var authEvents = helper.CaptureAuthenticationFailedEvents(); + helper.TrackPendingRequest("req-auth-2", "connect"); + + helper.ProcessRawMessage(""" + { + "type": "res", + "id": "req-auth-2", + "ok": false, + "error": "token mismatch — reconnect rejected" + } + """); + + Assert.Single(authEvents); + Assert.Contains("token mismatch", authEvents[0], StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void HandleRequestError_TerminalAuthError_RaisesErrorStatus() + { + var helper = new GatewayClientTestHelper(); + var statusChanges = helper.CaptureStatusChanges(); + helper.TrackPendingRequest("req-auth-3", "connect"); + + helper.ProcessRawMessage(""" + { + "type": "res", + "id": "req-auth-3", + "ok": false, + "error": "origin not allowed" + } + """); + + Assert.Contains(ConnectionStatus.Error, statusChanges); + } + + [Fact] + public void HandleRequestError_TerminalAuthError_OnNonConnectMethod_DoesNotSetAuthFailed() + { + // Terminal auth check only applies to "connect" method — other methods must not set the flag + var helper = new GatewayClientTestHelper(); + helper.TrackPendingRequest("req-auth-4", "sessions.list"); + + helper.ProcessRawMessage(""" + { + "type": "res", + "id": "req-auth-4", + "ok": false, + "error": "token mismatch" + } + """); + + Assert.False(helper.GetAuthFailedFlag()); + } + + [Fact] + public void HandleHelloOk_AfterAuthFailed_ClearsAuthFailedFlag() + { + var helper = new GatewayClientTestHelper(); + + // First, trigger auth failure + helper.TrackPendingRequest("req-auth-5", "connect"); + helper.ProcessRawMessage(""" + { + "type": "res", + "id": "req-auth-5", + "ok": false, + "error": "token mismatch" + } + """); + Assert.True(helper.GetAuthFailedFlag()); + + // Now receive hello-ok — flag must be cleared + helper.ProcessRawMessage(""" + { + "type": "res", + "id": "req-hello-1", + "payload": { + "type": "hello-ok" + } + } + """); + + Assert.False(helper.GetAuthFailedFlag()); + } + + [Fact] + public void HandleRequestError_AllDeviceSignatureModesExhausted_SetsAuthFailed() + { + var logger = new TestLogger(); + var helper = new GatewayClientTestHelper(logger); + var authEvents = helper.CaptureAuthenticationFailedEvents(); + + // Cycle through all 4 signature modes by sending 4 successive rejections + for (int i = 1; i <= 4; i++) + { + helper.TrackPendingRequest($"req-sig-exhaust-{i}", "connect"); + helper.ProcessRawMessage($$""" + { + "type": "res", + "id": "req-sig-exhaust-{{i}}", + "ok": false, + "error": "device signature invalid" + } + """); + } + + Assert.True(helper.GetAuthFailedFlag()); + Assert.Single(authEvents); + Assert.Contains("device signature", authEvents[0], StringComparison.OrdinalIgnoreCase); + } }