using System.Net; using System.Text.Json; using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure; namespace ItWorx.KnowledgeHub.IntegrationTests.Operations; [Collection(PostgresTestGroup.Name)] public sealed class DataSourceApiTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres) { private async Task ClientAsync() { var client = Factory.CreateClient(); await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword); return client; } [Fact] public async Task A_registered_source_is_persisted_as_unknown_and_requires_concurrency_for_disable() { using var client = await ClientAsync(); using var created = await SetupHelper.PostWithCsrfAsync(client, "/api/v1/admin/data-sources", new { sourceKey = $"test:{Guid.NewGuid():N}", displayName = "Test source", kind = "Inventory", expectedIntervalHours = 24 }); Assert.Equal(HttpStatusCode.OK, created.StatusCode); using var json = JsonDocument.Parse(await created.Content.ReadAsStringAsync()); var id = json.RootElement.GetProperty("id").GetGuid(); Assert.Equal("Unknown", json.RootElement.GetProperty("state").GetString()); Assert.Equal(JsonValueKind.Null, json.RootElement.GetProperty("lastSuccessAt").ValueKind); using var missing = await SetupHelper.PutWithCsrfAsync(client, $"/api/v1/admin/data-sources/{id}", new { displayName = "Test source", enabled = false, expectedIntervalHours = 24 }); Assert.Equal((HttpStatusCode)428, missing.StatusCode); using var changed = await SetupHelper.PutWithCsrfAsync(client, $"/api/v1/admin/data-sources/{id}", new { displayName = "Test source", enabled = false, expectedIntervalHours = 24 }, "1"); Assert.Equal(HttpStatusCode.OK, changed.StatusCode); using var stale = await SetupHelper.PutWithCsrfAsync(client, $"/api/v1/admin/data-sources/{id}", new { displayName = "Stale writer", enabled = true, expectedIntervalHours = 24 }, "1"); Assert.Equal(HttpStatusCode.PreconditionFailed, stale.StatusCode); using var response = await client.GetAsync("/api/v1/admin/data-sources?limit=100"); using var list = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); var row = list.RootElement.GetProperty("items").EnumerateArray().Single(item => item.GetProperty("id").GetGuid() == id); Assert.Equal("Disabled", row.GetProperty("state").GetString()); Assert.Equal("Test source", row.GetProperty("displayName").GetString()); } [Fact] public async Task The_inventory_importer_key_cannot_be_registered_as_an_operator_source() { using var client = await ClientAsync(); foreach (var kind in new[] { "Gitea", "Inventory" }) { using var response = await SetupHelper.PostWithCsrfAsync(client, "/api/v1/admin/data-sources", new { sourceKey = "inventory:default", displayName = "Reserved source", kind, expectedIntervalHours = 24 }); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } using var list = await client.GetAsync("/api/v1/admin/data-sources?limit=100"); using var json = JsonDocument.Parse(await list.Content.ReadAsStringAsync()); Assert.Empty(json.RootElement.GetProperty("items").EnumerateArray()); } [Fact] public async Task Unauthenticated_mutations_and_missing_csrf_do_not_register_sources() { using var anonymous = Factory.CreateClient(); using var denied = await anonymous.GetAsync("/api/v1/admin/data-sources"); Assert.Equal(HttpStatusCode.Unauthorized, denied.StatusCode); using var owner = await ClientAsync(); using var csrfDenied = await owner.PostAsync("/api/v1/admin/data-sources", new StringContent("{}", System.Text.Encoding.UTF8, "application/json")); Assert.Equal(HttpStatusCode.BadRequest, csrfDenied.StatusCode); } }