Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -20,6 +20,7 @@ using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.IdentityModel.Tokens;
using Xunit;
using Xunit.v3;
namespace StellaOps.Notify.WebService.Tests.W1;
@@ -61,8 +62,8 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
_adminToken = CreateToken("notify.viewer", "notify.operator", "notify.admin");
}
public Task InitializeAsync() => Task.CompletedTask;
public Task DisposeAsync() => Task.CompletedTask;
public ValueTask InitializeAsync() => ValueTask.CompletedTask;
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
#region Health Endpoints Contract
@@ -73,11 +74,11 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/healthz");
var response = await client.GetAsync("/healthz", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
var json = JsonNode.Parse(content);
json?["status"]?.GetValue<string>().Should().Be("ok");
}
@@ -89,7 +90,7 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/readyz");
var response = await client.GetAsync("/readyz", CancellationToken.None);
// Assert
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.ServiceUnavailable);
@@ -107,13 +108,13 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
// Act
var response = await client.GetAsync("/api/v1/notify/rules");
var response = await client.GetAsync("/api/v1/notify/rules", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Headers.ContentType?.MediaType.Should().Be("application/json");
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
var json = JsonNode.Parse(content);
json.Should().NotBeNull();
json!.AsArray().Should().NotBeNull();
@@ -132,7 +133,8 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
// Act
var response = await client.PostAsync(
"/api/v1/notify/rules",
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"));
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"),
CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
@@ -166,7 +168,7 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
// Act
var response = await client.GetAsync("/api/v1/notify/rules/nonexistent-rule-id");
var response = await client.GetAsync("/api/v1/notify/rules/nonexistent-rule-id", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
@@ -184,10 +186,11 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
var payload = CreateRulePayload(ruleId);
await client.PostAsync(
"/api/v1/notify/rules",
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"));
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"),
CancellationToken.None);
// Act
var response = await client.DeleteAsync($"/api/v1/notify/rules/{ruleId}");
var response = await client.DeleteAsync($"/api/v1/notify/rules/{ruleId}", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NoContent);
@@ -205,13 +208,13 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
// Act
var response = await client.GetAsync("/api/v1/notify/channels");
var response = await client.GetAsync("/api/v1/notify/channels", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Headers.ContentType?.MediaType.Should().Be("application/json");
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
var json = JsonNode.Parse(content);
json.Should().NotBeNull();
json!.AsArray().Should().NotBeNull();
@@ -230,7 +233,8 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
// Act
var response = await client.PostAsync(
"/api/v1/notify/channels",
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"));
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"),
CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
@@ -248,11 +252,11 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
// Act
var response = await client.GetAsync("/api/v1/notify/templates");
var response = await client.GetAsync("/api/v1/notify/templates", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
var json = JsonNode.Parse(content);
json.Should().NotBeNull();
json!.AsArray().Should().NotBeNull();
@@ -271,7 +275,8 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
// Act
var response = await client.PostAsync(
"/api/v1/notify/templates",
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"));
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"),
CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
@@ -294,7 +299,8 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
// Act
var response = await client.PostAsync(
"/api/v1/notify/deliveries",
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"));
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"),
CancellationToken.None);
// Assert - can be 201 Created or 202 Accepted depending on processing
response.StatusCode.Should().BeOneOf(HttpStatusCode.Created, HttpStatusCode.Accepted);
@@ -308,11 +314,11 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
// Act
var response = await client.GetAsync("/api/v1/notify/deliveries?limit=10");
var response = await client.GetAsync("/api/v1/notify/deliveries?limit=10", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
var json = JsonNode.Parse(content);
json.Should().NotBeNull();
json!.AsArray().Should().NotBeNull();
@@ -326,7 +332,7 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
// Act
var response = await client.GetAsync($"/api/v1/notify/deliveries/{Guid.NewGuid()}");
var response = await client.GetAsync($"/api/v1/notify/deliveries/{Guid.NewGuid()}", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
@@ -348,11 +354,12 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
// Act
var response = await client.PostAsync(
"/api/v1/notify/_internal/rules/normalize",
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"));
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"),
CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
var json = JsonNode.Parse(content);
json?["schemaVersion"]?.GetValue<string>().Should().NotBeNullOrEmpty();
}
@@ -372,15 +379,16 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
var payload = CreateRulePayload(ruleId);
await client.PostAsync(
"/api/v1/notify/rules",
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"));
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"),
CancellationToken.None);
// Act
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
var response = await client.GetAsync($"/api/v1/notify/rules/{ruleId}");
var response = await client.GetAsync($"/api/v1/notify/rules/{ruleId}", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
var json = JsonNode.Parse(content);
// Verify required fields exist
@@ -403,15 +411,16 @@ public class NotifyWebServiceContractTests : IClassFixture<WebApplicationFactory
var payload = CreateChannelPayload(channelId);
await client.PostAsync(
"/api/v1/notify/channels",
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"));
new StringContent(payload.ToJsonString(), Encoding.UTF8, "application/json"),
CancellationToken.None);
// Act
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _viewerToken);
var response = await client.GetAsync($"/api/v1/notify/channels/{channelId}");
var response = await client.GetAsync($"/api/v1/notify/channels/{channelId}", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
var json = JsonNode.Parse(content);
json?["channelId"].Should().NotBeNull();