using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using StellaOps.Scanner.Surface.Env; namespace StellaOps.Scanner.Surface.Env.Tests; public sealed class SurfaceEnvironmentBuilderTests { [Fact] public void Build_UsesDefaults_WhenVariablesMissing() { var services = CreateServices(); var environment = SurfaceEnvironmentFactory.Create(services, options => { options.RequireSurfaceEndpoint = false; }); Assert.Equal("surface-cache", environment.Settings.SurfaceFsBucket); Assert.Equal(4096, environment.Settings.CacheQuotaMegabytes); Assert.False(environment.Settings.PrefetchEnabled); Assert.NotNull(environment.Settings.CacheRoot); Assert.True(environment.Settings.CacheRoot.Exists); } [Fact] public void Build_ReadsEnvironmentVariables_WithPrefixes() { Environment.SetEnvironmentVariable("SCANNER_SURFACE_FS_BUCKET", "custom-bucket"); Environment.SetEnvironmentVariable("SCANNER_SURFACE_CACHE_QUOTA_MB", "512"); Environment.SetEnvironmentVariable("SCANNER_SURFACE_FS_ENDPOINT", "https://surface.example.test"); try { var services = CreateServices(); var environment = SurfaceEnvironmentFactory.Create(services); Assert.Equal("custom-bucket", environment.Settings.SurfaceFsBucket); Assert.Equal(512, environment.Settings.CacheQuotaMegabytes); } finally { Environment.SetEnvironmentVariable("SCANNER_SURFACE_FS_BUCKET", null); Environment.SetEnvironmentVariable("SCANNER_SURFACE_CACHE_QUOTA_MB", null); Environment.SetEnvironmentVariable("SCANNER_SURFACE_FS_ENDPOINT", null); } } [Fact] public void Build_Throws_WhenIntegerOutOfRange() { Environment.SetEnvironmentVariable("SCANNER_SURFACE_CACHE_QUOTA_MB", "1"); Environment.SetEnvironmentVariable("SCANNER_SURFACE_FS_ENDPOINT", "https://surface.example.test"); try { var services = CreateServices(); var exception = Assert.Throws(() => SurfaceEnvironmentFactory.Create(services)); Assert.Equal("SURFACE_CACHE_QUOTA_MB", exception.Variable); } finally { Environment.SetEnvironmentVariable("SCANNER_SURFACE_CACHE_QUOTA_MB", null); Environment.SetEnvironmentVariable("SCANNER_SURFACE_FS_ENDPOINT", null); } } [Fact] public void Build_Throws_WhenEndpointMissing_AndRequired() { var services = CreateServices(); var exception = Assert.Throws(() => SurfaceEnvironmentFactory.Create(services, options => options.RequireSurfaceEndpoint = true)); Assert.Equal("SURFACE_FS_ENDPOINT", exception.Variable); } [Fact] public void Build_Throws_WhenEndpointInvalid() { Environment.SetEnvironmentVariable("SCANNER_SURFACE_FS_ENDPOINT", "not-a-uri"); try { var services = CreateServices(); var ex = Assert.Throws(() => SurfaceEnvironmentFactory.Create(services)); Assert.Equal("SURFACE_FS_ENDPOINT", ex.Variable); } finally { Environment.SetEnvironmentVariable("SCANNER_SURFACE_FS_ENDPOINT", null); } } [Fact] public void Build_Throws_WhenTlsCertificateMissing() { Environment.SetEnvironmentVariable("SCANNER_SURFACE_FS_ENDPOINT", "https://surface.example.test"); Environment.SetEnvironmentVariable("SCANNER_SURFACE_TLS_CERT_PATH", "/does/not/exist.pem"); try { var services = CreateServices(); var ex = Assert.Throws(() => SurfaceEnvironmentFactory.Create(services)); Assert.Equal("SURFACE_TLS_CERT_PATH", ex.Variable); } finally { Environment.SetEnvironmentVariable("SCANNER_SURFACE_TLS_CERT_PATH", null); Environment.SetEnvironmentVariable("SCANNER_SURFACE_FS_ENDPOINT", null); } } [Fact] public void Build_UsesTenantResolver_WhenNotProvided() { Environment.SetEnvironmentVariable("SCANNER_SURFACE_FS_ENDPOINT", "https://surface.example.test"); try { var services = CreateServices(); var environment = SurfaceEnvironmentFactory.Create(services, options => { options.TenantResolver = _ => "resolved-tenant"; }); Assert.Equal("resolved-tenant", environment.Settings.Tenant); } finally { Environment.SetEnvironmentVariable("SCANNER_SURFACE_FS_ENDPOINT", null); } } private static IServiceProvider CreateServices(Action? configure = null) { var services = new ServiceCollection(); var configuration = new ConfigurationBuilder().Build(); services.AddSingleton(configuration); services.AddLogging(builder => builder.ClearProviders()); configure?.Invoke(services); return services.BuildServiceProvider(); } }