Restructure solution layout by module
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
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.Fetch;
|
||||
using StellaOps.Concelier.Connector.Common.Testing;
|
||||
using StellaOps.Concelier.Connector.Common.Packages;
|
||||
using StellaOps.Concelier.Connector.Vndr.Apple;
|
||||
using StellaOps.Concelier.Storage.Mongo;
|
||||
using StellaOps.Concelier.Storage.Mongo.Advisories;
|
||||
using StellaOps.Concelier.Storage.Mongo.PsirtFlags;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Vndr.Apple.Tests;
|
||||
|
||||
[Collection("mongo-fixture")]
|
||||
public sealed class AppleConnectorTests : IAsyncLifetime
|
||||
{
|
||||
private static readonly Uri IndexUri = new("https://support.example.com/index.json");
|
||||
private static readonly Uri DetailBaseUri = new("https://support.example.com/en-us/");
|
||||
|
||||
private readonly MongoIntegrationFixture _fixture;
|
||||
private readonly FakeTimeProvider _timeProvider;
|
||||
|
||||
public AppleConnectorTests(MongoIntegrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 10, 10, 0, 0, 0, TimeSpan.Zero));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FetchParseMap_EndToEnd_ProducesCanonicalAdvisories()
|
||||
{
|
||||
var handler = new CannedHttpMessageHandler();
|
||||
SeedIndex(handler);
|
||||
SeedDetail(handler);
|
||||
|
||||
await using var provider = await BuildServiceProviderAsync(handler);
|
||||
var connector = provider.GetRequiredService<AppleConnector>();
|
||||
|
||||
await connector.FetchAsync(provider, CancellationToken.None);
|
||||
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(5, advisories.Count);
|
||||
|
||||
var advisoriesByKey = advisories.ToDictionary(static advisory => advisory.AdvisoryKey, StringComparer.Ordinal);
|
||||
|
||||
var iosBaseline = advisoriesByKey["125326"];
|
||||
Assert.Contains("CVE-2025-43400", iosBaseline.Aliases, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal(3, iosBaseline.AffectedPackages.Count());
|
||||
AssertPackageDetails(iosBaseline, "iPhone 16 Pro", "26.0.1", "24A341", "iOS");
|
||||
AssertPackageDetails(iosBaseline, "iPhone 16 Pro", "26.0.1 (a)", "24A341a", "iOS");
|
||||
AssertPackageDetails(iosBaseline, "iPad Pro (M4)", "26", "24B120", "iPadOS");
|
||||
|
||||
var macBaseline = advisoriesByKey["125328"];
|
||||
Assert.Contains("CVE-2025-43400", macBaseline.Aliases, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal(2, macBaseline.AffectedPackages.Count());
|
||||
AssertPackageDetails(macBaseline, "MacBook Pro (M4)", "26.0.1", "26A123", "macOS");
|
||||
AssertPackageDetails(macBaseline, "Mac Studio", "26", "26A120b", "macOS");
|
||||
|
||||
var venturaRsr = advisoriesByKey["106355"];
|
||||
Assert.Contains("CVE-2023-37450", venturaRsr.Aliases, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal(2, venturaRsr.AffectedPackages.Count());
|
||||
AssertPackageDetails(venturaRsr, "macOS Ventura", string.Empty, "22F400", "macOS Ventura");
|
||||
AssertPackageDetails(venturaRsr, "macOS Ventura (Intel)", string.Empty, "22F400a", "macOS Ventura");
|
||||
|
||||
var visionOs = advisoriesByKey["HT214108"];
|
||||
Assert.Contains("CVE-2024-27800", visionOs.Aliases, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.False(visionOs.AffectedPackages.Any());
|
||||
|
||||
var rsrAdvisory = advisoriesByKey["HT215500"];
|
||||
Assert.Contains("CVE-2025-2468", rsrAdvisory.Aliases, StringComparer.OrdinalIgnoreCase);
|
||||
var rsrPackage = AssertPackageDetails(rsrAdvisory, "iPhone 15 Pro", "18.0.1 (c)", "22A123c", "iOS");
|
||||
Assert.True(rsrPackage.NormalizedVersions.IsDefaultOrEmpty || rsrPackage.NormalizedVersions.Length == 0);
|
||||
|
||||
var flagStore = provider.GetRequiredService<IPsirtFlagStore>();
|
||||
var rsrFlag = await flagStore.FindAsync("HT215500", CancellationToken.None);
|
||||
Assert.NotNull(rsrFlag);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static AffectedPackage AssertPackageDetails(
|
||||
Advisory advisory,
|
||||
string identifier,
|
||||
string expectedRangeExpression,
|
||||
string? expectedBuild,
|
||||
string expectedPlatform)
|
||||
{
|
||||
var candidates = advisory.AffectedPackages
|
||||
.Where(package => string.Equals(package.Identifier, identifier, StringComparison.Ordinal))
|
||||
.ToList();
|
||||
Assert.NotEmpty(candidates);
|
||||
|
||||
var package = Assert.Single(candidates, candidate =>
|
||||
{
|
||||
var rangeCandidate = candidate.VersionRanges.SingleOrDefault();
|
||||
return rangeCandidate is not null
|
||||
&& string.Equals(rangeCandidate.RangeExpression ?? string.Empty, expectedRangeExpression, StringComparison.Ordinal);
|
||||
});
|
||||
|
||||
Assert.Equal(expectedPlatform, package.Platform);
|
||||
|
||||
var range = Assert.Single(package.VersionRanges);
|
||||
Assert.Equal(expectedRangeExpression, range.RangeExpression ?? string.Empty);
|
||||
Assert.Equal("vendor", range.RangeKind);
|
||||
|
||||
Assert.NotNull(range.Primitives);
|
||||
Assert.NotNull(range.Primitives!.VendorExtensions);
|
||||
var vendorExtensions = range.Primitives.VendorExtensions;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(expectedRangeExpression))
|
||||
{
|
||||
if (!vendorExtensions.TryGetValue("apple.version.raw", out var rawVersion))
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException($"Missing apple.version.raw for {identifier}; available keys: {string.Join(", ", vendorExtensions.Keys)}");
|
||||
}
|
||||
|
||||
Assert.Equal(expectedRangeExpression, rawVersion);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(vendorExtensions.ContainsKey("apple.version.raw"));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(expectedPlatform))
|
||||
{
|
||||
if (vendorExtensions.TryGetValue("apple.platform", out var platformExtension))
|
||||
{
|
||||
Assert.Equal(expectedPlatform, platformExtension);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException($"Missing apple.platform extension for {identifier}; available keys: {string.Join(", ", vendorExtensions.Keys)}");
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(expectedBuild))
|
||||
{
|
||||
Assert.True(vendorExtensions.TryGetValue("apple.build", out var buildExtension));
|
||||
Assert.Equal(expectedBuild, buildExtension);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(vendorExtensions.ContainsKey("apple.build"));
|
||||
}
|
||||
|
||||
if (PackageCoordinateHelper.TryParseSemVer(expectedRangeExpression, out _, out var normalized))
|
||||
{
|
||||
var normalizedRule = Assert.Single(package.NormalizedVersions);
|
||||
Assert.Equal(NormalizedVersionSchemes.SemVer, normalizedRule.Scheme);
|
||||
Assert.Equal(NormalizedVersionRuleTypes.LessThanOrEqual, normalizedRule.Type);
|
||||
Assert.Equal(normalized, normalizedRule.Max);
|
||||
Assert.Equal($"apple:{expectedPlatform}:{identifier}", normalizedRule.Notes);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.True(package.NormalizedVersions.IsDefaultOrEmpty || package.NormalizedVersions.Length == 0);
|
||||
}
|
||||
|
||||
return package;
|
||||
}
|
||||
|
||||
public Task InitializeAsync() => Task.CompletedTask;
|
||||
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
private async Task<ServiceProvider> BuildServiceProviderAsync(CannedHttpMessageHandler handler)
|
||||
{
|
||||
await _fixture.Client.DropDatabaseAsync(_fixture.Database.DatabaseNamespace.DatabaseName);
|
||||
|
||||
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.AddAppleConnector(opts =>
|
||||
{
|
||||
opts.SoftwareLookupUri = IndexUri;
|
||||
opts.AdvisoryBaseUri = DetailBaseUri;
|
||||
opts.LocaleSegment = "en-us";
|
||||
opts.InitialBackfill = TimeSpan.FromDays(120);
|
||||
opts.ModifiedTolerance = TimeSpan.FromHours(2);
|
||||
opts.MaxAdvisoriesPerFetch = 10;
|
||||
});
|
||||
|
||||
services.Configure<HttpClientFactoryOptions>(AppleOptions.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 static void SeedIndex(CannedHttpMessageHandler handler)
|
||||
{
|
||||
handler.AddJsonResponse(IndexUri, ReadFixture("index.json"));
|
||||
}
|
||||
|
||||
private static void SeedDetail(CannedHttpMessageHandler handler)
|
||||
{
|
||||
AddHtmlResponse(handler, new Uri(DetailBaseUri, "125326"), "125326.html");
|
||||
AddHtmlResponse(handler, new Uri(DetailBaseUri, "125328"), "125328.html");
|
||||
AddHtmlResponse(handler, new Uri(DetailBaseUri, "106355"), "106355.html");
|
||||
AddHtmlResponse(handler, new Uri(DetailBaseUri, "HT214108"), "ht214108.html");
|
||||
AddHtmlResponse(handler, new Uri(DetailBaseUri, "HT215500"), "ht215500.html");
|
||||
}
|
||||
|
||||
private static void AddHtmlResponse(CannedHttpMessageHandler handler, Uri uri, string fixture)
|
||||
{
|
||||
handler.AddResponse(uri, () =>
|
||||
{
|
||||
var content = ReadFixture(fixture);
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(content, Encoding.UTF8, "text/html"),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private static string ReadFixture(string name)
|
||||
{
|
||||
var path = Path.Combine(
|
||||
AppContext.BaseDirectory,
|
||||
"Source",
|
||||
"Vndr",
|
||||
"Apple",
|
||||
"Fixtures",
|
||||
name);
|
||||
return File.ReadAllText(path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Concelier.Connector.Vndr.Apple.Internal;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Vndr.Apple.Tests.Apple;
|
||||
|
||||
internal static class AppleFixtureManager
|
||||
{
|
||||
private const string UpdateEnvVar = "UPDATE_APPLE_FIXTURES";
|
||||
private const string UpdateSentinelFileName = ".update-apple-fixtures";
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
};
|
||||
|
||||
private static readonly Uri ExampleDetailBaseUri = new("https://support.example.com/en-us/");
|
||||
|
||||
private static readonly AppleFixtureDefinition[] Definitions =
|
||||
{
|
||||
new(
|
||||
"125326",
|
||||
new Uri("https://support.apple.com/en-us/125326"),
|
||||
Products: new[]
|
||||
{
|
||||
new AppleFixtureProduct("iOS", "iPhone 16 Pro", "26.0.1", "24A341"),
|
||||
new AppleFixtureProduct("iOS", "iPhone 16 Pro", "26.0.1 (a)", "24A341a"),
|
||||
new AppleFixtureProduct("iPadOS", "iPad Pro (M4)", "26", "24B120"),
|
||||
}),
|
||||
new(
|
||||
"125328",
|
||||
new Uri("https://support.apple.com/en-us/125328"),
|
||||
Products: new[]
|
||||
{
|
||||
new AppleFixtureProduct("macOS", "MacBook Pro (M4)", "26.0.1", "26A123"),
|
||||
new AppleFixtureProduct("macOS", "Mac Studio", "26", "26A120b"),
|
||||
}),
|
||||
new(
|
||||
"106355",
|
||||
new Uri("https://support.apple.com/en-us/106355"),
|
||||
ForceRapidSecurityResponse: true,
|
||||
Products: new[]
|
||||
{
|
||||
new AppleFixtureProduct("macOS Ventura", "macOS Ventura", string.Empty, "22F400"),
|
||||
new AppleFixtureProduct("macOS Ventura", "macOS Ventura (Intel)", string.Empty, "22F400a"),
|
||||
}),
|
||||
new(
|
||||
"HT214108",
|
||||
new Uri("https://support.apple.com/en-us/HT214108")),
|
||||
new(
|
||||
"HT215500",
|
||||
new Uri("https://support.apple.com/en-us/HT215500"),
|
||||
ForceRapidSecurityResponse: true),
|
||||
};
|
||||
|
||||
private static readonly Lazy<Task> UpdateTask = new(
|
||||
() => UpdateFixturesAsync(CancellationToken.None),
|
||||
LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
|
||||
public static IReadOnlyList<AppleFixtureDefinition> Fixtures => Definitions;
|
||||
|
||||
public static Task EnsureUpdatedAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!ShouldUpdateFixtures())
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Console.WriteLine("[AppleFixtures] UPDATE flag detected; refreshing fixtures");
|
||||
return UpdateTask.Value;
|
||||
}
|
||||
|
||||
public static string ReadFixture(string name)
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, "Source", "Vndr", "Apple", "Fixtures", name);
|
||||
return File.ReadAllText(path);
|
||||
}
|
||||
|
||||
public static AppleDetailDto ReadExpectedDto(string articleId)
|
||||
{
|
||||
var json = ReadFixture($"{articleId}.expected.json");
|
||||
return JsonSerializer.Deserialize<AppleDetailDto>(json, SerializerOptions)
|
||||
?? throw new InvalidOperationException($"Unable to deserialize expected DTO for {articleId}.");
|
||||
}
|
||||
|
||||
private static bool ShouldUpdateFixtures()
|
||||
{
|
||||
var value = Environment.GetEnvironmentVariable(UpdateEnvVar)?.Trim();
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
var sentinelPath = Path.Combine(ResolveFixtureRoot(), UpdateSentinelFileName);
|
||||
return File.Exists(sentinelPath);
|
||||
}
|
||||
|
||||
if (string.Equals(value, "0", StringComparison.Ordinal)
|
||||
|| string.Equals(value, "false", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async Task UpdateFixturesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var handler = new HttpClientHandler
|
||||
{
|
||||
AutomaticDecompression = System.Net.DecompressionMethods.All,
|
||||
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
|
||||
};
|
||||
|
||||
using var httpClient = new HttpClient(handler);
|
||||
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("StellaOpsFixtureUpdater", "1.0"));
|
||||
httpClient.Timeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
var indexEntries = new List<object>(Definitions.Length);
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var definition in Definitions)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
var html = await httpClient.GetStringAsync(definition.DetailUri, cancellationToken).ConfigureAwait(false);
|
||||
var entryProducts = definition.Products?
|
||||
.Select(product => new AppleIndexProduct(
|
||||
product.Platform,
|
||||
product.Name,
|
||||
product.Version,
|
||||
product.Build))
|
||||
.ToArray() ?? Array.Empty<AppleIndexProduct>();
|
||||
|
||||
var entry = new AppleIndexEntry(
|
||||
UpdateId: definition.ArticleId,
|
||||
ArticleId: definition.ArticleId,
|
||||
Title: definition.ArticleId,
|
||||
PostingDate: DateTimeOffset.UtcNow,
|
||||
DetailUri: definition.DetailUri,
|
||||
Products: entryProducts,
|
||||
IsRapidSecurityResponse: definition.ForceRapidSecurityResponse ?? false);
|
||||
|
||||
var dto = AppleDetailParser.Parse(html, entry);
|
||||
var sanitizedHtml = BuildSanitizedHtml(dto);
|
||||
|
||||
WriteFixture(definition.HtmlFixtureName, sanitizedHtml);
|
||||
WriteFixture(definition.ExpectedFixtureName, JsonSerializer.Serialize(dto, SerializerOptions));
|
||||
|
||||
var exampleDetailUri = new Uri(ExampleDetailBaseUri, definition.ArticleId);
|
||||
indexEntries.Add(new
|
||||
{
|
||||
id = definition.ArticleId,
|
||||
articleId = definition.ArticleId,
|
||||
title = dto.Title,
|
||||
postingDate = dto.Published.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture),
|
||||
detailUrl = exampleDetailUri.ToString(),
|
||||
rapidSecurityResponse = definition.ForceRapidSecurityResponse
|
||||
?? dto.Title.Contains("Rapid Security Response", StringComparison.OrdinalIgnoreCase),
|
||||
products = dto.Affected
|
||||
.OrderBy(p => p.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(p => new
|
||||
{
|
||||
platform = p.Platform,
|
||||
name = p.Name,
|
||||
version = p.Version,
|
||||
build = p.Build,
|
||||
})
|
||||
.ToArray(),
|
||||
});
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or IOException)
|
||||
{
|
||||
Console.WriteLine($"[AppleFixtures] Skipped {definition.ArticleId}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
var sentinelPath = Path.Combine(ResolveFixtureRoot(), UpdateSentinelFileName);
|
||||
if (File.Exists(sentinelPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(sentinelPath);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// best effort
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var indexDocument = new { updates = indexEntries };
|
||||
WriteFixture("index.json", JsonSerializer.Serialize(indexDocument, SerializerOptions));
|
||||
}
|
||||
|
||||
private static string BuildSanitizedHtml(AppleDetailDto dto)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("<!DOCTYPE html>");
|
||||
builder.AppendLine("<html lang=\"en\">");
|
||||
builder.AppendLine("<body>");
|
||||
builder.AppendLine("<article>");
|
||||
|
||||
builder.AppendLine($" <h1 data-testid=\"update-title\">{Escape(dto.Title)}</h1>");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(dto.Summary))
|
||||
{
|
||||
builder.AppendLine($" <p data-testid=\"update-summary\">{Escape(dto.Summary)}</p>");
|
||||
}
|
||||
|
||||
var publishedDisplay = dto.Published.ToString("MMMM d, yyyy", CultureInfo.InvariantCulture);
|
||||
builder.AppendLine($" <time data-testid=\"published\" datetime=\"{dto.Published:O}\">{publishedDisplay}</time>");
|
||||
|
||||
if (dto.Updated.HasValue)
|
||||
{
|
||||
var updatedDisplay = dto.Updated.Value.ToString("MMMM d, yyyy", CultureInfo.InvariantCulture);
|
||||
builder.AppendLine($" <time data-testid=\"updated\" datetime=\"{dto.Updated:O}\">{updatedDisplay}</time>");
|
||||
}
|
||||
|
||||
if (dto.CveIds.Count > 0)
|
||||
{
|
||||
builder.AppendLine(" <section data-component=\"security-update\">");
|
||||
builder.AppendLine(" <h2>Security Issues</h2>");
|
||||
builder.AppendLine(" <ul data-testid=\"cve-list\">");
|
||||
foreach (var cve in dto.CveIds.OrderBy(id => id, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
builder.AppendLine($" <li>{Escape(cve)}</li>");
|
||||
}
|
||||
builder.AppendLine(" </ul>");
|
||||
builder.AppendLine(" </section>");
|
||||
}
|
||||
|
||||
if (dto.Affected.Count > 0)
|
||||
{
|
||||
builder.AppendLine(" <table>");
|
||||
builder.AppendLine(" <tbody>");
|
||||
foreach (var product in dto.Affected
|
||||
.OrderBy(p => p.Platform ?? string.Empty, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(p => p.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(p => p.Version ?? string.Empty, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(p => p.Build ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var platform = product.Platform is null ? string.Empty : Escape(product.Platform);
|
||||
var name = Escape(product.Name);
|
||||
var version = product.Version is null ? string.Empty : Escape(product.Version);
|
||||
var build = product.Build is null ? string.Empty : Escape(product.Build);
|
||||
|
||||
builder.Append(" <tr data-testid=\"product-row\"");
|
||||
builder.Append($" data-platform=\"{platform}\"");
|
||||
builder.Append($" data-product=\"{name}\"");
|
||||
builder.Append($" data-version=\"{version}\"");
|
||||
builder.Append($" data-build=\"{build}\">");
|
||||
builder.AppendLine();
|
||||
builder.AppendLine($" <td>{name}</td>");
|
||||
builder.AppendLine($" <td>{version}</td>");
|
||||
builder.AppendLine($" <td>{build}</td>");
|
||||
builder.AppendLine(" </tr>");
|
||||
}
|
||||
|
||||
builder.AppendLine(" </tbody>");
|
||||
builder.AppendLine(" </table>");
|
||||
}
|
||||
|
||||
if (dto.References.Count > 0)
|
||||
{
|
||||
builder.AppendLine(" <section>");
|
||||
builder.AppendLine(" <h2>References</h2>");
|
||||
foreach (var reference in dto.References
|
||||
.OrderBy(r => r.Url, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var title = reference.Title ?? string.Empty;
|
||||
builder.AppendLine($" <a href=\"{Escape(reference.Url)}\">{Escape(title)}</a>");
|
||||
}
|
||||
builder.AppendLine(" </section>");
|
||||
}
|
||||
|
||||
builder.AppendLine("</article>");
|
||||
builder.AppendLine("</body>");
|
||||
builder.AppendLine("</html>");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static void WriteFixture(string name, string contents)
|
||||
{
|
||||
var root = ResolveFixtureRoot();
|
||||
Directory.CreateDirectory(root);
|
||||
var normalized = NormalizeLineEndings(contents);
|
||||
|
||||
var sourcePath = Path.Combine(root, name);
|
||||
File.WriteAllText(sourcePath, normalized);
|
||||
|
||||
var outputPath = Path.Combine(AppContext.BaseDirectory, "Source", "Vndr", "Apple", "Fixtures", name);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);
|
||||
File.WriteAllText(outputPath, normalized);
|
||||
|
||||
Console.WriteLine($"[AppleFixtures] Wrote {name}");
|
||||
}
|
||||
|
||||
private static string ResolveFixtureRoot()
|
||||
{
|
||||
var baseDir = AppContext.BaseDirectory;
|
||||
// bin/Debug/net10.0/ -> project -> src -> repo root
|
||||
var root = Path.GetFullPath(Path.Combine(baseDir, "..", "..", "..", "..", ".."));
|
||||
return Path.Combine(root, "src", "StellaOps.Concelier.Connector.Vndr.Apple.Tests", "Apple", "Fixtures");
|
||||
}
|
||||
|
||||
private static string NormalizeLineEndings(string value)
|
||||
=> value.Replace("\r\n", "\n", StringComparison.Ordinal);
|
||||
|
||||
private static string Escape(string value)
|
||||
=> System.Net.WebUtility.HtmlEncode(value);
|
||||
}
|
||||
|
||||
internal sealed record AppleFixtureDefinition(
|
||||
string ArticleId,
|
||||
Uri DetailUri,
|
||||
bool? ForceRapidSecurityResponse = null,
|
||||
IReadOnlyList<AppleFixtureProduct>? Products = null)
|
||||
{
|
||||
public string HtmlFixtureName => $"{ArticleId}.html";
|
||||
public string ExpectedFixtureName => $"{ArticleId}.expected.json";
|
||||
}
|
||||
|
||||
internal sealed record AppleFixtureProduct(
|
||||
string Platform,
|
||||
string Name,
|
||||
string Version,
|
||||
string Build);
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Concelier.Connector.Vndr.Apple.Internal;
|
||||
using StellaOps.Concelier.Connector.Vndr.Apple.Tests.Apple;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Vndr.Apple.Tests;
|
||||
|
||||
public sealed class AppleLiveRegressionTests
|
||||
{
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
WriteIndented = false,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
};
|
||||
|
||||
public static IEnumerable<object[]> FixtureCases
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var definition in AppleFixtureManager.Fixtures)
|
||||
{
|
||||
yield return new object[] { definition.ArticleId };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(FixtureCases))]
|
||||
public async Task Parser_SanitizedFixture_MatchesExpectedDto(string articleId)
|
||||
{
|
||||
var updateFlag = Environment.GetEnvironmentVariable("UPDATE_APPLE_FIXTURES");
|
||||
if (!string.IsNullOrEmpty(updateFlag))
|
||||
{
|
||||
Console.WriteLine($"[AppleFixtures] UPDATE_APPLE_FIXTURES={updateFlag}");
|
||||
}
|
||||
await AppleFixtureManager.EnsureUpdatedAsync();
|
||||
var expected = AppleFixtureManager.ReadExpectedDto(articleId);
|
||||
var html = AppleFixtureManager.ReadFixture($"{articleId}.html");
|
||||
|
||||
var entry = new AppleIndexEntry(
|
||||
UpdateId: articleId,
|
||||
ArticleId: articleId,
|
||||
Title: expected.Title,
|
||||
PostingDate: expected.Published,
|
||||
DetailUri: new Uri($"https://support.apple.com/en-us/{articleId}"),
|
||||
Products: Array.Empty<AppleIndexProduct>(),
|
||||
IsRapidSecurityResponse: expected.RapidSecurityResponse);
|
||||
|
||||
var dto = AppleDetailParser.Parse(html, entry);
|
||||
var actualJson = JsonSerializer.Serialize(dto, SerializerOptions);
|
||||
var expectedJson = JsonSerializer.Serialize(expected, SerializerOptions);
|
||||
Assert.Equal(expectedJson, actualJson);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"advisoryId": "106355",
|
||||
"articleId": "106355",
|
||||
"title": "About the security content of Rapid Security Responses for macOS Ventura 13.4.1",
|
||||
"summary": "This document describes the content of Rapid Security Responses.",
|
||||
"published": "2025-10-12T13:19:10.4446382+00:00",
|
||||
"cveIds": [
|
||||
"CVE-2023-37450"
|
||||
],
|
||||
"affected": [
|
||||
{
|
||||
"platform": "macOS Ventura",
|
||||
"name": "macOS Ventura",
|
||||
"version": "",
|
||||
"build": "22F400"
|
||||
},
|
||||
{
|
||||
"platform": "macOS Ventura",
|
||||
"name": "macOS Ventura (Intel)",
|
||||
"version": "",
|
||||
"build": "22F400a"
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"url": "https://support.apple.com/103190",
|
||||
"title": "Contact the vendor",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://support.apple.com/en-us/106355/localeselector",
|
||||
"title": "United States",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://support.apple.com/kb/HT201222",
|
||||
"title": "Apple security releases",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://support.apple.com/kb/HT201224",
|
||||
"title": "Rapid Security Responses",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://www.cve.org/About/Overview",
|
||||
"title": "CVE-ID"
|
||||
}
|
||||
],
|
||||
"rapidSecurityResponse": true
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<article>
|
||||
<h1 data-testid="update-title">About the security content of Rapid Security Responses for macOS Ventura 13.4.1</h1>
|
||||
<p data-testid="update-summary">This document describes the content of Rapid Security Responses.</p>
|
||||
<time data-testid="published" datetime="2025-10-12T13:19:10.4446382+00:00">October 12, 2025</time>
|
||||
<section data-component="security-update">
|
||||
<h2>Security Issues</h2>
|
||||
<ul data-testid="cve-list">
|
||||
<li>CVE-2023-37450</li>
|
||||
</ul>
|
||||
</section>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr data-testid="product-row" data-platform="macOS Ventura" data-product="macOS Ventura" data-version="" data-build="22F400">
|
||||
<td>macOS Ventura</td>
|
||||
<td></td>
|
||||
<td>22F400</td>
|
||||
</tr>
|
||||
<tr data-testid="product-row" data-platform="macOS Ventura" data-product="macOS Ventura (Intel)" data-version="" data-build="22F400a">
|
||||
<td>macOS Ventura (Intel)</td>
|
||||
<td></td>
|
||||
<td>22F400a</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<section>
|
||||
<h2>References</h2>
|
||||
<a href="https://support.apple.com/103190">Contact the vendor</a>
|
||||
<a href="https://support.apple.com/en-us/106355/localeselector">United States</a>
|
||||
<a href="https://support.apple.com/kb/HT201222">Apple security releases</a>
|
||||
<a href="https://support.apple.com/kb/HT201224">Rapid Security Responses</a>
|
||||
<a href="https://www.cve.org/About/Overview">CVE-ID</a>
|
||||
</section>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"advisoryId": "125326",
|
||||
"articleId": "125326",
|
||||
"title": "About the security content of iOS 26.0.1 and iPadOS 26.0.1",
|
||||
"summary": "This document describes the security content of iOS 26.0.1 and iPadOS 26.0.1.",
|
||||
"published": "2025-10-12T13:19:09.732004+00:00",
|
||||
"cveIds": [
|
||||
"CVE-2025-43400"
|
||||
],
|
||||
"affected": [
|
||||
{
|
||||
"platform": "iOS",
|
||||
"name": "iPhone 16 Pro",
|
||||
"version": "26.0.1",
|
||||
"build": "24A341"
|
||||
},
|
||||
{
|
||||
"platform": "iOS",
|
||||
"name": "iPhone 16 Pro",
|
||||
"version": "26.0.1 (a)",
|
||||
"build": "24A341a"
|
||||
},
|
||||
{
|
||||
"platform": "iPadOS",
|
||||
"name": "iPad Pro (M4)",
|
||||
"version": "26",
|
||||
"build": "24B120"
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"url": "https://support.apple.com/103190",
|
||||
"title": "Contact the vendor",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://support.apple.com/en-us/100100",
|
||||
"title": "Apple security releases",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://support.apple.com/en-us/102549",
|
||||
"title": "Apple Product Security",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://support.apple.com/en-us/125326/localeselector",
|
||||
"title": "United States",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://www.cve.org/About/Overview",
|
||||
"title": "CVE-ID"
|
||||
}
|
||||
],
|
||||
"rapidSecurityResponse": false
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<article>
|
||||
<h1 data-testid="update-title">About the security content of iOS 26.0.1 and iPadOS 26.0.1</h1>
|
||||
<p data-testid="update-summary">This document describes the security content of iOS 26.0.1 and iPadOS 26.0.1.</p>
|
||||
<time data-testid="published" datetime="2025-10-12T13:19:09.7320040+00:00">October 12, 2025</time>
|
||||
<section data-component="security-update">
|
||||
<h2>Security Issues</h2>
|
||||
<ul data-testid="cve-list">
|
||||
<li>CVE-2025-43400</li>
|
||||
</ul>
|
||||
</section>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr data-testid="product-row" data-platform="iOS" data-product="iPhone 16 Pro" data-version="26.0.1" data-build="24A341">
|
||||
<td>iPhone 16 Pro</td>
|
||||
<td>26.0.1</td>
|
||||
<td>24A341</td>
|
||||
</tr>
|
||||
<tr data-testid="product-row" data-platform="iOS" data-product="iPhone 16 Pro" data-version="26.0.1 (a)" data-build="24A341a">
|
||||
<td>iPhone 16 Pro</td>
|
||||
<td>26.0.1 (a)</td>
|
||||
<td>24A341a</td>
|
||||
</tr>
|
||||
<tr data-testid="product-row" data-platform="iPadOS" data-product="iPad Pro (M4)" data-version="26" data-build="24B120">
|
||||
<td>iPad Pro (M4)</td>
|
||||
<td>26</td>
|
||||
<td>24B120</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<section>
|
||||
<h2>References</h2>
|
||||
<a href="https://support.apple.com/103190">Contact the vendor</a>
|
||||
<a href="https://support.apple.com/en-us/100100">Apple security releases</a>
|
||||
<a href="https://support.apple.com/en-us/102549">Apple Product Security</a>
|
||||
<a href="https://support.apple.com/en-us/125326/localeselector">United States</a>
|
||||
<a href="https://www.cve.org/About/Overview">CVE-ID</a>
|
||||
</section>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"advisoryId": "125328",
|
||||
"articleId": "125328",
|
||||
"title": "About the security content of macOS Tahoe 26.0.1",
|
||||
"summary": "This document describes the security content of macOS Tahoe 26.0.1.",
|
||||
"published": "2025-10-12T13:19:10.3703446+00:00",
|
||||
"cveIds": [
|
||||
"CVE-2025-43400"
|
||||
],
|
||||
"affected": [
|
||||
{
|
||||
"platform": "macOS",
|
||||
"name": "MacBook Pro (M4)",
|
||||
"version": "26.0.1",
|
||||
"build": "26A123"
|
||||
},
|
||||
{
|
||||
"platform": "macOS",
|
||||
"name": "Mac Studio",
|
||||
"version": "26",
|
||||
"build": "26A120b"
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"url": "https://support.apple.com/103190",
|
||||
"title": "Contact the vendor",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://support.apple.com/en-us/100100",
|
||||
"title": "Apple security releases",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://support.apple.com/en-us/102549",
|
||||
"title": "Apple Product Security",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://support.apple.com/en-us/125328/localeselector",
|
||||
"title": "United States",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://www.cve.org/About/Overview",
|
||||
"title": "CVE-ID"
|
||||
}
|
||||
],
|
||||
"rapidSecurityResponse": false
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<article>
|
||||
<h1 data-testid="update-title">About the security content of macOS Tahoe 26.0.1</h1>
|
||||
<p data-testid="update-summary">This document describes the security content of macOS Tahoe 26.0.1.</p>
|
||||
<time data-testid="published" datetime="2025-10-12T13:19:10.3703446+00:00">October 12, 2025</time>
|
||||
<section data-component="security-update">
|
||||
<h2>Security Issues</h2>
|
||||
<ul data-testid="cve-list">
|
||||
<li>CVE-2025-43400</li>
|
||||
</ul>
|
||||
</section>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr data-testid="product-row" data-platform="macOS" data-product="Mac Studio" data-version="26" data-build="26A120b">
|
||||
<td>Mac Studio</td>
|
||||
<td>26</td>
|
||||
<td>26A120b</td>
|
||||
</tr>
|
||||
<tr data-testid="product-row" data-platform="macOS" data-product="MacBook Pro (M4)" data-version="26.0.1" data-build="26A123">
|
||||
<td>MacBook Pro (M4)</td>
|
||||
<td>26.0.1</td>
|
||||
<td>26A123</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<section>
|
||||
<h2>References</h2>
|
||||
<a href="https://support.apple.com/103190">Contact the vendor</a>
|
||||
<a href="https://support.apple.com/en-us/100100">Apple security releases</a>
|
||||
<a href="https://support.apple.com/en-us/102549">Apple Product Security</a>
|
||||
<a href="https://support.apple.com/en-us/125328/localeselector">United States</a>
|
||||
<a href="https://www.cve.org/About/Overview">CVE-ID</a>
|
||||
</section>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"advisoryId": "HT214108",
|
||||
"articleId": "HT214108",
|
||||
"title": "About the security content of visionOS 1.2",
|
||||
"summary": "This document describes the security content of visionOS 1.2.",
|
||||
"published": "2025-10-12T13:19:10.5262006+00:00",
|
||||
"cveIds": [
|
||||
"CVE-2024-27800",
|
||||
"CVE-2024-27801",
|
||||
"CVE-2024-27802",
|
||||
"CVE-2024-27808",
|
||||
"CVE-2024-27811",
|
||||
"CVE-2024-27812",
|
||||
"CVE-2024-27815",
|
||||
"CVE-2024-27817",
|
||||
"CVE-2024-27820",
|
||||
"CVE-2024-27828",
|
||||
"CVE-2024-27830",
|
||||
"CVE-2024-27831",
|
||||
"CVE-2024-27832",
|
||||
"CVE-2024-27833",
|
||||
"CVE-2024-27836",
|
||||
"CVE-2024-27838",
|
||||
"CVE-2024-27840",
|
||||
"CVE-2024-27844",
|
||||
"CVE-2024-27850",
|
||||
"CVE-2024-27851",
|
||||
"CVE-2024-27856",
|
||||
"CVE-2024-27857",
|
||||
"CVE-2024-27884",
|
||||
"CVE-2024-40771"
|
||||
],
|
||||
"affected": [],
|
||||
"references": [
|
||||
{
|
||||
"url": "https://support.apple.com/103190",
|
||||
"title": "Contact the vendor",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://support.apple.com/en-us/120906/localeselector",
|
||||
"title": "United States",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://support.apple.com/kb/HT201220",
|
||||
"title": "Apple Product Security",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://support.apple.com/kb/HT201222",
|
||||
"title": "Apple security releases",
|
||||
"kind": "advisory"
|
||||
},
|
||||
{
|
||||
"url": "https://www.cve.org/About/Overview",
|
||||
"title": "CVE-ID"
|
||||
}
|
||||
],
|
||||
"rapidSecurityResponse": false
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"advisoryId": "HT215500",
|
||||
"articleId": "HT215500",
|
||||
"title": "Rapid Security Response iOS 18.0.1 (c)",
|
||||
"summary": "Rapid Security Response provides important security fixes between software updates.",
|
||||
"published": "2025-10-02T15:30:00+00:00",
|
||||
"cveIds": [
|
||||
"CVE-2025-2468"
|
||||
],
|
||||
"affected": [
|
||||
{
|
||||
"platform": "iOS",
|
||||
"name": "iPhone 15 Pro",
|
||||
"version": "18.0.1 (c)",
|
||||
"build": "22A123c"
|
||||
}
|
||||
],
|
||||
"references": [],
|
||||
"rapidSecurityResponse": true
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<article>
|
||||
<h1 data-testid="update-title">About the security content of visionOS 1.2</h1>
|
||||
<p data-testid="update-summary">This document describes the security content of visionOS 1.2.</p>
|
||||
<time data-testid="published" datetime="2025-10-12T13:19:10.5262006+00:00">October 12, 2025</time>
|
||||
<section data-component="security-update">
|
||||
<h2>Security Issues</h2>
|
||||
<ul data-testid="cve-list">
|
||||
<li>CVE-2024-27800</li>
|
||||
<li>CVE-2024-27801</li>
|
||||
<li>CVE-2024-27802</li>
|
||||
<li>CVE-2024-27808</li>
|
||||
<li>CVE-2024-27811</li>
|
||||
<li>CVE-2024-27812</li>
|
||||
<li>CVE-2024-27815</li>
|
||||
<li>CVE-2024-27817</li>
|
||||
<li>CVE-2024-27820</li>
|
||||
<li>CVE-2024-27828</li>
|
||||
<li>CVE-2024-27830</li>
|
||||
<li>CVE-2024-27831</li>
|
||||
<li>CVE-2024-27832</li>
|
||||
<li>CVE-2024-27833</li>
|
||||
<li>CVE-2024-27836</li>
|
||||
<li>CVE-2024-27838</li>
|
||||
<li>CVE-2024-27840</li>
|
||||
<li>CVE-2024-27844</li>
|
||||
<li>CVE-2024-27850</li>
|
||||
<li>CVE-2024-27851</li>
|
||||
<li>CVE-2024-27856</li>
|
||||
<li>CVE-2024-27857</li>
|
||||
<li>CVE-2024-27884</li>
|
||||
<li>CVE-2024-40771</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h2>References</h2>
|
||||
<a href="https://support.apple.com/103190">Contact the vendor</a>
|
||||
<a href="https://support.apple.com/en-us/120906/localeselector">United States</a>
|
||||
<a href="https://support.apple.com/kb/HT201220">Apple Product Security</a>
|
||||
<a href="https://support.apple.com/kb/HT201222">Apple security releases</a>
|
||||
<a href="https://www.cve.org/About/Overview">CVE-ID</a>
|
||||
</section>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<article>
|
||||
<h1 data-testid="update-title">Rapid Security Response iOS 18.0.1 (c)</h1>
|
||||
<p data-testid="update-summary">Rapid Security Response provides important security fixes between software updates.</p>
|
||||
<time data-testid="published" datetime="2025-10-02T15:30:00+00:00">October 2, 2025</time>
|
||||
<section data-component="security-update">
|
||||
<h2>Security Issues</h2>
|
||||
<ul data-testid="cve-list">
|
||||
<li>CVE-2025-2468</li>
|
||||
</ul>
|
||||
</section>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr data-testid="product-row" data-platform="iOS" data-product="iPhone 15 Pro" data-version="18.0.1 (c)" data-build="22A123c">
|
||||
<td>iPhone 15 Pro</td>
|
||||
<td>18.0.1 (c)</td>
|
||||
<td>22A123c</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"updates": [
|
||||
{
|
||||
"id": "125326",
|
||||
"articleId": "125326",
|
||||
"title": "About the security content of iOS 26.0.1 and iPadOS 26.0.1",
|
||||
"postingDate": "2025-10-12T13:19:09.7320040+00:00",
|
||||
"detailUrl": "https://support.example.com/en-us/125326",
|
||||
"rapidSecurityResponse": false,
|
||||
"products": [
|
||||
{
|
||||
"platform": "iPadOS",
|
||||
"name": "iPad Pro (M4)",
|
||||
"version": "26",
|
||||
"build": "24B120"
|
||||
},
|
||||
{
|
||||
"platform": "iOS",
|
||||
"name": "iPhone 16 Pro",
|
||||
"version": "26.0.1",
|
||||
"build": "24A341"
|
||||
},
|
||||
{
|
||||
"platform": "iOS",
|
||||
"name": "iPhone 16 Pro",
|
||||
"version": "26.0.1 (a)",
|
||||
"build": "24A341a"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "125328",
|
||||
"articleId": "125328",
|
||||
"title": "About the security content of macOS Tahoe 26.0.1",
|
||||
"postingDate": "2025-10-12T13:19:10.3703446+00:00",
|
||||
"detailUrl": "https://support.example.com/en-us/125328",
|
||||
"rapidSecurityResponse": false,
|
||||
"products": [
|
||||
{
|
||||
"platform": "macOS",
|
||||
"name": "Mac Studio",
|
||||
"version": "26",
|
||||
"build": "26A120b"
|
||||
},
|
||||
{
|
||||
"platform": "macOS",
|
||||
"name": "MacBook Pro (M4)",
|
||||
"version": "26.0.1",
|
||||
"build": "26A123"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "106355",
|
||||
"articleId": "106355",
|
||||
"title": "About the security content of Rapid Security Responses for macOS Ventura 13.4.1",
|
||||
"postingDate": "2025-10-12T13:19:10.4446382+00:00",
|
||||
"detailUrl": "https://support.example.com/en-us/106355",
|
||||
"rapidSecurityResponse": true,
|
||||
"products": [
|
||||
{
|
||||
"platform": "macOS Ventura",
|
||||
"name": "macOS Ventura",
|
||||
"version": "",
|
||||
"build": "22F400"
|
||||
},
|
||||
{
|
||||
"platform": "macOS Ventura",
|
||||
"name": "macOS Ventura (Intel)",
|
||||
"version": "",
|
||||
"build": "22F400a"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "HT214108",
|
||||
"articleId": "HT214108",
|
||||
"title": "About the security content of visionOS 1.2",
|
||||
"postingDate": "2025-10-12T13:19:10.5262006+00:00",
|
||||
"detailUrl": "https://support.example.com/en-us/HT214108",
|
||||
"rapidSecurityResponse": false,
|
||||
"products": []
|
||||
},
|
||||
{
|
||||
"id": "RSR-iOS-18.0.1-c",
|
||||
"articleId": "HT215500",
|
||||
"title": "Rapid Security Response iOS 18.0.1 (c)",
|
||||
"postingDate": "2025-10-02T15:30:00+00:00",
|
||||
"detailUrl": "https://support.example.com/en-us/HT215500",
|
||||
"rapidSecurityResponse": true,
|
||||
"products": [
|
||||
{
|
||||
"platform": "iOS",
|
||||
"name": "iPhone 15 Pro",
|
||||
"version": "18.0.1 (c)",
|
||||
"build": "22A123c"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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.Models/StellaOps.Concelier.Models.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Vndr.Apple/StellaOps.Concelier.Connector.Vndr.Apple.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Testing/StellaOps.Concelier.Testing.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Apple/Fixtures/*.html" CopyToOutputDirectory="Always" TargetPath="Source/Vndr/Apple/Fixtures/%(Filename)%(Extension)" />
|
||||
<None Include="Apple/Fixtures/*.json" CopyToOutputDirectory="Always" TargetPath="Source/Vndr/Apple/Fixtures/%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user