using ZB.MOM.WW.Secrets.Cli.Interactive; using ZB.MOM.WW.Secrets.MasterKey; namespace ZB.MOM.WW.Secrets.Tests.Cli.Interactive; /// /// Tests for : it must compose the target app's configuration /// (base json + optional environment overlay + environment variables) exactly the way the app /// would at startup, so fixes land in the store the app actually reads. /// public sealed class TargetConfigReaderTests : IDisposable { private readonly List _tempDirs = []; public void Dispose() { foreach (string dir in _tempDirs) { if (Directory.Exists(dir)) { try { Directory.Delete(dir, recursive: true); } catch (IOException) { /* best-effort temp cleanup */ } } } } private string NewTempDir() { string dir = Path.Combine(Path.GetTempPath(), "zb-secrets-cfg-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(dir); _tempDirs.Add(dir); return dir; } [Fact] public void Reads_secrets_section_and_binds_options() { string dir = NewTempDir(); string path = Path.Combine(dir, "appsettings.json"); File.WriteAllText(path, """ { "Secrets": { "SqlitePath": "custom.db", "MasterKey": { "Source": "Environment", "EnvVarName": "MY_KEY" } } } """); StoreTarget target = new TargetConfigReader().Read(path); Assert.Equal(MasterKeySource.Environment, target.Secrets.MasterKey.Source); Assert.Equal("MY_KEY", target.Secrets.MasterKey.EnvVarName); Assert.Equal(Path.Combine(dir, "custom.db"), target.Secrets.SqlitePath); } [Fact] public void Environment_overlay_wins_over_base() { string dir = NewTempDir(); File.WriteAllText(Path.Combine(dir, "appsettings.json"), """ { "Secrets": { "SqlitePath": "base.db" } } """); File.WriteAllText(Path.Combine(dir, "appsettings.Production.json"), """ { "Secrets": { "SqlitePath": "prod.db" } } """); StoreTarget target = new TargetConfigReader().Read( Path.Combine(dir, "appsettings.json"), environment: "Production"); Assert.Equal(Path.Combine(dir, "prod.db"), target.Secrets.SqlitePath); } [Fact] public void Binds_sqlserver_options_when_section_present() { string dir = NewTempDir(); File.WriteAllText(Path.Combine(dir, "appsettings.json"), """ { "Secrets": { "SqlitePath": "secrets.db", "SqlServer": { "ConnectionString": "Server=hub;Database=ZbSecrets;Trusted_Connection=True" } } } """); StoreTarget withSql = new TargetConfigReader().Read(Path.Combine(dir, "appsettings.json")); Assert.NotNull(withSql.SqlServer); Assert.Equal( "Server=hub;Database=ZbSecrets;Trusted_Connection=True", withSql.SqlServer!.ConnectionString); string dir2 = NewTempDir(); File.WriteAllText(Path.Combine(dir2, "appsettings.json"), """ { "Secrets": { "SqlitePath": "secrets.db" } } """); StoreTarget noSql = new TargetConfigReader().Read(Path.Combine(dir2, "appsettings.json")); Assert.Null(noSql.SqlServer); } [Fact] public void SqlitePath_is_resolved_relative_to_the_app_directory() { string dir = NewTempDir(); File.WriteAllText(Path.Combine(dir, "appsettings.json"), """ { "Secrets": { "SqlitePath": "secrets.db" } } """); StoreTarget target = new TargetConfigReader().Read(Path.Combine(dir, "appsettings.json")); Assert.True(Path.IsPathRooted(target.Secrets.SqlitePath)); Assert.Equal(Path.Combine(dir, "secrets.db"), target.Secrets.SqlitePath); } [Fact] public void Missing_file_throws_FileNotFoundException_with_path() { string dir = NewTempDir(); string path = Path.Combine(dir, "appsettings.json"); FileNotFoundException ex = Assert.Throws( () => new TargetConfigReader().Read(path)); Assert.Contains(path, ex.Message); } [Fact] public void Custom_section_path_is_honored() { string dir = NewTempDir(); File.WriteAllText(Path.Combine(dir, "appsettings.json"), """ { "MySecrets": { "SqlitePath": "elsewhere.db", "MasterKey": { "EnvVarName": "OTHER_KEY" } } } """); StoreTarget target = new TargetConfigReader().Read( Path.Combine(dir, "appsettings.json"), sectionPath: "MySecrets"); Assert.Equal("OTHER_KEY", target.Secrets.MasterKey.EnvVarName); Assert.Equal(Path.Combine(dir, "elsewhere.db"), target.Secrets.SqlitePath); } }