Rename Vexer to Excititor
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Excititor.Connectors.Oracle.CSAF.Configuration;
|
||||
using StellaOps.Excititor.Connectors.Oracle.CSAF.Metadata;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using Xunit;
|
||||
using System.Threading;
|
||||
|
||||
namespace StellaOps.Excititor.Connectors.Oracle.CSAF.Tests.Metadata;
|
||||
|
||||
public sealed class OracleCatalogLoaderTests
|
||||
{
|
||||
private const string SampleCatalog = """
|
||||
{
|
||||
"generated": "2025-09-30T18:00:00Z",
|
||||
"catalog": [
|
||||
{
|
||||
"id": "CPU2025Oct",
|
||||
"title": "Oracle Critical Patch Update Advisory - October 2025",
|
||||
"published": "2025-10-15T00:00:00Z",
|
||||
"revision": "2025-10-15T00:00:00Z",
|
||||
"document": {
|
||||
"url": "https://updates.oracle.com/cpu/2025-10/cpu2025oct.json",
|
||||
"sha256": "abc123",
|
||||
"size": 1024
|
||||
},
|
||||
"products": ["Oracle Database", "Java SE"]
|
||||
}
|
||||
],
|
||||
"schedule": [
|
||||
{ "window": "2025-Oct", "releaseDate": "2025-10-15T00:00:00Z" }
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
private const string SampleCalendar = """
|
||||
{
|
||||
"cpuWindows": [
|
||||
{ "name": "2026-Jan", "releaseDate": "2026-01-21T00:00:00Z" }
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
[Fact]
|
||||
public async Task LoadAsync_FetchesAndCachesCatalog()
|
||||
{
|
||||
var handler = new TestHttpMessageHandler(new Dictionary<Uri, HttpResponseMessage>
|
||||
{
|
||||
[new Uri("https://www.oracle.com/security-alerts/cpu/csaf/catalog.json")] = CreateResponse(SampleCatalog),
|
||||
[new Uri("https://www.oracle.com/security-alerts/cpu/cal.json")] = CreateResponse(SampleCalendar),
|
||||
});
|
||||
var client = new HttpClient(handler);
|
||||
var factory = new SingleClientHttpClientFactory(client);
|
||||
var cache = new MemoryCache(new MemoryCacheOptions());
|
||||
var fileSystem = new MockFileSystem();
|
||||
var loader = new OracleCatalogLoader(factory, cache, fileSystem, NullLogger<OracleCatalogLoader>.Instance, new AdjustableTimeProvider());
|
||||
|
||||
var options = new OracleConnectorOptions
|
||||
{
|
||||
CatalogUri = new Uri("https://www.oracle.com/security-alerts/cpu/csaf/catalog.json"),
|
||||
CpuCalendarUri = new Uri("https://www.oracle.com/security-alerts/cpu/cal.json"),
|
||||
OfflineSnapshotPath = "/snapshots/oracle-catalog.json",
|
||||
};
|
||||
|
||||
var result = await loader.LoadAsync(options, CancellationToken.None);
|
||||
result.Metadata.Entries.Should().HaveCount(1);
|
||||
result.Metadata.CpuSchedule.Should().Contain(r => r.Window == "2025-Oct");
|
||||
result.Metadata.CpuSchedule.Should().Contain(r => r.Window == "2026-Jan");
|
||||
result.FromCache.Should().BeFalse();
|
||||
fileSystem.FileExists(options.OfflineSnapshotPath!).Should().BeTrue();
|
||||
|
||||
handler.ResetInvocationCount();
|
||||
var cached = await loader.LoadAsync(options, CancellationToken.None);
|
||||
cached.FromCache.Should().BeTrue();
|
||||
handler.InvocationCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoadAsync_UsesOfflineSnapshotWhenNetworkFails()
|
||||
{
|
||||
var handler = new TestHttpMessageHandler(new Dictionary<Uri, HttpResponseMessage>());
|
||||
var client = new HttpClient(handler);
|
||||
var factory = new SingleClientHttpClientFactory(client);
|
||||
var cache = new MemoryCache(new MemoryCacheOptions());
|
||||
var fileSystem = new MockFileSystem();
|
||||
var offlineJson = """
|
||||
{
|
||||
"metadata": {
|
||||
"generatedAt": "2025-09-30T18:00:00Z",
|
||||
"entries": [
|
||||
{
|
||||
"id": "CPU2025Oct",
|
||||
"title": "Oracle Critical Patch Update Advisory - October 2025",
|
||||
"documentUri": "https://updates.oracle.com/cpu/2025-10/cpu2025oct.json",
|
||||
"publishedAt": "2025-10-15T00:00:00Z",
|
||||
"revision": "2025-10-15T00:00:00Z",
|
||||
"sha256": "abc123",
|
||||
"size": 1024,
|
||||
"products": [ "Oracle Database" ]
|
||||
}
|
||||
],
|
||||
"cpuSchedule": [
|
||||
{ "window": "2025-Oct", "releaseDate": "2025-10-15T00:00:00Z" }
|
||||
]
|
||||
},
|
||||
"fetchedAt": "2025-10-01T00:00:00Z"
|
||||
}
|
||||
""";
|
||||
fileSystem.AddFile("/snapshots/oracle-catalog.json", new MockFileData(offlineJson));
|
||||
var loader = new OracleCatalogLoader(factory, cache, fileSystem, NullLogger<OracleCatalogLoader>.Instance, new AdjustableTimeProvider());
|
||||
|
||||
var options = new OracleConnectorOptions
|
||||
{
|
||||
CatalogUri = new Uri("https://www.oracle.com/security-alerts/cpu/csaf/catalog.json"),
|
||||
OfflineSnapshotPath = "/snapshots/oracle-catalog.json",
|
||||
PreferOfflineSnapshot = true,
|
||||
};
|
||||
|
||||
var result = await loader.LoadAsync(options, CancellationToken.None);
|
||||
result.FromOfflineSnapshot.Should().BeTrue();
|
||||
result.Metadata.Entries.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoadAsync_ThrowsWhenOfflinePreferredButMissing()
|
||||
{
|
||||
var handler = new TestHttpMessageHandler(new Dictionary<Uri, HttpResponseMessage>());
|
||||
var client = new HttpClient(handler);
|
||||
var factory = new SingleClientHttpClientFactory(client);
|
||||
var cache = new MemoryCache(new MemoryCacheOptions());
|
||||
var fileSystem = new MockFileSystem();
|
||||
var loader = new OracleCatalogLoader(factory, cache, fileSystem, NullLogger<OracleCatalogLoader>.Instance);
|
||||
|
||||
var options = new OracleConnectorOptions
|
||||
{
|
||||
CatalogUri = new Uri("https://www.oracle.com/security-alerts/cpu/csaf/catalog.json"),
|
||||
PreferOfflineSnapshot = true,
|
||||
OfflineSnapshotPath = "/missing/oracle-catalog.json",
|
||||
};
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => loader.LoadAsync(options, CancellationToken.None));
|
||||
}
|
||||
|
||||
private static HttpResponseMessage CreateResponse(string payload)
|
||||
=> new(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(payload, Encoding.UTF8, "application/json"),
|
||||
};
|
||||
|
||||
private sealed class SingleClientHttpClientFactory : IHttpClientFactory
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public SingleClientHttpClientFactory(HttpClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public HttpClient CreateClient(string name) => _client;
|
||||
}
|
||||
|
||||
private sealed class AdjustableTimeProvider : TimeProvider
|
||||
{
|
||||
private DateTimeOffset _now = DateTimeOffset.UtcNow;
|
||||
|
||||
public override DateTimeOffset GetUtcNow() => _now;
|
||||
}
|
||||
|
||||
private sealed class TestHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
private readonly Dictionary<Uri, HttpResponseMessage> _responses;
|
||||
|
||||
public TestHttpMessageHandler(Dictionary<Uri, HttpResponseMessage> responses)
|
||||
{
|
||||
_responses = responses;
|
||||
}
|
||||
|
||||
public int InvocationCount { get; private set; }
|
||||
|
||||
public void ResetInvocationCount() => InvocationCount = 0;
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
InvocationCount++;
|
||||
if (request.RequestUri is not null && _responses.TryGetValue(request.RequestUri, out var response))
|
||||
{
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
return new HttpResponseMessage(response.StatusCode)
|
||||
{
|
||||
Content = new StringContent(payload, Encoding.UTF8, "application/json"),
|
||||
};
|
||||
}
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.InternalServerError)
|
||||
{
|
||||
Content = new StringContent("unexpected request"),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Excititor.Connectors.Oracle.CSAF\StellaOps.Excititor.Connectors.Oracle.CSAF.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.28" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user