Merge branch 'main' of https://git.stella-ops.org/stella-ops.org/git.stella-ops.org
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"]);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 _));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user