save progress
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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}";
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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. |
|
||||
|
||||
Reference in New Issue
Block a user