save progress

This commit is contained in:
StellaOps Bot
2026-01-03 11:02:24 +02:00
parent ca578801fd
commit 83c37243e0
446 changed files with 22798 additions and 4031 deletions

View File

@@ -18,8 +18,6 @@ using StellaOps.Concelier.Connector.Common.Fetch;
using StellaOps.Concelier.Connector.Common.Html;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Plugin;
@@ -27,7 +25,7 @@ namespace StellaOps.Concelier.Connector.Acsc;
public sealed class AcscConnector : IFeedConnector
{
private static readonly string[] AcceptHeaders =
internal static readonly string[] AcceptHeaders =
{
"application/rss+xml",
"application/atom+xml;q=0.9",
@@ -35,6 +33,8 @@ public sealed class AcscConnector : IFeedConnector
"text/xml;q=0.7",
};
internal static readonly string AcceptHeaderValue = string.Join(", ", AcceptHeaders);
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
{
PropertyNameCaseInsensitive = true,
@@ -293,10 +293,23 @@ public sealed class AcscConnector : IFeedConnector
var json = JsonSerializer.Serialize(dto, SerializerOptions);
var payload = DocumentObject.Parse(json);
if (dto.Entries.Count > 0
&& payload.TryGetValue("entries", out var entriesValue)
&& entriesValue.AsDocumentArray.Count == 0)
{
var fallbackEntries = new DocumentArray();
foreach (var entry in dto.Entries)
{
var entryJson = JsonSerializer.Serialize(entry, SerializerOptions);
fallbackEntries.Add(DocumentObject.Parse(entryJson));
}
payload["entries"] = fallbackEntries;
}
var existingDto = await _dtoStore.FindByDocumentIdAsync(document.Id, cancellationToken).ConfigureAwait(false);
var dtoRecord = existingDto is null
? new DtoRecord(Guid.NewGuid(), document.Id, SourceName, "acsc.feed.v1", payload, parsedAt)
? new DtoRecord(Guid.NewGuid(), document.Id, SourceName, "acsc.feed.v1", payload, parsedAt, SchemaVersion: "acsc.feed.v1")
: existingDto with
{
Payload = payload,

View File

@@ -39,13 +39,7 @@ public static class AcscServiceCollectionExtensions
clientOptions.AllowedHosts.Add(options.RelayEndpoint.Host);
}
clientOptions.DefaultRequestHeaders["Accept"] = string.Join(", ", new[]
{
"application/rss+xml",
"application/atom+xml;q=0.9",
"application/xml;q=0.8",
"text/xml;q=0.7",
});
clientOptions.DefaultRequestHeaders["Accept"] = AcscConnector.AcceptHeaderValue;
});
services.AddSingleton<AcscDiagnostics>();

View File

@@ -108,6 +108,11 @@ public sealed class AcscOptions
throw new InvalidOperationException("ACSC RelayEndpoint must include a trailing slash when specified.");
}
if (ForceRelay && RelayEndpoint is null)
{
throw new InvalidOperationException("ACSC ForceRelay requires RelayEndpoint to be configured.");
}
if (RequestTimeout <= TimeSpan.Zero)
{
throw new InvalidOperationException("ACSC RequestTimeout must be positive.");

View File

@@ -1,4 +1,5 @@
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml.Linq;
using AngleSharp.Dom;
@@ -27,7 +28,8 @@ internal static class AcscFeedParser
};
}
var xml = XDocument.Parse(Encoding.UTF8.GetString(payload));
using var stream = new MemoryStream(payload, writable: false);
var xml = XDocument.Load(stream, LoadOptions.None);
var (feedTitle, feedLink, feedUpdated) = ExtractFeedMetadata(xml);
var items = ExtractEntries(xml).ToArray();
@@ -230,7 +232,7 @@ internal static class AcscFeedParser
if (builder.Length == 0)
{
return Guid.NewGuid().ToString("n");
return GenerateStableKey(element.ToString(SaveOptions.DisableFormatting));
}
return GenerateStableKey(builder.ToString());
@@ -297,11 +299,6 @@ internal static class AcscFeedParser
return result.ToUniversalTime();
}
if (DateTimeOffset.TryParse(value, CultureInfo.CurrentCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AllowWhiteSpaces, out result))
{
return result.ToUniversalTime();
}
return null;
}
@@ -502,7 +499,7 @@ internal static class AcscFeedParser
return null;
}
value = value.TrimStart(':', '-', '', '—', ' ');
value = value.TrimStart(':', '-', ' ');
return value.Trim();
}

View File

@@ -3,7 +3,6 @@ using System.Text;
using System.Text.RegularExpressions;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage;
namespace StellaOps.Concelier.Connector.Acsc.Internal;
@@ -56,7 +55,7 @@ internal static class AcscMapper
"mapping",
entry.EntryId ?? entry.Link ?? advisoryKey,
mappedAt.ToUniversalTime(),
fieldMask: new[] { "summary", "aliases", "references", "affectedpackages" });
fieldMask: new[] { "summary", "aliases", "references", "affectedPackages" });
var provenance = new[]
{
@@ -208,7 +207,7 @@ internal static class AcscMapper
{
var provenance = new[]
{
new AdvisoryProvenance(sourceName, "affected", identifier, recordedAt.ToUniversalTime(), fieldMask: new[] { "affectedpackages" }),
new AdvisoryProvenance(sourceName, "affected", identifier, recordedAt.ToUniversalTime(), fieldMask: new[] { "affectedPackages" }),
};
packages.Add(new AffectedPackage(
@@ -262,9 +261,17 @@ internal static class AcscMapper
: entry.Title;
var identifier = !string.IsNullOrWhiteSpace(candidate) ? ToSlug(candidate!) : null;
if (string.IsNullOrEmpty(identifier))
if (string.IsNullOrEmpty(identifier) || string.Equals(identifier, "unknown", StringComparison.Ordinal))
{
identifier = CreateHash(entry.Title ?? Guid.NewGuid().ToString());
var seed = string.Join(
"|",
feedSlug ?? string.Empty,
entry.EntryId ?? string.Empty,
entry.Link ?? string.Empty,
entry.Title ?? string.Empty,
entry.Summary ?? string.Empty,
entry.Published?.ToUniversalTime().ToString("O") ?? string.Empty);
identifier = CreateHash(seed);
}
return $"{sourceName}/{slug}/{identifier}";

View File

@@ -5,6 +5,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>

View File

@@ -7,4 +7,4 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
| --- | --- | --- |
| AUDIT-0147-M | DONE | Maintainability audit for StellaOps.Concelier.Connector.Acsc. |
| AUDIT-0147-T | DONE | Test coverage audit for StellaOps.Concelier.Connector.Acsc. |
| AUDIT-0147-A | TODO | Pending approval for changes. |
| AUDIT-0147-A | BLOCKED | AcscConnectorParseTests returning empty DTO entries despite non-empty raw payload. |