using Nexus.Api.Data; using Nexus.Api.Models; using Nexus.Api.Services; using Xunit; namespace Nexus.Api.Tests; public sealed class OpenClawChatServiceTests { [Fact] public async Task Send_uses_durable_protocol_v4_run_and_canonical_agent_session() { var runId = Guid.NewGuid(); var runs = new CapturingRunService(runId, ok: true); var service = new OpenClawChatService(runs); var invocation = new OpenClawInvocationMetadata( "idem-chat", "corr-chat", "owner", "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"); var result = await service.SendAsync( "coordinate this work", "browser-conversation", "Iris", invocation); Assert.NotNull(runs.Request); Assert.Equal("iris", runs.Request.AgentId); Assert.Equal("agent:iris:main", runs.Request.SessionKey); Assert.Equal("coordinate this work", runs.Request.Prompt); Assert.Equal(runId, result.RunId); Assert.Equal("OpenClaw Protocol v4", result.Runtime); } [Fact] public async Task Send_does_not_claim_success_when_gateway_dispatch_is_blocked() { var runs = new CapturingRunService(Guid.NewGuid(), ok: false); var service = new OpenClawChatService(runs); var exception = await Assert.ThrowsAsync( () => service.SendAsync( "coordinate this work", "browser-conversation", "iris", new OpenClawInvocationMetadata( "idem-chat", "corr-chat", "owner", "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"))); Assert.Equal("blocked", exception.State); } private sealed class CapturingRunService(Guid runId, bool ok) : IOpenClawRunService { public StartOpenClawRunRequest? Request { get; private set; } public Task StartAsync( StartOpenClawRunRequest request, OpenClawInvocationMetadata invocation, CancellationToken cancellationToken = default) { Request = request; var run = new OpenClawRunDto( runId, "Chat", request.Prompt, request.AgentId, request.SessionKey, ok ? "running" : "blocked", request.TaskId, request.ProjectId, null, null, invocation.CorrelationId, invocation.Actor, ok ? null : "Gateway disconnected", null, false, ok, !ok, false, null, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, null, null); return Task.FromResult(new OpenClawRunOperationDto( ok, ok ? "running" : "blocked", ok ? "OpenClaw accepted the run." : "Gateway disconnected.", run, null, DateTimeOffset.UtcNow)); } public Task GetAsync( OpenClawRunQuery query, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task GetByIdAsync( Guid id, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task StopAsync( Guid id, string? reason, OpenClawInvocationMetadata invocation, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task ResumeAsync( Guid id, string? reason, OpenClawInvocationMetadata invocation, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task RetryAsync( Guid id, string? reason, OpenClawInvocationMetadata invocation, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task GetHistoryAsync( Guid id, int gatewayLimit = 200, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task ReconcileAsync( GatewayEventEnvelope gatewayEvent, CancellationToken cancellationToken = default) => throw new NotSupportedException(); } }