using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.GalaxyRepository;
using ZB.MOM.WW.MxGateway.Server;
using ZB.MOM.WW.MxGateway.Server.Configuration;
namespace ZB.MOM.WW.MxGateway.Tests.Configuration;
///
/// Gateway-owned validation of the shared (SEC-33): the Galaxy
/// package binds the options but ships no validator, so the gateway rejects a persisted snapshot
/// whose path is blank, invalid, or non-rooted on the running host, and supplies a rooted per-OS
/// default when the shipped config leaves the path blank.
///
public sealed class GalaxyRepositoryOptionsValidatorTests
{
/// Verifies a blank snapshot path with persistence enabled fails validation.
[Fact]
public void Validate_Fails_WhenPersistSnapshotAndPathBlank()
{
GalaxyRepositoryOptions options = new() { PersistSnapshot = true, SnapshotCachePath = "" };
ValidateOptionsResult result = new GalaxyRepositoryOptionsValidator().Validate(null, options);
Assert.True(result.Failed);
Assert.Contains(result.Failures!, f => f.Contains("MxGateway:Galaxy:SnapshotCachePath"));
}
/// Verifies a non-rooted snapshot path with persistence enabled fails validation.
[Fact]
public void Validate_Fails_WhenPersistSnapshotAndPathNotRooted()
{
GalaxyRepositoryOptions options = new()
{
PersistSnapshot = true,
SnapshotCachePath = "galaxy-snapshot.json",
};
ValidateOptionsResult result = new GalaxyRepositoryOptionsValidator().Validate(null, options);
Assert.True(result.Failed);
Assert.Contains(
result.Failures!,
f => f.Contains("MxGateway:Galaxy:SnapshotCachePath") && f.Contains("rooted"));
}
/// Verifies a blank path passes when persistence is disabled (the path is never used).
[Fact]
public void Validate_Succeeds_WhenPersistSnapshotDisabled()
{
GalaxyRepositoryOptions options = new() { PersistSnapshot = false, SnapshotCachePath = "" };
ValidateOptionsResult result = new GalaxyRepositoryOptionsValidator().Validate(null, options);
Assert.True(result.Succeeded);
}
/// Verifies a valid, host-rooted snapshot path with persistence enabled passes.
[Fact]
public void Validate_Succeeds_WhenPersistSnapshotAndPathRooted()
{
GalaxyRepositoryOptions options = new()
{
PersistSnapshot = true,
SnapshotCachePath = Path.Combine(Path.GetTempPath(), "galaxy-snapshot.json"),
};
ValidateOptionsResult result = new GalaxyRepositoryOptionsValidator().Validate(null, options);
Assert.True(result.Succeeded);
}
///
/// Verifies an absolute snapshot path inside the application directory fails. Rooted is not the
/// same as safe: the upgrade procedure renames that directory, so a snapshot cached there is
/// discarded on every deploy and the gateway starts cold each time.
///
[Fact]
public void Validate_Fails_WhenSnapshotPathIsUnderContentRoot()
{
string contentRoot = Path.Combine(Path.GetTempPath(), $"mxgw-root-{Guid.NewGuid():N}");
GalaxyRepositoryOptions options = new()
{
PersistSnapshot = true,
SnapshotCachePath = Path.Combine(contentRoot, "galaxy-snapshot.json"),
};
ValidateOptionsResult result =
new GalaxyRepositoryOptionsValidator(contentRoot).Validate(null, options);
Assert.True(result.Failed);
Assert.Contains(
result.Failures!,
f => f.Contains("MxGateway:Galaxy:SnapshotCachePath")
&& f.Contains("must not be inside the application directory"));
}
/// Verifies a snapshot path outside the application directory still passes.
[Fact]
public void Validate_Succeeds_WhenSnapshotPathIsOutsideContentRoot()
{
string contentRoot = Path.Combine(Path.GetTempPath(), $"mxgw-root-{Guid.NewGuid():N}");
GalaxyRepositoryOptions options = new()
{
PersistSnapshot = true,
SnapshotCachePath =
Path.Combine(Path.GetTempPath(), $"mxgw-data-{Guid.NewGuid():N}", "galaxy-snapshot.json"),
};
ValidateOptionsResult result =
new GalaxyRepositoryOptionsValidator(contentRoot).Validate(null, options);
Assert.True(result.Succeeded);
}
///
/// Verifies the gateway supplies a rooted per-OS default when the shipped config leaves
/// SnapshotCachePath blank, so the removed appsettings literal is not needed and validation
/// passes on any host (SEC-33). Resolving the options triggers the registered validator.
///
[Fact]
public void Build_DefaultsBlankSnapshotCachePathToRootedDefault()
{
using WebApplication app = GatewayApplication.Build([]);
GalaxyRepositoryOptions options =
app.Services.GetRequiredService>().Value;
Assert.False(string.IsNullOrWhiteSpace(options.SnapshotCachePath));
Assert.True(Path.IsPathRooted(options.SnapshotCachePath));
}
}