Files
itworx-knowledge-hub-Public/tests/backend/ItWorx.KnowledgeHub.IntegrationTests/InfrastructureKnowledge/ExpandedManualOverrideCatalogTests.cs
T

175 lines
9.1 KiB
C#

using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using ItWorx.KnowledgeHub.Contracts.Infrastructure;
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
namespace ItWorx.KnowledgeHub.IntegrationTests.InfrastructureKnowledge;
/// <summary>
/// V2-R5 (docs/adr/ADR-029-v2-r5-final-hardening.md, brief Phase 6): the manual-
/// override mechanism was already fully generic at the use-case/repository layer
/// (<c>SetManualOverrideUseCase</c>, <c>IInfrastructureRepository.TargetExistsAsync</c>)
/// — only the field catalog (previously an explicit, deliberately empty stub for
/// every kind but Service) and a reachable endpoint were missing. Proves the field
/// catalog is now real for every non-Service kind the domain models, and that the new
/// generic <c>POST /infrastructure/overrides</c> endpoint actually works end to end
/// for several of them.
/// </summary>
[Collection(PostgresTestGroup.Name)]
public sealed class ExpandedManualOverrideCatalogTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres)
{
[Theory]
[InlineData("Host", "hostname")]
[InlineData("Component", "name")]
[InlineData("Endpoint", "scheme")]
[InlineData("Network", "name")]
[InlineData("Volume", "name")]
[InlineData("Database", "name")]
[InlineData("Deployment", "repositoryUrl")]
[InlineData("Backup", "method")]
public async Task Every_non_service_object_kind_now_has_a_real_non_empty_override_catalog(string targetKind, string expectedField)
{
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var catalog = await client.GetFromJsonAsync<ManualOverrideFieldCatalogResponse>(
$"/api/v1/infrastructure/overrides/catalog?targetKind={targetKind}");
Assert.NotNull(catalog);
Assert.NotEmpty(catalog.Fields);
Assert.Contains(catalog.Fields, field => field.FieldName == expectedField);
}
[Fact]
public async Task A_host_fact_can_be_corrected_through_the_generic_endpoint()
{
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var (hostId, _, _, _, _, _, _, _) = await SeedFullServiceGraphAsync(client);
var response = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/infrastructure/overrides",
new SetInfrastructureOverrideRequest("Host", hostId, "hostname", JsonSerializer.SerializeToElement("gecorrigeerde-hostnaam"), "Reden."));
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Theory]
[InlineData("Network", "internal")]
[InlineData("Volume", "readOnly")]
public async Task A_boolean_shaped_fact_is_overridable_as_a_true_false_enum(string targetKind, string fieldName)
{
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var (_, _, networkId, volumeId, _, _, _, _) = await SeedFullServiceGraphAsync(client);
var targetId = targetKind == "Network" ? networkId : volumeId;
var response = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/infrastructure/overrides",
new SetInfrastructureOverrideRequest(targetKind, targetId, fieldName, JsonSerializer.SerializeToElement("true"), "Reden."));
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var invalid = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/infrastructure/overrides",
new SetInfrastructureOverrideRequest(targetKind, targetId, fieldName, JsonSerializer.SerializeToElement("misschien"), "Ongeldig."));
Assert.Equal(HttpStatusCode.UnprocessableEntity, invalid.StatusCode);
}
[Fact]
public async Task An_unknown_target_kind_string_is_rejected_not_a_crash()
{
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var (hostId, _, _, _, _, _, _, _) = await SeedFullServiceGraphAsync(client);
var response = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/infrastructure/overrides",
new SetInfrastructureOverrideRequest("NietBestaandType", hostId, "naam", JsonSerializer.SerializeToElement("x"), null));
Assert.Equal(HttpStatusCode.UnprocessableEntity, response.StatusCode);
}
[Fact]
public async Task A_target_id_that_does_not_exist_for_the_given_kind_is_not_found()
{
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
await SeedFullServiceGraphAsync(client);
var response = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/infrastructure/overrides",
new SetInfrastructureOverrideRequest("Host", Guid.NewGuid(), "hostname", JsonSerializer.SerializeToElement("x"), null));
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task An_override_set_through_the_generic_endpoint_can_be_removed_through_the_existing_generic_delete()
{
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var (hostId, _, _, _, _, _, _, _) = await SeedFullServiceGraphAsync(client);
var setResponse = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/infrastructure/overrides",
new SetInfrastructureOverrideRequest("Host", hostId, "operatingSystem", JsonSerializer.SerializeToElement("Custom OS"), "Reden."));
Assert.Equal(HttpStatusCode.OK, setResponse.StatusCode);
var created = await setResponse.Content.ReadFromJsonAsync<JsonElement>();
var overrideId = created.GetProperty("id").GetGuid();
var removeResponse = await SetupHelper.DeleteWithCsrfAsync(client, $"/api/v1/infrastructure/overrides/{overrideId}");
Assert.Equal(HttpStatusCode.NoContent, removeResponse.StatusCode);
}
/// <summary>Seeds one service with one of every nested fact kind via a single real
/// inventory sync, then resolves each fact's real generated ID through the normal
/// service-detail endpoint — never a raw SQL insert, so this also incidentally
/// re-proves the sync path itself creates every fact kind correctly.</summary>
private static async Task<(Guid HostId, Guid ServiceId, Guid NetworkId, Guid VolumeId, Guid DatabaseId, Guid EndpointId, Guid DeploymentId, Guid BackupId)>
SeedFullServiceGraphAsync(HttpClient client)
{
var hash = $"hash-{Guid.NewGuid():N}";
var request = new SynchronizeInventoryRequest(
$"inventory-{hash}", hash, DateTimeOffset.UtcNow.AddMinutes(-1),
[new InventoryHostRequest("host:unraid", "Tower", "Unraid", "6.12", "x86_64", 16, 64L * 1024 * 1024 * 1024)],
[new InventoryServiceRequest(
"docker:knowledge-hub", "host:unraid", "itworx-knowledge-hub", "container", "running",
"Healthy", "itworx/knowledge-hub:v2", "sha256:example", "10001:10001", true, true, false, "High",
[new InventoryComponentRequest("component:app", "app", "container", "rt-1", "running", "itworx/app:v2", "High")],
[new InventoryEndpointRequest("endpoint:main", "https", "wiki.example.com", 1235, 8080, "https://wiki.example.com", true, "High")],
[new InventoryNetworkRequest("network:main", "knowledge-net", "bridge", "local", false, "High")],
[new InventoryVolumeRequest("volume:data", "knowledge-data", "/data", "local", false, 1024, "High")],
[new InventoryDatabaseRequest("database:main", "knowledge_hub", "postgresql", "18", "db", 5432, false, "High")],
[],
[],
[new InventoryDeploymentRequest("deployment:main", "https://gitea.example.com/itworx/knowledge-hub", "abc123", "itworx/knowledge-hub:v2", "sha256:example", DateTimeOffset.UtcNow, "High")],
[new InventoryBackupRequest("backup:main", "pg_dump", "daily", DateTimeOffset.UtcNow.AddDays(-1), true, "High")])]);
var syncResponse = await SetupHelper.PostWithCsrfAsync(client, "/api/v1/infrastructure/inventory/snapshots", request);
Assert.Equal(HttpStatusCode.OK, syncResponse.StatusCode);
var overview = await client.GetFromJsonAsync<InfrastructureOverviewResponse>("/api/v1/infrastructure/overview");
var hostId = Assert.Single(overview!.Hosts).Id;
var services = await client.GetFromJsonAsync<InfrastructureServiceListResponse>("/api/v1/infrastructure/services?includeHidden=true");
var serviceId = Assert.Single(services!.Items).Id;
var detail = await client.GetFromJsonAsync<InfrastructureServiceDetail>($"/api/v1/infrastructure/services/{serviceId}");
Assert.NotNull(detail);
return (
hostId,
serviceId,
Assert.Single(detail.Networks).Id,
Assert.Single(detail.Volumes).Id,
Assert.Single(detail.Databases).Id,
Assert.Single(detail.Endpoints).Id,
Assert.Single(detail.Deployments).Id,
Assert.Single(detail.Backups).Id);
}
}