160 lines
5.7 KiB
C#
160 lines
5.7 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Options;
|
|
using Nexus.Api.Data;
|
|
using Nexus.Api.Models;
|
|
using Nexus.Api.Services;
|
|
using Xunit;
|
|
|
|
namespace Nexus.Api.Tests;
|
|
|
|
public sealed class OpenClawWriteGateTests
|
|
{
|
|
[Fact]
|
|
public async Task Adopted_primary_profile_allows_matching_write_boundary()
|
|
{
|
|
var connector = ConnectedConnector();
|
|
await using var fixture = await GateFixture.CreateAsync(connector);
|
|
|
|
var result = await fixture.Gate.EvaluateAsync("agents.create");
|
|
|
|
Assert.True(result.Allowed);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Endpoint_change_after_adoption_blocks_write()
|
|
{
|
|
var connector = ConnectedConnector();
|
|
await using var fixture = await GateFixture.CreateAsync(connector);
|
|
connector.ActiveEndpoint = "wss://other-openclaw.example.test:18789/";
|
|
|
|
var result = await fixture.Gate.EvaluateAsync("agents.create");
|
|
|
|
Assert.False(result.Allowed);
|
|
Assert.Equal("endpoint_trust_mismatch", result.State);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Device_change_after_adoption_blocks_write()
|
|
{
|
|
var connector = ConnectedConnector();
|
|
await using var fixture = await GateFixture.CreateAsync(connector);
|
|
connector.DeviceId = "unexpected-device";
|
|
|
|
var result = await fixture.Gate.EvaluateAsync("agents.create");
|
|
|
|
Assert.False(result.Allowed);
|
|
Assert.Equal("device_trust_mismatch", result.State);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Wss_profile_without_tls_fingerprint_blocks_write()
|
|
{
|
|
var connector = ConnectedConnector();
|
|
connector.ActiveTlsFingerprint = null;
|
|
await using var fixture = await GateFixture.CreateAsync(connector);
|
|
|
|
var result = await fixture.Gate.EvaluateAsync("agents.create");
|
|
|
|
Assert.False(result.Allowed);
|
|
Assert.Equal("tls_trust_missing", result.State);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Internal_ws_profile_without_tls_fingerprint_can_write()
|
|
{
|
|
var connector = ConnectedConnector();
|
|
connector.ActiveEndpoint = "ws://openclaw-gateway:18789/";
|
|
connector.ActiveTlsFingerprint = null;
|
|
await using var fixture = await GateFixture.CreateAsync(connector);
|
|
|
|
var result = await fixture.Gate.EvaluateAsync("agents.create");
|
|
|
|
Assert.True(result.Allowed);
|
|
}
|
|
|
|
private static StubOpenClawConnector ConnectedConnector()
|
|
=> new()
|
|
{
|
|
ConnectionState = GatewayConnectionState.Connected,
|
|
GatewayVersion = "2026.8.0",
|
|
RequiredVersion = "2026.8.0",
|
|
ProtocolVersion = 4,
|
|
ActiveEndpoint = "wss://openclaw.example.test:18789/",
|
|
ActiveTlsFingerprint = new string('A', 64),
|
|
DeviceId = "nexus-device",
|
|
GrantedScopes = new HashSet<string>(
|
|
["operator.read", "operator.admin"],
|
|
StringComparer.Ordinal),
|
|
AdvertisedMethods = new HashSet<string>(
|
|
["agents.create"],
|
|
StringComparer.Ordinal),
|
|
AdvertisedEvents = new HashSet<string>(
|
|
["agent.updated"],
|
|
StringComparer.Ordinal)
|
|
};
|
|
|
|
private sealed class GateFixture(
|
|
ServiceProvider provider,
|
|
OpenClawWriteGate gate) : IAsyncDisposable
|
|
{
|
|
public OpenClawWriteGate Gate { get; } = gate;
|
|
|
|
public static async Task<GateFixture> CreateAsync(
|
|
StubOpenClawConnector connector)
|
|
{
|
|
var services = new ServiceCollection();
|
|
var databaseName = $"write-gate-{Guid.NewGuid():N}";
|
|
services.AddDbContext<NexusDbContext>(options =>
|
|
options.UseInMemoryDatabase(databaseName));
|
|
var provider = services.BuildServiceProvider();
|
|
var configuration = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(new Dictionary<string, string?>
|
|
{
|
|
["OpenClawSetup:ExternalClientIdentitySupported"] = "true"
|
|
})
|
|
.Build();
|
|
var options = Options.Create(new GatewayConnectorOptions
|
|
{
|
|
ClientId = "nexus",
|
|
ClientMode = "backend",
|
|
ExternalClientIdentitySupported = true,
|
|
AllowReservedInternalClientIdentity = false
|
|
});
|
|
|
|
await using (var scope = provider.CreateAsyncScope())
|
|
{
|
|
var db = scope.ServiceProvider
|
|
.GetRequiredService<NexusDbContext>();
|
|
db.OpenClawConnectionProfiles.Add(
|
|
new OpenClawConnectionProfile
|
|
{
|
|
Endpoint = connector.ActiveEndpoint!,
|
|
DiscoverySource = "test",
|
|
RequiredVersion = connector.GatewayVersion,
|
|
TlsCertificateFingerprint =
|
|
connector.ActiveTlsFingerprint,
|
|
AdoptionState = OpenClawAdoptionStates.Adopted,
|
|
ManagementEnabled = true,
|
|
CapabilityHash =
|
|
OpenClawWriteGate.BuildCapabilityHash(connector),
|
|
DeviceId = connector.DeviceId,
|
|
Revision = 1
|
|
});
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
return new GateFixture(
|
|
provider,
|
|
new OpenClawWriteGate(
|
|
provider.GetRequiredService<IServiceScopeFactory>(),
|
|
connector,
|
|
options,
|
|
configuration));
|
|
}
|
|
|
|
public ValueTask DisposeAsync() => provider.DisposeAsync();
|
|
}
|
|
}
|