using JobTrackerApi.Services; using Microsoft.Extensions.Configuration; using Xunit; namespace JobTrackerApi.Tests; // Pins the configuration KEY, not just the behaviour. /health previously read the // APP_VERSION environment variable while docker-compose passed App__Version, so the // endpoint silently reported "unknown" in every container. Nothing failed; the value // was just wrong. These tests fail if the key drifts again. public sealed class BuildMetadataTests { private static IConfiguration Config(params (string Key, string Value)[] values) => new ConfigurationBuilder() .AddInMemoryCollection(values.Select(v => new KeyValuePair(v.Key, v.Value))) .Build(); [Fact] public void Reads_the_App_Version_configuration_key() { var version = BuildMetadata.ResolveVersion(Config(("App:Version", "1.2.3"))); Assert.Equal("1.2.3", version); } [Fact] public void App__Version_environment_variable_binds_to_the_same_key() { // This is the exact path docker-compose uses: App__Version in the container // environment must reach App:Version in configuration. var cfg = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary()) .Add(new FakeEnvironmentSource(("App__Version", "42"))) .Build(); Assert.Equal("42", BuildMetadata.ResolveVersion(cfg)); } [Fact] public void Falls_back_to_the_assembly_version_when_unconfigured() { // Local development sets nothing. "unknown" would be a regression, not a default. var version = BuildMetadata.ResolveVersion(Config()); Assert.False(string.IsNullOrWhiteSpace(version)); Assert.NotEqual("unknown", version); } [Theory] [InlineData("$(git rev-parse --short HEAD)")] [InlineData("${APP_COMMIT_SHA}")] [InlineData(" ")] [InlineData(null)] public void Unresolved_placeholders_and_blanks_are_rejected(string? raw) { Assert.Null(BuildMetadata.Normalize(raw)); } [Fact] public void A_real_value_survives_normalization_trimmed() { Assert.Equal("abc1234", BuildMetadata.Normalize(" abc1234 ")); } // Minimal stand-in for the environment-variable provider, so the test does not // mutate the real process environment and race other tests. private sealed class FakeEnvironmentSource : IConfigurationSource { private readonly (string Key, string Value)[] _values; public FakeEnvironmentSource(params (string Key, string Value)[] values) => _values = values; public IConfigurationProvider Build(IConfigurationBuilder builder) => new Provider(_values); private sealed class Provider : ConfigurationProvider { private readonly (string Key, string Value)[] _values; public Provider((string Key, string Value)[] values) => _values = values; public override void Load() { foreach (var (key, value) in _values) { // "__" is the environment-variable spelling of the ":" section separator. Data[key.Replace("__", ":")] = value; } } } } }