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;
///
/// Unit tests for the pure decision resolver behind the token-gated internal
/// deployment-config fetch endpoint ().
///
///
/// This endpoint is machine-to-machine: a site presents a per-deployment fetch
/// token in the X-Deployment-Token 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:
///
/// - unknown / superseded (null row) → 404 NotFound
/// - expired row → 404 NotFound (does not leak existence to a wrong token)
/// - missing / wrong token → 401 Unauthorized
/// - valid row + correct token + not expired → 200 with the staged JSON
///
///
///
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(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(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(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(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(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(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(result);
Assert.Equal(ConfigJson, content.ResponseContent);
Assert.Equal("application/json", content.ContentType);
}
}