Add channel test providers for Email, Slack, Teams, and Webhook
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Implemented EmailChannelTestProvider to generate email preview payloads.
- Implemented SlackChannelTestProvider to create Slack message previews.
- Implemented TeamsChannelTestProvider for generating Teams Adaptive Card previews.
- Implemented WebhookChannelTestProvider to create webhook payloads.
- Added INotifyChannelTestProvider interface for channel-specific preview generation.
- Created ChannelTestPreviewContracts for request and response models.
- Developed NotifyChannelTestService to handle test send requests and generate previews.
- Added rate limit policies for test sends and delivery history.
- Implemented unit tests for service registration and binding.
- Updated project files to include necessary dependencies and configurations.
This commit is contained in:
2025-10-19 23:29:34 +03:00
parent 8e7ce55542
commit 5fd4032c7c
239 changed files with 17245 additions and 3155 deletions

View File

@@ -283,6 +283,155 @@ public sealed class TrivyDbFeedExporterTests : IDisposable
Assert.Empty(orasPusher.Pushes);
}
[Fact]
public async Task ExportAsync_WritesMirrorBundlesWhenConfigured()
{
var advisoryOne = CreateSampleAdvisory("CVE-2025-1001", "Mirror Advisory One");
var advisoryTwo = CreateSampleAdvisory("CVE-2025-1002", "Mirror Advisory Two");
var advisoryStore = new StubAdvisoryStore(advisoryOne, advisoryTwo);
var optionsValue = new TrivyDbExportOptions
{
OutputRoot = _root,
ReferencePrefix = "example/trivy",
TargetRepository = "s3://mirror/trivy",
Json = new JsonExportOptions
{
OutputRoot = _jsonRoot,
MaintainLatestSymlink = false,
},
KeepWorkingTree = false,
};
optionsValue.Mirror.Enabled = true;
optionsValue.Mirror.DirectoryName = "mirror";
optionsValue.Mirror.Domains.Add(new TrivyDbMirrorDomainOptions
{
Id = "primary",
DisplayName = "Primary Mirror",
});
var options = Options.Create(optionsValue);
var packageBuilder = new TrivyDbPackageBuilder();
var ociWriter = new TrivyDbOciWriter();
var planner = new TrivyDbExportPlanner();
var stateStore = new InMemoryExportStateStore();
var exportedAt = DateTimeOffset.Parse("2024-09-18T12:00:00Z", CultureInfo.InvariantCulture);
var timeProvider = new TestTimeProvider(exportedAt);
var stateManager = new ExportStateManager(stateStore, timeProvider);
var builderMetadata = JsonSerializer.SerializeToUtf8Bytes(new
{
Version = 2,
NextUpdate = "2024-09-19T12:00:00Z",
UpdatedAt = "2024-09-18T12:00:00Z",
});
var builder = new StubTrivyDbBuilder(_root, builderMetadata);
var orasPusher = new StubTrivyDbOrasPusher();
var exporter = new TrivyDbFeedExporter(
advisoryStore,
new VulnListJsonExportPathResolver(),
options,
packageBuilder,
ociWriter,
stateManager,
planner,
builder,
orasPusher,
NullLogger<TrivyDbFeedExporter>.Instance,
timeProvider);
using var provider = new ServiceCollection().BuildServiceProvider();
await exporter.ExportAsync(provider, CancellationToken.None);
var exportId = exportedAt.ToString(optionsValue.TagFormat, CultureInfo.InvariantCulture);
var layoutPath = optionsValue.GetExportRoot(exportId);
var mirrorRoot = Path.Combine(layoutPath, "mirror");
var domainRoot = Path.Combine(mirrorRoot, "primary");
Assert.True(File.Exists(Path.Combine(mirrorRoot, "index.json")));
Assert.True(File.Exists(Path.Combine(domainRoot, "manifest.json")));
Assert.True(File.Exists(Path.Combine(domainRoot, "metadata.json")));
Assert.True(File.Exists(Path.Combine(domainRoot, "db.tar.gz")));
var reference = $"{optionsValue.ReferencePrefix}:{exportId}";
var manifestDigest = ReadManifestDigest(layoutPath);
var indexPath = Path.Combine(mirrorRoot, "index.json");
string? indexManifestDescriptorDigest = null;
string? indexMetadataDigest = null;
string? indexDatabaseDigest = null;
using (var indexDoc = JsonDocument.Parse(File.ReadAllBytes(indexPath)))
{
var root = indexDoc.RootElement;
Assert.Equal(1, root.GetProperty("schemaVersion").GetInt32());
Assert.Equal(reference, root.GetProperty("reference").GetString());
Assert.Equal(manifestDigest, root.GetProperty("manifestDigest").GetString());
Assert.Equal("full", root.GetProperty("mode").GetString());
Assert.Equal("s3://mirror/trivy", root.GetProperty("targetRepository").GetString());
Assert.False(root.TryGetProperty("delta", out _));
var domains = root.GetProperty("domains").EnumerateArray().ToArray();
var domain = Assert.Single(domains);
Assert.Equal("primary", domain.GetProperty("domainId").GetString());
Assert.Equal("Primary Mirror", domain.GetProperty("displayName").GetString());
Assert.Equal(2, domain.GetProperty("advisoryCount").GetInt32());
var manifestDescriptor = domain.GetProperty("manifest");
Assert.Equal("mirror/primary/manifest.json", manifestDescriptor.GetProperty("path").GetString());
indexManifestDescriptorDigest = manifestDescriptor.GetProperty("digest").GetString();
var metadataDescriptor = domain.GetProperty("metadata");
Assert.Equal("mirror/primary/metadata.json", metadataDescriptor.GetProperty("path").GetString());
indexMetadataDigest = metadataDescriptor.GetProperty("digest").GetString();
var databaseDescriptor = domain.GetProperty("database");
Assert.Equal("mirror/primary/db.tar.gz", databaseDescriptor.GetProperty("path").GetString());
indexDatabaseDigest = databaseDescriptor.GetProperty("digest").GetString();
}
var domainManifestPath = Path.Combine(domainRoot, "manifest.json");
var rootMetadataPath = Path.Combine(layoutPath, "metadata.json");
var domainMetadataPath = Path.Combine(domainRoot, "metadata.json");
var domainDbPath = Path.Combine(domainRoot, "db.tar.gz");
var domainManifestBytes = File.ReadAllBytes(domainManifestPath);
var domainManifestDigest = "sha256:" + Convert.ToHexString(SHA256.HashData(domainManifestBytes)).ToLowerInvariant();
var rootMetadataBytes = File.ReadAllBytes(rootMetadataPath);
var domainMetadataBytes = File.ReadAllBytes(domainMetadataPath);
Assert.Equal(rootMetadataBytes, domainMetadataBytes);
var metadataDigest = "sha256:" + Convert.ToHexString(SHA256.HashData(domainMetadataBytes)).ToLowerInvariant();
var databaseDigest = "sha256:" + Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(domainDbPath))).ToLowerInvariant();
Assert.Equal(domainManifestDigest, indexManifestDescriptorDigest);
Assert.Equal(metadataDigest, indexMetadataDigest);
Assert.Equal(databaseDigest, indexDatabaseDigest);
using (var manifestDoc = JsonDocument.Parse(File.ReadAllBytes(domainManifestPath)))
{
var manifestRoot = manifestDoc.RootElement;
Assert.Equal("primary", manifestRoot.GetProperty("domainId").GetString());
Assert.Equal("Primary Mirror", manifestRoot.GetProperty("displayName").GetString());
Assert.Equal(reference, manifestRoot.GetProperty("reference").GetString());
Assert.Equal(manifestDigest, manifestRoot.GetProperty("manifestDigest").GetString());
Assert.Equal("full", manifestRoot.GetProperty("mode").GetString());
Assert.Equal("s3://mirror/trivy", manifestRoot.GetProperty("targetRepository").GetString());
var metadataDescriptor = manifestRoot.GetProperty("metadata");
Assert.Equal("mirror/primary/metadata.json", metadataDescriptor.GetProperty("path").GetString());
Assert.Equal(metadataDigest, metadataDescriptor.GetProperty("digest").GetString());
var databaseDescriptor = manifestRoot.GetProperty("database");
Assert.Equal("mirror/primary/db.tar.gz", databaseDescriptor.GetProperty("path").GetString());
Assert.Equal(databaseDigest, databaseDescriptor.GetProperty("digest").GetString());
var sources = manifestRoot.GetProperty("sources").EnumerateArray().ToArray();
Assert.NotEmpty(sources);
Assert.Contains(sources, element => string.Equals(element.GetProperty("source").GetString(), "nvd", StringComparison.OrdinalIgnoreCase));
}
Assert.Empty(orasPusher.Pushes);
}
[Fact]
public async Task ExportAsync_SkipsOrasPushWhenDeltaPublishingDisabled()
{
@@ -774,7 +923,9 @@ public sealed class TrivyDbFeedExporterTests : IDisposable
private ExportStateRecord? _record;
public Task<ExportStateRecord?> FindAsync(string id, CancellationToken cancellationToken)
=> Task.FromResult(_record);
{
return Task.FromResult(_record);
}
public Task<ExportStateRecord> UpsertAsync(ExportStateRecord record, CancellationToken cancellationToken)
{