tests fixes and some product advisories tunes ups

This commit is contained in:
master
2026-01-30 07:57:43 +02:00
parent 644887997c
commit 55744f6a39
345 changed files with 26290 additions and 2267 deletions

View File

@@ -6,19 +6,18 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Xunit.v3;
@@ -45,16 +44,26 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
{
_factory = factory.WithWebHostBuilder(builder =>
{
builder.UseSetting("notify:storage:driver", "memory");
builder.UseSetting("notify:authority:enabled", "false");
builder.UseSetting("notify:authority:developmentSigningKey", SigningKey);
builder.UseSetting("notify:authority:issuer", Issuer);
builder.UseSetting("notify:authority:audiences:0", Audience);
builder.UseSetting("notify:authority:allowAnonymousFallback", "false");
builder.UseSetting("notify:authority:adminScope", "notify.admin");
builder.UseSetting("notify:authority:operatorScope", "notify.operator");
builder.UseSetting("notify:authority:viewerScope", "notify.viewer");
builder.UseSetting("notify:telemetry:enableRequestLogging", "false");
builder.ConfigureAppConfiguration((_, config) =>
{
config.AddInMemoryCollection(new Dictionary<string, string?>
{
["notify:storage:driver"] = "memory",
["notify:authority:enabled"] = "false",
["notify:authority:developmentSigningKey"] = SigningKey,
["notify:authority:issuer"] = Issuer,
["notify:authority:audiences:0"] = Audience,
["notify:authority:allowAnonymousFallback"] = "false",
["notify:authority:adminScope"] = "notify.admin",
["notify:authority:operatorScope"] = "notify.operator",
["notify:authority:viewerScope"] = "notify.viewer",
["notify:telemetry:enableRequestLogging"] = "false",
});
});
builder.ConfigureTestServices(services =>
{
NotifyTestServiceOverrides.ReplaceWithInMemory(services, signingKey: SigningKey, issuer: Issuer, audience: Audience);
});
});
_viewerToken = CreateToken("notify.viewer");
@@ -104,8 +113,7 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task ListRules_ReturnsJsonArray()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
var client = CreateAuthenticatedClient(_viewerToken);
// Act
var response = await client.GetAsync("/api/v1/notify/rules", CancellationToken.None);
@@ -113,7 +121,7 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Headers.ContentType?.MediaType.Should().Be("application/json");
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
var json = JsonNode.Parse(content);
json.Should().NotBeNull();
@@ -124,10 +132,9 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task CreateRule_ValidPayload_Returns201WithLocation()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _operatorToken);
var ruleId = $"rule-contract-{Guid.NewGuid():N}";
var client = CreateAuthenticatedClient(_operatorToken);
var ruleId = Guid.NewGuid().ToString();
var payload = CreateRulePayload(ruleId);
// Act
@@ -146,9 +153,8 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task CreateRule_InvalidPayload_Returns400()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _operatorToken);
var client = CreateAuthenticatedClient(_operatorToken);
var invalidPayload = new JsonObject { ["invalid"] = "data" };
// Act
@@ -164,11 +170,10 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task GetRule_NotFound_Returns404()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
var client = CreateAuthenticatedClient(_viewerToken);
// Act
var response = await client.GetAsync("/api/v1/notify/rules/nonexistent-rule-id", CancellationToken.None);
var response = await client.GetAsync($"/api/v1/notify/rules/{Guid.NewGuid()}", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
@@ -178,11 +183,10 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task DeleteRule_Existing_Returns204()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _operatorToken);
var client = CreateAuthenticatedClient(_operatorToken);
// First create a rule
var ruleId = $"rule-delete-{Guid.NewGuid():N}";
var ruleId = Guid.NewGuid().ToString();
var payload = CreateRulePayload(ruleId);
await client.PostAsync(
"/api/v1/notify/rules",
@@ -204,8 +208,7 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task ListChannels_ReturnsJsonArray()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
var client = CreateAuthenticatedClient(_viewerToken);
// Act
var response = await client.GetAsync("/api/v1/notify/channels", CancellationToken.None);
@@ -213,7 +216,7 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Headers.ContentType?.MediaType.Should().Be("application/json");
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
var json = JsonNode.Parse(content);
json.Should().NotBeNull();
@@ -224,10 +227,9 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task CreateChannel_ValidPayload_Returns201()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _operatorToken);
var channelId = $"channel-contract-{Guid.NewGuid():N}";
var client = CreateAuthenticatedClient(_operatorToken);
var channelId = Guid.NewGuid().ToString();
var payload = CreateChannelPayload(channelId);
// Act
@@ -248,8 +250,7 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task ListTemplates_ReturnsJsonArray()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
var client = CreateAuthenticatedClient(_viewerToken);
// Act
var response = await client.GetAsync("/api/v1/notify/templates", CancellationToken.None);
@@ -266,9 +267,8 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task CreateTemplate_ValidPayload_Returns201()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _operatorToken);
var client = CreateAuthenticatedClient(_operatorToken);
var templateId = Guid.NewGuid();
var payload = CreateTemplatePayload(templateId);
@@ -290,9 +290,8 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task CreateDelivery_ValidPayload_Returns201OrAccepted()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _operatorToken);
var client = CreateAuthenticatedClient(_operatorToken);
var deliveryId = Guid.NewGuid();
var payload = CreateDeliveryPayload(deliveryId);
@@ -310,8 +309,7 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task ListDeliveries_ReturnsJsonArrayWithPagination()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
var client = CreateAuthenticatedClient(_viewerToken);
// Act
var response = await client.GetAsync("/api/v1/notify/deliveries?limit=10", CancellationToken.None);
@@ -321,15 +319,13 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
var json = JsonNode.Parse(content);
json.Should().NotBeNull();
json!.AsArray().Should().NotBeNull();
}
[Fact]
public async Task GetDelivery_NotFound_Returns404()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
var client = CreateAuthenticatedClient(_viewerToken);
// Act
var response = await client.GetAsync($"/api/v1/notify/deliveries/{Guid.NewGuid()}", CancellationToken.None);
@@ -346,10 +342,9 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task NormalizeRule_ValidPayload_ReturnsUpgradedSchema()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _adminToken);
var payload = CreateRulePayload("rule-normalize-test");
var client = CreateAuthenticatedClient(_adminToken);
var payload = CreateRulePayload(Guid.NewGuid().ToString());
// Act
var response = await client.PostAsync(
@@ -372,10 +367,9 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task RuleResponse_ContainsRequiredFields()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _operatorToken);
var ruleId = $"rule-shape-{Guid.NewGuid():N}";
var client = CreateAuthenticatedClient(_operatorToken);
var ruleId = Guid.NewGuid().ToString();
var payload = CreateRulePayload(ruleId);
await client.PostAsync(
"/api/v1/notify/rules",
@@ -390,7 +384,7 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
var json = JsonNode.Parse(content);
// Verify required fields exist
json?["ruleId"].Should().NotBeNull();
json?["tenantId"].Should().NotBeNull();
@@ -404,10 +398,9 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
public async Task ChannelResponse_ContainsRequiredFields()
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _operatorToken);
var channelId = $"channel-shape-{Guid.NewGuid():N}";
var client = CreateAuthenticatedClient(_operatorToken);
var channelId = Guid.NewGuid().ToString();
var payload = CreateChannelPayload(channelId);
await client.PostAsync(
"/api/v1/notify/channels",
@@ -422,7 +415,7 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
var json = JsonNode.Parse(content);
json?["channelId"].Should().NotBeNull();
json?["tenantId"].Should().NotBeNull();
json?["channelType"].Should().NotBeNull();
@@ -433,48 +426,42 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
#region Helper Methods
private HttpClient CreateAuthenticatedClient(string token)
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", TestTenantId);
return client;
}
private static string CreateToken(params string[] scopes)
{
var handler = new JwtSecurityTokenHandler();
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SigningKey));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var claims = new List<Claim>
{
new(JwtRegisteredClaimNames.Sub, "test-user"),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new("tenant_id", TestTenantId)
};
claims.AddRange(scopes.Select(s => new Claim("scope", s)));
var token = new JwtSecurityToken(
issuer: Issuer,
audience: Audience,
claims: claims,
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: credentials);
return handler.WriteToken(token);
return NotifyTestServiceOverrides.CreateTestToken(
SigningKey, Issuer, Audience, scopes, tenantId: TestTenantId);
}
private static JsonObject CreateRulePayload(string ruleId)
{
return new JsonObject
{
["schemaVersion"] = "notify-rule@1",
["schemaVersion"] = "notify.rule@1",
["ruleId"] = ruleId,
["tenantId"] = TestTenantId,
["name"] = $"Test Rule {ruleId}",
["description"] = "Contract test rule",
["enabled"] = true,
["eventKinds"] = new JsonArray { "scan.completed" },
["match"] = new JsonObject
{
["eventKinds"] = new JsonArray { "scan.completed" }
},
["actions"] = new JsonArray
{
new JsonObject
{
["actionId"] = $"action-{Guid.NewGuid():N}",
["channel"] = "email:test",
["templateKey"] = "default"
["actionId"] = Guid.NewGuid().ToString(),
["channel"] = Guid.NewGuid().ToString(),
["template"] = "default",
["enabled"] = true
}
}
};
@@ -484,17 +471,15 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
{
return new JsonObject
{
["schemaVersion"] = "notify-channel@1",
["schemaVersion"] = "notify.channel@1",
["channelId"] = channelId,
["tenantId"] = TestTenantId,
["channelType"] = "email",
["type"] = "email",
["name"] = $"Test Channel {channelId}",
["enabled"] = true,
["config"] = new JsonObject
{
["smtpHost"] = "localhost",
["smtpPort"] = 25,
["from"] = "test@example.com"
["target"] = "test@example.com"
}
};
}
@@ -503,7 +488,7 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
{
return new JsonObject
{
["schemaVersion"] = "notify-template@1",
["schemaVersion"] = "notify.template@1",
["templateId"] = templateId.ToString(),
["tenantId"] = TestTenantId,
["channelType"] = "email",
@@ -520,11 +505,11 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
{
["deliveryId"] = deliveryId.ToString(),
["tenantId"] = TestTenantId,
["channelId"] = "email:default",
["status"] = "pending",
["recipient"] = "test@example.com",
["subject"] = "Test Notification",
["body"] = "This is a test notification."
["ruleId"] = Guid.NewGuid().ToString(),
["actionId"] = Guid.NewGuid().ToString(),
["eventId"] = Guid.NewGuid().ToString(),
["kind"] = "scanner.report.ready",
["status"] = "pending"
};
}