stabilizaiton work - projects rework for maintenanceability and ui livening
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class AuthorityPluginConfigurationLoaderTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_ReturnsWarning_WhenStandardPasswordPolicyWeaker()
|
||||
{
|
||||
var pluginDir = Path.Combine(_tempRoot, "etc", "authority.plugins");
|
||||
Directory.CreateDirectory(pluginDir);
|
||||
|
||||
var standardConfigPath = Path.Combine(pluginDir, "standard.yaml");
|
||||
File.WriteAllText(standardConfigPath, "passwordPolicy:\n minimumLength: 8\n requireSymbol: false\n");
|
||||
|
||||
var options = CreateOptions();
|
||||
options.Plugins.ConfigurationDirectory = "etc/authority.plugins";
|
||||
options.Plugins.Descriptors["standard"] = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Standard",
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
options.Validate();
|
||||
|
||||
var contexts = AuthorityPluginConfigurationLoader.Load(options, _tempRoot);
|
||||
var diagnostics = AuthorityPluginConfigurationAnalyzer.Analyze(contexts);
|
||||
|
||||
var diagnostic = Assert.Single(diagnostics);
|
||||
Assert.Equal(AuthorityConfigurationDiagnosticSeverity.Warning, diagnostic.Severity);
|
||||
Assert.Equal("standard", diagnostic.PluginName);
|
||||
Assert.Contains("minimum length 8", diagnostic.Message, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("symbol requirement disabled", diagnostic.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_ReturnsNoDiagnostics_WhenPasswordPolicyMatchesBaseline()
|
||||
{
|
||||
var pluginDir = Path.Combine(_tempRoot, "etc", "authority.plugins");
|
||||
Directory.CreateDirectory(pluginDir);
|
||||
|
||||
var standardConfigPath = Path.Combine(pluginDir, "standard.yaml");
|
||||
// Baseline configuration (no overrides)
|
||||
File.WriteAllText(standardConfigPath, "bootstrapUser:\n username: bootstrap\n password: Bootstrap1!\n");
|
||||
|
||||
var options = CreateOptions();
|
||||
options.Plugins.ConfigurationDirectory = "etc/authority.plugins";
|
||||
options.Plugins.Descriptors["standard"] = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Standard",
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
options.Validate();
|
||||
|
||||
var contexts = AuthorityPluginConfigurationLoader.Load(options, _tempRoot);
|
||||
var diagnostics = AuthorityPluginConfigurationAnalyzer.Analyze(contexts);
|
||||
|
||||
Assert.Empty(diagnostics);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using StellaOps.Configuration;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class AuthorityPluginConfigurationLoaderTests : IDisposable
|
||||
{
|
||||
private readonly string _tempRoot;
|
||||
|
||||
public AuthorityPluginConfigurationLoaderTests()
|
||||
{
|
||||
_tempRoot = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
"stellaops-tests",
|
||||
"authority-plugin-tests",
|
||||
Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(_tempRoot);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(_tempRoot))
|
||||
{
|
||||
Directory.Delete(_tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore cleanup failures in test environment
|
||||
}
|
||||
}
|
||||
|
||||
private static StellaOpsAuthorityOptions CreateOptions()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
{
|
||||
Issuer = new Uri("https://authority.stella-ops.test"),
|
||||
SchemaVersion = 1
|
||||
};
|
||||
|
||||
options.Storage.ConnectionString = "Host=localhost;Port=5432;Database=authority_test";
|
||||
options.Signing.ActiveKeyId = "test-key";
|
||||
options.Signing.KeyPath = "/tmp/authority-test-key.pem";
|
||||
options.Notifications.AckTokens.Enabled = false;
|
||||
options.Notifications.Webhooks.Enabled = false;
|
||||
return options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class AuthorityPluginConfigurationLoaderTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Load_ReturnsConfiguration_ForEnabledPlugin()
|
||||
{
|
||||
var pluginDir = Path.Combine(_tempRoot, "etc", "authority.plugins");
|
||||
Directory.CreateDirectory(pluginDir);
|
||||
|
||||
var standardConfigPath = Path.Combine(pluginDir, "standard.yaml");
|
||||
File.WriteAllText(standardConfigPath, "secretKey: value");
|
||||
|
||||
var options = CreateOptions();
|
||||
options.Plugins.ConfigurationDirectory = "etc/authority.plugins";
|
||||
options.Plugins.Descriptors["standard"] = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Standard",
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
options.Validate();
|
||||
|
||||
var contexts = AuthorityPluginConfigurationLoader.Load(options, _tempRoot);
|
||||
var context = Assert.Single(contexts);
|
||||
Assert.Equal("standard", context.Manifest.Name);
|
||||
Assert.Equal("value", context.Configuration["secretKey"]);
|
||||
Assert.True(context.Manifest.Enabled);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Load_Throws_WhenEnabledConfigMissing()
|
||||
{
|
||||
var options = CreateOptions();
|
||||
options.Plugins.ConfigurationDirectory = "etc/authority.plugins";
|
||||
options.Plugins.Descriptors["standard"] = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Standard",
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
options.Validate();
|
||||
|
||||
var ex = Assert.Throws<FileNotFoundException>(() =>
|
||||
AuthorityPluginConfigurationLoader.Load(options, _tempRoot));
|
||||
|
||||
Assert.Contains("standard.yaml", ex.FileName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Load_SkipsMissingFile_ForDisabledPlugin()
|
||||
{
|
||||
var options = CreateOptions();
|
||||
options.Plugins.ConfigurationDirectory = "etc/authority.plugins";
|
||||
options.Plugins.Descriptors["ldap"] = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Ldap",
|
||||
Enabled = false,
|
||||
ConfigFile = "ldap.yaml"
|
||||
};
|
||||
|
||||
options.Validate();
|
||||
|
||||
var contexts = AuthorityPluginConfigurationLoader.Load(options, _tempRoot);
|
||||
var context = Assert.Single(contexts);
|
||||
Assert.False(context.Manifest.Enabled);
|
||||
Assert.Equal("ldap", context.Manifest.Name);
|
||||
Assert.Null(context.Configuration["connection:host"]);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_ThrowsForUnknownCapability()
|
||||
{
|
||||
var options = CreateOptions();
|
||||
options.Plugins.Descriptors["standard"] = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Standard",
|
||||
Enabled = true
|
||||
};
|
||||
options.Plugins.Descriptors["standard"].Capabilities.Add("custom-flow");
|
||||
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("unknown capability", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using StellaOps.Authority.Plugins.Abstractions;
|
||||
using StellaOps.Configuration;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public class AuthorityPluginConfigurationLoaderTests : IDisposable
|
||||
{
|
||||
private readonly string tempRoot;
|
||||
|
||||
public AuthorityPluginConfigurationLoaderTests()
|
||||
{
|
||||
tempRoot = Path.Combine(Path.GetTempPath(), "authority-plugin-tests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempRoot);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Load_ReturnsConfiguration_ForEnabledPlugin()
|
||||
{
|
||||
var pluginDir = Path.Combine(tempRoot, "etc", "authority.plugins");
|
||||
Directory.CreateDirectory(pluginDir);
|
||||
|
||||
var standardConfigPath = Path.Combine(pluginDir, "standard.yaml");
|
||||
File.WriteAllText(standardConfigPath, "secretKey: value");
|
||||
|
||||
var options = CreateOptions();
|
||||
options.Plugins.ConfigurationDirectory = "etc/authority.plugins";
|
||||
options.Plugins.Descriptors["standard"] = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Standard",
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
options.Validate();
|
||||
|
||||
var contexts = AuthorityPluginConfigurationLoader.Load(options, tempRoot);
|
||||
var context = Assert.Single(contexts);
|
||||
Assert.Equal("standard", context.Manifest.Name);
|
||||
Assert.Equal("value", context.Configuration["secretKey"]);
|
||||
Assert.True(context.Manifest.Enabled);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Load_Throws_WhenEnabledConfigMissing()
|
||||
{
|
||||
var options = CreateOptions();
|
||||
options.Plugins.ConfigurationDirectory = "etc/authority.plugins";
|
||||
options.Plugins.Descriptors["standard"] = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Standard",
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
options.Validate();
|
||||
|
||||
var ex = Assert.Throws<FileNotFoundException>(() =>
|
||||
AuthorityPluginConfigurationLoader.Load(options, tempRoot));
|
||||
|
||||
Assert.Contains("standard.yaml", ex.FileName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Load_SkipsMissingFile_ForDisabledPlugin()
|
||||
{
|
||||
var options = CreateOptions();
|
||||
options.Plugins.ConfigurationDirectory = "etc/authority.plugins";
|
||||
options.Plugins.Descriptors["ldap"] = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Ldap",
|
||||
Enabled = false,
|
||||
ConfigFile = "ldap.yaml"
|
||||
};
|
||||
|
||||
options.Validate();
|
||||
|
||||
var contexts = AuthorityPluginConfigurationLoader.Load(options, tempRoot);
|
||||
var context = Assert.Single(contexts);
|
||||
Assert.False(context.Manifest.Enabled);
|
||||
Assert.Equal("ldap", context.Manifest.Name);
|
||||
Assert.Null(context.Configuration["connection:host"]);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_ThrowsForUnknownCapability()
|
||||
{
|
||||
var options = CreateOptions();
|
||||
options.Plugins.Descriptors["standard"] = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Standard",
|
||||
Enabled = true
|
||||
};
|
||||
options.Plugins.Descriptors["standard"].Capabilities.Add("custom-flow");
|
||||
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("unknown capability", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_ReturnsWarning_WhenStandardPasswordPolicyWeaker()
|
||||
{
|
||||
var pluginDir = Path.Combine(tempRoot, "etc", "authority.plugins");
|
||||
Directory.CreateDirectory(pluginDir);
|
||||
|
||||
var standardConfigPath = Path.Combine(pluginDir, "standard.yaml");
|
||||
File.WriteAllText(standardConfigPath, "passwordPolicy:\n minimumLength: 8\n requireSymbol: false\n");
|
||||
|
||||
var options = CreateOptions();
|
||||
options.Plugins.ConfigurationDirectory = "etc/authority.plugins";
|
||||
options.Plugins.Descriptors["standard"] = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Standard",
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
options.Validate();
|
||||
|
||||
var contexts = AuthorityPluginConfigurationLoader.Load(options, tempRoot);
|
||||
var diagnostics = AuthorityPluginConfigurationAnalyzer.Analyze(contexts);
|
||||
|
||||
var diagnostic = Assert.Single(diagnostics);
|
||||
Assert.Equal(AuthorityConfigurationDiagnosticSeverity.Warning, diagnostic.Severity);
|
||||
Assert.Equal("standard", diagnostic.PluginName);
|
||||
Assert.Contains("minimum length 8", diagnostic.Message, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("symbol requirement disabled", diagnostic.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_ReturnsNoDiagnostics_WhenPasswordPolicyMatchesBaseline()
|
||||
{
|
||||
var pluginDir = Path.Combine(tempRoot, "etc", "authority.plugins");
|
||||
Directory.CreateDirectory(pluginDir);
|
||||
|
||||
var standardConfigPath = Path.Combine(pluginDir, "standard.yaml");
|
||||
// Baseline configuration (no overrides)
|
||||
File.WriteAllText(standardConfigPath, "bootstrapUser:\n username: bootstrap\n password: Bootstrap1!\n");
|
||||
|
||||
var options = CreateOptions();
|
||||
options.Plugins.ConfigurationDirectory = "etc/authority.plugins";
|
||||
options.Plugins.Descriptors["standard"] = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Standard",
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
options.Validate();
|
||||
|
||||
var contexts = AuthorityPluginConfigurationLoader.Load(options, tempRoot);
|
||||
var diagnostics = AuthorityPluginConfigurationAnalyzer.Analyze(contexts);
|
||||
|
||||
Assert.Empty(diagnostics);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(tempRoot))
|
||||
{
|
||||
Directory.Delete(tempRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore cleanup failures in test environment
|
||||
}
|
||||
}
|
||||
|
||||
private static StellaOpsAuthorityOptions CreateOptions()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
{
|
||||
Issuer = new Uri("https://authority.stella-ops.test"),
|
||||
SchemaVersion = 1
|
||||
};
|
||||
|
||||
options.Storage.ConnectionString = "Host=localhost;Port=5432;Database=authority_test";
|
||||
options.Signing.ActiveKeyId = "test-key";
|
||||
options.Signing.KeyPath = "/tmp/authority-test-key.pem";
|
||||
options.Notifications.AckTokens.Enabled = false;
|
||||
options.Notifications.Webhooks.Enabled = false;
|
||||
return options;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
using StellaOps.Auth;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public class AuthorityTelemetryTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
[Fact]
|
||||
public void ServiceName_AndNamespace_MatchExpectations()
|
||||
{
|
||||
Assert.Equal("stellaops-authority", AuthorityTelemetry.ServiceName);
|
||||
@@ -15,7 +15,7 @@ public class AuthorityTelemetryTests
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
[Fact]
|
||||
public void BuildDefaultResourceAttributes_ContainsExpectedKeys()
|
||||
{
|
||||
var attributes = AuthorityTelemetry.BuildDefaultResourceAttributes();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.Configuration/StellaOps.Configuration.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.Configuration.AuthorityPlugin/StellaOps.Configuration.AuthorityPlugin.csproj" />
|
||||
<ProjectReference Include="../../../Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class StellaOpsAuthorityOptionsTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Normalises_Collections()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
|
||||
options.PluginDirectories.Add(" ./plugins ");
|
||||
options.PluginDirectories.Add("./plugins");
|
||||
options.PluginDirectories.Add("./other");
|
||||
|
||||
options.BypassNetworks.Add(" 10.0.0.0/24 ");
|
||||
options.BypassNetworks.Add("10.0.0.0/24");
|
||||
options.BypassNetworks.Add("192.168.0.0/16");
|
||||
|
||||
options.AdvisoryAi.RemoteInference.AllowedProfiles.Add(" cloud-openai ");
|
||||
options.AdvisoryAi.RemoteInference.AllowedProfiles.Add("CLOUD-OPENAI");
|
||||
options.AdvisoryAi.RemoteInference.AllowedProfiles.Add("sovereign-local");
|
||||
|
||||
options.Validate();
|
||||
|
||||
Assert.Equal(new[] { "./plugins", "./other" }, options.PluginDirectories);
|
||||
Assert.Equal(new[] { "10.0.0.0/24", "192.168.0.0/16" }, options.BypassNetworks);
|
||||
Assert.Equal(new[] { "cloud-openai", "sovereign-local" }, options.AdvisoryAi.RemoteInference.AllowedProfiles);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class StellaOpsAuthorityOptionsTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_Binds_From_Configuration()
|
||||
{
|
||||
var context = StellaOpsAuthorityConfiguration.Build(options =>
|
||||
{
|
||||
options.ConfigureBuilder = builder =>
|
||||
{
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["Authority:SchemaVersion"] = "2",
|
||||
["Authority:Issuer"] = "https://authority.internal",
|
||||
["Authority:AccessTokenLifetime"] = "00:30:00",
|
||||
["Authority:RefreshTokenLifetime"] = "30.00:00:00",
|
||||
["Authority:Storage:ConnectionString"] = "Host=example;Database=stellaops",
|
||||
["Authority:Storage:DatabaseName"] = "overrideDb",
|
||||
["Authority:Storage:CommandTimeout"] = "00:01:30",
|
||||
["Authority:PluginDirectories:0"] = "/var/lib/stellaops/plugins",
|
||||
["Authority:BypassNetworks:0"] = "127.0.0.1/32",
|
||||
["Authority:Security:RateLimiting:Token:PermitLimit"] = "25",
|
||||
["Authority:Security:RateLimiting:Token:Window"] = "00:00:30",
|
||||
["Authority:Security:RateLimiting:Authorize:Enabled"] = "true",
|
||||
["Authority:Security:RateLimiting:Internal:Enabled"] = "true",
|
||||
["Authority:Security:RateLimiting:Internal:PermitLimit"] = "3",
|
||||
["Authority:Signing:Enabled"] = "true",
|
||||
["Authority:Signing:ActiveKeyId"] = "authority-signing-dev",
|
||||
["Authority:Signing:KeyPath"] = "../certificates/authority-signing-dev.pem",
|
||||
["Authority:Signing:KeySource"] = "file",
|
||||
["Authority:Notifications:AckTokens:Enabled"] = "false",
|
||||
["Authority:Notifications:Webhooks:Enabled"] = "false"
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
var options = context.Options;
|
||||
|
||||
Assert.Equal(2, options.SchemaVersion);
|
||||
Assert.Equal(new Uri("https://authority.internal"), options.Issuer);
|
||||
Assert.Equal(TimeSpan.FromMinutes(30), options.AccessTokenLifetime);
|
||||
Assert.Equal(TimeSpan.FromDays(30), options.RefreshTokenLifetime);
|
||||
Assert.Equal(new[] { "/var/lib/stellaops/plugins" }, options.PluginDirectories);
|
||||
Assert.Equal(new[] { "127.0.0.1/32" }, options.BypassNetworks);
|
||||
Assert.Equal("Host=example;Database=stellaops", options.Storage.ConnectionString);
|
||||
Assert.Equal("overrideDb", options.Storage.DatabaseName);
|
||||
Assert.Equal(TimeSpan.FromMinutes(1.5), options.Storage.CommandTimeout);
|
||||
Assert.Equal(25, options.Security.RateLimiting.Token.PermitLimit);
|
||||
Assert.Equal(TimeSpan.FromSeconds(30), options.Security.RateLimiting.Token.Window);
|
||||
Assert.True(options.Security.RateLimiting.Authorize.Enabled);
|
||||
Assert.True(options.Security.RateLimiting.Internal.Enabled);
|
||||
Assert.Equal(3, options.Security.RateLimiting.Internal.PermitLimit);
|
||||
Assert.True(options.Signing.Enabled);
|
||||
Assert.Equal("authority-signing-dev", options.Signing.ActiveKeyId);
|
||||
Assert.Equal("../certificates/authority-signing-dev.pem", options.Signing.KeyPath);
|
||||
Assert.Equal("file", options.Signing.KeySource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class StellaOpsAuthorityOptionsTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Normalises_ExceptionRoutingTemplates()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
|
||||
options.Exceptions.RoutingTemplates.Add(new AuthorityExceptionRoutingTemplateOptions
|
||||
{
|
||||
Id = " SecOps ",
|
||||
AuthorityRouteId = " approvals/secops ",
|
||||
RequireMfa = true,
|
||||
Description = " Security approvals "
|
||||
});
|
||||
|
||||
options.Validate();
|
||||
|
||||
Assert.True(options.Exceptions.RequiresMfaForApprovals);
|
||||
var template = Assert.Single(options.Exceptions.NormalizedRoutingTemplates);
|
||||
Assert.Equal("SecOps", template.Key);
|
||||
Assert.Equal("SecOps", template.Value.Id);
|
||||
Assert.Equal("approvals/secops", template.Value.AuthorityRouteId);
|
||||
Assert.Equal("Security approvals", template.Value.Description);
|
||||
Assert.True(template.Value.RequireMfa);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_ExceptionRoutingTemplatesDuplicate()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
|
||||
options.Exceptions.RoutingTemplates.Add(new AuthorityExceptionRoutingTemplateOptions
|
||||
{
|
||||
Id = "secops",
|
||||
AuthorityRouteId = "route/a"
|
||||
});
|
||||
options.Exceptions.RoutingTemplates.Add(new AuthorityExceptionRoutingTemplateOptions
|
||||
{
|
||||
Id = "SecOps",
|
||||
AuthorityRouteId = "route/b"
|
||||
});
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("secops", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using StellaOps.Configuration;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class StellaOpsAuthorityOptionsTests
|
||||
{
|
||||
private static StellaOpsAuthorityOptions CreateValidOptions()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
{
|
||||
Issuer = new Uri("https://authority.stella-ops.test"),
|
||||
SchemaVersion = 1
|
||||
};
|
||||
|
||||
options.Storage.ConnectionString = "Host=localhost;Port=5432;Database=authority";
|
||||
options.Signing.ActiveKeyId = "test-key";
|
||||
options.Signing.KeyPath = "/tmp/test-key.pem";
|
||||
options.Notifications.AckTokens.Enabled = false;
|
||||
options.Notifications.Webhooks.Enabled = false;
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class StellaOpsAuthorityOptionsTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Normalises_PluginDescriptors()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
|
||||
var descriptor = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Standard",
|
||||
ConfigFile = " standard.yaml ",
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
descriptor.Capabilities.Add("password");
|
||||
descriptor.Capabilities.Add("PASSWORD");
|
||||
options.Plugins.Descriptors["standard"] = descriptor;
|
||||
|
||||
options.Validate();
|
||||
|
||||
var normalized = options.Plugins.Descriptors["standard"];
|
||||
Assert.Equal("standard.yaml", normalized.ConfigFile);
|
||||
Assert.Single(normalized.Capabilities);
|
||||
Assert.Equal("password", normalized.Capabilities[0]);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_PluginDescriptorMissingAssemblyInfo()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
options.Plugins.Descriptors["standard"] = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("assembly", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class StellaOpsAuthorityOptionsTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Allows_TenantRemoteInferenceConsent_WhenConfigured()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
options.AdvisoryAi.RemoteInference.Enabled = true;
|
||||
options.AdvisoryAi.RemoteInference.RequireTenantConsent = true;
|
||||
options.AdvisoryAi.RemoteInference.AllowedProfiles.Add("cloud-openai");
|
||||
|
||||
var tenant = new AuthorityTenantOptions
|
||||
{
|
||||
Id = "tenant-default",
|
||||
DisplayName = "Tenant Default"
|
||||
};
|
||||
|
||||
tenant.AdvisoryAi.RemoteInference.ConsentGranted = true;
|
||||
tenant.AdvisoryAi.RemoteInference.ConsentVersion = "2025-10";
|
||||
tenant.AdvisoryAi.RemoteInference.ConsentedAt = DateTimeOffset.Parse("2025-10-31T12:34:56Z", CultureInfo.InvariantCulture);
|
||||
tenant.AdvisoryAi.RemoteInference.ConsentedBy = "legal@example.com";
|
||||
|
||||
options.Tenants.Add(tenant);
|
||||
|
||||
options.Validate();
|
||||
|
||||
Assert.Equal("2025-10", tenant.AdvisoryAi.RemoteInference.ConsentVersion);
|
||||
Assert.Equal(DateTimeOffset.Parse("2025-10-31T12:34:56Z", CultureInfo.InvariantCulture), tenant.AdvisoryAi.RemoteInference.ConsentedAt);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_TenantRemoteInferenceConsentMissingVersion()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
options.AdvisoryAi.RemoteInference.Enabled = true;
|
||||
options.AdvisoryAi.RemoteInference.RequireTenantConsent = true;
|
||||
options.AdvisoryAi.RemoteInference.AllowedProfiles.Add("cloud-openai");
|
||||
|
||||
var tenant = new AuthorityTenantOptions
|
||||
{
|
||||
Id = "tenant-default",
|
||||
DisplayName = "Tenant Default"
|
||||
};
|
||||
|
||||
tenant.AdvisoryAi.RemoteInference.ConsentGranted = true;
|
||||
tenant.AdvisoryAi.RemoteInference.ConsentedAt = DateTimeOffset.Parse("2025-10-31T12:34:56Z", CultureInfo.InvariantCulture);
|
||||
|
||||
options.Tenants.Add(tenant);
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
|
||||
Assert.Contains("consentVersion", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class StellaOpsAuthorityOptionsTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_IssuerMissing()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
options.Issuer = null;
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("issuer", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_IssuerIsNotAbsolute()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
options.Issuer = new Uri("/authority", UriKind.Relative);
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("absolute", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_IssuerIsHttpNonLoopback()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
options.Issuer = new Uri("http://authority.stella-ops.test");
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("https", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_SchemaVersionNonPositive()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
options.SchemaVersion = 0;
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("schemaVersion", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_StorageConnectionStringMissing()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
options.Storage.ConnectionString = string.Empty;
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("connection string", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class StellaOpsAuthorityOptionsTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_AckTokensJwksLifetimeTooLong()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
options.Notifications.AckTokens.Enabled = true;
|
||||
options.Notifications.AckTokens.ActiveKeyId = "test-kid";
|
||||
options.Notifications.AckTokens.KeyPath = "/tmp/test-key.pem";
|
||||
options.Notifications.AckTokens.JwksCacheLifetime = TimeSpan.FromHours(2);
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("jwks", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class StellaOpsAuthorityOptionsTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_RateLimitingInvalid()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
options.Security.RateLimiting.Token.PermitLimit = 0;
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("permitLimit", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class StellaOpsAuthorityOptionsTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_RemoteInferenceEnabledWithoutProfiles()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
options.AdvisoryAi.RemoteInference.Enabled = true;
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("remote inference", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public partial class StellaOpsAuthorityOptionsTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_VulnerabilityExplorerContextLimitNegative()
|
||||
{
|
||||
var options = CreateValidOptions();
|
||||
options.VulnerabilityExplorer.Workflow.AntiForgery.MaxContextEntries = -1;
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("maxContextEntries", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -1,343 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using StellaOps.Configuration;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Configuration.Tests;
|
||||
|
||||
public class StellaOpsAuthorityOptionsTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_IssuerMissing()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions();
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
|
||||
Assert.Contains("issuer", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Normalises_Collections()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
{
|
||||
Issuer = new Uri("https://authority.stella-ops.test"),
|
||||
SchemaVersion = 1
|
||||
};
|
||||
options.Storage.ConnectionString = "Host=localhost;Port=5432;Database=authority";
|
||||
options.Signing.ActiveKeyId = "test-key";
|
||||
options.Signing.KeyPath = "/tmp/test-key.pem";
|
||||
options.Notifications.AckTokens.Enabled = false;
|
||||
options.Notifications.Webhooks.Enabled = false;
|
||||
|
||||
options.PluginDirectories.Add(" ./plugins ");
|
||||
options.PluginDirectories.Add("./plugins");
|
||||
options.PluginDirectories.Add("./other");
|
||||
|
||||
options.BypassNetworks.Add(" 10.0.0.0/24 ");
|
||||
options.BypassNetworks.Add("10.0.0.0/24");
|
||||
options.BypassNetworks.Add("192.168.0.0/16");
|
||||
|
||||
options.AdvisoryAi.RemoteInference.AllowedProfiles.Add(" cloud-openai ");
|
||||
options.AdvisoryAi.RemoteInference.AllowedProfiles.Add("CLOUD-OPENAI");
|
||||
options.AdvisoryAi.RemoteInference.AllowedProfiles.Add("sovereign-local");
|
||||
|
||||
options.Validate();
|
||||
|
||||
Assert.Equal(new[] { "./plugins", "./other" }, options.PluginDirectories);
|
||||
Assert.Equal(new[] { "10.0.0.0/24", "192.168.0.0/16" }, options.BypassNetworks);
|
||||
Assert.Equal(new[] { "cloud-openai", "sovereign-local" }, options.AdvisoryAi.RemoteInference.AllowedProfiles);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_RemoteInferenceEnabledWithoutProfiles()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
{
|
||||
Issuer = new Uri("https://authority.stella-ops.test"),
|
||||
SchemaVersion = 1
|
||||
};
|
||||
options.Storage.ConnectionString = "Host=localhost;Port=5432;Database=authority";
|
||||
options.Signing.ActiveKeyId = "test-key";
|
||||
options.Signing.KeyPath = "/tmp/test-key.pem";
|
||||
options.Notifications.AckTokens.Enabled = false;
|
||||
options.Notifications.Webhooks.Enabled = false;
|
||||
options.AdvisoryAi.RemoteInference.Enabled = true;
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
|
||||
Assert.Contains("remote inference", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Normalises_PluginDescriptors()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
{
|
||||
Issuer = new Uri("https://authority.stella-ops.test"),
|
||||
SchemaVersion = 1
|
||||
};
|
||||
options.Storage.ConnectionString = "Host=localhost;Port=5432;Database=authority";
|
||||
options.Signing.ActiveKeyId = "test-key";
|
||||
options.Signing.KeyPath = "/tmp/test-key.pem";
|
||||
options.Notifications.AckTokens.Enabled = false;
|
||||
options.Notifications.Webhooks.Enabled = false;
|
||||
|
||||
var descriptor = new AuthorityPluginDescriptorOptions
|
||||
{
|
||||
AssemblyName = "StellaOps.Authority.Plugin.Standard",
|
||||
ConfigFile = " standard.yaml ",
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
descriptor.Capabilities.Add("password");
|
||||
descriptor.Capabilities.Add("PASSWORD");
|
||||
options.Plugins.Descriptors["standard"] = descriptor;
|
||||
|
||||
options.Validate();
|
||||
|
||||
var normalized = options.Plugins.Descriptors["standard"];
|
||||
Assert.Equal("standard.yaml", normalized.ConfigFile);
|
||||
Assert.Single(normalized.Capabilities);
|
||||
Assert.Equal("password", normalized.Capabilities[0]);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Allows_TenantRemoteInferenceConsent_WhenConfigured()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
{
|
||||
Issuer = new Uri("https://authority.stella-ops.test"),
|
||||
SchemaVersion = 1
|
||||
};
|
||||
options.Storage.ConnectionString = "Host=localhost;Port=5432;Database=authority";
|
||||
options.Signing.ActiveKeyId = "test-key";
|
||||
options.Signing.KeyPath = "/tmp/test-key.pem";
|
||||
options.Notifications.AckTokens.Enabled = false;
|
||||
options.Notifications.Webhooks.Enabled = false;
|
||||
options.AdvisoryAi.RemoteInference.Enabled = true;
|
||||
options.AdvisoryAi.RemoteInference.RequireTenantConsent = true;
|
||||
options.AdvisoryAi.RemoteInference.AllowedProfiles.Add("cloud-openai");
|
||||
|
||||
var tenant = new AuthorityTenantOptions
|
||||
{
|
||||
Id = "tenant-default",
|
||||
DisplayName = "Tenant Default"
|
||||
};
|
||||
|
||||
tenant.AdvisoryAi.RemoteInference.ConsentGranted = true;
|
||||
tenant.AdvisoryAi.RemoteInference.ConsentVersion = "2025-10";
|
||||
tenant.AdvisoryAi.RemoteInference.ConsentedAt = DateTimeOffset.Parse("2025-10-31T12:34:56Z", CultureInfo.InvariantCulture);
|
||||
tenant.AdvisoryAi.RemoteInference.ConsentedBy = "legal@example.com";
|
||||
|
||||
options.Tenants.Add(tenant);
|
||||
|
||||
options.Validate();
|
||||
|
||||
Assert.Equal("2025-10", tenant.AdvisoryAi.RemoteInference.ConsentVersion);
|
||||
Assert.Equal(DateTimeOffset.Parse("2025-10-31T12:34:56Z", CultureInfo.InvariantCulture), tenant.AdvisoryAi.RemoteInference.ConsentedAt);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_TenantRemoteInferenceConsentMissingVersion()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
{
|
||||
Issuer = new Uri("https://authority.stella-ops.test"),
|
||||
SchemaVersion = 1
|
||||
};
|
||||
options.Storage.ConnectionString = "Host=localhost;Port=5432;Database=authority";
|
||||
options.Signing.ActiveKeyId = "test-key";
|
||||
options.Signing.KeyPath = "/tmp/test-key.pem";
|
||||
options.Notifications.AckTokens.Enabled = false;
|
||||
options.Notifications.Webhooks.Enabled = false;
|
||||
options.AdvisoryAi.RemoteInference.Enabled = true;
|
||||
options.AdvisoryAi.RemoteInference.RequireTenantConsent = true;
|
||||
options.AdvisoryAi.RemoteInference.AllowedProfiles.Add("cloud-openai");
|
||||
|
||||
var tenant = new AuthorityTenantOptions
|
||||
{
|
||||
Id = "tenant-default",
|
||||
DisplayName = "Tenant Default"
|
||||
};
|
||||
|
||||
tenant.AdvisoryAi.RemoteInference.ConsentGranted = true;
|
||||
tenant.AdvisoryAi.RemoteInference.ConsentedAt = DateTimeOffset.Parse("2025-10-31T12:34:56Z", CultureInfo.InvariantCulture);
|
||||
|
||||
options.Tenants.Add(tenant);
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
|
||||
Assert.Contains("consentVersion", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_StorageConnectionStringMissing()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
{
|
||||
Issuer = new Uri("https://authority.stella-ops.test"),
|
||||
SchemaVersion = 1
|
||||
};
|
||||
options.Signing.ActiveKeyId = "test-key";
|
||||
options.Signing.KeyPath = "/tmp/test-key.pem";
|
||||
options.Notifications.AckTokens.Enabled = false;
|
||||
options.Notifications.Webhooks.Enabled = false;
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
|
||||
Assert.Contains("connection string", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_Binds_From_Configuration()
|
||||
{
|
||||
var context = StellaOpsAuthorityConfiguration.Build(options =>
|
||||
{
|
||||
options.ConfigureBuilder = builder =>
|
||||
{
|
||||
builder.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["Authority:SchemaVersion"] = "2",
|
||||
["Authority:Issuer"] = "https://authority.internal",
|
||||
["Authority:AccessTokenLifetime"] = "00:30:00",
|
||||
["Authority:RefreshTokenLifetime"] = "30.00:00:00",
|
||||
["Authority:Storage:ConnectionString"] = "Host=example;Database=stellaops",
|
||||
["Authority:Storage:DatabaseName"] = "overrideDb",
|
||||
["Authority:Storage:CommandTimeout"] = "00:01:30",
|
||||
["Authority:PluginDirectories:0"] = "/var/lib/stellaops/plugins",
|
||||
["Authority:BypassNetworks:0"] = "127.0.0.1/32",
|
||||
["Authority:Security:RateLimiting:Token:PermitLimit"] = "25",
|
||||
["Authority:Security:RateLimiting:Token:Window"] = "00:00:30",
|
||||
["Authority:Security:RateLimiting:Authorize:Enabled"] = "true",
|
||||
["Authority:Security:RateLimiting:Internal:Enabled"] = "true",
|
||||
["Authority:Security:RateLimiting:Internal:PermitLimit"] = "3",
|
||||
["Authority:Signing:Enabled"] = "true",
|
||||
["Authority:Signing:ActiveKeyId"] = "authority-signing-dev",
|
||||
["Authority:Signing:KeyPath"] = "../certificates/authority-signing-dev.pem",
|
||||
["Authority:Signing:KeySource"] = "file",
|
||||
["Authority:Notifications:AckTokens:Enabled"] = "false",
|
||||
["Authority:Notifications:Webhooks:Enabled"] = "false"
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
var options = context.Options;
|
||||
|
||||
Assert.Equal(2, options.SchemaVersion);
|
||||
Assert.Equal(new Uri("https://authority.internal"), options.Issuer);
|
||||
Assert.Equal(TimeSpan.FromMinutes(30), options.AccessTokenLifetime);
|
||||
Assert.Equal(TimeSpan.FromDays(30), options.RefreshTokenLifetime);
|
||||
Assert.Equal(new[] { "/var/lib/stellaops/plugins" }, options.PluginDirectories);
|
||||
Assert.Equal(new[] { "127.0.0.1/32" }, options.BypassNetworks);
|
||||
Assert.Equal("Host=example;Database=stellaops", options.Storage.ConnectionString);
|
||||
Assert.Equal("overrideDb", options.Storage.DatabaseName);
|
||||
Assert.Equal(TimeSpan.FromMinutes(1.5), options.Storage.CommandTimeout);
|
||||
Assert.Equal(25, options.Security.RateLimiting.Token.PermitLimit);
|
||||
Assert.Equal(TimeSpan.FromSeconds(30), options.Security.RateLimiting.Token.Window);
|
||||
Assert.True(options.Security.RateLimiting.Authorize.Enabled);
|
||||
Assert.True(options.Security.RateLimiting.Internal.Enabled);
|
||||
Assert.Equal(3, options.Security.RateLimiting.Internal.PermitLimit);
|
||||
Assert.True(options.Signing.Enabled);
|
||||
Assert.Equal("authority-signing-dev", options.Signing.ActiveKeyId);
|
||||
Assert.Equal("../certificates/authority-signing-dev.pem", options.Signing.KeyPath);
|
||||
Assert.Equal("file", options.Signing.KeySource);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Normalises_ExceptionRoutingTemplates()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
{
|
||||
Issuer = new Uri("https://authority.stella-ops.test"),
|
||||
SchemaVersion = 1
|
||||
};
|
||||
options.Storage.ConnectionString = "Host=localhost;Port=5432;Database=authority";
|
||||
options.Signing.ActiveKeyId = "test-key";
|
||||
options.Signing.KeyPath = "/tmp/test-key.pem";
|
||||
options.Notifications.AckTokens.Enabled = false;
|
||||
options.Notifications.Webhooks.Enabled = false;
|
||||
|
||||
options.Exceptions.RoutingTemplates.Add(new AuthorityExceptionRoutingTemplateOptions
|
||||
{
|
||||
Id = " SecOps ",
|
||||
AuthorityRouteId = " approvals/secops ",
|
||||
RequireMfa = true,
|
||||
Description = " Security approvals "
|
||||
});
|
||||
|
||||
options.Validate();
|
||||
|
||||
Assert.True(options.Exceptions.RequiresMfaForApprovals);
|
||||
var template = Assert.Single(options.Exceptions.NormalizedRoutingTemplates);
|
||||
Assert.Equal("SecOps", template.Key);
|
||||
Assert.Equal("SecOps", template.Value.Id);
|
||||
Assert.Equal("approvals/secops", template.Value.AuthorityRouteId);
|
||||
Assert.Equal("Security approvals", template.Value.Description);
|
||||
Assert.True(template.Value.RequireMfa);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_ExceptionRoutingTemplatesDuplicate()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
{
|
||||
Issuer = new Uri("https://authority.stella-ops.test"),
|
||||
SchemaVersion = 1
|
||||
};
|
||||
options.Storage.ConnectionString = "Host=localhost;Port=5432;Database=authority";
|
||||
options.Signing.ActiveKeyId = "test-key";
|
||||
options.Signing.KeyPath = "/tmp/test-key.pem";
|
||||
options.Notifications.AckTokens.Enabled = false;
|
||||
options.Notifications.Webhooks.Enabled = false;
|
||||
|
||||
options.Exceptions.RoutingTemplates.Add(new AuthorityExceptionRoutingTemplateOptions
|
||||
{
|
||||
Id = "secops",
|
||||
AuthorityRouteId = "route/a"
|
||||
});
|
||||
options.Exceptions.RoutingTemplates.Add(new AuthorityExceptionRoutingTemplateOptions
|
||||
{
|
||||
Id = "SecOps",
|
||||
AuthorityRouteId = "route/b"
|
||||
});
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
Assert.Contains("secops", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Throws_When_RateLimitingInvalid()
|
||||
{
|
||||
var options = new StellaOpsAuthorityOptions
|
||||
{
|
||||
Issuer = new Uri("https://authority.stella-ops.test"),
|
||||
SchemaVersion = 1
|
||||
};
|
||||
options.Storage.ConnectionString = "Host=localhost;Port=5432;Database=authority";
|
||||
options.Security.RateLimiting.Token.PermitLimit = 0;
|
||||
options.Signing.ActiveKeyId = "test-key";
|
||||
options.Signing.KeyPath = "/tmp/test-key.pem";
|
||||
options.Notifications.AckTokens.Enabled = false;
|
||||
options.Notifications.Webhooks.Enabled = false;
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => options.Validate());
|
||||
|
||||
Assert.Contains("permitLimit", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -12,3 +12,7 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
||||
| AUDIT-0245-T | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0245-A | DONE | Waived (test project; revalidated 2026-01-07). |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
| REMED-03 | DONE | Tier 0 remediation (usings normalized); dotnet test passed 2026-02-02 (23 tests). |
|
||||
| REMED-04 | DONE | Private field naming updated; dotnet test passed 2026-02-02 (23 tests). |
|
||||
| REMED-05 | DONE | Files split <= 100 lines; helper extraction; validation coverage added; dotnet test passed 2026-02-02 (23 tests). |
|
||||
| REMED-20260203-01 | DONE | Added validation coverage for ack token cache lifetime and vulnerability explorer limits; dotnet test passed 2026-02-03 (25 tests). |
|
||||
|
||||
Reference in New Issue
Block a user