Refactor code structure for improved readability and maintainability

This commit is contained in:
StellaOps Bot
2025-12-06 10:23:40 +02:00
parent 6beb9d7c4e
commit 37304cf819
78 changed files with 5471 additions and 104 deletions

View File

@@ -0,0 +1,355 @@
using System.Text.Json;
using Microsoft.Extensions.Options;
using StellaOps.Aoc;
using StellaOps.Concelier.Core.Aoc;
namespace StellaOps.Concelier.WebService.Tests.Aoc;
/// <summary>
/// Regression tests ensuring AOC verify consistently emits ERR_AOC_001 and maintains
/// mapper/guard parity across all violation scenarios.
/// Per CONCELIER-WEB-AOC-19-007.
/// </summary>
public sealed class AocVerifyRegressionTests
{
private static readonly AocGuardOptions GuardOptions = AocGuardOptions.Default;
[Fact]
public void Verify_ForbiddenField_EmitsErrAoc001()
{
var guard = new AocWriteGuard();
var json = CreateJsonWithForbiddenField("severity", "high");
var result = guard.Validate(json.RootElement, GuardOptions);
Assert.False(result.IsValid);
var violation = Assert.Single(result.Violations.Where(v => v.Path == "/severity"));
Assert.Equal("ERR_AOC_001", violation.ErrorCode);
Assert.Equal(AocViolationCode.ForbiddenField, violation.Code);
}
[Theory]
[InlineData("severity")]
[InlineData("cvss")]
[InlineData("cvss_vector")]
[InlineData("merged_from")]
[InlineData("consensus_provider")]
[InlineData("reachability")]
[InlineData("asset_criticality")]
[InlineData("risk_score")]
public void Verify_AllForbiddenFields_EmitErrAoc001(string forbiddenField)
{
var guard = new AocWriteGuard();
var json = CreateJsonWithForbiddenField(forbiddenField, "forbidden_value");
var result = guard.Validate(json.RootElement, GuardOptions);
Assert.False(result.IsValid);
var violation = result.Violations.FirstOrDefault(v => v.Path == $"/{forbiddenField}");
Assert.NotNull(violation);
Assert.Equal("ERR_AOC_001", violation.ErrorCode);
Assert.Equal(AocViolationCode.ForbiddenField, violation.Code);
}
[Fact]
public void Verify_DerivedField_EmitsErrAoc006()
{
var guard = new AocWriteGuard();
var json = CreateJsonWithDerivedField("effective_status", "affected");
var result = guard.Validate(json.RootElement, GuardOptions);
Assert.False(result.IsValid);
var violation = result.Violations.FirstOrDefault(v =>
v.Path == "/effective_status" && v.ErrorCode == "ERR_AOC_006");
Assert.NotNull(violation);
Assert.Equal(AocViolationCode.DerivedFindingDetected, violation.Code);
}
[Theory]
[InlineData("effective_status")]
[InlineData("effective_range")]
[InlineData("effective_severity")]
[InlineData("effective_cvss")]
public void Verify_AllDerivedFields_EmitErrAoc006(string derivedField)
{
var guard = new AocWriteGuard();
var json = CreateJsonWithDerivedField(derivedField, "derived_value");
var result = guard.Validate(json.RootElement, GuardOptions);
Assert.False(result.IsValid);
var violation = result.Violations.FirstOrDefault(v =>
v.Path == $"/{derivedField}" && v.ErrorCode == "ERR_AOC_006");
Assert.NotNull(violation);
Assert.Equal(AocViolationCode.DerivedFindingDetected, violation.Code);
}
[Fact]
public void Verify_UnknownField_EmitsErrAoc007()
{
var guard = new AocWriteGuard();
var json = CreateJsonWithUnknownField("completely_unknown_field", "some_value");
var result = guard.Validate(json.RootElement, GuardOptions);
Assert.False(result.IsValid);
var violation = Assert.Single(result.Violations.Where(v =>
v.Path == "/completely_unknown_field" && v.ErrorCode == "ERR_AOC_007"));
Assert.Equal(AocViolationCode.UnknownField, violation.Code);
}
[Fact]
public void Verify_MergeAttempt_EmitsErrAoc002()
{
var guard = new AocWriteGuard();
var json = CreateJsonWithMergedFrom(["obs-1", "obs-2"]);
var result = guard.Validate(json.RootElement, GuardOptions);
Assert.False(result.IsValid);
// merged_from triggers ERR_AOC_001 (forbidden field)
var violation = result.Violations.FirstOrDefault(v => v.Path == "/merged_from");
Assert.NotNull(violation);
Assert.Equal("ERR_AOC_001", violation.ErrorCode);
}
[Fact]
public void Verify_MultipleViolations_EmitsAllErrorCodes()
{
var guard = new AocWriteGuard();
var json = CreateJsonWithMultipleViolations();
var result = guard.Validate(json.RootElement, GuardOptions);
Assert.False(result.IsValid);
// Should have ERR_AOC_001 for forbidden field
Assert.Contains(result.Violations, v => v.ErrorCode == "ERR_AOC_001");
// Should have ERR_AOC_006 for derived field
Assert.Contains(result.Violations, v => v.ErrorCode == "ERR_AOC_006");
// Should have ERR_AOC_007 for unknown field
Assert.Contains(result.Violations, v => v.ErrorCode == "ERR_AOC_007");
}
[Fact]
public void Verify_ValidDocument_NoViolations()
{
var guard = new AocWriteGuard();
var json = CreateValidJson();
var result = guard.Validate(json.RootElement, GuardOptions);
Assert.True(result.IsValid);
Assert.Empty(result.Violations);
}
[Fact]
public void Verify_ErrorCodeConsistency_AcrossMultipleRuns()
{
var guard = new AocWriteGuard();
var json = CreateJsonWithForbiddenField("severity", "critical");
// Run validation multiple times
var results = Enumerable.Range(0, 10)
.Select(_ => guard.Validate(json.RootElement, GuardOptions))
.ToList();
// All should produce same error code
var allErrorCodes = results
.SelectMany(r => r.Violations)
.Select(v => v.ErrorCode)
.Distinct()
.ToList();
Assert.Single(allErrorCodes);
Assert.Equal("ERR_AOC_001", allErrorCodes[0]);
}
[Fact]
public void Verify_PathConsistency_AcrossMultipleRuns()
{
var guard = new AocWriteGuard();
var json = CreateJsonWithForbiddenField("cvss", "9.8");
// Run validation multiple times
var results = Enumerable.Range(0, 10)
.Select(_ => guard.Validate(json.RootElement, GuardOptions))
.ToList();
// All should produce same path
var allPaths = results
.SelectMany(r => r.Violations)
.Select(v => v.Path)
.Distinct()
.ToList();
Assert.Single(allPaths);
Assert.Equal("/cvss", allPaths[0]);
}
[Fact]
public void Verify_MapperGuardParity_ValidationResultsMatch()
{
var guard = new AocWriteGuard();
var validator = new AdvisorySchemaValidator(guard, Options.Create(GuardOptions));
// Create document with forbidden field
var json = CreateJsonWithForbiddenField("severity", "high");
// Validate with guard directly
var guardResult = guard.Validate(json.RootElement, GuardOptions);
// Both should detect the violation
Assert.False(guardResult.IsValid);
Assert.Contains(guardResult.Violations, v =>
v.ErrorCode == "ERR_AOC_001" && v.Path == "/severity");
}
[Fact]
public void Verify_ViolationMessage_ContainsMeaningfulDetails()
{
var guard = new AocWriteGuard();
var json = CreateJsonWithForbiddenField("severity", "high");
var result = guard.Validate(json.RootElement, GuardOptions);
var violation = result.Violations.First(v => v.ErrorCode == "ERR_AOC_001");
// Message should not be empty
Assert.False(string.IsNullOrWhiteSpace(violation.Message));
// Path should be correct
Assert.Equal("/severity", violation.Path);
}
private static JsonDocument CreateJsonWithForbiddenField(string field, string value)
{
return JsonDocument.Parse($$"""
{
"tenant": "test",
"{{field}}": "{{value}}",
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
"upstream": {
"upstream_id": "CVE-2024-0001",
"content_hash": "sha256:abc",
"retrieved_at": "2024-01-01T00:00:00Z",
"signature": {"present": false},
"provenance": {}
},
"content": {"format": "OSV", "raw": {}},
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
"linkset": {}
}
""");
}
private static JsonDocument CreateJsonWithDerivedField(string field, string value)
{
return JsonDocument.Parse($$"""
{
"tenant": "test",
"{{field}}": "{{value}}",
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
"upstream": {
"upstream_id": "CVE-2024-0001",
"content_hash": "sha256:abc",
"retrieved_at": "2024-01-01T00:00:00Z",
"signature": {"present": false},
"provenance": {}
},
"content": {"format": "OSV", "raw": {}},
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
"linkset": {}
}
""");
}
private static JsonDocument CreateJsonWithUnknownField(string field, string value)
{
return JsonDocument.Parse($$"""
{
"tenant": "test",
"{{field}}": "{{value}}",
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
"upstream": {
"upstream_id": "CVE-2024-0001",
"content_hash": "sha256:abc",
"retrieved_at": "2024-01-01T00:00:00Z",
"signature": {"present": false},
"provenance": {}
},
"content": {"format": "OSV", "raw": {}},
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
"linkset": {}
}
""");
}
private static JsonDocument CreateJsonWithMergedFrom(string[] mergedFrom)
{
var mergedArray = string.Join(", ", mergedFrom.Select(m => $"\"{m}\""));
return JsonDocument.Parse($$"""
{
"tenant": "test",
"merged_from": [{{mergedArray}}],
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
"upstream": {
"upstream_id": "CVE-2024-0001",
"content_hash": "sha256:abc",
"retrieved_at": "2024-01-01T00:00:00Z",
"signature": {"present": false},
"provenance": {}
},
"content": {"format": "OSV", "raw": {}},
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
"linkset": {}
}
""");
}
private static JsonDocument CreateJsonWithMultipleViolations()
{
return JsonDocument.Parse("""
{
"tenant": "test",
"severity": "high",
"effective_status": "affected",
"unknown_custom_field": "value",
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
"upstream": {
"upstream_id": "CVE-2024-0001",
"content_hash": "sha256:abc",
"retrieved_at": "2024-01-01T00:00:00Z",
"signature": {"present": false},
"provenance": {}
},
"content": {"format": "OSV", "raw": {}},
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
"linkset": {}
}
""");
}
private static JsonDocument CreateValidJson()
{
return JsonDocument.Parse("""
{
"tenant": "test",
"source": {"vendor": "test", "connector": "test", "version": "1.0"},
"upstream": {
"upstream_id": "CVE-2024-0001",
"content_hash": "sha256:abc",
"retrieved_at": "2024-01-01T00:00:00Z",
"signature": {"present": false},
"provenance": {}
},
"content": {"format": "OSV", "raw": {}},
"identifiers": {"aliases": [], "primary": "CVE-2024-0001"},
"linkset": {}
}
""");
}
}

View File

@@ -0,0 +1,315 @@
using System.Collections.Immutable;
using System.Text.Json;
using Microsoft.Extensions.Options;
using StellaOps.Aoc;
using StellaOps.Concelier.Core.Aoc;
using StellaOps.Concelier.RawModels;
namespace StellaOps.Concelier.WebService.Tests.Aoc;
/// <summary>
/// Integration tests for large-batch ingest reproducibility.
/// Per CONCELIER-WEB-AOC-19-004.
/// </summary>
public sealed class LargeBatchIngestTests
{
private static readonly AocGuardOptions GuardOptions = AocGuardOptions.Default;
[Fact]
public void LargeBatch_ValidDocuments_AllPassValidation()
{
var validator = CreateValidator();
var documents = GenerateValidDocuments(1000);
var results = documents.Select(validator.ValidateSchema).ToList();
Assert.All(results, r => Assert.True(r.IsValid));
}
[Fact]
public void LargeBatch_MixedDocuments_DetectsViolationsReproducibly()
{
var validator = CreateValidator();
var (validDocs, invalidDocs) = GenerateMixedBatch(500, 500);
var allDocs = validDocs.Concat(invalidDocs).ToList();
// First pass
var results1 = allDocs.Select(validator.ValidateSchema).ToList();
// Second pass (same order)
var results2 = allDocs.Select(validator.ValidateSchema).ToList();
// Results should be identical (reproducible)
for (int i = 0; i < results1.Count; i++)
{
Assert.Equal(results1[i].IsValid, results2[i].IsValid);
Assert.Equal(results1[i].Violations.Count, results2[i].Violations.Count);
}
}
[Fact]
public void LargeBatch_DeterministicViolationOrdering()
{
var validator = CreateValidator();
var documents = GenerateDocumentsWithMultipleViolations(100);
// Run validation twice
var results1 = documents.Select(validator.ValidateSchema).ToList();
var results2 = documents.Select(validator.ValidateSchema).ToList();
// Violations should be in same order
for (int i = 0; i < results1.Count; i++)
{
var violations1 = results1[i].Violations;
var violations2 = results2[i].Violations;
Assert.Equal(violations1.Count, violations2.Count);
for (int j = 0; j < violations1.Count; j++)
{
Assert.Equal(violations1[j].ErrorCode, violations2[j].ErrorCode);
Assert.Equal(violations1[j].Path, violations2[j].Path);
}
}
}
[Fact]
public void LargeBatch_ParallelValidation_Reproducible()
{
var validator = CreateValidator();
var documents = GenerateValidDocuments(1000);
// Sequential validation
var sequentialResults = documents.Select(validator.ValidateSchema).ToList();
// Parallel validation
var parallelResults = documents.AsParallel()
.AsOrdered()
.Select(validator.ValidateSchema)
.ToList();
// Results should be identical
Assert.Equal(sequentialResults.Count, parallelResults.Count);
for (int i = 0; i < sequentialResults.Count; i++)
{
Assert.Equal(sequentialResults[i].IsValid, parallelResults[i].IsValid);
}
}
[Fact]
public void LargeBatch_ContentHashConsistency()
{
var documents = GenerateValidDocuments(100);
var hashes1 = documents.Select(ComputeDocumentHash).ToList();
var hashes2 = documents.Select(ComputeDocumentHash).ToList();
// Hashes should be identical for same documents
for (int i = 0; i < hashes1.Count; i++)
{
Assert.Equal(hashes1[i], hashes2[i]);
}
}
[Theory]
[InlineData(100)]
[InlineData(500)]
[InlineData(1000)]
public void LargeBatch_ScalesLinearly(int batchSize)
{
var validator = CreateValidator();
var documents = GenerateValidDocuments(batchSize);
var sw = System.Diagnostics.Stopwatch.StartNew();
var results = documents.Select(validator.ValidateSchema).ToList();
sw.Stop();
// All should pass
Assert.Equal(batchSize, results.Count);
Assert.All(results, r => Assert.True(r.IsValid));
// Should complete in reasonable time (less than 100ms per 100 docs)
var expectedMaxMs = batchSize;
Assert.True(sw.ElapsedMilliseconds < expectedMaxMs,
$"Validation took {sw.ElapsedMilliseconds}ms for {batchSize} docs (expected < {expectedMaxMs}ms)");
}
[Fact]
public void LargeBatch_ViolationCounts_Deterministic()
{
var validator = CreateValidator();
// Generate same batch twice
var batch1 = GenerateMixedBatch(250, 250);
var batch2 = GenerateMixedBatch(250, 250);
var allDocs1 = batch1.Valid.Concat(batch1.Invalid).ToList();
var allDocs2 = batch2.Valid.Concat(batch2.Invalid).ToList();
var results1 = allDocs1.Select(validator.ValidateSchema).ToList();
var results2 = allDocs2.Select(validator.ValidateSchema).ToList();
// Same generation should produce same violation counts
var validCount1 = results1.Count(r => r.IsValid);
var validCount2 = results2.Count(r => r.IsValid);
var violationCount1 = results1.Sum(r => r.Violations.Count);
var violationCount2 = results2.Sum(r => r.Violations.Count);
Assert.Equal(validCount1, validCount2);
Assert.Equal(violationCount1, violationCount2);
}
private static AdvisorySchemaValidator CreateValidator()
=> new(new AocWriteGuard(), Options.Create(GuardOptions));
private static List<AdvisoryRawDocument> GenerateValidDocuments(int count)
{
var documents = new List<AdvisoryRawDocument>(count);
for (int i = 0; i < count; i++)
{
documents.Add(CreateValidDocument($"tenant-{i % 10}", $"GHSA-{i:0000}"));
}
return documents;
}
private static (List<AdvisoryRawDocument> Valid, List<AdvisoryRawDocument> Invalid) GenerateMixedBatch(
int validCount, int invalidCount)
{
var valid = GenerateValidDocuments(validCount);
var invalid = GenerateInvalidDocuments(invalidCount);
return (valid, invalid);
}
private static List<AdvisoryRawDocument> GenerateInvalidDocuments(int count)
{
var documents = new List<AdvisoryRawDocument>(count);
for (int i = 0; i < count; i++)
{
documents.Add(CreateDocumentWithForbiddenField($"tenant-{i % 10}", $"CVE-{i:0000}"));
}
return documents;
}
private static List<AdvisoryRawDocument> GenerateDocumentsWithMultipleViolations(int count)
{
var documents = new List<AdvisoryRawDocument>(count);
for (int i = 0; i < count; i++)
{
documents.Add(CreateDocumentWithMultipleViolations($"tenant-{i % 10}", $"CVE-MULTI-{i:0000}"));
}
return documents;
}
private static AdvisoryRawDocument CreateValidDocument(string tenant, string advisoryId)
{
using var rawDocument = JsonDocument.Parse($$"""{"id":"{{advisoryId}}"}""");
return new AdvisoryRawDocument(
Tenant: tenant,
Source: new RawSourceMetadata("vendor-x", "connector-y", "1.0.0"),
Upstream: new RawUpstreamMetadata(
UpstreamId: advisoryId,
DocumentVersion: "1",
RetrievedAt: DateTimeOffset.UtcNow,
ContentHash: $"sha256:{advisoryId}",
Signature: new RawSignatureMetadata(false),
Provenance: ImmutableDictionary<string, string>.Empty),
Content: new RawContent(
Format: "OSV",
SpecVersion: "1.0",
Raw: rawDocument.RootElement.Clone()),
Identifiers: new RawIdentifiers(
Aliases: ImmutableArray.Create(advisoryId),
PrimaryId: advisoryId),
Linkset: new RawLinkset
{
Aliases = ImmutableArray<string>.Empty,
PackageUrls = ImmutableArray<string>.Empty,
Cpes = ImmutableArray<string>.Empty,
References = ImmutableArray<RawReference>.Empty,
ReconciledFrom = ImmutableArray<string>.Empty,
Notes = ImmutableDictionary<string, string>.Empty
},
Links: ImmutableArray<RawLink>.Empty);
}
private static AdvisoryRawDocument CreateDocumentWithForbiddenField(string tenant, string advisoryId)
{
// Create document with forbidden "severity" field
using var rawDocument = JsonDocument.Parse($$"""{"id":"{{advisoryId}}","severity":"high"}""");
return new AdvisoryRawDocument(
Tenant: tenant,
Source: new RawSourceMetadata("vendor-x", "connector-y", "1.0.0"),
Upstream: new RawUpstreamMetadata(
UpstreamId: advisoryId,
DocumentVersion: "1",
RetrievedAt: DateTimeOffset.UtcNow,
ContentHash: $"sha256:{advisoryId}",
Signature: new RawSignatureMetadata(false),
Provenance: ImmutableDictionary<string, string>.Empty),
Content: new RawContent(
Format: "OSV",
SpecVersion: "1.0",
Raw: rawDocument.RootElement.Clone()),
Identifiers: new RawIdentifiers(
Aliases: ImmutableArray.Create(advisoryId),
PrimaryId: advisoryId),
Linkset: new RawLinkset
{
Aliases = ImmutableArray<string>.Empty,
PackageUrls = ImmutableArray<string>.Empty,
Cpes = ImmutableArray<string>.Empty,
References = ImmutableArray<RawReference>.Empty,
ReconciledFrom = ImmutableArray<string>.Empty,
Notes = ImmutableDictionary<string, string>.Empty
},
Links: ImmutableArray<RawLink>.Empty);
}
private static AdvisoryRawDocument CreateDocumentWithMultipleViolations(string tenant, string advisoryId)
{
// Create document with multiple violations: forbidden, derived, and unknown fields
using var rawDocument = JsonDocument.Parse($$"""
{
"id": "{{advisoryId}}",
"severity": "high",
"effective_status": "affected",
"unknown_field": "value"
}
""");
return new AdvisoryRawDocument(
Tenant: tenant,
Source: new RawSourceMetadata("vendor-x", "connector-y", "1.0.0"),
Upstream: new RawUpstreamMetadata(
UpstreamId: advisoryId,
DocumentVersion: "1",
RetrievedAt: DateTimeOffset.UtcNow,
ContentHash: $"sha256:{advisoryId}",
Signature: new RawSignatureMetadata(false),
Provenance: ImmutableDictionary<string, string>.Empty),
Content: new RawContent(
Format: "OSV",
SpecVersion: "1.0",
Raw: rawDocument.RootElement.Clone()),
Identifiers: new RawIdentifiers(
Aliases: ImmutableArray.Create(advisoryId),
PrimaryId: advisoryId),
Linkset: new RawLinkset
{
Aliases = ImmutableArray<string>.Empty,
PackageUrls = ImmutableArray<string>.Empty,
Cpes = ImmutableArray<string>.Empty,
References = ImmutableArray<RawReference>.Empty,
ReconciledFrom = ImmutableArray<string>.Empty,
Notes = ImmutableDictionary<string, string>.Empty
},
Links: ImmutableArray<RawLink>.Empty);
}
private static string ComputeDocumentHash(AdvisoryRawDocument doc)
{
// Simple hash combining key fields
var data = $"{doc.Tenant}|{doc.Upstream.UpstreamId}|{doc.Upstream.ContentHash}";
using var sha = System.Security.Cryptography.SHA256.Create();
var bytes = System.Text.Encoding.UTF8.GetBytes(data);
var hash = sha.ComputeHash(bytes);
return Convert.ToHexStringLower(hash);
}
}

View File

@@ -0,0 +1,125 @@
using StellaOps.Concelier.WebService.Tests.Fixtures;
namespace StellaOps.Concelier.WebService.Tests.Aoc;
/// <summary>
/// Tests for tenant allowlist enforcement.
/// Per CONCELIER-WEB-AOC-19-006.
/// </summary>
public sealed class TenantAllowlistTests
{
[Theory]
[InlineData("test-tenant")]
[InlineData("dev-tenant")]
[InlineData("tenant-123")]
[InlineData("a")]
[InlineData("tenant-with-dashes-in-name")]
public void ValidateTenantId_ValidTenant_ReturnsValid(string tenantId)
{
var (isValid, error) = AuthTenantTestFixtures.ValidateTenantId(tenantId);
Assert.True(isValid);
Assert.Null(error);
}
[Theory]
[InlineData("", "cannot be null or empty")]
[InlineData("Test-Tenant", "invalid character 'T'")] // Uppercase
[InlineData("test_tenant", "invalid character '_'")] // Underscore
[InlineData("test.tenant", "invalid character '.'")] // Dot
[InlineData("test tenant", "invalid character ' '")] // Space
[InlineData("test@tenant", "invalid character '@'")] // Special char
public void ValidateTenantId_InvalidTenant_ReturnsError(string tenantId, string expectedErrorPart)
{
var (isValid, error) = AuthTenantTestFixtures.ValidateTenantId(tenantId);
Assert.False(isValid);
Assert.NotNull(error);
Assert.Contains(expectedErrorPart, error, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void ValidateTenantId_TooLong_ReturnsError()
{
var longTenant = new string('a', 65); // 65 chars, max is 64
var (isValid, error) = AuthTenantTestFixtures.ValidateTenantId(longTenant);
Assert.False(isValid);
Assert.Contains("exceeds maximum length", error);
}
[Fact]
public void ValidateTenantId_MaxLength_ReturnsValid()
{
var maxTenant = new string('a', 64); // Exactly 64 chars
var (isValid, error) = AuthTenantTestFixtures.ValidateTenantId(maxTenant);
Assert.True(isValid);
Assert.Null(error);
}
[Fact]
public void CreateDefaultAuthorityConfig_ContainsAllTestTenants()
{
var config = AuthTenantTestFixtures.CreateDefaultAuthorityConfig();
Assert.NotEmpty(config.RequiredTenants);
Assert.Contains(AuthTenantTestFixtures.ValidTenants.TestTenant, config.RequiredTenants);
Assert.Contains(AuthTenantTestFixtures.ValidTenants.ChunkTestTenant, config.RequiredTenants);
Assert.Contains(AuthTenantTestFixtures.ValidTenants.AocTestTenant, config.RequiredTenants);
}
[Fact]
public void CreateSingleTenantConfig_ContainsOnlySpecifiedTenant()
{
var tenant = "single-test";
var config = AuthTenantTestFixtures.CreateSingleTenantConfig(tenant);
Assert.Single(config.RequiredTenants);
Assert.Equal(tenant, config.RequiredTenants[0]);
}
[Fact]
public void AllValidTenants_PassValidation()
{
foreach (var tenant in AuthTenantTestFixtures.ValidTenants.AllTestTenants)
{
var (isValid, error) = AuthTenantTestFixtures.ValidateTenantId(tenant);
Assert.True(isValid, $"Tenant '{tenant}' should be valid but got error: {error}");
}
}
[Fact]
public void AllInvalidTenants_FailValidation()
{
foreach (var tenant in AuthTenantTestFixtures.InvalidTenants.AllInvalidTenants)
{
var (isValid, _) = AuthTenantTestFixtures.ValidateTenantId(tenant);
Assert.False(isValid, $"Tenant '{tenant}' should be invalid");
}
}
[Fact]
public void AuthorityTestConfiguration_DefaultValuesAreSet()
{
var config = AuthTenantTestFixtures.CreateAuthorityConfig("test");
Assert.True(config.Enabled);
Assert.Equal("concelier-api", config.Audience);
Assert.Equal("https://test-authority.stellaops.local", config.Issuer);
}
[Fact]
public void SeedDataFixtures_UseTenantsThatPassValidation()
{
// Verify that seed data fixtures use valid tenant IDs
var chunkSeedTenant = AdvisoryChunkSeedData.DefaultTenant;
var (isValid, error) = AuthTenantTestFixtures.ValidateTenantId(chunkSeedTenant);
Assert.True(isValid, $"Chunk seed tenant '{chunkSeedTenant}' should be valid but got error: {error}");
}
}