Add channel test providers for Email, Slack, Teams, and Webhook
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			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:
		| @@ -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) | ||||
|         { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user