Restructure solution layout by module

This commit is contained in:
master
2025-10-28 15:10:40 +02:00
parent 95daa159c4
commit d870da18ce
4103 changed files with 192899 additions and 187024 deletions

View File

@@ -0,0 +1,40 @@
{
"offset": 0,
"limit": 1,
"total_results": 2,
"notices": [
{
"id": "USN-9001-1",
"title": "Kernel update",
"summary": "Kernel fixes",
"published": "2025-01-20T08:30:00Z",
"cves_ids": [
"CVE-2025-2000"
],
"cves": [
{
"id": "CVE-2025-2000"
}
],
"references": [],
"release_packages": {
"noble": [
{
"name": "linux-image",
"version": "6.8.0-1010.11",
"pocket": "security",
"is_source": false
}
],
"focal": [
{
"name": "linux-image",
"version": "5.15.0-200.0",
"pocket": "esm-infra",
"is_source": false
}
]
}
}
]
}

View File

@@ -0,0 +1,42 @@
{
"offset": 1,
"limit": 1,
"total_results": 2,
"notices": [
{
"id": "USN-9000-1",
"title": "Example security update",
"summary": "Package fixes",
"published": "2025-01-15T12:00:00Z",
"cves_ids": [
"CVE-2025-1000",
"CVE-2025-1001"
],
"cves": [
{
"id": "CVE-2025-1000"
},
{
"id": "CVE-2025-1001"
}
],
"references": [
{
"url": "https://ubuntu.com/security/USN-9000-1",
"category": "self",
"summary": "USN"
}
],
"release_packages": {
"jammy": [
{
"name": "examplepkg",
"version": "1.2.3-0ubuntu0.22.04.1",
"pocket": "security",
"is_source": false
}
]
}
}
]
}

View File

@@ -0,0 +1,19 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Distro.Ubuntu/StellaOps.Concelier.Connector.Distro.Ubuntu.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Fixtures\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,176 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
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 Microsoft.Extensions.Time.Testing;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Http;
using StellaOps.Concelier.Connector.Common.Testing;
using StellaOps.Concelier.Connector.Distro.Ubuntu;
using StellaOps.Concelier.Connector.Distro.Ubuntu.Configuration;
using StellaOps.Concelier.Storage.Mongo;
using StellaOps.Concelier.Storage.Mongo.Advisories;
using StellaOps.Concelier.Testing;
using Xunit;
namespace StellaOps.Concelier.Connector.Distro.Ubuntu.Tests;
[Collection("mongo-fixture")]
public sealed class UbuntuConnectorTests : IAsyncLifetime
{
private static readonly Uri IndexPage0Uri = new("https://ubuntu.com/security/notices.json?offset=0&limit=1");
private static readonly Uri IndexPage1Uri = new("https://ubuntu.com/security/notices.json?offset=1&limit=1");
private readonly MongoIntegrationFixture _fixture;
private readonly FakeTimeProvider _timeProvider;
private readonly CannedHttpMessageHandler _handler;
public UbuntuConnectorTests(MongoIntegrationFixture fixture)
{
_fixture = fixture;
_timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 1, 25, 0, 0, 0, TimeSpan.Zero));
_handler = new CannedHttpMessageHandler();
}
[Fact]
public async Task FetchParseMap_GeneratesEvrRangePrimitives()
{
await using var provider = await BuildServiceProviderAsync();
SeedInitialResponses();
var connector = provider.GetRequiredService<UbuntuConnector>();
await connector.FetchAsync(provider, CancellationToken.None);
_timeProvider.Advance(TimeSpan.FromMinutes(1));
await connector.ParseAsync(provider, CancellationToken.None);
await connector.MapAsync(provider, CancellationToken.None);
var advisoryStore = provider.GetRequiredService<IAdvisoryStore>();
var advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None);
Assert.Equal(2, advisories.Count);
var kernelNotice = advisories.Single(a => a.AdvisoryKey == "USN-9001-1");
var noblePackage = Assert.Single(kernelNotice.AffectedPackages, pkg => pkg.Platform == "noble");
var range = Assert.Single(noblePackage.VersionRanges);
Assert.Equal("evr", range.RangeKind);
Assert.NotNull(range.Primitives);
Assert.NotNull(range.Primitives!.Evr?.Fixed);
Assert.Contains("CVE-2025-2000", kernelNotice.Aliases);
var normalizedRule = Assert.Single(noblePackage.NormalizedVersions);
Assert.Equal(NormalizedVersionSchemes.Evr, normalizedRule.Scheme);
Assert.Equal(NormalizedVersionRuleTypes.LessThan, normalizedRule.Type);
Assert.Equal(range.Primitives.Evr!.Fixed!.ToCanonicalString(), normalizedRule.Max);
Assert.Equal("ubuntu:noble", normalizedRule.Notes);
SeedNotModifiedResponses();
await connector.FetchAsync(provider, CancellationToken.None);
_timeProvider.Advance(TimeSpan.FromMinutes(1));
await connector.ParseAsync(provider, CancellationToken.None);
await connector.MapAsync(provider, CancellationToken.None);
advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None);
Assert.Equal(2, advisories.Count);
_handler.AssertNoPendingResponses();
}
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<TimeProvider>(_timeProvider);
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.AddUbuntuConnector(options =>
{
options.NoticesEndpoint = new Uri("https://ubuntu.com/security/notices.json");
options.NoticeDetailBaseUri = new Uri("https://ubuntu.com/security/");
options.MaxNoticesPerFetch = 2;
options.IndexPageSize = 1;
});
services.Configure<HttpClientFactoryOptions>(UbuntuOptions.HttpClientName, builderOptions =>
{
builderOptions.HttpMessageHandlerBuilderActions.Add(builder =>
{
builder.PrimaryHandler = _handler;
});
});
var provider = services.BuildServiceProvider();
var bootstrapper = provider.GetRequiredService<MongoBootstrapper>();
await bootstrapper.InitializeAsync(CancellationToken.None);
return provider;
}
private void SeedInitialResponses()
{
_handler.AddResponse(IndexPage0Uri, () =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(ReadFixture("Fixtures/ubuntu-notices-page0.json"), Encoding.UTF8, "application/json")
};
response.Headers.ETag = new EntityTagHeaderValue("\"index-page0-v1\"");
return response;
});
_handler.AddResponse(IndexPage1Uri, () =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(ReadFixture("Fixtures/ubuntu-notices-page1.json"), Encoding.UTF8, "application/json")
};
response.Headers.ETag = new EntityTagHeaderValue("\"index-page1-v1\"");
return response;
});
}
private void SeedNotModifiedResponses()
{
_handler.AddResponse(IndexPage0Uri, () =>
{
var response = new HttpResponseMessage(HttpStatusCode.NotModified);
response.Headers.ETag = new EntityTagHeaderValue("\"index-page0-v1\"");
return response;
});
// Page 1 remains cached; the connector should skip fetching it when page 0 is unchanged.
}
private static string ReadFixture(string relativePath)
{
var path = Path.Combine(AppContext.BaseDirectory, relativePath.Replace('/', Path.DirectorySeparatorChar));
if (!File.Exists(path))
{
throw new FileNotFoundException($"Fixture '{relativePath}' not found.", path);
}
return File.ReadAllText(path);
}
public Task InitializeAsync() => Task.CompletedTask;
public Task DisposeAsync() => Task.CompletedTask;
}