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."));
}