diff --git a/docs-builder.sln b/docs-builder.sln index 055eccce7..afba2ed48 100644 --- a/docs-builder.sln +++ b/docs-builder.sln @@ -147,6 +147,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.Isola EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.Api.Infrastructure.Tests", "tests\Elastic.Documentation.Api.Infrastructure.Tests\Elastic.Documentation.Api.Infrastructure.Tests.csproj", "{77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.Configuration.Tests", "tests\Elastic.Documentation.Configuration.Tests\Elastic.Documentation.Configuration.Tests.csproj", "{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -504,6 +506,18 @@ Global {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Release|x64.Build.0 = Release|Any CPU {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Release|x86.ActiveCfg = Release|Any CPU {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Release|x86.Build.0 = Release|Any CPU + {102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Debug|x64.ActiveCfg = Debug|Any CPU + {102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Debug|x64.Build.0 = Debug|Any CPU + {102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Debug|x86.ActiveCfg = Debug|Any CPU + {102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Debug|x86.Build.0 = Debug|Any CPU + {102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Release|Any CPU.Build.0 = Release|Any CPU + {102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Release|x64.ActiveCfg = Release|Any CPU + {102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Release|x64.Build.0 = Release|Any CPU + {102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Release|x86.ActiveCfg = Release|Any CPU + {102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -546,5 +560,6 @@ Global {153FC4AD-F5B0-4100-990E-0987C86DBF01} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A} {AABD3EF7-8C86-4981-B1D2-B1F786F33069} = {7AACA67B-3C56-4C7C-9891-558589FC52DB} {77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4} = {67B576EE-02FA-4F9B-94BC-3630BC09ECE5} + {102570C0-1FAD-4DBE-8C7D-234E71BDFF1C} = {67B576EE-02FA-4F9B-94BC-3630BC09ECE5} EndGlobalSection EndGlobal diff --git a/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs b/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs index 307be4724..6917db5d6 100644 --- a/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs +++ b/src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs @@ -219,17 +219,26 @@ public ContentSourceMatch Match(ILoggerFactory logFactory, string repository, st else if (product?.VersioningSystem is { } versioningSystem) { logger.LogInformation("Current is not using versioned branches checking product info"); - var productCurrentVersion = versioningSystem.Current; - if (v >= productCurrentVersion) + var productVersion = versioningSystem.Current; + var previousMinorVersion = new SemVersion(productVersion.Major, Math.Max(productVersion.Minor - 1, 0), 0); + if (v >= productVersion) { - logger.LogInformation("Speculative build {Branch} is gte product current '{ProductCurrent}'", branchOrTag, productCurrentVersion); + logger.LogInformation("Speculative build {Branch} is gte product current '{ProductCurrent}'", branchOrTag, productVersion); + match = match with + { + Speculative = true + }; + } + else if (v == previousMinorVersion) + { + logger.LogInformation("Speculative build {Branch} is gte product current previous minor '{ProductPreviousMinor}'", branchOrTag, previousMinorVersion); match = match with { Speculative = true }; } else - logger.LogInformation("NO speculative build {Branch} is lte product current '{ProductCurrent}'", branchOrTag, productCurrentVersion); + logger.LogInformation("NO speculative build {Branch} is lte product current '{ProductCurrent}'", branchOrTag, productVersion); } else logger.LogInformation("No versioning system found for {Repository} on {Branch}", repository, branchOrTag); diff --git a/tests/Elastic.Documentation.Configuration.Tests/AssemblyConfigurationMatchTests.cs b/tests/Elastic.Documentation.Configuration.Tests/AssemblyConfigurationMatchTests.cs new file mode 100644 index 000000000..dfa943362 --- /dev/null +++ b/tests/Elastic.Documentation.Configuration.Tests/AssemblyConfigurationMatchTests.cs @@ -0,0 +1,268 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using Elastic.Documentation.Configuration.Assembler; +using Elastic.Documentation.Configuration.Products; +using Elastic.Documentation.Configuration.Versions; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using MatchResult = Elastic.Documentation.Configuration.Assembler.AssemblyConfiguration.ContentSourceMatch; + +namespace Elastic.Documentation.Configuration.Tests; + + +public class AssemblyConfigurationMatchTests +{ + private static ILoggerFactory LoggerFactory => NullLoggerFactory.Instance; + + private static MatchResult NoMatch => new(null, null, null, false); + private static MatchResult Speculative => new(null, null, null, true); + + private static AssemblyConfiguration CreateConfiguration(Dictionary? repositories = null) + { + repositories ??= new Dictionary + { + ["test-repo"] = new() + { + Name = "test-repo", + GitReferenceCurrent = "8.0", + GitReferenceNext = "8.1", + GitReferenceEdge = "main" + } + }; + + var config = new AssemblyConfiguration + { + ReferenceRepositories = repositories, + Narrative = new NarrativeRepository() + }; + + // Simulate the deserialization process that sets AvailableRepositories + config.GetType().GetProperty("AvailableRepositories")! + .SetValue(config, repositories.Values.Concat([config.Narrative]).ToDictionary(r => r.Name, r => r)); + + return config; + } + + private static Repository CreateRepository(string current = "8.0", string next = "8.1", string edge = "main") => + new() + { + Name = "test-repo", + GitReferenceCurrent = current, + GitReferenceNext = next, + GitReferenceEdge = edge + }; + + private static Product CreateProduct(SemVersion currentVersion) => + new() + { + Id = "test-product", + DisplayName = "Test Product", + VersioningSystem = new VersioningSystem + { + Id = VersioningSystemId.Stack, + Current = currentVersion, + Base = new SemVersion(8, 0, 0) + } + }; + + [Theory] + [InlineData("test-repo")] + [InlineData("other/test-repo")] + public void InvalidRepositoryFormatReturnsNoMatch(string repository) + { + var config = CreateConfiguration(); + + var result = config.Match(LoggerFactory, repository, "main", null); + + result.Should().BeEquivalentTo(NoMatch); + } + + [Theory] + [InlineData("main")] + [InlineData("master")] + [InlineData("8.15")] + public void UnknownElasticRepositoryReturnsSpeculativeForIntegrationBranches(string branch) + { + var config = CreateConfiguration(); + + var result = config.Match(LoggerFactory, "elastic/unknown-repo", branch, null); + + result.Should().BeEquivalentTo(Speculative); + } + + [Fact] + public void UnknownElasticRepositoryReturnsNoMatchForFeatureBranches() + { + var config = CreateConfiguration(); + + var result = config.Match(LoggerFactory, "elastic/unknown-repo", "feature-branch", null); + + result.Should().BeEquivalentTo(NoMatch); + } + + [Theory] + [InlineData("8.0", ContentSource.Current)] + [InlineData("8.1", ContentSource.Next)] + [InlineData("main", ContentSource.Edge)] + public void MatchesCorrectContentSource(string branch, ContentSource expectedSource) + { + var config = CreateConfiguration(); + + var result = config.Match(LoggerFactory, "elastic/test-repo", branch, null); + + // Version branches set Speculative if they're >= current version (8.0) + var isVersionBranch = branch.Contains('.'); + var speculative = isVersionBranch; // Both 8.0 and 8.1 are >= 8.0 + + var expected = expectedSource switch + { + ContentSource.Current => new MatchResult(ContentSource.Current, null, null, speculative), + ContentSource.Next => new MatchResult(null, ContentSource.Next, null, speculative), + ContentSource.Edge => new MatchResult(null, null, ContentSource.Edge, false), + _ => throw new ArgumentOutOfRangeException(nameof(expectedSource)) + }; + result.Should().BeEquivalentTo(expected); + } + + [Fact] + public void MatchesMultipleContentSourcesWhenBranchMatchesAll() + { + var repositories = new Dictionary + { + ["test-repo"] = CreateRepository(current: "main", next: "main", edge: "main") + }; + var config = CreateConfiguration(repositories); + + var result = config.Match(LoggerFactory, "elastic/test-repo", "main", null); + + result.Should().BeEquivalentTo(new MatchResult( + ContentSource.Current, + ContentSource.Next, + ContentSource.Edge, + false + )); + } + + [Theory] + [InlineData("8.15", "8.0", true)] // Greater than current + [InlineData("8.15", "8.15", true)] // Equal to current + [InlineData("8.0", "8.15", false)] // Less than current + public void VersionBranchSpeculativeBuildBasedOnCurrentVersion(string branch, string currentVersion, bool shouldBeSpeculative) + { + var repositories = new Dictionary + { + ["test-repo"] = CreateRepository(current: currentVersion, next: "main", edge: "main") + }; + var config = CreateConfiguration(repositories); + + var result = config.Match(LoggerFactory, "elastic/test-repo", branch, null); + + result.Speculative.Should().Be(shouldBeSpeculative); + } + + [Theory] + [InlineData("8.16", "8.15", true)] // Greater than product version + [InlineData("8.15", "8.15", true)] // Equal to product version + [InlineData("8.14", "8.15", true)] // Previous minor version + [InlineData("8.13", "8.15", false)] // Less than previous minor + [InlineData("8.0", "8.0", true)] // Edge case: minor version 0 + public void VersionBranchSpeculativeBuildBasedOnProductVersion(string branch, string productVersion, bool shouldBeSpeculative) + { + var repositories = new Dictionary + { + ["test-repo"] = CreateRepository(current: "main", next: "main", edge: "main") + }; + var config = CreateConfiguration(repositories); + var versionParts = productVersion.Split('.'); + var product = CreateProduct(new SemVersion(int.Parse(versionParts[0], null), int.Parse(versionParts[1], null), 0)); + + var result = config.Match(LoggerFactory, "elastic/test-repo", branch, product); + + result.Speculative.Should().Be(shouldBeSpeculative); + } + + [Theory] + [InlineData("main")] + [InlineData("master")] + public void FallbackToSpeculativeBuildForMainOrMasterWhenNoMatch(string branch) + { + var repositories = new Dictionary + { + ["test-repo"] = CreateRepository(current: "8.0", next: "8.1", edge: "8.2") + }; + var config = CreateConfiguration(repositories); + + var result = config.Match(LoggerFactory, "elastic/test-repo", branch, null); + + result.Current.Should().BeNull(); + result.Next.Should().BeNull(); + result.Edge.Should().BeNull(); + result.Speculative.Should().BeTrue(); + } + + [Fact] + public void NoFallbackToSpeculativeBuildForFeatureBranches() + { + var repositories = new Dictionary + { + ["test-repo"] = CreateRepository(current: "8.0", next: "8.1", edge: "8.2") + }; + var config = CreateConfiguration(repositories); + + var result = config.Match(LoggerFactory, "elastic/test-repo", "feature-branch", null); + + result.Should().BeEquivalentTo(NoMatch); + } + + [Fact] + public void DoesNotFallbackToSpeculativeWhenContentSourceMatched() + { + var repositories = new Dictionary + { + ["test-repo"] = CreateRepository(current: "main", next: "8.1", edge: "8.2") + }; + var config = CreateConfiguration(repositories); + + var result = config.Match(LoggerFactory, "elastic/test-repo", "main", null); + + result.Current.Should().Be(ContentSource.Current); + } + + [Fact] + public void HandlesInvalidVersionBranchGracefully() + { + var config = CreateConfiguration(); + + var result = config.Match(LoggerFactory, "elastic/test-repo", "8.x", null); + + result.Should().NotBeNull(); + } + + [Fact] + public void ExtractsRepositoryNameFromFullPath() + { + var config = CreateConfiguration(); + + var result = config.Match(LoggerFactory, "elastic/test-repo", "8.0", null); + + result.Current.Should().Be(ContentSource.Current); + } + + [Fact] + public void CurrentVersionMatchAlsoSetsSpeculative() + { + var repositories = new Dictionary + { + ["test-repo"] = CreateRepository(current: "8.15", next: "main", edge: "main") + }; + var config = CreateConfiguration(repositories); + + var result = config.Match(LoggerFactory, "elastic/test-repo", "8.15", null); + + result.Current.Should().Be(ContentSource.Current); + result.Speculative.Should().BeTrue(); + } +} diff --git a/tests/Elastic.Documentation.Configuration.Tests/Elastic.Documentation.Configuration.Tests.csproj b/tests/Elastic.Documentation.Configuration.Tests/Elastic.Documentation.Configuration.Tests.csproj new file mode 100644 index 000000000..f5acc84c4 --- /dev/null +++ b/tests/Elastic.Documentation.Configuration.Tests/Elastic.Documentation.Configuration.Tests.csproj @@ -0,0 +1,11 @@ + + + + net9.0 + + + + + + +