using System.Net; using System.Net.Http.Json; using System.Text.Json; using ItWorx.KnowledgeHub.Contracts.Ingestion; using ItWorx.KnowledgeHub.Contracts.Platform; using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure; namespace ItWorx.KnowledgeHub.IntegrationTests.Platform; [Collection(PostgresTestGroup.Name)] public sealed class PlatformSettingsApiTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres) { private async Task AuthenticatedClientAsync() { var client = Factory.CreateClient(); await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword); return client; } private static PlatformSettingsRequest Settings( bool ingestionEnabled = false, int maximumSourceCount = 20, long maximumSourceBytes = 10_485_760) => new( "ItWorx Knowledge Hub Test", "nl-BE", "Europe/Brussels", new IngestionProposalPolicyContract(ingestionEnabled, maximumSourceCount, maximumSourceBytes, 2), new BackupPolicyExpectationRequest(24, "Extern beheerd", 7, 4, 6, 1, 90), new InventoryFreshnessPolicyContract(24)); [Fact] public async Task Missing_settings_are_reported_as_environment_backed_defaults() { using var client = await AuthenticatedClientAsync(); using var response = await client.GetAsync("/api/v1/admin/platform-settings"); using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("\"0\"", response.Headers.ETag?.Tag); Assert.Equal("0", json.RootElement.GetProperty("eTag").GetString()); Assert.True(json.RootElement.GetProperty("isDefault").GetBoolean()); Assert.Equal("nl-BE", json.RootElement.GetProperty("primaryLocale").GetString()); Assert.Equal("operator-managed", json.RootElement.GetProperty("backupPolicyExpectation").GetProperty("enforcement").GetString()); } [Fact] public async Task Admin_replacement_persists_and_changes_the_effective_ingestion_policy() { using var client = await AuthenticatedClientAsync(); using var updated = await SetupHelper.PutWithCsrfAsync( client, "/api/v1/admin/platform-settings", Settings(ingestionEnabled: true, maximumSourceCount: 5), "0"); using var updatedJson = JsonDocument.Parse(await updated.Content.ReadAsStringAsync()); using var session = await client.GetAsync("/api/v1/auth/session"); using var sessionJson = JsonDocument.Parse(await session.Content.ReadAsStringAsync()); using var intake = await client.GetAsync("/api/v1/ingestion-proposals"); var csrf = await SetupHelper.GetCsrfTokenAsync(client); using var createRequest = new HttpRequestMessage(HttpMethod.Post, "/api/v1/ingestion-proposals") { Content = JsonContent.Create(new CreateIngestionProposalRequest("Platform policy test", "settings-policy")) }; createRequest.Headers.Add(csrf.HeaderName, csrf.Value); createRequest.Headers.Add("Idempotency-Key", "settings-policy-test"); using var created = await client.SendAsync(createRequest); using var createdJson = JsonDocument.Parse(await created.Content.ReadAsStringAsync()); Assert.Equal(HttpStatusCode.OK, updated.StatusCode); Assert.Equal("\"1\"", updated.Headers.ETag?.Tag); Assert.Equal("operator-managed", updatedJson.RootElement.GetProperty("backupPolicyExpectation").GetProperty("enforcement").GetString()); Assert.True(sessionJson.RootElement.GetProperty("features").GetProperty("ingestionProposals").GetBoolean()); Assert.Equal(HttpStatusCode.OK, intake.StatusCode); Assert.Equal(HttpStatusCode.Created, created.StatusCode); Assert.Equal(2, createdJson.RootElement.GetProperty("maxPublishRetries").GetInt32()); } [Fact] public async Task Stale_settings_etags_and_values_over_the_intake_ceiling_are_rejected() { using var client = await AuthenticatedClientAsync(); using var invalid = await SetupHelper.PutWithCsrfAsync( client, "/api/v1/admin/platform-settings", Settings(maximumSourceCount: 21), "0"); using var current = await SetupHelper.PutWithCsrfAsync( client, "/api/v1/admin/platform-settings", Settings(), "0"); using var stale = await SetupHelper.PutWithCsrfAsync( client, "/api/v1/admin/platform-settings", Settings(ingestionEnabled: true), "0"); Assert.Equal(HttpStatusCode.UnprocessableEntity, invalid.StatusCode); Assert.Equal(HttpStatusCode.OK, current.StatusCode); Assert.Equal(HttpStatusCode.PreconditionFailed, stale.StatusCode); } [Fact] public async Task Settings_updates_require_csrf_even_with_a_valid_etag() { using var client = await AuthenticatedClientAsync(); using var request = new HttpRequestMessage(HttpMethod.Put, "/api/v1/admin/platform-settings") { Content = System.Net.Http.Json.JsonContent.Create(Settings()) }; request.Headers.Add("If-Match", "\"0\""); using var response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } }