122 lines
4.4 KiB
C#
122 lines
4.4 KiB
C#
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Http.HttpResults;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment;
|
|
using ZB.MOM.WW.ScadaBridge.ManagementService;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.ManagementService.Tests;
|
|
|
|
/// <summary>
|
|
/// Unit tests for the pure decision resolver behind the token-gated internal
|
|
/// deployment-config fetch endpoint (<see cref="DeploymentConfigEndpoints.Resolve"/>).
|
|
///
|
|
/// <para>
|
|
/// This endpoint is machine-to-machine: a site presents a per-deployment fetch
|
|
/// token in the <c>X-Deployment-Token</c> header and the token is the entire
|
|
/// security boundary. The resolver is extracted so the security-critical
|
|
/// decision logic is unit-testable without standing up a web host. The mapping
|
|
/// from the resolver result to the documented HTTP status codes is:
|
|
/// <list type="bullet">
|
|
/// <item>unknown / superseded (null row) → 404 NotFound</item>
|
|
/// <item>expired row → 404 NotFound (does not leak existence to a wrong token)</item>
|
|
/// <item>missing / wrong token → 401 Unauthorized</item>
|
|
/// <item>valid row + correct token + not expired → 200 with the staged JSON</item>
|
|
/// </list>
|
|
/// </para>
|
|
/// </summary>
|
|
public class DeploymentConfigEndpointsTests
|
|
{
|
|
private const string ValidToken = "test-deployment-token-abc123";
|
|
private const string ConfigJson = "{\"instanceId\":42,\"attributes\":[]}";
|
|
|
|
private static readonly DateTimeOffset Now = new(2026, 6, 26, 12, 0, 0, TimeSpan.Zero);
|
|
|
|
private static PendingDeployment Row(string token = ValidToken, DateTimeOffset? expiresAtUtc = null) =>
|
|
new(
|
|
deploymentId: "deploy-1",
|
|
instanceId: 42,
|
|
revisionHash: "hash-1",
|
|
configurationJson: ConfigJson,
|
|
token: token,
|
|
createdAtUtc: Now.AddMinutes(-1),
|
|
expiresAtUtc: expiresAtUtc ?? Now.AddMinutes(5));
|
|
|
|
private static int? StatusOf(IResult result) => (result as IStatusCodeHttpResult)?.StatusCode;
|
|
|
|
[Fact]
|
|
public void Resolve_NullRow_ReturnsNotFound()
|
|
{
|
|
var result = DeploymentConfigEndpoints.Resolve(row: null, token: ValidToken, nowUtc: Now);
|
|
|
|
Assert.IsType<NotFound>(result);
|
|
Assert.Equal(StatusCodes.Status404NotFound, StatusOf(result));
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_ExpiredRow_WithCorrectToken_ReturnsNotFound()
|
|
{
|
|
// Expired exactly at now: ExpiresAtUtc <= now is the boundary that purges.
|
|
var row = Row(expiresAtUtc: Now);
|
|
|
|
var result = DeploymentConfigEndpoints.Resolve(row, token: ValidToken, nowUtc: Now);
|
|
|
|
Assert.IsType<NotFound>(result);
|
|
Assert.Equal(StatusCodes.Status404NotFound, StatusOf(result));
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_ExpiredRowInThePast_WithCorrectToken_ReturnsNotFound()
|
|
{
|
|
var row = Row(expiresAtUtc: Now.AddSeconds(-1));
|
|
|
|
var result = DeploymentConfigEndpoints.Resolve(row, token: ValidToken, nowUtc: Now);
|
|
|
|
Assert.IsType<NotFound>(result);
|
|
Assert.Equal(StatusCodes.Status404NotFound, StatusOf(result));
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_ValidRow_WrongToken_ReturnsUnauthorized()
|
|
{
|
|
var row = Row();
|
|
|
|
var result = DeploymentConfigEndpoints.Resolve(row, token: "wrong-token", nowUtc: Now);
|
|
|
|
Assert.IsType<UnauthorizedHttpResult>(result);
|
|
Assert.Equal(StatusCodes.Status401Unauthorized, StatusOf(result));
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_ValidRow_EmptyToken_ReturnsUnauthorized()
|
|
{
|
|
var row = Row();
|
|
|
|
var result = DeploymentConfigEndpoints.Resolve(row, token: string.Empty, nowUtc: Now);
|
|
|
|
Assert.IsType<UnauthorizedHttpResult>(result);
|
|
Assert.Equal(StatusCodes.Status401Unauthorized, StatusOf(result));
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_ValidRow_NullToken_ReturnsUnauthorized()
|
|
{
|
|
var row = Row();
|
|
|
|
var result = DeploymentConfigEndpoints.Resolve(row, token: null!, nowUtc: Now);
|
|
|
|
Assert.IsType<UnauthorizedHttpResult>(result);
|
|
Assert.Equal(StatusCodes.Status401Unauthorized, StatusOf(result));
|
|
}
|
|
|
|
[Fact]
|
|
public void Resolve_ValidRow_CorrectToken_NotExpired_ReturnsConfigJson()
|
|
{
|
|
var row = Row();
|
|
|
|
var result = DeploymentConfigEndpoints.Resolve(row, token: ValidToken, nowUtc: Now);
|
|
|
|
var content = Assert.IsType<ContentHttpResult>(result);
|
|
Assert.Equal(ConfigJson, content.ResponseContent);
|
|
Assert.Equal("application/json", content.ContentType);
|
|
}
|
|
}
|