feat: add Reachability Center and Why Drawer components with tests
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled

- Implemented ReachabilityCenterComponent for displaying asset reachability status with summary and filtering options.
- Added ReachabilityWhyDrawerComponent to show detailed reachability evidence and call paths.
- Created unit tests for both components to ensure functionality and correctness.
- Updated accessibility test results for the new components.
This commit is contained in:
master
2025-12-12 18:50:35 +02:00
parent efaf3cb789
commit 3f3473ee3a
320 changed files with 10635 additions and 3677 deletions

View File

@@ -3,50 +3,45 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Concelier.Connector.Common.Testing;
using StellaOps.Concelier.Connector.Common.Http;
using StellaOps.Concelier.Connector.Ics.Cisa;
using StellaOps.Concelier.Connector.Ics.Cisa.Configuration;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo.Advisories;
using StellaOps.Concelier.Testing;
using Xunit;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Concelier.Connector.Common.Testing;
using StellaOps.Concelier.Connector.Ics.Cisa;
using StellaOps.Concelier.Connector.Ics.Cisa.Configuration;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Testing;
using Xunit;
namespace StellaOps.Concelier.Connector.Ics.Cisa.Tests;
[Collection("mongo-fixture")]
public sealed class IcsCisaConnectorTests : IAsyncLifetime
{
private readonly MongoIntegrationFixture _fixture;
private readonly CannedHttpMessageHandler _handler = new();
[Collection(ConcelierFixtureCollection.Name)]
public sealed class IcsCisaConnectorTests
{
private readonly ConcelierPostgresFixture _fixture;
public IcsCisaConnectorTests(ConcelierPostgresFixture fixture)
{
_fixture = fixture ?? throw new ArgumentNullException(nameof(fixture));
}
public IcsCisaConnectorTests(MongoIntegrationFixture fixture)
{
_fixture = fixture ?? throw new ArgumentNullException(nameof(fixture));
}
[Fact]
public async Task FetchParseMap_EndToEnd_ProducesCanonicalAdvisories()
{
await using var provider = await BuildServiceProviderAsync();
RegisterResponses();
var connector = provider.GetRequiredService<IcsCisaConnector>();
await connector.FetchAsync(provider, CancellationToken.None);
await connector.ParseAsync(provider, CancellationToken.None);
await connector.MapAsync(provider, CancellationToken.None);
_handler.AssertNoPendingResponses();
var advisoryStore = provider.GetRequiredService<IAdvisoryStore>();
var advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None);
[Fact]
public async Task FetchParseMap_EndToEnd_ProducesCanonicalAdvisories()
{
await using var harness = await BuildHarnessAsync();
RegisterResponses(harness.Handler);
var connector = harness.ServiceProvider.GetRequiredService<IcsCisaConnector>();
await connector.FetchAsync(harness.ServiceProvider, CancellationToken.None);
await connector.ParseAsync(harness.ServiceProvider, CancellationToken.None);
await connector.MapAsync(harness.ServiceProvider, CancellationToken.None);
harness.Handler.AssertNoPendingResponses();
var advisoryStore = harness.ServiceProvider.GetRequiredService<IAdvisoryStore>();
var advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None);
Assert.Equal(2, advisories.Count);
@@ -78,78 +73,48 @@ public sealed class IcsCisaConnectorTests : IAsyncLifetime
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));
var infusionRange = Assert.Single(infusionPackage.VersionRanges);
Assert.Equal("2.1", infusionRange.RangeExpression);
}
private async Task<ServiceProvider> BuildServiceProviderAsync()
{
await _fixture.Client.DropDatabaseAsync(_fixture.Database.DatabaseNamespace.DatabaseName);
_handler.Clear();
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddProvider(NullLoggerProvider.Instance));
services.AddSingleton(_handler);
services.AddMongoStorage(options =>
{
options.ConnectionString = _fixture.Runner.ConnectionString;
options.DatabaseName = _fixture.Database.DatabaseNamespace.DatabaseName;
options.CommandTimeout = TimeSpan.FromSeconds(5);
});
services.AddSourceCommon();
services.AddIcsCisaConnector(options =>
{
options.GovDeliveryCode = "TESTCODE";
options.TopicsEndpoint = new Uri("https://feed.test/topics.rss", UriKind.Absolute);
options.TopicIds.Clear();
options.TopicIds.Add("USDHSCISA_TEST");
options.RequestDelay = TimeSpan.Zero;
options.DetailBaseUri = new Uri("https://www.cisa.gov/", UriKind.Absolute);
options.AdditionalHosts.Add("files.cisa.gov");
});
services.Configure<HttpClientFactoryOptions>(IcsCisaOptions.HttpClientName, builder =>
{
builder.HttpMessageHandlerBuilderActions.Add(handlerBuilder =>
{
handlerBuilder.PrimaryHandler = _handler;
});
});
var provider = services.BuildServiceProvider();
var bootstrapper = provider.GetRequiredService<MongoBootstrapper>();
await bootstrapper.InitializeAsync(CancellationToken.None);
return provider;
}
private void RegisterResponses()
{
var feedUri = new Uri("https://feed.test/topics.rss?code=TESTCODE&format=xml&topic_id=USDHSCISA_TEST", UriKind.Absolute);
_handler.AddResponse(feedUri, () => CreateTextResponse("IcsCisa/Fixtures/sample-feed.xml", "application/rss+xml"));
var icsaDetail = new Uri("https://www.cisa.gov/news-events/ics-advisories/icsa-25-123-01", UriKind.Absolute);
_handler.AddResponse(icsaDetail, () => CreateTextResponse("IcsCisa/Fixtures/icsa-25-123-01.html", "text/html"));
var icsmaDetail = new Uri("https://www.cisa.gov/news-events/ics-medical-advisories/icsma-25-045-01", UriKind.Absolute);
_handler.AddResponse(icsmaDetail, () => CreateTextResponse("IcsCisa/Fixtures/icsma-25-045-01.html", "text/html"));
}
Assert.Equal("2.1", infusionRange.RangeExpression);
}
private async Task<ConnectorTestHarness> BuildHarnessAsync()
{
var initialTime = new DateTimeOffset(2025, 10, 12, 0, 0, 0, TimeSpan.Zero);
var harness = new ConnectorTestHarness(_fixture, initialTime, IcsCisaOptions.HttpClientName);
await harness.EnsureServiceProviderAsync(services =>
{
services.AddIcsCisaConnector(options =>
{
options.GovDeliveryCode = "TESTCODE";
options.TopicsEndpoint = new Uri("https://feed.test/topics.rss", UriKind.Absolute);
options.TopicIds.Clear();
options.TopicIds.Add("USDHSCISA_TEST");
options.RequestDelay = TimeSpan.Zero;
options.DetailBaseUri = new Uri("https://www.cisa.gov/", UriKind.Absolute);
options.AdditionalHosts.Add("files.cisa.gov");
});
});
return harness;
}
private static void RegisterResponses(CannedHttpMessageHandler handler)
{
var feedUri = new Uri("https://feed.test/topics.rss?code=TESTCODE&format=xml&topic_id=USDHSCISA_TEST", UriKind.Absolute);
handler.AddResponse(feedUri, () => CreateTextResponse("IcsCisa/Fixtures/sample-feed.xml", "application/rss+xml"));
var icsaDetail = new Uri("https://www.cisa.gov/news-events/ics-advisories/icsa-25-123-01", UriKind.Absolute);
handler.AddResponse(icsaDetail, () => CreateTextResponse("IcsCisa/Fixtures/icsa-25-123-01.html", "text/html"));
var icsmaDetail = new Uri("https://www.cisa.gov/news-events/ics-medical-advisories/icsma-25-045-01", UriKind.Absolute);
handler.AddResponse(icsmaDetail, () => CreateTextResponse("IcsCisa/Fixtures/icsma-25-045-01.html", "text/html"));
}
private static HttpResponseMessage CreateTextResponse(string relativePath, string contentType)
{
var fullPath = Path.Combine(AppContext.BaseDirectory, relativePath);
var content = File.ReadAllText(fullPath);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(content, Encoding.UTF8, contentType),
};
}
public Task InitializeAsync() => Task.CompletedTask;
public Task DisposeAsync()
{
_handler.Clear();
return Task.CompletedTask;
}
}
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(content, Encoding.UTF8, contentType),
};
}
}