using System.Text.Json.Nodes; using Nexus.Api.Models; using Nexus.Api.Services; namespace Nexus.Api.Tests; /// /// Minimal live-RPC test double for consumers that only need the OpenClaw /// agent/session inventory. Unsupported control-plane calls fail loudly so a /// test cannot accidentally fall back to a fabricated legacy source. /// internal sealed class StubOpenClawControlService : IOpenClawControlService { public StubOpenClawControlService( IReadOnlyList? agents = null, IReadOnlyList? sessions = null, bool connected = true) { Agents = agents ?? [ Agent("iris", "Iris", "openai/gpt-5.5", "/workspace/iris"), Agent("product-owner", "Product Owner", "openai/gpt-5.5", "/workspace-po"), Agent("programmer", "Programmer", "openai/gpt-5.4", "/workspace/programmer"), Agent("programmer-fast", "Programmer Fast", "openai/gpt-5.3-codex-spark", "/workspace/programmer-fast"), Agent("reviewer", "Reviewer", "openai/gpt-5.5", "/workspace/reviewer"), Agent("architekt", "Architekt", "openai/gpt-5.5", "/workspace/architekt") ]; Sessions = sessions ?? []; Connected = connected; } public IReadOnlyList Agents { get; } public IReadOnlyList Sessions { get; } public bool Connected { get; } public OpenClawConnectionDto GetConnection() => new( State: Connected ? "connected" : "disconnected", Configured: true, CredentialConfigured: true, Connected: Connected, Endpoint: "ws://openclaw-gateway:18789", GatewayVersion: "2026.7.1", RequiredVersion: "2026.7.1", VersionPinned: true, VersionMatches: true, ProtocolVersion: 4, GrantedScopes: ["operator.read"], AdvertisedEvents: [], LastConnectedAt: Connected ? DateTimeOffset.UtcNow : null, LastEventAt: Connected ? DateTimeOffset.UtcNow : null, ReconnectAttempts: 0, Message: null, Recovery: null, CheckedAt: DateTimeOffset.UtcNow); public Task> GetAgentsAsync( CancellationToken cancellationToken = default) => Task.FromResult(Collection(Agents)); public Task> GetSessionsAsync( int limit = 100, CancellationToken cancellationToken = default) => Task.FromResult(Collection( Sessions.Take(limit).ToArray())); public IReadOnlyList GetCapabilities() => []; public Task GetOverviewAsync(CancellationToken cancellationToken = default) => Unsupported(); public Task> GetTasksAsync( int limit = 100, string? cursor = null, CancellationToken cancellationToken = default) => Unsupported>(); public Task> GetCronJobsAsync( int limit = 100, CancellationToken cancellationToken = default) => Unsupported>(); public Task> GetCronJobsAsync( bool includeDisabled, int limit = 100, string? cursor = null, CancellationToken cancellationToken = default) => Unsupported>(); public Task> GetCronJobAsync( string jobId, CancellationToken cancellationToken = default) => Unsupported>(); public Task> GetCronRunsAsync( string jobId, int limit = 100, string? cursor = null, string? runId = null, CancellationToken cancellationToken = default) => Unsupported>(); public Task> GetApprovalsAsync( int limit = 100, CancellationToken cancellationToken = default) => Unsupported>(); public Task> GetActivityAsync( int limit = 100, string? cursor = null, CancellationToken cancellationToken = default) => Unsupported>(); public Task> GetModelsAsync( CancellationToken cancellationToken = default) => Unsupported>(); public Task> GetModelAuthStatusAsync( bool refresh = false, CancellationToken cancellationToken = default) => Unsupported>(); public Task> CancelTaskAsync( string taskId, string? reason, CancellationToken cancellationToken = default, OpenClawInvocationContext? invocationContext = null) => Unsupported>(); public Task> AbortSessionAsync( string sessionKey, string? runId, bool clearQueued, CancellationToken cancellationToken = default, OpenClawInvocationContext? invocationContext = null) => Unsupported>(); public Task> PatchSessionModelAsync( string sessionKey, string model, CancellationToken cancellationToken = default, OpenClawInvocationContext? invocationContext = null) => Unsupported>(); public Task> RunCronJobAsync( string jobId, CancellationToken cancellationToken = default, OpenClawInvocationContext? invocationContext = null) => Unsupported>(); public Task> RunCronJobAsync( string jobId, string? expectedHash, CancellationToken cancellationToken = default, OpenClawInvocationContext? invocationContext = null) => Unsupported>(); public Task> CreateCronJobAsync( CreateOpenClawCronJobRequest request, CancellationToken cancellationToken = default, OpenClawInvocationContext? invocationContext = null) => Unsupported>(); public Task> PatchCronJobAsync( string jobId, JsonObject patch, string? expectedHash, CancellationToken cancellationToken = default, OpenClawInvocationContext? invocationContext = null) => Unsupported>(); public Task> DeleteCronJobAsync( string jobId, string? expectedHash = null, CancellationToken cancellationToken = default, OpenClawInvocationContext? invocationContext = null) => Unsupported>(); public Task> ResolveApprovalAsync( string approvalId, string kind, string decision, CancellationToken cancellationToken = default, OpenClawInvocationContext? invocationContext = null) => Unsupported>(); private static OpenClawAgentDto Agent( string id, string name, string model, string workspace) => new( Id: id, Name: name, Description: null, Model: model, Provider: "openai", Workspace: workspace, Status: "ready"); private static OpenClawCollectionDto Collection(IReadOnlyList items) => new( State: "ready", Items: items, NextCursor: null, Message: null, Recovery: null, CheckedAt: DateTimeOffset.UtcNow); private static Task Unsupported() => Task.FromException(new NotSupportedException( "This test double only supports live agent and session inventory.")); }