ad7f9e731f
v2-ci / build (push) Failing after 50s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
A thin gateway over the admin-operations cluster singleton so CI/scripts can trigger a deployment without the Blazor button. Forwards to the same IAdminOperationsClient. StartDeploymentAsync; mounted on admin-role nodes. Auth is a fixed-time X-Api-Key check against Security:DeployApiKey (orthogonal to the cookie-only web auth); AllowAnonymous so the auth fallback doesn't 401 it, self-disabling (503) until the key is set. Outcome->status: 202/200/409/422. Unit tests for the key check + outcome mapping; HTTP E2E (real auth + real deploy via the 2-node harness). Documented in docs/security.md.
52 lines
2.1 KiB
C#
52 lines
2.1 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public sealed class DeployApiE2eTests
|
|
{
|
|
private static CancellationToken Ct => TestContext.Current.CancellationToken;
|
|
|
|
/// <summary>Verifies the deploy endpoint enforces the API key and accepts a correctly-keyed call.</summary>
|
|
[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<DeployApiEndpoints.DeployResponse>(Ct);
|
|
payload.ShouldNotBeNull();
|
|
payload!.Outcome.ShouldBe("Accepted");
|
|
payload.DeploymentId.ShouldNotBeNull();
|
|
}
|
|
|
|
private static Task<HttpResponseMessage> 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);
|
|
}
|
|
}
|