162 lines
6.5 KiB
C#
162 lines
6.5 KiB
C#
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;
|
|
using StellaOps.Concelier.Core.Jobs;
|
|
using StellaOps.Concelier.Core.Orchestration;
|
|
using StellaOps.Concelier.WebService;
|
|
using StellaOps.Concelier.WebService.Options;
|
|
using Xunit;
|
|
|
|
using StellaOps.TestKit;
|
|
namespace StellaOps.Concelier.WebService.Tests;
|
|
|
|
public sealed class OrchestratorTestWebAppFactory : WebApplicationFactory<Program>
|
|
{
|
|
public OrchestratorTestWebAppFactory()
|
|
{
|
|
Environment.SetEnvironmentVariable("CONCELIER__POSTGRESSTORAGE__CONNECTIONSTRING", "Host=localhost;Port=5432;Database=orch-tests");
|
|
Environment.SetEnvironmentVariable("CONCELIER__POSTGRESSTORAGE__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", "Host=localhost;Port=5432;Database=orch-tests");
|
|
Environment.SetEnvironmentVariable("CONCELIER_BYPASS_EXTERNAL_STORAGE", "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:PostgresStorage:ConnectionString"] = "Host=localhost;Port=5432;Database=orch-tests",
|
|
["Concelier:PostgresStorage:CommandTimeoutSeconds"] = "30",
|
|
["Concelier:Telemetry:Enabled"] = "false",
|
|
["Concelier:Authority:Enabled"] = "false"
|
|
});
|
|
});
|
|
|
|
builder.ConfigureServices(services =>
|
|
{
|
|
services.RemoveAll<ILeaseStore>();
|
|
services.AddSingleton<ILeaseStore, Fixtures.TestLeaseStore>();
|
|
services.RemoveAll<IOrchestratorRegistryStore>();
|
|
services.AddSingleton<IOrchestratorRegistryStore, InMemoryOrchestratorRegistryStore>();
|
|
|
|
// Pre-bind options to keep Program from trying to rebind/validate during tests.
|
|
services.RemoveAll<ConcelierOptions>();
|
|
services.RemoveAll<IOptions<ConcelierOptions>>();
|
|
var forcedOptions = new ConcelierOptions
|
|
{
|
|
PostgresStorage = new ConcelierOptions.PostgresStorageOptions
|
|
{
|
|
ConnectionString = "Host=localhost;Port=5432;Database=orch-tests",
|
|
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 storage options to a deterministic in-memory test DSN.
|
|
services.PostConfigure<StorageOptions>(opts =>
|
|
{
|
|
opts.ConnectionString = "Host=localhost;Port=5432;Database=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;
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[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);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[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);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[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();
|
|
}
|
|
}
|
|
|