using System.Net; using System.Net.Http.Json; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Api; namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests; /// /// End-to-end test of the headless deploy REST endpoint over real HTTP against an in-process admin /// node: an unauthenticated/wrong-key POST is rejected, and a correctly-keyed POST triggers a real /// deployment through the same AdminOperations singleton the AdminUI button uses. /// public sealed class DeployApiE2eTests { private static CancellationToken Ct => TestContext.Current.CancellationToken; /// Verifies the deploy endpoint enforces the API key and accepts a correctly-keyed call. [Fact] public async Task Deploy_endpoint_enforces_api_key_and_triggers_a_deployment() { await using var harness = await TwoNodeClusterHarness.StartAsync(); using var http = new HttpClient { BaseAddress = new Uri(harness.NodeABaseAddress) }; // No key → 401 var noKey = await http.PostAsJsonAsync("/api/deployments", new { createdBy = "ci" }, Ct); noKey.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); // Wrong key → 401 (await SendWithKeyAsync(http, "wrong-key")).StatusCode.ShouldBe(HttpStatusCode.Unauthorized); // Correct key → 202 Accepted (an empty config still composes + seals an empty deployment) var accepted = await SendWithKeyAsync(http, TwoNodeClusterHarness.HarnessDeployApiKey); accepted.StatusCode.ShouldBe(HttpStatusCode.Accepted); var payload = await accepted.Content.ReadFromJsonAsync(Ct); payload.ShouldNotBeNull(); payload!.Outcome.ShouldBe("Accepted"); payload.DeploymentId.ShouldNotBeNull(); } private static Task SendWithKeyAsync(HttpClient http, string key) { var req = new HttpRequestMessage(HttpMethod.Post, "/api/deployments") { Content = JsonContent.Create(new { createdBy = "ci-bot" }), }; req.Headers.Add(DeployApiEndpoints.ApiKeyHeader, key); return http.SendAsync(req, Ct); } }