Add tests and implement StubBearer authentication for Signer endpoints
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Docs CI / lint-and-preview (push) Has been cancelled
				
			- Created SignerEndpointsTests to validate the SignDsse and VerifyReferrers endpoints. - Implemented StubBearerAuthenticationDefaults and StubBearerAuthenticationHandler for token-based authentication. - Developed ConcelierExporterClient for managing Trivy DB settings and export operations. - Added TrivyDbSettingsPageComponent for UI interactions with Trivy DB settings, including form handling and export triggering. - Implemented styles and HTML structure for Trivy DB settings page. - Created NotifySmokeCheck tool for validating Redis event streams and Notify deliveries.
This commit is contained in:
		| @@ -0,0 +1,96 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using StellaOps.Notify.Engine; | ||||
| using StellaOps.Notify.Models; | ||||
| using Xunit; | ||||
|  | ||||
| namespace StellaOps.Notify.Connectors.Slack.Tests; | ||||
|  | ||||
| public sealed class SlackChannelHealthProviderTests | ||||
| { | ||||
|     private static readonly SlackChannelHealthProvider Provider = new(); | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task CheckAsync_ReturnsHealthy() | ||||
|     { | ||||
|         var channel = CreateChannel(enabled: true, target: "#sec-ops"); | ||||
|  | ||||
|         var context = new ChannelHealthContext( | ||||
|             channel.TenantId, | ||||
|             channel, | ||||
|             channel.Config.Target!, | ||||
|             new DateTimeOffset(2025, 10, 20, 14, 0, 0, TimeSpan.Zero), | ||||
|             "trace-slack-001"); | ||||
|  | ||||
|         var result = await Provider.CheckAsync(context, CancellationToken.None); | ||||
|  | ||||
|         Assert.Equal(ChannelHealthStatus.Healthy, result.Status); | ||||
|         Assert.Equal("true", result.Metadata["slack.channel.enabled"]); | ||||
|         Assert.Equal("true", result.Metadata["slack.validation.targetPresent"]); | ||||
|         Assert.Equal("#sec-ops", result.Metadata["slack.channel"]); | ||||
|         Assert.Equal(ComputeSecretHash(channel.Config.SecretRef), result.Metadata["slack.secretRef.hash"]); | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task CheckAsync_ReturnsDegradedWhenDisabled() | ||||
|     { | ||||
|         var channel = CreateChannel(enabled: false, target: "#sec-ops"); | ||||
|  | ||||
|         var context = new ChannelHealthContext( | ||||
|             channel.TenantId, | ||||
|             channel, | ||||
|             channel.Config.Target!, | ||||
|             DateTimeOffset.UtcNow, | ||||
|             "trace-slack-002"); | ||||
|  | ||||
|         var result = await Provider.CheckAsync(context, CancellationToken.None); | ||||
|  | ||||
|         Assert.Equal(ChannelHealthStatus.Degraded, result.Status); | ||||
|         Assert.Equal("false", result.Metadata["slack.channel.enabled"]); | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task CheckAsync_ReturnsUnhealthyWhenTargetMissing() | ||||
|     { | ||||
|         var channel = CreateChannel(enabled: true, target: null); | ||||
|  | ||||
|         var context = new ChannelHealthContext( | ||||
|             channel.TenantId, | ||||
|             channel, | ||||
|             channel.Name, | ||||
|             DateTimeOffset.UtcNow, | ||||
|             "trace-slack-003"); | ||||
|  | ||||
|         var result = await Provider.CheckAsync(context, CancellationToken.None); | ||||
|  | ||||
|         Assert.Equal(ChannelHealthStatus.Unhealthy, result.Status); | ||||
|         Assert.Equal("false", result.Metadata["slack.validation.targetPresent"]); | ||||
|     } | ||||
|  | ||||
|     private static NotifyChannel CreateChannel(bool enabled, string? target) | ||||
|     { | ||||
|         return NotifyChannel.Create( | ||||
|             channelId: "channel-slack-sec-ops", | ||||
|             tenantId: "tenant-sec", | ||||
|             name: "slack:sec-ops", | ||||
|             type: NotifyChannelType.Slack, | ||||
|             config: NotifyChannelConfig.Create( | ||||
|                 secretRef: "ref://notify/channels/slack/sec-ops", | ||||
|                 target: target, | ||||
|                 properties: new Dictionary<string, string> | ||||
|                 { | ||||
|                     ["workspace"] = "stellaops-sec", | ||||
|                     ["botToken"] = "xoxb-123456789012-abcdefghijklmnop" | ||||
|                 }), | ||||
|             enabled: enabled); | ||||
|     } | ||||
|  | ||||
|     private static string ComputeSecretHash(string secretRef) | ||||
|     { | ||||
|         var bytes = System.Text.Encoding.UTF8.GetBytes(secretRef.Trim()); | ||||
|         var hash = System.Security.Cryptography.SHA256.HashData(bytes); | ||||
|         return Convert.ToHexString(hash.AsSpan(0, 8)).ToLowerInvariant(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,113 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Text.Json; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using StellaOps.Notify.Engine; | ||||
| using StellaOps.Notify.Models; | ||||
| using Xunit; | ||||
|  | ||||
| namespace StellaOps.Notify.Connectors.Slack.Tests; | ||||
|  | ||||
| public sealed class SlackChannelTestProviderTests | ||||
| { | ||||
|     private static readonly ChannelTestPreviewRequest EmptyRequest = new( | ||||
|         TargetOverride: null, | ||||
|         TemplateId: null, | ||||
|         Title: null, | ||||
|         Summary: null, | ||||
|         Body: null, | ||||
|         TextBody: null, | ||||
|         Locale: null, | ||||
|         Metadata: new Dictionary<string, string>(), | ||||
|         Attachments: new List<string>()); | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task BuildPreviewAsync_ProducesDeterministicMetadata() | ||||
|     { | ||||
|         var provider = new SlackChannelTestProvider(); | ||||
|         var channel = CreateChannel(properties: new Dictionary<string, string> | ||||
|         { | ||||
|             ["workspace"] = "stellaops-sec", | ||||
|             ["botToken"] = "xoxb-123456789012-abcdefghijklmnop" | ||||
|         }); | ||||
|  | ||||
|         var context = new ChannelTestPreviewContext( | ||||
|             channel.TenantId, | ||||
|             channel, | ||||
|             channel.Config.Target!, | ||||
|             EmptyRequest, | ||||
|             Timestamp: new DateTimeOffset(2025, 10, 20, 12, 00, 00, TimeSpan.Zero), | ||||
|             TraceId: "trace-001"); | ||||
|  | ||||
|         var result = await provider.BuildPreviewAsync(context, CancellationToken.None); | ||||
|  | ||||
|         Assert.Equal("slack", result.Preview.ChannelType.ToString().ToLowerInvariant()); | ||||
|         Assert.Equal(channel.Config.Target, result.Preview.Target); | ||||
|         Assert.Equal("chat:write,chat:write.public", result.Metadata["slack.scopes.required"]); | ||||
|         Assert.Equal("stellaops-sec", result.Metadata["slack.config.workspace"]); | ||||
|  | ||||
|         var redactedToken = result.Metadata["slack.config.botToken"]; | ||||
|         Assert.DoesNotContain("abcdefghijklmnop", redactedToken); | ||||
|         Assert.StartsWith("xoxb-", redactedToken); | ||||
|         Assert.EndsWith("mnop", redactedToken); | ||||
|  | ||||
|         using var parsed = JsonDocument.Parse(result.Preview.Body); | ||||
|         var contextText = parsed.RootElement | ||||
|             .GetProperty("blocks")[1] | ||||
|             .GetProperty("elements")[0] | ||||
|             .GetProperty("text") | ||||
|             .GetString(); | ||||
|         Assert.NotNull(contextText); | ||||
|         Assert.Contains("trace-001", contextText); | ||||
|  | ||||
|         Assert.Equal(ComputeSecretHash(channel.Config.SecretRef), result.Metadata["slack.secretRef.hash"]); | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task BuildPreviewAsync_RedactsSensitiveProperties() | ||||
|     { | ||||
|         var provider = new SlackChannelTestProvider(); | ||||
|         var channel = CreateChannel(properties: new Dictionary<string, string> | ||||
|         { | ||||
|             ["SigningSecret"] = "whsec_super-secret-value", | ||||
|             ["apiToken"] = "xoxs-000000000000-super", | ||||
|             ["endpoint"] = "https://hooks.slack.com/services/T000/B000/AAA" | ||||
|         }); | ||||
|  | ||||
|         var context = new ChannelTestPreviewContext( | ||||
|             channel.TenantId, | ||||
|             channel, | ||||
|             channel.Config.Target!, | ||||
|             EmptyRequest, | ||||
|             Timestamp: DateTimeOffset.UtcNow, | ||||
|             TraceId: "trace-002"); | ||||
|  | ||||
|         var result = await provider.BuildPreviewAsync(context, CancellationToken.None); | ||||
|  | ||||
|         Assert.Equal("***", result.Metadata["slack.config.SigningSecret"]); | ||||
|         Assert.DoesNotContain("xoxs-000000000000-super", result.Metadata["slack.config.apiToken"]); | ||||
|         Assert.Equal("https://hooks.slack.com/services/T000/B000/AAA", result.Metadata["slack.config.endpoint"]); | ||||
|     } | ||||
|  | ||||
|     private static NotifyChannel CreateChannel(IDictionary<string, string> properties) | ||||
|     { | ||||
|         return NotifyChannel.Create( | ||||
|             channelId: "channel-slack-sec-ops", | ||||
|             tenantId: "tenant-sec", | ||||
|             name: "slack:sec-ops", | ||||
|             type: NotifyChannelType.Slack, | ||||
|             config: NotifyChannelConfig.Create( | ||||
|                 secretRef: "ref://notify/channels/slack/sec-ops", | ||||
|                 target: "#sec-ops", | ||||
|                 properties: properties)); | ||||
|     } | ||||
|  | ||||
|     private static string ComputeSecretHash(string secretRef) | ||||
|     { | ||||
|         using var sha = System.Security.Cryptography.SHA256.Create(); | ||||
|         var bytes = System.Text.Encoding.UTF8.GetBytes(secretRef.Trim()); | ||||
|         var hash = sha.ComputeHash(bytes); | ||||
|         return System.Convert.ToHexString(hash, 0, 8).ToLowerInvariant(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,21 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net10.0</TargetFramework> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <UseConcelierTestInfra>false</UseConcelierTestInfra> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="../StellaOps.Notify.Connectors.Slack/StellaOps.Notify.Connectors.Slack.csproj" /> | ||||
|     <ProjectReference Include="../StellaOps.Notify.Engine/StellaOps.Notify.Engine.csproj" /> | ||||
|     <ProjectReference Include="../StellaOps.Notify.Models/StellaOps.Notify.Models.csproj" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" /> | ||||
|     <PackageReference Include="xunit" Version="2.9.2" /> | ||||
|     <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" /> | ||||
|     <PackageReference Include="coverlet.collector" Version="6.0.4" /> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
		Reference in New Issue
	
	Block a user