This commit is contained in:
Codex Assistant
2026-01-08 09:02:11 +02:00
384 changed files with 2245 additions and 1981 deletions

View File

@@ -44,7 +44,7 @@ public sealed class AttestorVerificationEngine : IAttestorVerificationEngine
{
ArgumentNullException.ThrowIfNull(entry);
var signatureIssuer = await EvaluateSignatureAndIssuerAsync(entry, bundle, cancellationToken).ConfigureAwait(false);
var signatureIssuer = await EvaluateSignatureAndIssuerAsync(entry, bundle, evaluationTime, cancellationToken).ConfigureAwait(false);
var freshness = EvaluateFreshness(entry, evaluationTime);
var transparency = EvaluateTransparency(entry);
var policy = EvaluatePolicy(entry, signatureIssuer.Signatures, signatureIssuer.Issuer, freshness, transparency, bundle is not null);
@@ -55,6 +55,7 @@ public sealed class AttestorVerificationEngine : IAttestorVerificationEngine
private async Task<(SignatureEvaluationResult Signatures, IssuerEvaluationResult Issuer)> EvaluateSignatureAndIssuerAsync(
AttestorEntry entry,
AttestorSubmissionRequest.SubmissionBundle? bundle,
DateTimeOffset evaluationTime,
CancellationToken cancellationToken)
{
var signatureIssues = new List<string>();
@@ -178,7 +179,7 @@ public sealed class AttestorVerificationEngine : IAttestorVerificationEngine
break;
case "keyless":
var keylessResult = EvaluateKeylessSignature(entry, bundle, preAuth, signatureIssues, issuerIssues);
var keylessResult = EvaluateKeylessSignature(entry, bundle, preAuth, signatureIssues, issuerIssues, evaluationTime);
verifiedSignatures = keylessResult.VerifiedSignatures;
subjectAlternativeName = keylessResult.SubjectAlternativeName;
break;
@@ -270,7 +271,8 @@ public sealed class AttestorVerificationEngine : IAttestorVerificationEngine
AttestorSubmissionRequest.SubmissionBundle bundle,
byte[] preAuthEncoding,
List<string> signatureIssues,
List<string> issuerIssues)
List<string> issuerIssues,
DateTimeOffset evaluationTime)
{
if (bundle.CertificateChain.Count == 0)
{
@@ -296,46 +298,47 @@ public sealed class AttestorVerificationEngine : IAttestorVerificationEngine
var leafCertificate = certificates[0];
var subjectAltName = GetSubjectAlternativeNames(leafCertificate).FirstOrDefault();
if (_options.Security.SignerIdentity.FulcioRoots.Count > 0)
if (_options.Security.SignerIdentity.FulcioRoots.Count > 0)
{
using var chain = new X509Chain
{
using var chain = new X509Chain
{
ChainPolicy =
ChainPolicy =
{
RevocationMode = X509RevocationMode.NoCheck,
VerificationFlags = X509VerificationFlags.NoFlag,
TrustMode = X509ChainTrustMode.CustomRootTrust
TrustMode = X509ChainTrustMode.CustomRootTrust,
VerificationTime = evaluationTime.UtcDateTime
}
};
foreach (var rootPath in _options.Security.SignerIdentity.FulcioRoots)
foreach (var rootPath in _options.Security.SignerIdentity.FulcioRoots)
{
try
{
try
if (File.Exists(rootPath))
{
if (File.Exists(rootPath))
{
var rootCertificate = X509CertificateLoader.LoadCertificateFromFile(rootPath);
chain.ChainPolicy.CustomTrustStore.Add(rootCertificate);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to load Fulcio root {Root}", rootPath);
var rootCertificate = X509CertificateLoader.LoadCertificateFromFile(rootPath);
chain.ChainPolicy.CustomTrustStore.Add(rootCertificate);
}
}
for (var i = 1; i < certificates.Count; i++)
catch (Exception ex)
{
chain.ChainPolicy.ExtraStore.Add(certificates[i]);
}
if (!chain.Build(leafCertificate))
{
var status = string.Join(";", chain.ChainStatus.Select(s => s.StatusInformation.Trim())).Trim(';');
issuerIssues.Add(string.IsNullOrEmpty(status) ? "certificate_chain_untrusted" : $"certificate_chain_untrusted:{status}");
_logger.LogWarning(ex, "Failed to load Fulcio root {Root}", rootPath);
}
}
for (var i = 1; i < certificates.Count; i++)
{
chain.ChainPolicy.ExtraStore.Add(certificates[i]);
}
if (!chain.Build(leafCertificate))
{
var status = string.Join(";", chain.ChainStatus.Select(s => s.StatusInformation.Trim())).Trim(';');
issuerIssues.Add(string.IsNullOrEmpty(status) ? "certificate_chain_untrusted" : $"certificate_chain_untrusted:{status}");
}
}
if (_options.Security.SignerIdentity.AllowedSans.Count > 0)
{
var sans = GetSubjectAlternativeNames(leafCertificate);

View File

@@ -223,9 +223,12 @@ public class AttestationChainBuilderTests
result.IsSuccess.Should().BeTrue();
result.LinksCreated.Should().HaveCount(3);
// Links should be created in layer order
result.LinksCreated[0].Metadata!.Annotations["layerIndex"].Should().Be("0");
result.LinksCreated[1].Metadata!.Annotations["layerIndex"].Should().Be("1");
result.LinksCreated[2].Metadata!.Annotations["layerIndex"].Should().Be("2");
Assert.NotNull(result.LinksCreated[0].Metadata?.Annotations);
Assert.NotNull(result.LinksCreated[1].Metadata?.Annotations);
Assert.NotNull(result.LinksCreated[2].Metadata?.Annotations);
result.LinksCreated[0].Metadata!.Annotations!["layerIndex"].Should().Be("0");
result.LinksCreated[1].Metadata!.Annotations!["layerIndex"].Should().Be("1");
result.LinksCreated[2].Metadata!.Annotations!["layerIndex"].Should().Be("2");
}
[Fact]

View File

@@ -319,7 +319,21 @@ public sealed class AttestorVerificationEngineTests
}
private static X509Certificate2 EnsurePrivateKey(X509Certificate2 certificate, ECDsa key)
=> certificate.HasPrivateKey ? certificate : certificate.CopyWithPrivateKey(key);
{
if (certificate.HasPrivateKey)
{
return certificate;
}
try
{
return certificate.CopyWithPrivateKey(key);
}
catch (InvalidOperationException)
{
return certificate;
}
}
private static string ToPem(X509Certificate2 certificate)
{

View File

@@ -65,35 +65,35 @@ public class AuthAbstractionsConstantsTests
[nameof(StellaOpsClaimTypes.SessionId)] = "sid"
};
Assert.Equal(expected[nameof(StellaOpsClaimTypes.Subject)], StellaOpsClaimTypes.Subject);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.Tenant)], StellaOpsClaimTypes.Tenant);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.Project)], StellaOpsClaimTypes.Project);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.ClientId)], StellaOpsClaimTypes.ClientId);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.ServiceAccount)], StellaOpsClaimTypes.ServiceAccount);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.TokenId)], StellaOpsClaimTypes.TokenId);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.AuthenticationMethod)], StellaOpsClaimTypes.AuthenticationMethod);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.Scope)], StellaOpsClaimTypes.Scope);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.ScopeItem)], StellaOpsClaimTypes.ScopeItem);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.Audience)], StellaOpsClaimTypes.Audience);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.IdentityProvider)], StellaOpsClaimTypes.IdentityProvider);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.OperatorReason)], StellaOpsClaimTypes.OperatorReason);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.OperatorTicket)], StellaOpsClaimTypes.OperatorTicket);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.QuotaReason)], StellaOpsClaimTypes.QuotaReason);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.QuotaTicket)], StellaOpsClaimTypes.QuotaTicket);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.BackfillReason)], StellaOpsClaimTypes.BackfillReason);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.BackfillTicket)], StellaOpsClaimTypes.BackfillTicket);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PolicyDigest)], StellaOpsClaimTypes.PolicyDigest);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PolicyTicket)], StellaOpsClaimTypes.PolicyTicket);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PolicyReason)], StellaOpsClaimTypes.PolicyReason);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PackRunId)], StellaOpsClaimTypes.PackRunId);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PackGateId)], StellaOpsClaimTypes.PackGateId);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PackPlanHash)], StellaOpsClaimTypes.PackPlanHash);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PolicyOperation)], StellaOpsClaimTypes.PolicyOperation);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.IncidentReason)], StellaOpsClaimTypes.IncidentReason);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.VulnerabilityEnvironment)], StellaOpsClaimTypes.VulnerabilityEnvironment);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.VulnerabilityOwner)], StellaOpsClaimTypes.VulnerabilityOwner);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.VulnerabilityBusinessTier)], StellaOpsClaimTypes.VulnerabilityBusinessTier);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.SessionId)], StellaOpsClaimTypes.SessionId);
Assert.Equal(StellaOpsClaimTypes.Subject, expected[nameof(StellaOpsClaimTypes.Subject)]);
Assert.Equal(StellaOpsClaimTypes.Tenant, expected[nameof(StellaOpsClaimTypes.Tenant)]);
Assert.Equal(StellaOpsClaimTypes.Project, expected[nameof(StellaOpsClaimTypes.Project)]);
Assert.Equal(StellaOpsClaimTypes.ClientId, expected[nameof(StellaOpsClaimTypes.ClientId)]);
Assert.Equal(StellaOpsClaimTypes.ServiceAccount, expected[nameof(StellaOpsClaimTypes.ServiceAccount)]);
Assert.Equal(StellaOpsClaimTypes.TokenId, expected[nameof(StellaOpsClaimTypes.TokenId)]);
Assert.Equal(StellaOpsClaimTypes.AuthenticationMethod, expected[nameof(StellaOpsClaimTypes.AuthenticationMethod)]);
Assert.Equal(StellaOpsClaimTypes.Scope, expected[nameof(StellaOpsClaimTypes.Scope)]);
Assert.Equal(StellaOpsClaimTypes.ScopeItem, expected[nameof(StellaOpsClaimTypes.ScopeItem)]);
Assert.Equal(StellaOpsClaimTypes.Audience, expected[nameof(StellaOpsClaimTypes.Audience)]);
Assert.Equal(StellaOpsClaimTypes.IdentityProvider, expected[nameof(StellaOpsClaimTypes.IdentityProvider)]);
Assert.Equal(StellaOpsClaimTypes.OperatorReason, expected[nameof(StellaOpsClaimTypes.OperatorReason)]);
Assert.Equal(StellaOpsClaimTypes.OperatorTicket, expected[nameof(StellaOpsClaimTypes.OperatorTicket)]);
Assert.Equal(StellaOpsClaimTypes.QuotaReason, expected[nameof(StellaOpsClaimTypes.QuotaReason)]);
Assert.Equal(StellaOpsClaimTypes.QuotaTicket, expected[nameof(StellaOpsClaimTypes.QuotaTicket)]);
Assert.Equal(StellaOpsClaimTypes.BackfillReason, expected[nameof(StellaOpsClaimTypes.BackfillReason)]);
Assert.Equal(StellaOpsClaimTypes.BackfillTicket, expected[nameof(StellaOpsClaimTypes.BackfillTicket)]);
Assert.Equal(StellaOpsClaimTypes.PolicyDigest, expected[nameof(StellaOpsClaimTypes.PolicyDigest)]);
Assert.Equal(StellaOpsClaimTypes.PolicyTicket, expected[nameof(StellaOpsClaimTypes.PolicyTicket)]);
Assert.Equal(StellaOpsClaimTypes.PolicyReason, expected[nameof(StellaOpsClaimTypes.PolicyReason)]);
Assert.Equal(StellaOpsClaimTypes.PackRunId, expected[nameof(StellaOpsClaimTypes.PackRunId)]);
Assert.Equal(StellaOpsClaimTypes.PackGateId, expected[nameof(StellaOpsClaimTypes.PackGateId)]);
Assert.Equal(StellaOpsClaimTypes.PackPlanHash, expected[nameof(StellaOpsClaimTypes.PackPlanHash)]);
Assert.Equal(StellaOpsClaimTypes.PolicyOperation, expected[nameof(StellaOpsClaimTypes.PolicyOperation)]);
Assert.Equal(StellaOpsClaimTypes.IncidentReason, expected[nameof(StellaOpsClaimTypes.IncidentReason)]);
Assert.Equal(StellaOpsClaimTypes.VulnerabilityEnvironment, expected[nameof(StellaOpsClaimTypes.VulnerabilityEnvironment)]);
Assert.Equal(StellaOpsClaimTypes.VulnerabilityOwner, expected[nameof(StellaOpsClaimTypes.VulnerabilityOwner)]);
Assert.Equal(StellaOpsClaimTypes.VulnerabilityBusinessTier, expected[nameof(StellaOpsClaimTypes.VulnerabilityBusinessTier)]);
Assert.Equal(StellaOpsClaimTypes.SessionId, expected[nameof(StellaOpsClaimTypes.SessionId)]);
}
[Trait("Category", TestCategories.Unit)]

View File

@@ -72,7 +72,7 @@ public class ServiceCollectionExtensionsTests
using var provider = services.BuildServiceProvider();
var cache = provider.GetRequiredService<StellaOpsDiscoveryCache>();
var configuration = await cache.GetAsync(CancellationToken.None);
var configuration = await cache.GetAsync(TestContext.Current.CancellationToken);
Assert.Equal(new Uri("https://authority.test/connect/token"), configuration.TokenEndpoint);
Assert.Equal(2, attemptCount);

View File

@@ -47,12 +47,12 @@ public class StellaOpsDiscoveryCacheTests
var monitor = new TestOptionsMonitor<StellaOpsAuthClientOptions>(options);
var cache = new StellaOpsDiscoveryCache(httpClient, monitor, timeProvider, NullLogger<StellaOpsDiscoveryCache>.Instance);
var configuration = await cache.GetAsync(CancellationToken.None);
var configuration = await cache.GetAsync(TestContext.Current.CancellationToken);
Assert.Equal(new Uri("https://authority.test/connect/token"), configuration.TokenEndpoint);
timeProvider.Advance(TimeSpan.FromMinutes(1) + TimeSpan.FromSeconds(5));
configuration = await cache.GetAsync(CancellationToken.None);
configuration = await cache.GetAsync(TestContext.Current.CancellationToken);
Assert.Equal(new Uri("https://authority.test/connect/token"), configuration.TokenEndpoint);
Assert.Equal(2, callCount);
@@ -66,7 +66,7 @@ public class StellaOpsDiscoveryCacheTests
HttpRequestException? exception = null;
try
{
await cache.GetAsync(CancellationToken.None);
await cache.GetAsync(TestContext.Current.CancellationToken);
}
catch (HttpRequestException ex)
{

View File

@@ -52,12 +52,12 @@ public class StellaOpsJwksCacheTests
var discoveryCache = new StellaOpsDiscoveryCache(discoveryClient, monitor, timeProvider, NullLogger<StellaOpsDiscoveryCache>.Instance);
var jwksCache = new StellaOpsJwksCache(jwksClient, discoveryCache, monitor, timeProvider, NullLogger<StellaOpsJwksCache>.Instance);
var keys = await jwksCache.GetAsync(CancellationToken.None);
var keys = await jwksCache.GetAsync(TestContext.Current.CancellationToken);
Assert.NotNull(keys);
timeProvider.Advance(TimeSpan.FromMinutes(1) + TimeSpan.FromSeconds(5));
keys = await jwksCache.GetAsync(CancellationToken.None);
keys = await jwksCache.GetAsync(TestContext.Current.CancellationToken);
Assert.NotNull(keys);
Assert.Equal(2, jwksCallCount);
@@ -68,7 +68,7 @@ public class StellaOpsJwksCacheTests
Assert.True(offlineExpiry < timeProvider.GetUtcNow());
await Assert.ThrowsAsync<HttpRequestException>(() => jwksCache.GetAsync(CancellationToken.None));
await Assert.ThrowsAsync<HttpRequestException>(() => jwksCache.GetAsync(TestContext.Current.CancellationToken));
}
private static HttpResponseMessage CreateJsonResponse(string json)

View File

@@ -38,7 +38,7 @@ public class StellaOpsAuthorityConfigurationManagerTests
var initialMetadataRequests = handler.MetadataRequests;
var initialJwksRequests = handler.JwksRequests;
var second = await manager.GetConfigurationAsync(CancellationToken.None);
var second = await manager.GetConfigurationAsync(TestContext.Current.CancellationToken);
// Cache must return same instance
Assert.Same(first, second);
@@ -70,11 +70,11 @@ public class StellaOpsAuthorityConfigurationManagerTests
timeProvider,
NullLogger<StellaOpsAuthorityConfigurationManager>.Instance);
var first = await manager.GetConfigurationAsync(CancellationToken.None);
var first = await manager.GetConfigurationAsync(TestContext.Current.CancellationToken);
timeProvider.Advance(TimeSpan.FromMinutes(2));
var second = await manager.GetConfigurationAsync(CancellationToken.None);
var second = await manager.GetConfigurationAsync(TestContext.Current.CancellationToken);
Assert.Same(first, second);
Assert.Equal(2, handler.MetadataRequests);
@@ -100,12 +100,12 @@ public class StellaOpsAuthorityConfigurationManagerTests
timeProvider,
NullLogger<StellaOpsAuthorityConfigurationManager>.Instance);
await manager.GetConfigurationAsync(CancellationToken.None);
await manager.GetConfigurationAsync(TestContext.Current.CancellationToken);
var updated = CreateOptions("https://authority2.test");
optionsMonitor.Set(updated);
await manager.GetConfigurationAsync(CancellationToken.None);
await manager.GetConfigurationAsync(TestContext.Current.CancellationToken);
Assert.Equal(2, handler.MetadataRequests);
}

View File

@@ -21,8 +21,8 @@ public sealed class InMemoryLdapClaimsCacheTests
["displayName"] = "Alice Example"
});
await cache.SetAsync("uid=alice,ou=people,dc=example,dc=internal", claims, CancellationToken.None);
var fetched = await cache.GetAsync("uid=alice,ou=people,dc=example,dc=internal", CancellationToken.None);
await cache.SetAsync("uid=alice,ou=people,dc=example,dc=internal", claims, TestContext.Current.CancellationToken);
var fetched = await cache.GetAsync("uid=alice,ou=people,dc=example,dc=internal", TestContext.Current.CancellationToken);
Assert.NotNull(fetched);
Assert.Contains("operators", fetched!.Roles);
@@ -35,10 +35,10 @@ public sealed class InMemoryLdapClaimsCacheTests
var timeProvider = new TestTimeProvider(new DateTimeOffset(2025, 11, 9, 6, 0, 0, TimeSpan.Zero));
var cache = CreateCache(enabled: true, ttlSeconds: 60, timeProvider: timeProvider);
await cache.SetAsync("uid=expired,ou=people,dc=example,dc=internal", new LdapCachedClaims(Array.Empty<string>(), new Dictionary<string, string>()), CancellationToken.None);
await cache.SetAsync("uid=expired,ou=people,dc=example,dc=internal", new LdapCachedClaims(Array.Empty<string>(), new Dictionary<string, string>()), TestContext.Current.CancellationToken);
timeProvider.Advance(TimeSpan.FromMinutes(5));
var fetched = await cache.GetAsync("uid=expired,ou=people,dc=example,dc=internal", CancellationToken.None);
var fetched = await cache.GetAsync("uid=expired,ou=people,dc=example,dc=internal", TestContext.Current.CancellationToken);
Assert.Null(fetched);
}
@@ -48,11 +48,11 @@ public sealed class InMemoryLdapClaimsCacheTests
{
var cache = CreateCache(enabled: true, ttlSeconds: 600, maxEntries: 1);
await cache.SetAsync("uid=first,ou=people,dc=example,dc=internal", new LdapCachedClaims(Array.Empty<string>(), new Dictionary<string, string>()), CancellationToken.None);
await cache.SetAsync("uid=second,ou=people,dc=example,dc=internal", new LdapCachedClaims(Array.Empty<string>(), new Dictionary<string, string>()), CancellationToken.None);
await cache.SetAsync("uid=first,ou=people,dc=example,dc=internal", new LdapCachedClaims(Array.Empty<string>(), new Dictionary<string, string>()), TestContext.Current.CancellationToken);
await cache.SetAsync("uid=second,ou=people,dc=example,dc=internal", new LdapCachedClaims(Array.Empty<string>(), new Dictionary<string, string>()), TestContext.Current.CancellationToken);
var first = await cache.GetAsync("uid=first,ou=people,dc=example,dc=internal", CancellationToken.None);
var second = await cache.GetAsync("uid=second,ou=people,dc=example,dc=internal", CancellationToken.None);
var first = await cache.GetAsync("uid=first,ou=people,dc=example,dc=internal", TestContext.Current.CancellationToken);
var second = await cache.GetAsync("uid=second,ou=people,dc=example,dc=internal", TestContext.Current.CancellationToken);
Assert.Null(first);
Assert.NotNull(second);

View File

@@ -41,7 +41,7 @@ public class LdapClaimsEnricherTests
var identity = new ClaimsIdentity();
var context = CreateContext("uid=j.doe,ou=people,dc=example,dc=internal");
await enricher.EnrichAsync(identity, context, CancellationToken.None);
await enricher.EnrichAsync(identity, context, TestContext.Current.CancellationToken);
Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "operators");
}
@@ -74,7 +74,7 @@ public class LdapClaimsEnricherTests
var identity = new ClaimsIdentity();
var context = CreateContext("uid=ops,ou=people,dc=example,dc=internal");
await enricher.EnrichAsync(identity, context, CancellationToken.None);
await enricher.EnrichAsync(identity, context, TestContext.Current.CancellationToken);
Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "incident");
}
@@ -107,7 +107,7 @@ public class LdapClaimsEnricherTests
var identity = new ClaimsIdentity();
var context = CreateContext("uid=alice,ou=people,dc=example,dc=internal");
await enricher.EnrichAsync(identity, context, CancellationToken.None);
await enricher.EnrichAsync(identity, context, TestContext.Current.CancellationToken);
Assert.Contains(identity.Claims, claim => claim.Type == "displayName" && claim.Value == "Alice Example");
Assert.Contains(identity.Claims, claim => claim.Type == "email" && claim.Value == "alice@example.test");
@@ -134,11 +134,11 @@ public class LdapClaimsEnricherTests
var identity = new ClaimsIdentity();
var context = CreateContext("uid=cached,ou=people,dc=example,dc=internal");
await enricher.EnrichAsync(identity, context, CancellationToken.None);
await enricher.EnrichAsync(identity, context, TestContext.Current.CancellationToken);
Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "operators");
Assert.Contains(identity.Claims, claim => claim.Type == "displayName" && claim.Value == "Cached User");
Assert.Equal(0, connection.Operations.Count);
Assert.Empty(connection.Operations);
Assert.Equal(0, cache.SetCount);
}

View File

@@ -25,7 +25,7 @@ public class LdapCapabilityProbeTests
checkClientProvisioning: true,
checkBootstrap: true,
options.CapabilityProbe.Timeout,
CancellationToken.None);
TestContext.Current.CancellationToken);
Assert.True(snapshot.ClientProvisioningWritable);
Assert.True(snapshot.BootstrapWritable);
@@ -47,7 +47,7 @@ public class LdapCapabilityProbeTests
checkClientProvisioning: true,
checkBootstrap: true,
options.CapabilityProbe.Timeout,
CancellationToken.None);
TestContext.Current.CancellationToken);
Assert.False(snapshot.ClientProvisioningWritable);
Assert.False(snapshot.BootstrapWritable);

View File

@@ -49,7 +49,7 @@ public sealed class LdapClientProvisioningStoreTests
allowedScopes: new[] { "signer.sign" },
allowedAudiences: new[] { "signer" });
var result = await store.CreateOrUpdateAsync(registration, CancellationToken.None);
var result = await store.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
Assert.True(result.Succeeded);
Assert.True(clientStore.Documents.ContainsKey("svc-bootstrap"));
@@ -88,7 +88,7 @@ public sealed class LdapClientProvisioningStoreTests
SecretHash = "hash"
};
var result = await store.DeleteAsync("svc-bootstrap", CancellationToken.None);
var result = await store.DeleteAsync("svc-bootstrap", TestContext.Current.CancellationToken);
Assert.True(result.Succeeded);
Assert.DoesNotContain("svc-bootstrap", clientStore.Documents.Keys);
@@ -124,7 +124,7 @@ public sealed class LdapClientProvisioningStoreTests
allowedGrantTypes: new[] { "client_credentials" },
allowedScopes: new[] { "signer.sign" });
var result = await store.CreateOrUpdateAsync(registration, CancellationToken.None);
var result = await store.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
Assert.False(result.Succeeded);
Assert.Equal("disabled", result.ErrorCode);

View File

@@ -44,7 +44,7 @@ public class LdapCredentialStoreTests
monitor,
new FakeLdapConnectionFactory(connection));
var result = await store.VerifyPasswordAsync("J.Doe", "Password1!", CancellationToken.None);
var result = await store.VerifyPasswordAsync("J.Doe", "Password1!", TestContext.Current.CancellationToken);
Assert.True(result.Succeeded);
Assert.Equal(2, bindCalls.Count);
@@ -86,7 +86,7 @@ public class LdapCredentialStoreTests
monitor,
new FakeLdapConnectionFactory(connection));
var result = await store.VerifyPasswordAsync("J.Doe", "Password1!", CancellationToken.None);
var result = await store.VerifyPasswordAsync("J.Doe", "Password1!", TestContext.Current.CancellationToken);
Assert.True(result.Succeeded);
Assert.NotNull(result.User);
@@ -119,7 +119,7 @@ public class LdapCredentialStoreTests
new FakeLdapConnectionFactory(connection),
delayAsync: (_, _) => Task.CompletedTask);
var result = await store.VerifyPasswordAsync("jdoe", "Password1!", CancellationToken.None);
var result = await store.VerifyPasswordAsync("jdoe", "Password1!", TestContext.Current.CancellationToken);
Assert.True(result.Succeeded);
Assert.Equal(2, attempts);
@@ -142,7 +142,7 @@ public class LdapCredentialStoreTests
new FakeLdapConnectionFactory(connection),
delayAsync: (_, _) => Task.CompletedTask);
var result = await store.VerifyPasswordAsync("jdoe", "bad", CancellationToken.None);
var result = await store.VerifyPasswordAsync("jdoe", "bad", TestContext.Current.CancellationToken);
Assert.False(result.Succeeded);
Assert.Equal(AuthorityCredentialFailureCode.InvalidCredentials, result.FailureCode);
@@ -176,7 +176,7 @@ public class LdapCredentialStoreTests
monitor,
new FakeLdapConnectionFactory(connection));
var result = await store.FindBySubjectAsync("uid=j.doe,ou=people,dc=example,dc=internal", CancellationToken.None);
var result = await store.FindBySubjectAsync("uid=j.doe,ou=people,dc=example,dc=internal", TestContext.Current.CancellationToken);
Assert.NotNull(result);
Assert.Equal("uid=j.doe,ou=people,dc=example,dc=internal", result!.SubjectId);
@@ -201,7 +201,7 @@ public class LdapCredentialStoreTests
email: "bootstrap@example.internal",
requirePasswordReset: true);
var result = await store.UpsertUserAsync(registration, CancellationToken.None);
var result = await store.UpsertUserAsync(registration, TestContext.Current.CancellationToken);
Assert.True(result.Succeeded);
Assert.Contains(connection.Operations, op => op.StartsWith("add:uid=bootstrap.user", StringComparison.OrdinalIgnoreCase));
@@ -234,7 +234,7 @@ public class LdapCredentialStoreTests
email: "bootstrap@example.internal",
requirePasswordReset: false);
var result = await store.UpsertUserAsync(registration, CancellationToken.None);
var result = await store.UpsertUserAsync(registration, TestContext.Current.CancellationToken);
Assert.True(result.Succeeded);
Assert.Contains(connection.Operations, op => op.StartsWith("modify:uid=bootstrap.user", StringComparison.OrdinalIgnoreCase));

View File

@@ -61,7 +61,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection);
// Act
var result = await store.VerifyPasswordAsync("noname", "Password1!", CancellationToken.None);
var result = await store.VerifyPasswordAsync("noname", "Password1!", TestContext.Current.CancellationToken);
// Assert
result.Succeeded.Should().BeTrue("Missing displayName should not prevent authentication");
@@ -88,7 +88,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection);
// Act
var result = await store.VerifyPasswordAsync("nomail", "Password1!", CancellationToken.None);
var result = await store.VerifyPasswordAsync("nomail", "Password1!", TestContext.Current.CancellationToken);
// Assert
result.Succeeded.Should().BeTrue("Missing mail should not prevent authentication");
@@ -113,7 +113,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection);
// Act
var result = await store.VerifyPasswordAsync("nogroups", "Password1!", CancellationToken.None);
var result = await store.VerifyPasswordAsync("nogroups", "Password1!", TestContext.Current.CancellationToken);
// Assert
result.Succeeded.Should().BeTrue("Empty memberOf should not prevent authentication");
@@ -135,7 +135,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection);
// Act
var result = await store.VerifyPasswordAsync("nonexistent", "Password1!", CancellationToken.None);
var result = await store.VerifyPasswordAsync("nonexistent", "Password1!", TestContext.Current.CancellationToken);
// Assert
result.Succeeded.Should().BeFalse("Nonexistent user should fail authentication");
@@ -177,7 +177,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act
var result = await store.VerifyPasswordAsync("user", "WrongPassword!", CancellationToken.None);
var result = await store.VerifyPasswordAsync("user", "WrongPassword!", TestContext.Current.CancellationToken);
// Assert
result.Succeeded.Should().BeFalse("Wrong password should fail authentication");
@@ -200,7 +200,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection);
// Act
var result = await store.VerifyPasswordAsync("malformed", "Password1!", CancellationToken.None);
var result = await store.VerifyPasswordAsync("malformed", "Password1!", TestContext.Current.CancellationToken);
// Assert - should handle gracefully (either succeed with warning or fail cleanly)
// The exact behavior depends on implementation
@@ -225,7 +225,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act
Func<Task> act = async () => await store.VerifyPasswordAsync("user", "Password1!", CancellationToken.None);
Func<Task> act = async () => await store.VerifyPasswordAsync("user", "Password1!", TestContext.Current.CancellationToken);
// Assert
await act.Should().ThrowAsync<TimeoutException>();
@@ -247,7 +247,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act
Func<Task> act = async () => await store.VerifyPasswordAsync("user", "Password1!", CancellationToken.None);
Func<Task> act = async () => await store.VerifyPasswordAsync("user", "Password1!", TestContext.Current.CancellationToken);
// Assert
await act.Should().ThrowAsync<InvalidOperationException>();
@@ -303,7 +303,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection);
// Act
var result = await store.VerifyPasswordAsync("münchen-user", "Password1!", CancellationToken.None);
var result = await store.VerifyPasswordAsync("münchen-user", "Password1!", TestContext.Current.CancellationToken);
// Assert
result.Succeeded.Should().BeTrue("Unicode username should be handled");
@@ -329,7 +329,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection);
// Act
var result = await store.VerifyPasswordAsync("user+test", "Password1!", CancellationToken.None);
var result = await store.VerifyPasswordAsync("user+test", "Password1!", TestContext.Current.CancellationToken);
// Assert
result.Succeeded.Should().BeTrue("Special characters in DN should be handled");

View File

@@ -69,7 +69,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act
var result = await store.VerifyPasswordAsync(maliciousUsername, "Password1!", CancellationToken.None);
var result = await store.VerifyPasswordAsync(maliciousUsername, "Password1!", TestContext.Current.CancellationToken);
// Assert
result.Succeeded.Should().BeFalse("Injection attempt should fail");
@@ -103,7 +103,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act
var result = await store.VerifyPasswordAsync(emptyUsername, "Password1!", CancellationToken.None);
var result = await store.VerifyPasswordAsync(emptyUsername, "Password1!", TestContext.Current.CancellationToken);
// Assert
result.Succeeded.Should().BeFalse("Empty username should be rejected");
@@ -120,7 +120,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act
var result = await store.VerifyPasswordAsync("user", null!, CancellationToken.None);
var result = await store.VerifyPasswordAsync("user", null!, TestContext.Current.CancellationToken);
// Assert
result.Succeeded.Should().BeFalse("Null password should be rejected");
@@ -137,7 +137,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act
var result = await store.VerifyPasswordAsync("user", "", CancellationToken.None);
var result = await store.VerifyPasswordAsync("user", "", TestContext.Current.CancellationToken);
// Assert
result.Succeeded.Should().BeFalse("Empty password should be rejected");
@@ -169,7 +169,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act
Func<Task> act = async () => await store.VerifyPasswordAsync("user", "Password1!", CancellationToken.None);
Func<Task> act = async () => await store.VerifyPasswordAsync("user", "Password1!", TestContext.Current.CancellationToken);
// Assert
await act.Should().ThrowAsync<InvalidOperationException>();
@@ -201,7 +201,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act
await store.VerifyPasswordAsync("targetuser", "Password1!", CancellationToken.None);
await store.VerifyPasswordAsync("targetuser", "Password1!", TestContext.Current.CancellationToken);
// Assert
bindDns.Should().HaveCountGreaterThanOrEqualTo(2, "Should bind as service then as user");
@@ -289,7 +289,7 @@ public sealed class LdapConnectorSecurityTests
// Act
try
{
await store.VerifyPasswordAsync("user", "SuperSecret123!", CancellationToken.None);
await store.VerifyPasswordAsync("user", "SuperSecret123!", TestContext.Current.CancellationToken);
}
catch
{
@@ -308,7 +308,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act
var result = await store.VerifyPasswordAsync("user", "MyPassword123", CancellationToken.None);
var result = await store.VerifyPasswordAsync("user", "MyPassword123", TestContext.Current.CancellationToken);
// Assert
var resultString = result.ToString();

View File

@@ -112,13 +112,13 @@ public sealed class LdapConnectorSnapshotTests
{
// Arrange
var fixtureFiles = Directory.Exists(FixturesPath)
? Directory.EnumerateFiles(FixturesPath, "*.json").Select(Path.GetFileNameWithoutExtension).ToList()
? Directory.EnumerateFiles(FixturesPath, "*.json").Select(Path.GetFileNameWithoutExtension).Where(n => n is not null).Cast<string>().ToList()
: new List<string>();
var expectedFiles = Directory.Exists(ExpectedPath)
? Directory.EnumerateFiles(ExpectedPath, "*.canonical.json")
.Select(f => Path.GetFileNameWithoutExtension(f).Replace(".canonical", ""))
.ToList()
.Select(f => Path.GetFileNameWithoutExtension(f)?.Replace(".canonical", ""))
.Where(n => n is not null).Cast<string>().ToList()
: new List<string>();
// Assert

View File

@@ -52,7 +52,7 @@ public sealed class OidcCredentialStoreTests
username: "user@example.com",
signingCredentials: new SigningCredentials(symmetricKey, SecurityAlgorithms.HmacSha256));
var result = await store.VerifyPasswordAsync("user@example.com", token, CancellationToken.None);
var result = await store.VerifyPasswordAsync("user@example.com", token, TestContext.Current.CancellationToken);
Assert.False(result.Succeeded);
Assert.Equal(AuthorityCredentialFailureCode.InvalidCredentials, result.FailureCode);
@@ -102,10 +102,10 @@ public sealed class OidcCredentialStoreTests
username: "user2@example.com",
signingCredentials: new SigningCredentials(rsaKey, SecurityAlgorithms.RsaSha256));
var result = await storeA.VerifyPasswordAsync("user2@example.com", token, CancellationToken.None);
var result = await storeA.VerifyPasswordAsync("user2@example.com", token, TestContext.Current.CancellationToken);
Assert.True(result.Succeeded);
var cached = await storeB.FindBySubjectAsync("user-2", CancellationToken.None);
var cached = await storeB.FindBySubjectAsync("user-2", TestContext.Current.CancellationToken);
Assert.Null(cached);
}
@@ -220,8 +220,8 @@ public sealed class OidcCredentialStoreTests
public OidcPluginOptions CurrentValue => options.Values.First();
public OidcPluginOptions Get(string name)
=> options.TryGetValue(name, out var value) ? value : options.Values.First();
public OidcPluginOptions Get(string? name)
=> name is not null && options.TryGetValue(name, out var value) ? value : options.Values.First();
public IDisposable OnChange(Action<OidcPluginOptions, string> listener)
=> new NoopDisposable();

View File

@@ -23,7 +23,7 @@ public sealed class OidcIdentityProviderPluginTests
{
var (plugin, _) = CreatePlugin(HttpStatusCode.OK);
var result = await plugin.CheckHealthAsync(CancellationToken.None);
var result = await plugin.CheckHealthAsync(TestContext.Current.CancellationToken);
Assert.Equal(AuthorityPluginHealthStatus.Healthy, result.Status);
}
@@ -33,7 +33,7 @@ public sealed class OidcIdentityProviderPluginTests
{
var (plugin, _) = CreatePlugin(HttpStatusCode.ServiceUnavailable);
var result = await plugin.CheckHealthAsync(CancellationToken.None);
var result = await plugin.CheckHealthAsync(TestContext.Current.CancellationToken);
Assert.Equal(AuthorityPluginHealthStatus.Degraded, result.Status);
}
@@ -132,7 +132,7 @@ public sealed class OidcIdentityProviderPluginTests
public OidcPluginOptions CurrentValue => options;
public OidcPluginOptions Get(string name)
public OidcPluginOptions Get(string? name)
=> string.Equals(name, pluginName, StringComparison.Ordinal) ? options : options;
public IDisposable OnChange(Action<OidcPluginOptions, string> listener)

View File

@@ -129,13 +129,13 @@ public sealed class OidcConnectorSnapshotTests
{
// Arrange
var fixtureFiles = Directory.Exists(FixturesPath)
? Directory.EnumerateFiles(FixturesPath, "*.json").Select(Path.GetFileNameWithoutExtension).ToList()
? Directory.EnumerateFiles(FixturesPath, "*.json").Select(Path.GetFileNameWithoutExtension).Where(n => n is not null).Cast<string>().ToList()
: new List<string>();
var expectedFiles = Directory.Exists(ExpectedPath)
? Directory.EnumerateFiles(ExpectedPath, "*.canonical.json")
.Select(f => Path.GetFileNameWithoutExtension(f)?.Replace(".canonical", ""))
.ToList()
.Where(n => n is not null).Cast<string>().ToList()
: new List<string>();
// Assert

View File

@@ -23,7 +23,7 @@ public sealed class SamlIdentityProviderPluginTests
{
var plugin = CreatePlugin(HttpStatusCode.OK);
var result = await plugin.CheckHealthAsync(CancellationToken.None);
var result = await plugin.CheckHealthAsync(TestContext.Current.CancellationToken);
Assert.Equal(AuthorityPluginHealthStatus.Healthy, result.Status);
}
@@ -33,7 +33,7 @@ public sealed class SamlIdentityProviderPluginTests
{
var plugin = CreatePlugin(HttpStatusCode.ServiceUnavailable);
var result = await plugin.CheckHealthAsync(CancellationToken.None);
var result = await plugin.CheckHealthAsync(TestContext.Current.CancellationToken);
Assert.Equal(AuthorityPluginHealthStatus.Degraded, result.Status);
}
@@ -130,7 +130,7 @@ public sealed class SamlIdentityProviderPluginTests
public SamlPluginOptions CurrentValue => options;
public SamlPluginOptions Get(string name)
public SamlPluginOptions Get(string? name)
=> string.Equals(name, pluginName, StringComparison.Ordinal) ? options : options;
public IDisposable OnChange(Action<SamlPluginOptions, string> listener)

View File

@@ -122,13 +122,13 @@ public sealed class SamlConnectorSnapshotTests
{
// Arrange
var fixtureFiles = Directory.Exists(FixturesPath)
? Directory.EnumerateFiles(FixturesPath, "*.xml").Select(Path.GetFileNameWithoutExtension).ToList()
? Directory.EnumerateFiles(FixturesPath, "*.xml").Select(Path.GetFileNameWithoutExtension).Where(n => n is not null).Cast<string>().ToList()
: new List<string>();
var expectedFiles = Directory.Exists(ExpectedPath)
? Directory.EnumerateFiles(ExpectedPath, "*.canonical.json")
.Select(f => Path.GetFileNameWithoutExtension(f)?.Replace(".canonical", ""))
.ToList()
.Where(n => n is not null).Cast<string>().ToList()
: new List<string>();
// Assert

View File

@@ -39,7 +39,7 @@ public class StandardCredentialAuditLoggerTests
failureCode: null,
reason: null,
properties: Array.Empty<AuthEventProperty>(),
CancellationToken.None);
TestContext.Current.CancellationToken);
var record = Assert.Single(sink.Records);
Assert.Equal("authority.plugin.standard.password_verification", record.EventType);
@@ -92,7 +92,7 @@ public class StandardCredentialAuditLoggerTests
failureCode: AuthorityCredentialFailureCode.InvalidCredentials,
reason: "Invalid credentials.",
properties,
CancellationToken.None);
TestContext.Current.CancellationToken);
var record = Assert.Single(sink.Records);
Assert.Equal(AuthEventOutcome.Failure, record.Outcome);
@@ -147,7 +147,7 @@ public class StandardCredentialAuditLoggerTests
failureCode: AuthorityCredentialFailureCode.LockedOut,
reason: "Account locked.",
properties,
CancellationToken.None);
TestContext.Current.CancellationToken);
var record = Assert.Single(sink.Records);
Assert.Equal(AuthEventOutcome.LockedOut, record.Outcome);
@@ -189,7 +189,7 @@ public class StandardCredentialAuditLoggerTests
failureCode: AuthorityCredentialFailureCode.RequiresMfa,
reason: "MFA required.",
properties: null,
CancellationToken.None);
TestContext.Current.CancellationToken);
var record = Assert.Single(sink.Records);
var property = Assert.Single(record.Properties);

View File

@@ -43,7 +43,7 @@ public class StandardClaimsEnricherTests
var identity = new ClaimsIdentity();
var enricher = new StandardClaimsEnricher();
await enricher.EnrichAsync(identity, context, CancellationToken.None);
await enricher.EnrichAsync(identity, context, TestContext.Current.CancellationToken);
Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "admin");
Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "ops");

View File

@@ -32,7 +32,7 @@ public class StandardClientProvisioningStoreTests
allowedGrantTypes: new[] { "client_credentials" },
allowedScopes: new[] { "scopeA" });
var result = await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None);
var result = await provisioning.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
Assert.True(result.Succeeded);
Assert.True(store.Documents.TryGetValue("bootstrap-client", out var document));
@@ -40,7 +40,7 @@ public class StandardClientProvisioningStoreTests
Assert.Equal(AuthoritySecretHasher.ComputeHash("SuperSecret1!"), document!.SecretHash);
Assert.Equal("standard", document.Plugin);
var descriptor = await provisioning.FindByClientIdAsync("bootstrap-client", CancellationToken.None);
var descriptor = await provisioning.FindByClientIdAsync("bootstrap-client", TestContext.Current.CancellationToken);
Assert.NotNull(descriptor);
Assert.Equal("bootstrap-client", descriptor!.ClientId);
Assert.True(descriptor.Confidential);
@@ -65,13 +65,13 @@ public class StandardClientProvisioningStoreTests
allowedScopes: new[] { "scopeA" },
tenant: " Tenant-Alpha " );
await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None);
await provisioning.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
Assert.True(store.Documents.TryGetValue("tenant-client", out var document));
Assert.NotNull(document);
Assert.Equal("tenant-alpha", document!.Properties[AuthorityClientMetadataKeys.Tenant]);
var descriptor = await provisioning.FindByClientIdAsync("tenant-client", CancellationToken.None);
var descriptor = await provisioning.FindByClientIdAsync("tenant-client", TestContext.Current.CancellationToken);
Assert.NotNull(descriptor);
Assert.Equal("tenant-alpha", descriptor!.Tenant);
}
@@ -92,14 +92,14 @@ public class StandardClientProvisioningStoreTests
allowedScopes: new[] { "signer.sign" },
allowedAudiences: new[] { "attestor", "signer" });
var result = await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None);
var result = await provisioning.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
Assert.True(result.Succeeded);
Assert.True(store.Documents.TryGetValue("signer", out var document));
Assert.NotNull(document);
Assert.Equal("attestor signer", document!.Properties[AuthorityClientMetadataKeys.Audiences]);
var descriptor = await provisioning.FindByClientIdAsync("signer", CancellationToken.None);
var descriptor = await provisioning.FindByClientIdAsync("signer", TestContext.Current.CancellationToken);
Assert.NotNull(descriptor);
Assert.Equal(new[] { "attestor", "signer" }, descriptor!.AllowedAudiences.OrderBy(value => value, StringComparer.Ordinal));
}
@@ -132,7 +132,7 @@ public class StandardClientProvisioningStoreTests
allowedAudiences: new[] { "signer" },
certificateBindings: new[] { bindingRegistration });
await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None);
await provisioning.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
Assert.True(store.Documents.TryGetValue("mtls-client", out var document));
Assert.NotNull(document);
@@ -164,9 +164,9 @@ public class StandardClientProvisioningStoreTests
allowedGrantTypes: new[] { "client_credentials" },
allowedScopes: new[] { "scopeA" });
await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None);
await provisioning.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
var result = await provisioning.DeleteAsync("delete-me", CancellationToken.None);
var result = await provisioning.DeleteAsync("delete-me", TestContext.Current.CancellationToken);
Assert.True(result.Succeeded);
Assert.False(store.Documents.ContainsKey("delete-me"));

View File

@@ -58,7 +58,7 @@ public class StandardIdentityProviderPluginTests
new StandardClaimsEnricher(),
NullLogger<StandardIdentityProviderPlugin>.Instance);
var health = await plugin.CheckHealthAsync(CancellationToken.None);
var health = await plugin.CheckHealthAsync(TestContext.Current.CancellationToken);
Assert.Equal(AuthorityPluginHealthStatus.Healthy, health.Status);
}

View File

@@ -67,7 +67,7 @@ public class StandardPluginBootstrapperTests
using var provider = services.BuildServiceProvider();
var bootstrapper = provider.GetRequiredService<StandardPluginBootstrapper>();
var exception = await Record.ExceptionAsync(() => bootstrapper.StartAsync(CancellationToken.None));
var exception = await Record.ExceptionAsync(() => bootstrapper.StartAsync(TestContext.Current.CancellationToken));
Assert.Null(exception);
}

View File

@@ -228,6 +228,7 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
var updated = await store.UpsertUserAsync(update, CancellationToken.None);
Assert.True(updated.Succeeded);
Assert.NotNull(updated.Value);
Assert.Contains("editor", updated.Value.Roles);
Assert.Contains("admin", updated.Value.Roles);
Assert.Equal("us", updated.Value.Attributes["region"]);
@@ -250,6 +251,7 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
var created = await store.UpsertUserAsync(registration, CancellationToken.None);
Assert.True(created.Succeeded);
Assert.NotNull(created.Value);
var found = await store.FindBySubjectAsync(created.Value.SubjectId, CancellationToken.None);
Assert.NotNull(found);

View File

@@ -115,7 +115,7 @@ public sealed class AdvisoryAiRemoteInferenceEndpointTests : IClassFixture<Autho
var expectedHash = ComputeSha256(payload.Prompt);
Assert.Equal(expectedHash, body["prompt_hash"]);
var doc = Assert.Single(lastLoginAttemptStore!.Records.Where(record => record.EventType == "authority.advisory_ai.remote_inference"));
var doc = Assert.Single(lastLoginAttemptStore!.Records, record => record.EventType == "authority.advisory_ai.remote_inference");
Assert.Equal("authority.advisory_ai.remote_inference", doc.EventType);
var properties = doc.Properties.ToDictionary(p => p.Name, p => p.Value);
Assert.Equal(expectedHash, properties["advisory_ai.prompt.hash"]);

View File

@@ -77,7 +77,7 @@ public class AuthorityAuditSinkTests
Assert.Equal(record.OccurredAt, document.OccurredAt);
Assert.Equal(new[] { "openid", "profile" }, document.Scopes);
var pluginProperty = Assert.Single(document.Properties.Where(property => property.Name == "plugin.failed_attempts"));
var pluginProperty = Assert.Single(document.Properties, property => property.Name == "plugin.failed_attempts");
Assert.Equal("0", pluginProperty.Value);
Assert.Equal("none", pluginProperty.Classification);

View File

@@ -2926,9 +2926,9 @@ public class ClientCredentialsHandlersTests
Assert.False(validateContext.IsRejected);
Assert.False(validateContext.Transaction.Properties.ContainsKey(AuthorityOpenIddictConstants.SenderConstraintProperty));
var bypassEvent = Assert.Single(auditSink.Events.Where(record => record.EventType == "authority.dpop.proof.bypass"));
var bypassEvent = Assert.Single(auditSink.Events, record => record.EventType == "authority.dpop.proof.bypass");
Assert.Equal(AuthEventOutcome.Success, bypassEvent.Outcome);
var reasonProperty = Assert.Single(bypassEvent.Properties.Where(property => property.Name == "dpop.reason_code"));
var reasonProperty = Assert.Single(bypassEvent.Properties, property => property.Name == "dpop.reason_code");
Assert.Equal("bypass", reasonProperty.Value.Value);
}
@@ -3387,7 +3387,7 @@ public class ClientCredentialsHandlersTests
var grantEvent = authSink.Events.LastOrDefault(evt => evt.EventType == "authority.client_credentials.grant");
Assert.NotNull(grantEvent);
var serviceProperty = Assert.Single(grantEvent!.Properties.Where(prop => prop.Name == "delegation.service_account"));
var serviceProperty = Assert.Single(grantEvent!.Properties, prop => prop.Name == "delegation.service_account");
Assert.Equal(serviceAccount.AccountId, serviceProperty.Value.Value);
var actorPropertyValues = grantEvent.Properties

View File

@@ -22,6 +22,9 @@ namespace StellaOps.Authority.ConfigDiff.Tests;
[Trait("BlastRadius", TestCategories.BlastRadius.Auth)]
public class AuthorityConfigDiffTests : ConfigDiffTestBase
{
private static readonly DateTimeOffset SnapshotTimestamp =
new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
/// <summary>
/// Initializes a new instance of the <see cref="AuthorityConfigDiffTests"/> class.
/// </summary>
@@ -61,7 +64,8 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
async config => await GetSessionBehaviorAsync(config),
async config => await GetRefreshBehaviorAsync(config),
async config => await GetAuthenticationBehaviorAsync(config)
]);
],
ct: TestContext.Current.CancellationToken);
// Assert
result.IsSuccess.Should().BeTrue(
@@ -93,7 +97,8 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
changedConfig,
getBehavior: async config => await CaptureSessionBehaviorAsync(config),
computeDelta: ComputeBehaviorSnapshotDelta,
expectedDelta: expectedDelta);
expectedDelta: expectedDelta,
ct: TestContext.Current.CancellationToken);
// Assert
result.IsSuccess.Should().BeTrue(
@@ -119,7 +124,8 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
[
async config => await GetSessionBehaviorAsync(config),
async config => await GetPasswordPolicyBehaviorAsync(config)
]);
],
ct: TestContext.Current.CancellationToken);
// Assert
result.IsSuccess.Should().BeTrue(
@@ -151,7 +157,8 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
changedConfig,
getBehavior: async config => await CapturePasswordPolicyBehaviorAsync(config),
computeDelta: ComputeBehaviorSnapshotDelta,
expectedDelta: expectedDelta);
expectedDelta: expectedDelta,
ct: TestContext.Current.CancellationToken);
// Assert
result.IsSuccess.Should().BeTrue();
@@ -176,7 +183,8 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
[
async config => await GetTokenBehaviorAsync(config),
async config => await GetSessionBehaviorAsync(config)
]);
],
ct: TestContext.Current.CancellationToken);
// Assert
result.IsSuccess.Should().BeTrue(
@@ -216,11 +224,11 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
ConfigurationId: $"sessions-{config.MaxConcurrentSessions}",
Behaviors:
[
new CapturedBehavior("SessionLimit", config.MaxConcurrentSessions.ToString(), DateTimeOffset.UtcNow),
new CapturedBehavior("SessionLimit", config.MaxConcurrentSessions.ToString(), SnapshotTimestamp),
new CapturedBehavior("ConcurrencyPolicy",
config.MaxConcurrentSessions > 5 ? "permissive" : "restrictive", DateTimeOffset.UtcNow)
config.MaxConcurrentSessions > 5 ? "permissive" : "restrictive", SnapshotTimestamp)
],
CapturedAt: DateTimeOffset.UtcNow);
CapturedAt: SnapshotTimestamp);
return Task.FromResult(snapshot);
}
@@ -232,11 +240,11 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
Behaviors:
[
new CapturedBehavior("PasswordComplexity",
config.MinPasswordLength >= 12 ? "enhanced" : "standard", DateTimeOffset.UtcNow),
config.MinPasswordLength >= 12 ? "enhanced" : "standard", SnapshotTimestamp),
new CapturedBehavior("ValidationRejectionRate",
config.MinPasswordLength >= 12 ? "increase" : "standard", DateTimeOffset.UtcNow)
config.MinPasswordLength >= 12 ? "increase" : "standard", SnapshotTimestamp)
],
CapturedAt: DateTimeOffset.UtcNow);
CapturedAt: SnapshotTimestamp);
return Task.FromResult(snapshot);
}

View File

@@ -33,7 +33,7 @@ public sealed class AuthorityPostgresFixture : PostgresIntegrationFixture, IColl
public PostgresOptions CreateOptions()
{
var options = Fixture.CreateOptions();
options.SchemaName = SchemaName;
options.SchemaName = AuthorityDataSource.DefaultSchemaName;
options.MaxPoolSize = 10;
options.MinPoolSize = 0;
return options;

View File

@@ -1,5 +1,4 @@
using System.Collections.Immutable;
using System.Threading;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
@@ -7,6 +6,7 @@ using Npgsql;
using StellaOps.Authority.Core.Verdicts;
using StellaOps.Authority.Persistence.Postgres;
using StellaOps.TestKit;
using System.Text.Json;
using Xunit;
namespace StellaOps.Authority.Persistence.Tests;
@@ -37,7 +37,7 @@ public sealed class VerdictManifestStoreTests : IAsyncLifetime
[Fact]
public async Task StoreAndGetById_RoundTripsManifest()
{
var evaluatedAt = DateTimeOffset.Parse("2025-01-15T10:00:00Z");
var evaluatedAt = new DateTimeOffset(2025, 1, 15, 10, 0, 0, TimeSpan.Zero);
var manifest = CreateManifest("tenant-1", "manifest-001", evaluatedAt, VexStatus.NotAffected);
await _store.StoreAsync(manifest);
@@ -58,12 +58,15 @@ public sealed class VerdictManifestStoreTests : IAsyncLifetime
[Fact]
public async Task StoreAsync_WritesStringEnumJson()
{
var evaluatedAt = DateTimeOffset.Parse("2025-01-15T11:00:00Z");
var evaluatedAt = new DateTimeOffset(2025, 1, 15, 11, 0, 0, TimeSpan.Zero);
var manifest = CreateManifest("tenant-2", "manifest-002", evaluatedAt, VexStatus.UnderInvestigation);
await _store.StoreAsync(manifest);
await using var conn = await _dataSource.OpenConnectionAsync(manifest.Tenant, "reader", CancellationToken.None);
await using var conn = await _dataSource.OpenConnectionAsync(
manifest.Tenant,
"reader",
TestContext.Current.CancellationToken);
await using var cmd = new NpgsqlCommand("""
SELECT result_json::text
FROM verdict_manifests
@@ -77,7 +80,8 @@ public sealed class VerdictManifestStoreTests : IAsyncLifetime
var json = (string?)await cmd.ExecuteScalarAsync();
json.Should().NotBeNull();
json.Should().Contain("\"status\":\"under_investigation\"");
using var document = JsonDocument.Parse(json!);
document.RootElement.GetProperty("status").GetString().Should().Be("under_investigation");
}
private static VerdictManifest CreateManifest(string tenant, string manifestId, DateTimeOffset evaluatedAt, VexStatus status)

View File

@@ -223,7 +223,7 @@ public sealed class CommandHandlersTests
Assert.Equal("scan-missing", backend.LastEntryTraceScanId);
Assert.Contains("No EntryTrace data", output.Combined, StringComparison.OrdinalIgnoreCase);
var warning = Assert.Single(loggerProvider.Entries.Where(entry => entry.Level == LogLevel.Warning));
var warning = Assert.Single(loggerProvider.Entries, entry => entry.Level == LogLevel.Warning);
Assert.Contains("No EntryTrace data", warning.Message, StringComparison.OrdinalIgnoreCase);
}
finally

View File

@@ -1,5 +1,7 @@
## Release History
; Unshipped analyzer releases
### Unreleased
### New Rules
- CONCELIER0004: Flag direct `new HttpClient()` usage inside `StellaOps.Concelier.Connector*` namespaces; require sandboxed `IHttpClientFactory` to enforce allow/deny lists. Exempts test assemblies and uses symbol-based namespace matching.
Rule ID | Category | Severity | Notes
--------|----------|----------|------
CONCELIER0004 | Sandbox | Warning | Flag direct `new HttpClient()` usage inside `StellaOps.Concelier.Connector*` namespaces

View File

@@ -15,7 +15,7 @@ public sealed class ConnectorHttpClientSandboxAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor Rule = new(
id: DiagnosticId,
title: "Connector HTTP clients must use sandboxed factory",
messageFormat: "Use IHttpClientFactory or connector sandbox helpers instead of 'new HttpClient()' inside Concelier connectors.",
messageFormat: "Use IHttpClientFactory or connector sandbox helpers instead of 'new HttpClient()' inside Concelier connectors",
category: "Sandbox",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
@@ -73,7 +73,7 @@ public sealed class ConnectorHttpClientSandboxAnalyzer : DiagnosticAnalyzer
return false;
}
return assemblyName.EndsWith(".Tests", StringComparison.OrdinalIgnoreCase)
return assemblyName!.EndsWith(".Tests", StringComparison.OrdinalIgnoreCase)
|| assemblyName.EndsWith(".Test", StringComparison.OrdinalIgnoreCase)
|| assemblyName.EndsWith(".Testing", StringComparison.OrdinalIgnoreCase);
}

View File

@@ -1,9 +1,7 @@
## Release History
; Unshipped analyzer releases
### Unreleased
### New Rules
#### New Rules
Rule ID | Title | Notes
--------|-------|------
CONCELIER0002 | Legacy merge service usage detected | Flags references to `AdvisoryMergeService` and `AddMergeModule`.
Rule ID | Category | Severity | Notes
--------|----------|----------|------
CONCELIER0002 | Usage | Warning | Legacy merge service usage detected - flags references to `AdvisoryMergeService` and `AddMergeModule`

View File

@@ -185,6 +185,11 @@ public sealed class AstraConnector : IFeedConnector
/// </remarks>
private async Task<string> FetchOvalDatabaseAsync(string version, CancellationToken cancellationToken)
{
if (_fetchService is null || _rawDocumentStorage is null)
{
throw new InvalidOperationException("Fetch and raw document storage services are required for OVAL database fetch");
}
var uri = _options.BuildOvalDatabaseUri(version);
_logger.LogDebug("Fetching OVAL database for Astra Linux {Version} from {Uri}", version, uri);
@@ -197,18 +202,24 @@ public sealed class AstraConnector : IFeedConnector
var result = await _fetchService.FetchAsync(request, cancellationToken).ConfigureAwait(false);
if (!result.IsSuccess || result.Document is null)
if (result is null || !result.IsSuccess || result.Document is null)
{
throw new InvalidOperationException($"Failed to fetch OVAL database for version {version}");
}
if (!result.Document.PayloadId.HasValue)
var document = result.Document;
if (!document.PayloadId.HasValue)
{
throw new InvalidOperationException($"OVAL database document for version {version} has no payload");
}
// Download the raw XML content
var payloadBytes = await _rawDocumentStorage.DownloadAsync(result.Document.PayloadId.Value, cancellationToken).ConfigureAwait(false);
var payloadBytes = await _rawDocumentStorage.DownloadAsync(document.PayloadId.Value, cancellationToken).ConfigureAwait(false);
if (payloadBytes is null)
{
throw new InvalidOperationException($"OVAL database payload for version {version} not found");
}
return System.Text.Encoding.UTF8.GetString(payloadBytes);
}

View File

@@ -359,7 +359,7 @@ public sealed class CccsHtmlParser
var candidate = href.Trim();
var hasAbsolute = Uri.TryCreate(candidate, UriKind.Absolute, out var absolute);
if (!hasAbsolute || string.Equals(absolute.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
if (!hasAbsolute || absolute is null || string.Equals(absolute.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
{
if (baseUri is null || !Uri.TryCreate(baseUri, candidate, out absolute))
{

View File

@@ -177,7 +177,7 @@ public sealed class CertCcConnector : IFeedConnector
await _documentStore.UpdateStatusAsync(result.Document.Id, DocumentStatuses.Mapped, cancellationToken).ConfigureAwait(false);
}
if (!shouldProcessNotes)
if (!shouldProcessNotes || result.Document is null)
{
continue;
}

View File

@@ -168,7 +168,7 @@ internal sealed record CertCcCursor(
}
var bytes = binary.AsByteArray;
if (bytes.Length == 16)
if (bytes is not null && bytes.Length == 16)
{
guid = new Guid(bytes);
return true;

View File

@@ -605,7 +605,7 @@ public sealed class DebianConnector : IFeedConnector
{
["advisoryId"] = dto.AdvisoryId,
["sourcePackage"] = dto.SourcePackage,
["title"] = dto.Title,
["title"] = dto.Title ?? string.Empty,
["description"] = dto.Description ?? string.Empty,
["cves"] = new DocumentArray(dto.CveIds),
["packages"] = packages,

View File

@@ -29,7 +29,12 @@ internal static class RedHatMapper
ArgumentNullException.ThrowIfNull(payload);
var csaf = JsonSerializer.Deserialize<RedHatCsafEnvelope>(payload.RootElement.GetRawText(), SerializerOptions);
var documentSection = csaf?.Document;
if (csaf is null)
{
return null;
}
var documentSection = csaf.Document;
if (documentSection is null)
{
return null;
@@ -722,7 +727,7 @@ internal sealed class RedHatProductIndex
return new RedHatProductIndex(products);
}
public bool TryGetValue(string productId, out RedHatProductNode node)
public bool TryGetValue(string productId, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out RedHatProductNode? node)
=> _products.TryGetValue(productId, out node);
private static void Traverse(RedHatProductBranch? branch, IDictionary<string, RedHatProductNode> products)

View File

@@ -464,7 +464,7 @@ public sealed class EpssConnector : IFeedConnector
["epss.file"] = GetSnapshotFileName(fetchResult.SnapshotDate)
};
if (_options.AirgapMode)
if (_options.AirgapMode && fetchResult.Content is not null)
{
TryApplyBundleManifest(fetchResult.SnapshotDate, fetchResult.Content, metadata);
}
@@ -473,6 +473,11 @@ public sealed class EpssConnector : IFeedConnector
// Use existing ID or derive deterministic ID from source + uri
var recordId = existing?.Id ?? ComputeDeterministicId(SourceName, fetchResult.SourceUri);
if (fetchResult.Content is null)
{
throw new InvalidOperationException($"EPSS fetch returned null content for {fetchResult.SourceUri}");
}
await _rawDocumentStorage.UploadAsync(
SourceName,
fetchResult.SourceUri,

View File

@@ -25,8 +25,6 @@ using StellaOps.Concelier.Connector.Ics.Cisa.Configuration;
using StellaOps.Concelier.Connector.Ics.Cisa.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Normalization.SemVer;
using StellaOps.Cryptography;
using StellaOps.Plugin;

View File

@@ -7,7 +7,6 @@ using StellaOps.Concelier.Normalization.Cvss;
using StellaOps.Concelier.Normalization.Identifiers;
using StellaOps.Concelier.Normalization.Text;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.JpFlags;
namespace StellaOps.Concelier.Connector.Jvn.Internal;
@@ -68,7 +67,7 @@ internal static class JvnAdvisoryMapper
var flag = new JpFlagRecord(
detail.VulnerabilityId,
JvnConnectorPlugin.SourceName,
detail.JvnCategory,
detail.JvnCategory ?? string.Empty,
vendorStatus,
timeProvider.GetUtcNow());

View File

@@ -13,8 +13,6 @@ using StellaOps.Concelier.Connector.Jvn.Configuration;
using StellaOps.Concelier.Connector.Jvn.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.JpFlags;
using StellaOps.Cryptography;
using StellaOps.Plugin;

View File

@@ -18,8 +18,6 @@ using StellaOps.Concelier.Connector.Kev.Configuration;
using StellaOps.Concelier.Connector.Kev.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Cryptography;
using StellaOps.Plugin;

View File

@@ -14,8 +14,6 @@ using StellaOps.Concelier.Connector.Kisa.Configuration;
using StellaOps.Concelier.Connector.Kisa.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Cryptography;
using StellaOps.Plugin;

View File

@@ -10,7 +10,6 @@ using StellaOps.Concelier.Normalization.Identifiers;
using StellaOps.Concelier.Normalization.Text;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
namespace StellaOps.Concelier.Connector.Osv.Internal;

View File

@@ -17,8 +17,6 @@ using StellaOps.Concelier.Connector.Ru.Bdu.Configuration;
using StellaOps.Concelier.Connector.Ru.Bdu.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Plugin;
using StellaOps.Cryptography;

View File

@@ -18,8 +18,6 @@ using StellaOps.Concelier.Connector.Ru.Nkcki.Configuration;
using StellaOps.Concelier.Connector.Ru.Nkcki.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Plugin;
using StellaOps.Cryptography;

View File

@@ -14,8 +14,6 @@ using StellaOps.Concelier.Connector.StellaOpsMirror.Settings;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Plugin;
using StellaOps.Cryptography;

View File

@@ -18,8 +18,6 @@ using StellaOps.Concelier.Connector.Vndr.Adobe.Configuration;
using StellaOps.Concelier.Connector.Vndr.Adobe.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.PsirtFlags;
using StellaOps.Concelier.Models;
using StellaOps.Cryptography;

View File

@@ -16,8 +16,6 @@ using StellaOps.Concelier.Connector.Common.Fetch;
using StellaOps.Concelier.Connector.Vndr.Apple.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.PsirtFlags;
using StellaOps.Cryptography;
using StellaOps.Plugin;

View File

@@ -118,23 +118,24 @@ internal static class AppleIndexParser
return entries.Count == 0 ? Array.Empty<AppleIndexEntry>() : entries;
}
private static bool TryResolveDetailUri(AppleIndexEntryDto dto, Uri baseUri, out Uri uri)
private static bool TryResolveDetailUri(AppleIndexEntryDto dto, Uri baseUri, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out Uri? uri)
{
if (!string.IsNullOrWhiteSpace(dto.DetailUrl) && Uri.TryCreate(dto.DetailUrl, UriKind.Absolute, out uri))
if (!string.IsNullOrWhiteSpace(dto.DetailUrl) && Uri.TryCreate(dto.DetailUrl, UriKind.Absolute, out var parsedUri))
{
uri = parsedUri;
return true;
}
if (string.IsNullOrWhiteSpace(dto.ArticleId))
{
uri = default!;
uri = null;
return false;
}
var article = dto.ArticleId.Trim();
if (article.Length == 0)
{
uri = default!;
uri = null;
return false;
}

View File

@@ -5,7 +5,6 @@ using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Packages;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.PsirtFlags;
namespace StellaOps.Concelier.Connector.Vndr.Apple.Internal;

View File

@@ -14,8 +14,6 @@ using StellaOps.Concelier.Connector.Vndr.Chromium.Configuration;
using StellaOps.Concelier.Connector.Vndr.Chromium.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.PsirtFlags;
using StellaOps.Cryptography;
using StellaOps.Plugin;

View File

@@ -69,7 +69,7 @@ internal sealed record ChromiumCursor(
public ChromiumCursor WithFetchCache(IDictionary<string, ChromiumFetchCacheEntry> cache)
=> this with { FetchCache = cache is null ? new Dictionary<string, ChromiumFetchCacheEntry>(StringComparer.Ordinal) : new Dictionary<string, ChromiumFetchCacheEntry>(cache, StringComparer.Ordinal) };
public bool TryGetFetchCache(string key, out ChromiumFetchCacheEntry entry)
public bool TryGetFetchCache(string key, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out ChromiumFetchCacheEntry? entry)
=> FetchCache.TryGetValue(key, out entry);
private static DateTimeOffset? ReadDateTime(DocumentValue value)

View File

@@ -12,8 +12,6 @@ using StellaOps.Concelier.Connector.Vndr.Cisco.Configuration;
using StellaOps.Concelier.Connector.Vndr.Cisco.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Cryptography;
using StellaOps.Plugin;

View File

@@ -4,7 +4,6 @@ using System.Linq;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common.Packages;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Normalization.SemVer;
namespace StellaOps.Concelier.Connector.Vndr.Cisco.Internal;

View File

@@ -17,8 +17,6 @@ using StellaOps.Concelier.Connector.Vndr.Msrc.Configuration;
using StellaOps.Concelier.Connector.Vndr.Msrc.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Cryptography;
using StellaOps.Plugin;

View File

@@ -5,7 +5,6 @@ using System.Text.RegularExpressions;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common.Packages;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.PsirtFlags;
namespace StellaOps.Concelier.Connector.Vndr.Oracle.Internal;

View File

@@ -5,7 +5,6 @@ using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Packages;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.PsirtFlags;
namespace StellaOps.Concelier.Connector.Vndr.Vmware.Internal;

View File

@@ -3,6 +3,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Npgsql;
using NpgsqlTypes;
using StellaOps.Concelier.Core.Linksets;
using StellaOps.Concelier.Persistence.Postgres.Models;
using StellaOps.Infrastructure.Postgres.Repositories;
@@ -127,8 +128,14 @@ public sealed class AdvisoryLinksetCacheRepository
if (cursor is null)
{
AddParameter(cmd, "cursor_created_at", DBNull.Value);
AddParameter(cmd, "cursor_advisory_id", DBNull.Value);
cmd.Parameters.Add(new NpgsqlParameter("cursor_created_at", NpgsqlDbType.TimestampTz)
{
Value = DBNull.Value
});
cmd.Parameters.Add(new NpgsqlParameter("cursor_advisory_id", NpgsqlDbType.Text)
{
Value = DBNull.Value
});
}
else
{

View File

@@ -22,6 +22,9 @@ namespace StellaOps.Concelier.ConfigDiff.Tests;
[Trait("BlastRadius", TestCategories.BlastRadius.Advisories)]
public class ConcelierConfigDiffTests : ConfigDiffTestBase
{
private static readonly DateTimeOffset SnapshotTimestamp =
new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
/// <summary>
/// Initializes a new instance of the <see cref="ConcelierConfigDiffTests"/> class.
/// </summary>
@@ -61,7 +64,8 @@ public class ConcelierConfigDiffTests : ConfigDiffTestBase
async config => await GetDownloadBehaviorAsync(config),
async config => await GetRetryBehaviorAsync(config),
async config => await GetParseBehaviorAsync(config)
]);
],
ct: TestContext.Current.CancellationToken);
// Assert
result.IsSuccess.Should().BeTrue(
@@ -93,7 +97,8 @@ public class ConcelierConfigDiffTests : ConfigDiffTestBase
changedConfig,
getBehavior: async config => await CaptureRetryBehaviorAsync(config),
computeDelta: ComputeBehaviorSnapshotDelta,
expectedDelta: expectedDelta);
expectedDelta: expectedDelta,
ct: TestContext.Current.CancellationToken);
// Assert
result.IsSuccess.Should().BeTrue(
@@ -120,7 +125,8 @@ public class ConcelierConfigDiffTests : ConfigDiffTestBase
async config => await GetCacheBehaviorAsync(config),
async config => await GetRetryBehaviorAsync(config),
async config => await GetParseBehaviorAsync(config)
]);
],
ct: TestContext.Current.CancellationToken);
// Assert
result.IsSuccess.Should().BeTrue(
@@ -152,7 +158,8 @@ public class ConcelierConfigDiffTests : ConfigDiffTestBase
changedConfig,
getBehavior: async config => await CaptureValidationBehaviorAsync(config),
computeDelta: ComputeBehaviorSnapshotDelta,
expectedDelta: expectedDelta);
expectedDelta: expectedDelta,
ct: TestContext.Current.CancellationToken);
// Assert
result.IsSuccess.Should().BeTrue();
@@ -186,11 +193,11 @@ public class ConcelierConfigDiffTests : ConfigDiffTestBase
ConfigurationId: $"retry-{config.RetryCount}",
Behaviors:
[
new CapturedBehavior("MaxRetryAttempts", config.RetryCount.ToString(), DateTimeOffset.UtcNow),
new CapturedBehavior("MaxRetryAttempts", config.RetryCount.ToString(), SnapshotTimestamp),
new CapturedBehavior("FailureRecoveryWindow",
config.RetryCount > 3 ? "increase" : "standard", DateTimeOffset.UtcNow)
config.RetryCount > 3 ? "increase" : "standard", SnapshotTimestamp)
],
CapturedAt: DateTimeOffset.UtcNow);
CapturedAt: SnapshotTimestamp);
return Task.FromResult(snapshot);
}
@@ -202,11 +209,11 @@ public class ConcelierConfigDiffTests : ConfigDiffTestBase
Behaviors:
[
new CapturedBehavior("ValidationStrictness",
config.StrictValidation ? "strict" : "relaxed", DateTimeOffset.UtcNow),
config.StrictValidation ? "strict" : "relaxed", SnapshotTimestamp),
new CapturedBehavior("RejectionRate",
config.StrictValidation ? "increase" : "standard", DateTimeOffset.UtcNow)
config.StrictValidation ? "increase" : "standard", SnapshotTimestamp)
],
CapturedAt: DateTimeOffset.UtcNow);
CapturedAt: SnapshotTimestamp);
return Task.FromResult(snapshot);
}

View File

@@ -51,9 +51,10 @@ public sealed class AcscConnectorFetchTests
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.Equal("Direct", state!.Cursor.GetValue("preferredEndpoint").AsString);
var stateValue = state!;
Assert.Equal("Direct", stateValue.Cursor.GetValue("preferredEndpoint").AsString);
var feeds = state.Cursor.GetValue("feeds").AsDocumentObject;
var feeds = stateValue.Cursor.GetValue("feeds").AsDocumentObject;
Assert.True(feeds.TryGetValue("alerts", out var published));
Assert.Equal(DateTime.Parse("2025-10-11T05:30:00Z").ToUniversalTime(), published.ToUniversalTime());
@@ -90,9 +91,10 @@ public sealed class AcscConnectorFetchTests
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.Equal("Relay", state!.Cursor.GetValue("preferredEndpoint").AsString);
var stateValue = state!;
Assert.Equal("Relay", stateValue.Cursor.GetValue("preferredEndpoint").AsString);
var feeds = state.Cursor.GetValue("feeds").AsDocumentObject;
var feeds = stateValue.Cursor.GetValue("feeds").AsDocumentObject;
Assert.True(feeds.TryGetValue("alerts", out var published));
Assert.Equal(DateTime.Parse("2025-10-11T00:00:00Z").ToUniversalTime(), published.ToUniversalTime());
@@ -142,7 +144,8 @@ public sealed class AcscConnectorFetchTests
var state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.Equal("Direct", state!.Cursor.GetValue("preferredEndpoint").AsString);
var stateValue = state!;
Assert.Equal("Direct", stateValue.Cursor.GetValue("preferredEndpoint").AsString);
Assert.Collection(harness.Handler.Requests,
request =>
@@ -175,7 +178,8 @@ public sealed class AcscConnectorFetchTests
var state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.Equal("Direct", state!.Cursor.GetValue("preferredEndpoint").AsString);
var stateValue = state!;
Assert.Equal("Direct", stateValue.Cursor.GetValue("preferredEndpoint").AsString);
Assert.Empty(harness.Handler.Requests);
}

View File

@@ -80,8 +80,9 @@ public sealed class AcscConnectorParseTests
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.DoesNotContain(document.Id.ToString(), state!.Cursor.GetValue("pendingDocuments").AsDocumentArray.Select(v => v.AsString));
Assert.Contains(document.Id.ToString(), state.Cursor.GetValue("pendingMappings").AsDocumentArray.Select(v => v.AsString));
var stateValue = state!;
Assert.DoesNotContain(document.Id.ToString(), stateValue.Cursor.GetValue("pendingDocuments").AsDocumentArray.Select(v => v.AsString));
Assert.Contains(document.Id.ToString(), stateValue.Cursor.GetValue("pendingMappings").AsDocumentArray.Select(v => v.AsString));
await connector.MapAsync(harness.ServiceProvider, CancellationToken.None);
@@ -103,7 +104,8 @@ public sealed class AcscConnectorParseTests
state = await stateRepository.TryGetAsync(AcscConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.True(state!.Cursor.GetValue("pendingMappings").AsDocumentArray.Count == 0);
stateValue = state!;
Assert.True(stateValue.Cursor.GetValue("pendingMappings").AsDocumentArray.Count == 0);
}
[Fact]

View File

@@ -18,7 +18,6 @@ using StellaOps.Concelier.Connector.Common.Http;
using StellaOps.Concelier.Connector.Common.Cursors;
using StellaOps.Concelier.Connector.Common.Testing;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Persistence.Postgres;
using StellaOps.Concelier.Testing;
using Xunit;
@@ -110,9 +109,10 @@ public sealed class CertCcConnectorFetchTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(CertCcConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
var stateValue = state!;
DocumentValue summaryValue;
Assert.True(state!.Cursor.TryGetValue("summary", out summaryValue));
Assert.True(stateValue.Cursor.TryGetValue("summary", out summaryValue));
var summaryDocument = Assert.IsType<DocumentObject>(summaryValue);
Assert.True(summaryDocument.TryGetValue("start", out _));
Assert.True(summaryDocument.TryGetValue("end", out _));

View File

@@ -23,7 +23,6 @@ using StellaOps.Concelier.Connector.Common.Cursors;
using StellaOps.Concelier.Connector.Common.Http;
using StellaOps.Concelier.Connector.Common.Testing;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Testing;
using Xunit;

View File

@@ -22,7 +22,6 @@ using StellaOps.Concelier.Connector.Common.Cursors;
using StellaOps.Concelier.Connector.Common.Http;
using StellaOps.Concelier.Connector.Common.Testing;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Testing;
using Xunit;
@@ -135,8 +134,9 @@ public sealed class CertCcConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(CertCcConnectorPlugin.SourceName, CancellationToken.None);
state.Should().NotBeNull();
state!.Cursor.Should().NotBeNull();
var pendingNotesCount = state.Cursor.TryGetValue("pendingNotes", out var pendingNotesValue)
var stateValue = state!;
stateValue.Cursor.Should().NotBeNull();
var pendingNotesCount = stateValue.Cursor.TryGetValue("pendingNotes", out var pendingNotesValue)
? pendingNotesValue!.AsDocumentArray.Count
: 0;
pendingNotesCount.Should().Be(0);
@@ -216,10 +216,11 @@ public sealed class CertCcConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(CertCcConnectorPlugin.SourceName, CancellationToken.None);
state.Should().NotBeNull();
state!.FailCount.Should().BeGreaterThan(0);
state.BackoffUntil.Should().NotBeNull();
state.BackoffUntil.Should().BeAfter(_timeProvider.GetUtcNow());
state.Cursor.TryGetValue("pendingNotes", out var pendingNotesValue).Should().BeTrue();
var stateValue = state!;
stateValue.FailCount.Should().BeGreaterThan(0);
stateValue.BackoffUntil.Should().NotBeNull();
stateValue.BackoffUntil.Should().BeAfter(_timeProvider.GetUtcNow());
stateValue.Cursor.TryGetValue("pendingNotes", out var pendingNotesValue).Should().BeTrue();
pendingNotesValue!.AsDocumentArray.Should().Contain(value => value.AsString == "294418");
var pendingSummaries = state.Cursor.TryGetValue("pendingSummaries", out var pendingSummariesValue)
? pendingSummariesValue!.AsDocumentArray.Count
@@ -259,11 +260,12 @@ public sealed class CertCcConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(CertCcConnectorPlugin.SourceName, CancellationToken.None);
state.Should().NotBeNull();
state!.Cursor.TryGetValue("pendingNotes", out var pendingNotesValue).Should().BeTrue();
var stateValue = state!;
stateValue.Cursor.TryGetValue("pendingNotes", out var pendingNotesValue).Should().BeTrue();
pendingNotesValue!.AsDocumentArray.Should().BeEmpty();
state.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue).Should().BeTrue();
stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue).Should().BeTrue();
pendingDocsValue!.AsDocumentArray.Should().BeEmpty();
state.Cursor.TryGetValue("pendingMappings", out var pendingMappingsValue).Should().BeTrue();
stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappingsValue).Should().BeTrue();
pendingMappingsValue!.AsDocumentArray.Should().BeEmpty();
}
@@ -298,11 +300,12 @@ public sealed class CertCcConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(CertCcConnectorPlugin.SourceName, CancellationToken.None);
state.Should().NotBeNull();
var pendingDocuments = state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
var stateValue = state!;
var pendingDocuments = stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
? pendingDocsValue!.AsDocumentArray.Count
: 0;
pendingDocuments.Should().BeGreaterThan(0);
var pendingMappings = state.Cursor.TryGetValue("pendingMappings", out var pendingMappingsValue)
var pendingMappings = stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappingsValue)
? pendingMappingsValue!.AsDocumentArray.Count
: 0;
pendingMappings.Should().Be(0);

View File

@@ -4,7 +4,6 @@ using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.CertCc.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using Xunit;
namespace StellaOps.Concelier.Connector.CertCc.Tests.Internal;

View File

@@ -95,11 +95,12 @@ public sealed class CertFrConnectorTests
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(CertFrConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.Equal(1, state!.FailCount);
Assert.NotNull(state.LastFailureReason);
Assert.Contains("500", state.LastFailureReason, StringComparison.Ordinal);
Assert.NotNull(state.BackoffUntil);
Assert.True(state.BackoffUntil > harness.TimeProvider.GetUtcNow());
var stateValue = state!;
Assert.Equal(1, stateValue.FailCount);
Assert.NotNull(stateValue.LastFailureReason);
Assert.Contains("500", stateValue.LastFailureReason, StringComparison.Ordinal);
Assert.NotNull(stateValue.BackoffUntil);
Assert.True(stateValue.BackoffUntil > harness.TimeProvider.GetUtcNow());
}
[Fact]
@@ -139,8 +140,9 @@ public sealed class CertFrConnectorTests
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(CertFrConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMaps) && pendingMaps.AsDocumentArray.Count == 0);
var stateValue = state!;
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMaps) && pendingMaps.AsDocumentArray.Count == 0);
}
[Fact]
@@ -182,8 +184,9 @@ public sealed class CertFrConnectorTests
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(CertFrConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMaps) && pendingMaps.AsDocumentArray.Count == 0);
var stateValue = state!;
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMaps) && pendingMaps.AsDocumentArray.Count == 0);
}
private async Task<ConnectorTestHarness> BuildHarnessAsync()

View File

@@ -25,8 +25,6 @@ using StellaOps.Concelier.Connector.Common.Testing;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Persistence.Postgres;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Testing;
namespace StellaOps.Concelier.Connector.CertIn.Tests;
@@ -100,7 +98,8 @@ public sealed class CertInConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(CertInConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pending));
var stateValue = state!;
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pending));
Assert.Empty(pending.AsDocumentArray);
}
@@ -131,11 +130,12 @@ public sealed class CertInConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(CertInConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.Equal(1, state!.FailCount);
Assert.NotNull(state.LastFailureReason);
Assert.Contains("500", state.LastFailureReason, StringComparison.Ordinal);
Assert.True(state.BackoffUntil.HasValue);
Assert.True(state.BackoffUntil!.Value > _timeProvider.GetUtcNow());
var stateValue = state!;
Assert.Equal(1, stateValue.FailCount);
Assert.NotNull(stateValue.LastFailureReason);
Assert.Contains("500", stateValue.LastFailureReason, StringComparison.Ordinal);
Assert.True(stateValue.BackoffUntil.HasValue);
Assert.True(stateValue.BackoffUntil!.Value > _timeProvider.GetUtcNow());
}
[Fact]
@@ -209,9 +209,9 @@ public sealed class CertInConnectorTests : IAsyncLifetime
var state = await stateRepository.TryGetAsync(CertInConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
Assert.Equal(0, pendingDocs.AsDocumentArray.Count);
Assert.Empty(pendingDocs.AsDocumentArray);
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings));
Assert.Equal(0, pendingMappings.AsDocumentArray.Count);
Assert.Empty(pendingMappings.AsDocumentArray);
}
[Fact]
@@ -263,10 +263,11 @@ public sealed class CertInConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(CertInConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
Assert.Equal(0, pendingDocs.AsDocumentArray.Count);
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings));
Assert.Equal(0, pendingMappings.AsDocumentArray.Count);
var stateValue = state!;
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
Assert.Empty(pendingDocs.AsDocumentArray);
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappings));
Assert.Empty(pendingMappings.AsDocumentArray);
}
private async Task EnsureServiceProviderAsync(CertInOptions template)

View File

@@ -14,7 +14,6 @@ using StellaOps.Concelier.Core.Aoc;
using StellaOps.Concelier.Core.Linksets;
using StellaOps.Concelier.RawModels;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Cryptography;
namespace StellaOps.Concelier.Connector.Common.Tests;

View File

@@ -10,7 +10,6 @@ using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Fetch;
using StellaOps.Concelier.Connector.Common.State;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Cryptography;
namespace StellaOps.Concelier.Connector.Common.Tests;
@@ -105,9 +104,10 @@ public sealed class SourceStateSeedProcessorTests : IAsyncLifetime
var state = await _stateRepository.TryGetAsync("vndr.test", CancellationToken.None);
Assert.NotNull(state);
Assert.Equal(_timeProvider.GetUtcNow().UtcDateTime, state!.LastSuccess);
var stateValue = state!;
Assert.Equal(_timeProvider.GetUtcNow().UtcDateTime, stateValue.LastSuccess);
var cursor = state.Cursor;
var cursor = stateValue.Cursor;
var pendingDocs = cursor["pendingDocuments"].AsDocumentArray.Select(v => Guid.Parse(v.AsString)).ToList();
Assert.Contains(documentId, pendingDocs);

View File

@@ -6,6 +6,8 @@
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<!-- This is a shared test library, not a standalone test project - disable ConcelierTestInfra to avoid duplicate types (CS0436) when referenced by actual test projects -->
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" />

View File

@@ -17,8 +17,6 @@ using StellaOps.Concelier.Connector.Cve.Internal;
using StellaOps.Concelier.Testing;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
namespace StellaOps.Concelier.Connector.Cve.Tests;

View File

@@ -56,9 +56,10 @@ public sealed class AlpineConnectorTests
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(AlpineConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs)
var stateValue = state!;
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs)
&& pendingDocs.AsDocumentArray.Count == 0);
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings)
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappings)
&& pendingMappings.AsDocumentArray.Count == 0);
harness.Handler.AssertNoPendingResponses();

View File

@@ -22,8 +22,6 @@ using StellaOps.Concelier.Connector.Common.Testing;
using StellaOps.Concelier.Connector.Distro.Debian.Configuration;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Testing;
using Xunit;

View File

@@ -107,8 +107,9 @@ public sealed class RedHatConnectorHarnessTests : IAsyncLifetime
var state = await stateRepository.TryGetAsync(RedHatConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings) && pendingMappings.AsDocumentArray.Count == 0);
var stateValue = state!;
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappings) && pendingMappings.AsDocumentArray.Count == 0);
}
public ValueTask InitializeAsync() => ValueTask.CompletedTask;

View File

@@ -25,8 +25,6 @@ using StellaOps.Concelier.Connector.Distro.RedHat.Internal;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Persistence.Postgres;
using StellaOps.Concelier.Testing;
using StellaOps.Plugin;
@@ -170,8 +168,9 @@ public sealed class RedHatConnectorTests : IAsyncLifetime
var state = await stateRepository.TryGetAsync(RedHatConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs2) && pendingDocs2.AsDocumentArray.Count == 0);
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings2) && pendingMappings2.AsDocumentArray.Count == 0);
var stateValue = state!;
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs2) && pendingDocs2.AsDocumentArray.Count == 0);
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappings2) && pendingMappings2.AsDocumentArray.Count == 0);
const string fetchKind = "source:redhat:fetch";
const string parseKind = "source:redhat:parse";
@@ -241,8 +240,9 @@ public sealed class RedHatConnectorTests : IAsyncLifetime
state = await stateRepository.TryGetAsync(RedHatConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs3) && pendingDocs3.AsDocumentArray.Count == 0);
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings3) && pendingMappings3.AsDocumentArray.Count == 0);
stateValue = state!;
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs3) && pendingDocs3.AsDocumentArray.Count == 0);
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappings3) && pendingMappings3.AsDocumentArray.Count == 0);
}
[Fact]
@@ -338,7 +338,8 @@ public sealed class RedHatConnectorTests : IAsyncLifetime
var state = await stateRepository.TryGetAsync(RedHatConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
var pendingDocs = state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
var stateValue = state!;
var pendingDocs = stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
? pendingDocsValue.AsDocumentArray
: new DocumentArray();
Assert.NotEmpty(pendingDocs);
@@ -368,9 +369,10 @@ public sealed class RedHatConnectorTests : IAsyncLifetime
var stateRepository = resumeProvider.GetRequiredService<ISourceStateRepository>();
var finalState = await stateRepository.TryGetAsync(RedHatConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(finalState);
var finalPendingDocs = finalState!.Cursor.TryGetValue("pendingDocuments", out var docsValue) ? docsValue.AsDocumentArray : new DocumentArray();
var finalStateValue = finalState!;
var finalPendingDocs = finalStateValue.Cursor.TryGetValue("pendingDocuments", out var docsValue) ? docsValue.AsDocumentArray : new DocumentArray();
Assert.Empty(finalPendingDocs);
var finalPendingMappings = finalState.Cursor.TryGetValue("pendingMappings", out var mappingsValue) ? mappingsValue.AsDocumentArray : new DocumentArray();
var finalPendingMappings = finalStateValue.Cursor.TryGetValue("pendingMappings", out var mappingsValue) ? mappingsValue.AsDocumentArray : new DocumentArray();
Assert.Empty(finalPendingMappings);
}
}

View File

@@ -142,10 +142,11 @@ public sealed class GhsaConnectorTests : IAsyncLifetime
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(GhsaConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
var stateValue = state!;
Assert.True(state!.Cursor.TryGetValue("currentWindowStart", out var startValue));
Assert.True(state.Cursor.TryGetValue("currentWindowEnd", out var endValue));
Assert.True(state.Cursor.TryGetValue("nextPage", out var nextPageValue));
Assert.True(stateValue.Cursor.TryGetValue("currentWindowStart", out var startValue));
Assert.True(stateValue.Cursor.TryGetValue("currentWindowEnd", out var endValue));
Assert.True(stateValue.Cursor.TryGetValue("nextPage", out var nextPageValue));
Assert.Equal(since.UtcDateTime, startValue.ToUniversalTime());
Assert.Equal(until.UtcDateTime, endValue.ToUniversalTime());

View File

@@ -70,7 +70,7 @@ public sealed class IcsCisaConnectorTests
var icsma = Assert.Single(advisories, advisory => advisory.AdvisoryKey == "ICSMA-25-045-01");
Assert.Contains("CVE-2025-11111", icsma.Aliases);
var icsmaMitigation = Assert.Single(icsma.References.Where(reference => reference.Kind == "mitigation"));
var icsmaMitigation = Assert.Single(icsma.References, reference => reference.Kind == "mitigation");
Assert.Contains("Contact HealthTech support", icsmaMitigation.Summary, StringComparison.Ordinal);
Assert.Contains(icsma.References, reference => reference.Url == "https://www.cisa.gov/sites/default/files/2025-10/ICSMA-25-045-01_Supplement.pdf");
var infusionPackage = Assert.Single(icsma.AffectedPackages, package => string.Equals(package.Identifier, "InfusionManager", StringComparison.OrdinalIgnoreCase));

View File

@@ -21,8 +21,6 @@ using StellaOps.Concelier.Connector.Ics.Kaspersky;
using StellaOps.Concelier.Connector.Ics.Kaspersky.Configuration;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Persistence.Postgres;
using StellaOps.Concelier.Testing;
@@ -97,7 +95,8 @@ public sealed class KasperskyConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(KasperskyConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
var pendingDocuments = state!.Cursor.TryGetValue("pendingDocuments", out var pending)
var stateValue = state!;
var pendingDocuments = stateValue.Cursor.TryGetValue("pendingDocuments", out var pending)
? pending.AsDocumentArray
: new DocumentArray();
Assert.Empty(pendingDocuments);
@@ -130,11 +129,12 @@ public sealed class KasperskyConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(KasperskyConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.Equal(1, state!.FailCount);
Assert.NotNull(state.LastFailureReason);
Assert.Contains("500", state.LastFailureReason, StringComparison.Ordinal);
Assert.True(state.BackoffUntil.HasValue);
Assert.True(state.BackoffUntil!.Value > _timeProvider.GetUtcNow());
var stateValue = state!;
Assert.Equal(1, stateValue.FailCount);
Assert.NotNull(stateValue.LastFailureReason);
Assert.Contains("500", stateValue.LastFailureReason, StringComparison.Ordinal);
Assert.True(stateValue.BackoffUntil.HasValue);
Assert.True(stateValue.BackoffUntil!.Value > _timeProvider.GetUtcNow());
}
[Fact]
@@ -202,10 +202,11 @@ public sealed class KasperskyConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(KasperskyConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
Assert.Equal(0, pendingDocs.AsDocumentArray.Count);
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings));
Assert.Equal(0, pendingMappings.AsDocumentArray.Count);
var stateValue = state!;
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
Assert.Empty(pendingDocs.AsDocumentArray);
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappings));
Assert.Empty(pendingMappings.AsDocumentArray);
}
[Fact]

View File

@@ -20,8 +20,6 @@ using StellaOps.Concelier.Connector.Jvn;
using StellaOps.Concelier.Connector.Jvn.Configuration;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.JpFlags;
using StellaOps.Concelier.Persistence.Postgres;
using StellaOps.Concelier.Testing;
@@ -123,7 +121,8 @@ public sealed class JvnConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(JvnConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
var stateValue = state!;
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
Assert.Empty(pendingDocs.AsDocumentArray);
}

View File

@@ -71,10 +71,11 @@ public sealed class KevConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(KevConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.Equal("2025.10.09", state!.Cursor.TryGetValue("catalogVersion", out var versionValue) ? versionValue.AsString : null);
Assert.True(state.Cursor.TryGetValue("catalogReleased", out var releasedValue) && releasedValue.DocumentType is DocumentType.DateTime);
Assert.True(IsEmptyArray(state.Cursor, "pendingDocuments"));
Assert.True(IsEmptyArray(state.Cursor, "pendingMappings"));
var stateValue = state!;
Assert.Equal("2025.10.09", stateValue.Cursor.TryGetValue("catalogVersion", out var versionValue) ? versionValue.AsString : null);
Assert.True(stateValue.Cursor.TryGetValue("catalogReleased", out var releasedValue) && releasedValue.DocumentType is DocumentType.DateTime);
Assert.True(IsEmptyArray(stateValue.Cursor, "pendingDocuments"));
Assert.True(IsEmptyArray(stateValue.Cursor, "pendingMappings"));
}
private async Task<ServiceProvider> BuildServiceProviderAsync()

View File

@@ -23,8 +23,6 @@ using StellaOps.Concelier.Connector.Kisa.Internal;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Persistence.Postgres;
using StellaOps.Concelier.Testing;
using Xunit;

View File

@@ -10,7 +10,6 @@ using StellaOps.Concelier.Connector.Nvd;
using StellaOps.Concelier.Connector.Nvd.Configuration;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Testing;
using StellaOps.Concelier.Testing;
using System.Net;
@@ -74,15 +73,18 @@ public sealed class NvdConnectorHarnessTests : IAsyncLifetime
var firstDocument = await documentStore.FindBySourceAndUriAsync(NvdConnectorPlugin.SourceName, firstUri.ToString(), CancellationToken.None);
Assert.NotNull(firstDocument);
Assert.Equal("0", firstDocument!.Metadata["startIndex"]);
Assert.NotNull(firstDocument!.Metadata);
Assert.Equal("0", firstDocument.Metadata["startIndex"]);
var secondDocument = await documentStore.FindBySourceAndUriAsync(NvdConnectorPlugin.SourceName, secondUri.ToString(), CancellationToken.None);
Assert.NotNull(secondDocument);
Assert.Equal("2", secondDocument!.Metadata["startIndex"]);
Assert.NotNull(secondDocument!.Metadata);
Assert.Equal("2", secondDocument.Metadata["startIndex"]);
var thirdDocument = await documentStore.FindBySourceAndUriAsync(NvdConnectorPlugin.SourceName, thirdUri.ToString(), CancellationToken.None);
Assert.NotNull(thirdDocument);
Assert.Equal("4", thirdDocument!.Metadata["startIndex"]);
Assert.NotNull(thirdDocument!.Metadata);
Assert.Equal("4", thirdDocument.Metadata["startIndex"]);
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);

View File

@@ -22,8 +22,6 @@ using StellaOps.Concelier.Connector.Nvd.Configuration;
using StellaOps.Concelier.Connector.Nvd.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.ChangeHistory;
using StellaOps.Concelier.Persistence.Postgres;
using StellaOps.Concelier.Testing;
@@ -146,7 +144,8 @@ public sealed class NvdConnectorTests : IAsyncLifetime
var documentStore = provider.GetRequiredService<IDocumentStore>();
var finalState = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(finalState);
var pendingDocuments = finalState!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs)
var finalStateValue = finalState!;
var pendingDocuments = finalStateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs)
? pendingDocs.AsDocumentArray
: new DocumentArray();
Assert.Empty(pendingDocuments);
@@ -188,7 +187,8 @@ public sealed class NvdConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
var pendingDocuments = state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs)
var stateValue = state!;
var pendingDocuments = stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs)
? pendingDocs.AsDocumentArray.Select(v => Guid.Parse(v.AsString)).ToArray()
: Array.Empty<Guid>();
Assert.Equal(3, pendingDocuments.Length);
@@ -280,8 +280,9 @@ public sealed class NvdConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
var stateValue = state!;
var cursorDocument = state!.Cursor;
var cursorDocument = stateValue.Cursor;
var lastWindowEnd = cursorDocument.TryGetValue("windowEnd", out var endValue) ? ReadDateTime(endValue) : (DateTimeOffset?)null;
var startCandidate = (lastWindowEnd ?? windowEnd) - options.WindowOverlap;
var backfillLimit = now - options.InitialBackfill;
@@ -350,9 +351,10 @@ public sealed class NvdConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
var pendingDocs = state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue) ? pendingDocsValue.AsDocumentArray : new DocumentArray();
var stateValue = state!;
var pendingDocs = stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue) ? pendingDocsValue.AsDocumentArray : new DocumentArray();
Assert.Empty(pendingDocs);
var pendingMappings = state.Cursor.TryGetValue("pendingMappings", out var pendingMappingsValue) ? pendingMappingsValue.AsDocumentArray : new DocumentArray();
var pendingMappings = stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMappingsValue) ? pendingMappingsValue.AsDocumentArray : new DocumentArray();
Assert.Empty(pendingMappings);
Assert.Equal(1, collector.GetValue("nvd.fetch.documents"));
@@ -462,7 +464,8 @@ public sealed class NvdConnectorTests : IAsyncLifetime
var stateRepository = fetchProvider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
var pending = state!.Cursor.TryGetValue("pendingDocuments", out var value)
var stateValue = state!;
var pending = stateValue.Cursor.TryGetValue("pendingDocuments", out var value)
? value.AsDocumentArray
: new DocumentArray();
Assert.NotEmpty(pending);
@@ -492,7 +495,8 @@ public sealed class NvdConnectorTests : IAsyncLifetime
var stateRepository = resumeProvider.GetRequiredService<ISourceStateRepository>();
var finalState = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(finalState);
var cursor = finalState!.Cursor;
var finalStateValue = finalState!;
var cursor = finalStateValue.Cursor;
var finalPendingDocs = cursor.TryGetValue("pendingDocuments", out var pendingDocs) ? pendingDocs.AsDocumentArray : new DocumentArray();
Assert.Empty(finalPendingDocs);
var finalPendingMappings = cursor.TryGetValue("pendingMappings", out var pendingMappings) ? pendingMappings.AsDocumentArray : new DocumentArray();

View File

@@ -3,7 +3,6 @@ using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Osv.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
namespace StellaOps.Concelier.Connector.Osv.Tests;

View File

@@ -14,7 +14,6 @@ using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Osv;
using StellaOps.Concelier.Connector.Osv.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Cryptography;
using Xunit;

View File

@@ -10,7 +10,6 @@ using StellaOps.Concelier.Connector.Osv;
using StellaOps.Concelier.Connector.Osv.Internal;
using StellaOps.Concelier.Normalization.Identifiers;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using Xunit;
namespace StellaOps.Concelier.Connector.Osv.Tests;

View File

@@ -7,7 +7,6 @@ using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Osv;
using StellaOps.Concelier.Connector.Osv.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Connector.Common;
using Xunit;

View File

@@ -21,8 +21,6 @@ using StellaOps.Concelier.Connector.Ru.Bdu.Configuration;
using StellaOps.Concelier.Connector.Ru.Bdu.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Testing;
using StellaOps.Cryptography;
using Xunit;

View File

@@ -21,7 +21,6 @@ using StellaOps.Concelier.Connector.Ru.Nkcki;
using StellaOps.Concelier.Connector.Ru.Nkcki.Configuration;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Testing;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Persistence.Postgres;

View File

@@ -20,8 +20,6 @@ using StellaOps.Concelier.Connector.StellaOpsMirror.Internal;
using StellaOps.Concelier.Connector.StellaOpsMirror.Settings;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Persistence.Postgres;
using StellaOps.Concelier.Testing;
using StellaOps.Cryptography;

View File

@@ -23,8 +23,6 @@ using StellaOps.Concelier.Connector.Vndr.Adobe;
using StellaOps.Concelier.Connector.Vndr.Adobe.Configuration;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.PsirtFlags;
using StellaOps.Concelier.Persistence.Postgres;
using StellaOps.Concelier.Testing;
@@ -132,7 +130,8 @@ public sealed class AdobeConnectorFetchTests : IAsyncLifetime
var state = await stateRepository.TryGetAsync(VndrAdobeConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
var cursor = state!.Cursor;
var stateValue = state!;
var cursor = stateValue.Cursor;
Assert.True(!cursor.TryGetValue("pendingDocuments", out _)
|| cursor.GetValue("pendingDocuments").AsDocumentArray.Count == 0);
Assert.True(!cursor.TryGetValue("pendingMappings", out _)
@@ -314,8 +313,9 @@ public sealed class AdobeConnectorFetchTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(VndrAdobeConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
Assert.True(state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMap) && pendingMap.AsDocumentArray.Count == 0);
var stateValue = state!;
Assert.True(stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocs) && pendingDocs.AsDocumentArray.Count == 0);
Assert.True(stateValue.Cursor.TryGetValue("pendingMappings", out var pendingMap) && pendingMap.AsDocumentArray.Count == 0);
}
private async Task<ServiceProvider> BuildServiceProviderAsync(CannedHttpMessageHandler handler)

View File

@@ -19,7 +19,6 @@ using StellaOps.Concelier.Connector.Vndr.Chromium;
using StellaOps.Concelier.Connector.Vndr.Chromium.Configuration;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.PsirtFlags;
using StellaOps.Concelier.Testing;
@@ -137,7 +136,8 @@ public sealed class ChromiumConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(VndrChromiumConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
var pendingDocuments = state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
var stateValue = state!;
var pendingDocuments = stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
? pendingDocsValue.AsDocumentArray
: new DocumentArray();
Assert.Empty(pendingDocuments);
@@ -167,7 +167,8 @@ public sealed class ChromiumConnectorTests : IAsyncLifetime
var stateRepository = fetchProvider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(VndrChromiumConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
var pendingDocuments = state!.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
var stateValue = state!;
var pendingDocuments = stateValue.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
? pendingDocsValue.AsDocumentArray
: new DocumentArray();
Assert.NotEmpty(pendingDocuments);
@@ -180,7 +181,8 @@ public sealed class ChromiumConnectorTests : IAsyncLifetime
var stateRepositoryBefore = resumeProvider.GetRequiredService<ISourceStateRepository>();
var resumeState = await stateRepositoryBefore.TryGetAsync(VndrChromiumConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(resumeState);
var resumePendingDocs = resumeState!.Cursor.TryGetValue("pendingDocuments", out var resumePendingValue)
var resumeStateValue = resumeState!;
var resumePendingDocs = resumeStateValue.Cursor.TryGetValue("pendingDocuments", out var resumePendingValue)
? resumePendingValue.AsDocumentArray
: new DocumentArray();
Assert.Equal(pendingDocumentIds.Length, resumePendingDocs.Count);
@@ -202,12 +204,13 @@ public sealed class ChromiumConnectorTests : IAsyncLifetime
var stateRepositoryAfter = resumeProvider.GetRequiredService<ISourceStateRepository>();
var finalState = await stateRepositoryAfter.TryGetAsync(VndrChromiumConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(finalState);
var finalPending = finalState!.Cursor.TryGetValue("pendingDocuments", out var finalPendingDocs)
var finalStateValue = finalState!;
var finalPending = finalStateValue.Cursor.TryGetValue("pendingDocuments", out var finalPendingDocs)
? finalPendingDocs.AsDocumentArray
: new DocumentArray();
Assert.Empty(finalPending);
var finalPendingMappings = finalState.Cursor.TryGetValue("pendingMappings", out var finalPendingMappingsValue)
var finalPendingMappings = finalStateValue.Cursor.TryGetValue("pendingMappings", out var finalPendingMappingsValue)
? finalPendingMappingsValue.AsDocumentArray
: new DocumentArray();
Assert.Empty(finalPendingMappings);
@@ -246,7 +249,8 @@ public sealed class ChromiumConnectorTests : IAsyncLifetime
var stateRepository = provider.GetRequiredService<ISourceStateRepository>();
var state = await stateRepository.TryGetAsync(VndrChromiumConnectorPlugin.SourceName, CancellationToken.None);
Assert.NotNull(state);
var cursor = state!.Cursor;
var stateValue = state!;
var cursor = stateValue.Cursor;
var pendingDocuments = cursor.TryGetValue("pendingDocuments", out var pendingDocsValue)
? pendingDocsValue.AsDocumentArray
: new DocumentArray();

View File

@@ -8,7 +8,6 @@ using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Vndr.Cisco;
using StellaOps.Concelier.Connector.Vndr.Cisco.Internal;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using Xunit;
using StellaOps.TestKit;

Some files were not shown because too many files have changed in this diff Show More