texts fixes, search bar fixes, global menu fixes.
This commit is contained in:
@@ -6,3 +6,4 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
||||
| --- | --- | --- |
|
||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/TaskRunner/StellaOps.TaskRunner/StellaOps.TaskRunner.Tests/StellaOps.TaskRunner.Tests.md. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
| SPRINT-20260305-002 | DONE | Added `TaskRunnerStartupContractTests` covering postgres non-dev fail-fast and object-store driver contract (`seed-fs` only, rustfs/unknown rejected). |
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
[Collection(TaskRunnerStartupEnvironmentCollection.Name)]
|
||||
public sealed class TaskRunnerStartupContractTests
|
||||
{
|
||||
[Fact]
|
||||
public void Startup_FailsWithoutPostgresConnectionString_InProduction()
|
||||
{
|
||||
using var environment = TaskRunnerStartupEnvironmentScope.ProductionPostgresWithoutConnection();
|
||||
using var factory = new WebApplicationFactory<Program>();
|
||||
|
||||
var exception = Assert.ThrowsAny<Exception>(() =>
|
||||
{
|
||||
using var client = factory.CreateClient();
|
||||
});
|
||||
|
||||
Assert.Contains(
|
||||
"TaskRunner requires PostgreSQL connection settings in non-development mode.",
|
||||
exception.ToString(),
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Startup_RejectsRustFsObjectStoreDriver()
|
||||
{
|
||||
using var environment = TaskRunnerStartupEnvironmentScope.ProductionWithObjectStoreDriver("rustfs");
|
||||
using var factory = new WebApplicationFactory<Program>();
|
||||
|
||||
var exception = Assert.ThrowsAny<Exception>(() =>
|
||||
{
|
||||
using var client = factory.CreateClient();
|
||||
});
|
||||
|
||||
Assert.Contains(
|
||||
"RustFS object store is configured for TaskRunner, but no RustFS adapter is implemented. Use seed-fs.",
|
||||
exception.ToString(),
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Startup_RejectsUnsupportedObjectStoreDriver()
|
||||
{
|
||||
using var environment = TaskRunnerStartupEnvironmentScope.ProductionWithObjectStoreDriver("unknown-store");
|
||||
using var factory = new WebApplicationFactory<Program>();
|
||||
|
||||
var exception = Assert.ThrowsAny<Exception>(() =>
|
||||
{
|
||||
using var client = factory.CreateClient();
|
||||
});
|
||||
|
||||
Assert.Contains(
|
||||
"Unsupported object store driver 'unknown-store' for TaskRunner. Allowed values: seed-fs.",
|
||||
exception.ToString(),
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Startup_AllowsSeedFsObjectStoreDriver()
|
||||
{
|
||||
using var environment = TaskRunnerStartupEnvironmentScope.TestingInMemorySeedFs();
|
||||
using var factory = new WebApplicationFactory<Program>();
|
||||
|
||||
using var client = factory.CreateClient();
|
||||
var response = await client.GetAsync("/.well-known/openapi", TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
[CollectionDefinition(Name, DisableParallelization = true)]
|
||||
public sealed class TaskRunnerStartupEnvironmentCollection
|
||||
{
|
||||
public const string Name = "TaskRunnerStartupEnvironment";
|
||||
}
|
||||
|
||||
internal sealed class TaskRunnerStartupEnvironmentScope : IDisposable
|
||||
{
|
||||
private static readonly string[] ManagedKeys =
|
||||
[
|
||||
"DOTNET_ENVIRONMENT",
|
||||
"ASPNETCORE_ENVIRONMENT",
|
||||
"STORAGE__DRIVER",
|
||||
"TASKRUNNER__STORAGE__DRIVER",
|
||||
"STORAGE__OBJECTSTORE__DRIVER",
|
||||
"TASKRUNNER__STORAGE__OBJECTSTORE__DRIVER",
|
||||
"STORAGE__POSTGRES__CONNECTIONSTRING",
|
||||
"TASKRUNNER__STORAGE__POSTGRES__CONNECTIONSTRING",
|
||||
"CONNECTIONSTRINGS__TASKRUNNER",
|
||||
"CONNECTIONSTRINGS__DEFAULT"
|
||||
];
|
||||
|
||||
private readonly Dictionary<string, string?> _originalValues = new(StringComparer.Ordinal);
|
||||
|
||||
private TaskRunnerStartupEnvironmentScope()
|
||||
{
|
||||
foreach (var key in ManagedKeys)
|
||||
{
|
||||
_originalValues[key] = Environment.GetEnvironmentVariable(key);
|
||||
}
|
||||
}
|
||||
|
||||
public static TaskRunnerStartupEnvironmentScope ProductionPostgresWithoutConnection()
|
||||
{
|
||||
var scope = new TaskRunnerStartupEnvironmentScope();
|
||||
scope.Set("DOTNET_ENVIRONMENT", "Production");
|
||||
scope.Set("ASPNETCORE_ENVIRONMENT", "Production");
|
||||
scope.Set("STORAGE__DRIVER", "postgres");
|
||||
scope.Set("TASKRUNNER__STORAGE__DRIVER", "postgres");
|
||||
scope.Set("TASKRUNNER__STORAGE__OBJECTSTORE__DRIVER", "seed-fs");
|
||||
scope.Set("STORAGE__OBJECTSTORE__DRIVER", "seed-fs");
|
||||
scope.Set("STORAGE__POSTGRES__CONNECTIONSTRING", null);
|
||||
scope.Set("TASKRUNNER__STORAGE__POSTGRES__CONNECTIONSTRING", null);
|
||||
scope.Set("CONNECTIONSTRINGS__TASKRUNNER", null);
|
||||
scope.Set("CONNECTIONSTRINGS__DEFAULT", null);
|
||||
return scope;
|
||||
}
|
||||
|
||||
public static TaskRunnerStartupEnvironmentScope ProductionWithObjectStoreDriver(string objectStoreDriver)
|
||||
{
|
||||
var scope = new TaskRunnerStartupEnvironmentScope();
|
||||
scope.Set("DOTNET_ENVIRONMENT", "Production");
|
||||
scope.Set("ASPNETCORE_ENVIRONMENT", "Production");
|
||||
scope.Set("STORAGE__DRIVER", "postgres");
|
||||
scope.Set("TASKRUNNER__STORAGE__DRIVER", "postgres");
|
||||
scope.Set("TASKRUNNER__STORAGE__OBJECTSTORE__DRIVER", objectStoreDriver);
|
||||
scope.Set("STORAGE__OBJECTSTORE__DRIVER", objectStoreDriver);
|
||||
var connectionString = "Host=localhost;Database=stellaops_taskrunner;Username=stellaops;Password=stellaops";
|
||||
scope.Set("STORAGE__POSTGRES__CONNECTIONSTRING", connectionString);
|
||||
scope.Set("TASKRUNNER__STORAGE__POSTGRES__CONNECTIONSTRING", connectionString);
|
||||
scope.Set("CONNECTIONSTRINGS__TASKRUNNER", connectionString);
|
||||
scope.Set("CONNECTIONSTRINGS__DEFAULT", connectionString);
|
||||
return scope;
|
||||
}
|
||||
|
||||
public static TaskRunnerStartupEnvironmentScope TestingInMemorySeedFs()
|
||||
{
|
||||
var scope = new TaskRunnerStartupEnvironmentScope();
|
||||
scope.Set("DOTNET_ENVIRONMENT", "Testing");
|
||||
scope.Set("ASPNETCORE_ENVIRONMENT", "Testing");
|
||||
scope.Set("STORAGE__DRIVER", "inmemory");
|
||||
scope.Set("TASKRUNNER__STORAGE__DRIVER", "inmemory");
|
||||
scope.Set("TASKRUNNER__STORAGE__OBJECTSTORE__DRIVER", "seed-fs");
|
||||
scope.Set("STORAGE__OBJECTSTORE__DRIVER", "seed-fs");
|
||||
scope.Set("STORAGE__POSTGRES__CONNECTIONSTRING", null);
|
||||
scope.Set("TASKRUNNER__STORAGE__POSTGRES__CONNECTIONSTRING", null);
|
||||
scope.Set("CONNECTIONSTRINGS__TASKRUNNER", null);
|
||||
scope.Set("CONNECTIONSTRINGS__DEFAULT", null);
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var entry in _originalValues)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(entry.Key, entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void Set(string key, string? value)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(key, value);
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ builder.Services.AddStellaOpsTelemetry(
|
||||
|
||||
var storageDriver = ResolveStorageDriver(builder.Configuration, "TaskRunner");
|
||||
RegisterStateStores(builder.Services, builder.Configuration, builder.Environment.IsDevelopment(), storageDriver);
|
||||
ValidateObjectStoreContract(builder.Configuration, builder.Environment.IsDevelopment(), "TaskRunner");
|
||||
ValidateObjectStoreContract(builder.Configuration, "TaskRunner");
|
||||
|
||||
builder.Services.AddSingleton<IPackRunArtifactReader>(sp =>
|
||||
{
|
||||
@@ -1066,27 +1066,19 @@ static string? ResolveSchemaName(IConfiguration configuration, string serviceNam
|
||||
configuration[$"Postgres:{serviceName}:SchemaName"]);
|
||||
}
|
||||
|
||||
static void ValidateObjectStoreContract(IConfiguration configuration, bool isDevelopment, string serviceName)
|
||||
static void ValidateObjectStoreContract(IConfiguration configuration, string serviceName)
|
||||
{
|
||||
var objectStoreDriver = ResolveObjectStoreDriver(configuration, serviceName);
|
||||
if (!string.Equals(objectStoreDriver, "seed-fs", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(objectStoreDriver, "rustfs", StringComparison.OrdinalIgnoreCase))
|
||||
if (!string.Equals(objectStoreDriver, "seed-fs", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Unsupported object store driver '{objectStoreDriver}' for {serviceName}. Allowed values: seed-fs, rustfs.");
|
||||
}
|
||||
|
||||
if (string.Equals(objectStoreDriver, "rustfs", StringComparison.OrdinalIgnoreCase) && !isDevelopment)
|
||||
{
|
||||
var rustFsBaseUrl = FirstNonEmpty(
|
||||
configuration[$"{serviceName}:Storage:ObjectStore:RustFs:BaseUrl"],
|
||||
configuration["Storage:ObjectStore:RustFs:BaseUrl"]);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rustFsBaseUrl))
|
||||
if (string.Equals(objectStoreDriver, "rustfs", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"RustFS object store is configured for {serviceName}, but BaseUrl is missing.");
|
||||
$"RustFS object store is configured for {serviceName}, but no RustFS adapter is implemented. Use seed-fs.");
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Unsupported object store driver '{objectStoreDriver}' for {serviceName}. Allowed values: seed-fs.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1342,5 +1334,7 @@ internal static class RunStateMapper
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Program;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,3 +7,4 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/TaskRunner/StellaOps.TaskRunner/StellaOps.TaskRunner.WebService/StellaOps.TaskRunner.WebService.md. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
| SPRINT-312-004 | DONE | Runtime storage driver migration verified: Postgres state/log/approval default plus seed-fs artifact object-store path. |
|
||||
| SPRINT-20260305-002 | DONE | Startup contract hardened: non-dev postgres missing-connection fails fast; object-store accepts seed-fs only and rejects rustfs/unknown values. |
|
||||
|
||||
@@ -58,7 +58,7 @@ builder.Services.AddStellaOpsTelemetry(
|
||||
|
||||
var storageDriver = ResolveStorageDriver(builder.Configuration, "TaskRunner");
|
||||
RegisterStateStores(builder.Services, builder.Configuration, builder.Environment.IsDevelopment(), storageDriver);
|
||||
ValidateObjectStoreContract(builder.Configuration, builder.Environment.IsDevelopment(), "TaskRunner");
|
||||
ValidateObjectStoreContract(builder.Configuration, "TaskRunner");
|
||||
|
||||
builder.Services.AddSingleton<IPackRunArtifactUploader>(sp =>
|
||||
{
|
||||
@@ -179,27 +179,19 @@ static string? ResolveSchemaName(IConfiguration configuration, string serviceNam
|
||||
configuration[$"Postgres:{serviceName}:SchemaName"]);
|
||||
}
|
||||
|
||||
static void ValidateObjectStoreContract(IConfiguration configuration, bool isDevelopment, string serviceName)
|
||||
static void ValidateObjectStoreContract(IConfiguration configuration, string serviceName)
|
||||
{
|
||||
var objectStoreDriver = ResolveObjectStoreDriver(configuration, serviceName);
|
||||
if (!string.Equals(objectStoreDriver, "seed-fs", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(objectStoreDriver, "rustfs", StringComparison.OrdinalIgnoreCase))
|
||||
if (!string.Equals(objectStoreDriver, "seed-fs", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Unsupported object store driver '{objectStoreDriver}' for {serviceName}. Allowed values: seed-fs, rustfs.");
|
||||
}
|
||||
|
||||
if (string.Equals(objectStoreDriver, "rustfs", StringComparison.OrdinalIgnoreCase) && !isDevelopment)
|
||||
{
|
||||
var rustFsBaseUrl = FirstNonEmpty(
|
||||
configuration[$"{serviceName}:Storage:ObjectStore:RustFs:BaseUrl"],
|
||||
configuration["Storage:ObjectStore:RustFs:BaseUrl"]);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rustFsBaseUrl))
|
||||
if (string.Equals(objectStoreDriver, "rustfs", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"RustFS object store is configured for {serviceName}, but BaseUrl is missing.");
|
||||
$"RustFS object store is configured for {serviceName}, but no RustFS adapter is implemented. Use seed-fs.");
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Unsupported object store driver '{objectStoreDriver}' for {serviceName}. Allowed values: seed-fs.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,3 +7,4 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/TaskRunner/StellaOps.TaskRunner/StellaOps.TaskRunner.Worker/StellaOps.TaskRunner.Worker.md. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
| SPRINT-312-004 | DONE | Worker storage wiring aligned to Postgres state/log/approval and seed-fs artifact/provenance object-store contract. |
|
||||
| SPRINT-20260305-002 | DONE | Worker startup contract now rejects rustfs/unknown object-store drivers and keeps seed-fs as the deterministic supported payload channel. |
|
||||
|
||||
Reference in New Issue
Block a user