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)); } }