up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-25 22:09:44 +02:00
parent 6bee1fdcf5
commit 9f6e6f7fb3
116 changed files with 4495 additions and 730 deletions

View File

@@ -0,0 +1,196 @@
using System;
using System.Net;
using System.Collections.Generic;
using System.Net.Http.Json;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo.Orchestrator;
using StellaOps.Concelier.WebService;
using StellaOps.Concelier.WebService.Options;
using Xunit;
namespace StellaOps.Concelier.WebService.Tests;
public sealed class OrchestratorTestWebAppFactory : WebApplicationFactory<Program>
{
public OrchestratorTestWebAppFactory()
{
Environment.SetEnvironmentVariable("CONCELIER__STORAGE__DSN", "mongodb://localhost:27017/orch-tests");
Environment.SetEnvironmentVariable("CONCELIER__STORAGE__DRIVER", "mongo");
Environment.SetEnvironmentVariable("CONCELIER__STORAGE__COMMANDTIMEOUTSECONDS", "30");
Environment.SetEnvironmentVariable("CONCELIER__TELEMETRY__ENABLED", "false");
Environment.SetEnvironmentVariable("CONCELIER__AUTHORITY__ENABLED", "false"); // disable auth so tests can hit endpoints without tokens
Environment.SetEnvironmentVariable("CONCELIER_SKIP_OPTIONS_VALIDATION", "1");
Environment.SetEnvironmentVariable("CONCELIER_TEST_STORAGE_DSN", "mongodb://localhost:27017/orch-tests");
Environment.SetEnvironmentVariable("CONCELIER_BYPASS_MONGO", "1");
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", "Testing");
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Testing");
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Testing");
builder.ConfigureAppConfiguration(cfg =>
{
cfg.AddInMemoryCollection(new Dictionary<string, string?>
{
["Concelier:Storage:Dsn"] = "mongodb://localhost:27017/orch-tests",
["Concelier:Storage:Driver"] = "mongo",
["Concelier:Storage:CommandTimeoutSeconds"] = "30",
["Concelier:Telemetry:Enabled"] = "false",
["Concelier:Authority:Enabled"] = "false"
});
});
builder.ConfigureServices(services =>
{
services.RemoveAll<IOrchestratorRegistryStore>();
services.AddSingleton<IOrchestratorRegistryStore, InMemoryOrchestratorStore>();
// Pre-bind options to keep Program from trying to rebind/validate during tests.
services.RemoveAll<ConcelierOptions>();
services.RemoveAll<IOptions<ConcelierOptions>>();
var forcedOptions = new ConcelierOptions
{
Storage = new ConcelierOptions.StorageOptions
{
Dsn = "mongodb://localhost:27017/orch-tests",
Driver = "mongo",
CommandTimeoutSeconds = 30
},
Telemetry = new ConcelierOptions.TelemetryOptions
{
Enabled = false
},
Authority = new ConcelierOptions.AuthorityOptions
{
Enabled = false
}
};
services.AddSingleton(forcedOptions);
services.AddSingleton<IOptions<ConcelierOptions>>(_ => Microsoft.Extensions.Options.Options.Create(forcedOptions));
// Force Mongo storage options to a deterministic in-memory test DSN.
services.PostConfigure<MongoStorageOptions>(opts =>
{
opts.ConnectionString = "mongodb://localhost:27017/orch-tests";
opts.DatabaseName = "orch-tests";
opts.CommandTimeout = TimeSpan.FromSeconds(30);
});
});
}
}
public sealed class OrchestratorEndpointsTests : IClassFixture<OrchestratorTestWebAppFactory>
{
private readonly OrchestratorTestWebAppFactory _factory;
public OrchestratorEndpointsTests(OrchestratorTestWebAppFactory factory) => _factory = factory;
[Fact]
public async Task Registry_accepts_valid_request_with_tenant()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-a");
var request = new
{
connectorId = "demo-connector",
source = "demo-source",
capabilities = new[] { "ingest" },
authRef = "secret",
schedule = new { cron = "0 0 * * *", timeZone = "UTC", maxParallelRuns = 1, maxLagMinutes = 5 },
ratePolicy = new { rpm = 100, burst = 10, cooldownSeconds = 5 },
artifactKinds = new[] { "advisory" },
lockKey = "lk-1",
egressGuard = new { allowlist = new[] { "example.com" }, airgapMode = false }
};
var response = await client.PostAsJsonAsync("/internal/orch/registry", request);
response.StatusCode.Should().Be(HttpStatusCode.Accepted);
}
[Fact]
public async Task Heartbeat_accepts_valid_request_with_tenant()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-a");
var request = new
{
connectorId = "demo-connector",
runId = "11111111-1111-1111-1111-111111111111",
sequence = 1,
status = "running",
progress = 10,
queueDepth = 0
};
var response = await client.PostAsJsonAsync("/internal/orch/heartbeat", request);
response.StatusCode.Should().Be(HttpStatusCode.Accepted);
}
[Fact]
public async Task Commands_get_returns_ok_with_empty_list()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Stella-Tenant", "tenant-a");
var response = await client.GetAsync("/internal/orch/commands?connectorId=demo-connector&runId=11111111-1111-1111-1111-111111111111");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var payload = await response.Content.ReadFromJsonAsync<List<OrchestratorCommandRecord>>();
payload.Should().NotBeNull();
payload!.Should().BeEmpty();
}
}
internal sealed class InMemoryOrchestratorStore : IOrchestratorRegistryStore
{
private readonly Dictionary<(string Tenant, string ConnectorId), OrchestratorRegistryRecord> _registry = new();
private readonly List<OrchestratorHeartbeatRecord> _heartbeats = new();
private readonly List<OrchestratorCommandRecord> _commands = new();
public Task UpsertAsync(OrchestratorRegistryRecord record, CancellationToken cancellationToken)
{
_registry[(record.Tenant, record.ConnectorId)] = record;
return Task.CompletedTask;
}
public Task<OrchestratorRegistryRecord?> GetAsync(string tenant, string connectorId, CancellationToken cancellationToken)
{
_registry.TryGetValue((tenant, connectorId), out var record);
return Task.FromResult(record);
}
public Task EnqueueCommandAsync(OrchestratorCommandRecord command, CancellationToken cancellationToken)
{
_commands.Add(command);
return Task.CompletedTask;
}
public Task<IReadOnlyList<OrchestratorCommandRecord>> GetPendingCommandsAsync(string tenant, string connectorId, Guid runId, long? afterSequence, CancellationToken cancellationToken)
{
var items = _commands
.Where(c => c.Tenant == tenant && c.ConnectorId == connectorId && c.RunId == runId && (afterSequence is null || c.Sequence > afterSequence))
.ToList()
.AsReadOnly();
return Task.FromResult<IReadOnlyList<OrchestratorCommandRecord>>(items);
}
public Task AppendHeartbeatAsync(OrchestratorHeartbeatRecord heartbeat, CancellationToken cancellationToken)
{
_heartbeats.Add(heartbeat);
return Task.CompletedTask;
}
}

View File

@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using FluentAssertions;
using StellaOps.Concelier.WebService.Services;
using StellaOps.Concelier.WebService;
using StellaOps.Concelier.WebService.Options;
using Xunit;
namespace StellaOps.Concelier.WebService.Tests.Services;