Skip to content

Commit 97220f2

Browse files
Mpdreamzclaude
andauthored
Add unit tests for AssemblyConfiguration.Match with relaxed version matching (#2177)
This commit adds comprehensive unit tests for the AssemblyConfiguration.Match method and includes a fix to support speculative builds for the previous minor version when onboarding repositories to version-based content sources. Changes: - Created new test project Elastic.Documentation.Configuration.Tests - Added 25 unit tests for AssemblyConfiguration.Match covering: - Repository validation (invalid formats, non-elastic owners) - Unknown repository handling (integration vs feature branches) - Content source matching (Current, Next, Edge, multiple sources) - Version branch speculative build logic (against current and product versions) - Fallback behavior for main/master branches - Edge cases (invalid versions, minor version 0) - Enhanced AssemblyConfiguration.Match to trigger speculative builds for version branches that match the previous minor version of the product. This supports gradual onboarding of repositories moving from non-versioned to versioned content sources. Test improvements: - Used xUnit theories to parameterize repetitive test cases - Created type alias for ContentSourceMatch for improved readability - Removed underscores from test method names - Added helper methods for cleaner test setup All 25 tests pass successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent bdda8b3 commit 97220f2

File tree

4 files changed

+307
-4
lines changed

4 files changed

+307
-4
lines changed

docs-builder.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Documentation.Isola
147147
EndProject
148148
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}"
149149
EndProject
150+
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}"
151+
EndProject
150152
Global
151153
GlobalSection(SolutionConfigurationPlatforms) = preSolution
152154
Debug|Any CPU = Debug|Any CPU
@@ -504,6 +506,18 @@ Global
504506
{77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Release|x64.Build.0 = Release|Any CPU
505507
{77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Release|x86.ActiveCfg = Release|Any CPU
506508
{77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4}.Release|x86.Build.0 = Release|Any CPU
509+
{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
510+
{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
511+
{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Debug|x64.ActiveCfg = Debug|Any CPU
512+
{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Debug|x64.Build.0 = Debug|Any CPU
513+
{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Debug|x86.ActiveCfg = Debug|Any CPU
514+
{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Debug|x86.Build.0 = Debug|Any CPU
515+
{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
516+
{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Release|Any CPU.Build.0 = Release|Any CPU
517+
{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Release|x64.ActiveCfg = Release|Any CPU
518+
{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Release|x64.Build.0 = Release|Any CPU
519+
{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Release|x86.ActiveCfg = Release|Any CPU
520+
{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C}.Release|x86.Build.0 = Release|Any CPU
507521
EndGlobalSection
508522
GlobalSection(SolutionProperties) = preSolution
509523
HideSolutionNode = FALSE
@@ -546,5 +560,6 @@ Global
546560
{153FC4AD-F5B0-4100-990E-0987C86DBF01} = {BE6011CC-1200-4957-B01F-FCCA10C5CF5A}
547561
{AABD3EF7-8C86-4981-B1D2-B1F786F33069} = {7AACA67B-3C56-4C7C-9891-558589FC52DB}
548562
{77BF3BF2-C78B-4B5D-8E7D-DD3716235BF4} = {67B576EE-02FA-4F9B-94BC-3630BC09ECE5}
563+
{102570C0-1FAD-4DBE-8C7D-234E71BDFF1C} = {67B576EE-02FA-4F9B-94BC-3630BC09ECE5}
549564
EndGlobalSection
550565
EndGlobal

src/Elastic.Documentation.Configuration/Assembler/AssemblyConfiguration.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,17 +219,26 @@ public ContentSourceMatch Match(ILoggerFactory logFactory, string repository, st
219219
else if (product?.VersioningSystem is { } versioningSystem)
220220
{
221221
logger.LogInformation("Current is not using versioned branches checking product info");
222-
var productCurrentVersion = versioningSystem.Current;
223-
if (v >= productCurrentVersion)
222+
var productVersion = versioningSystem.Current;
223+
var previousMinorVersion = new SemVersion(productVersion.Major, Math.Max(productVersion.Minor - 1, 0), 0);
224+
if (v >= productVersion)
224225
{
225-
logger.LogInformation("Speculative build {Branch} is gte product current '{ProductCurrent}'", branchOrTag, productCurrentVersion);
226+
logger.LogInformation("Speculative build {Branch} is gte product current '{ProductCurrent}'", branchOrTag, productVersion);
227+
match = match with
228+
{
229+
Speculative = true
230+
};
231+
}
232+
else if (v == previousMinorVersion)
233+
{
234+
logger.LogInformation("Speculative build {Branch} is gte product current previous minor '{ProductPreviousMinor}'", branchOrTag, previousMinorVersion);
226235
match = match with
227236
{
228237
Speculative = true
229238
};
230239
}
231240
else
232-
logger.LogInformation("NO speculative build {Branch} is lte product current '{ProductCurrent}'", branchOrTag, productCurrentVersion);
241+
logger.LogInformation("NO speculative build {Branch} is lte product current '{ProductCurrent}'", branchOrTag, productVersion);
233242
}
234243
else
235244
logger.LogInformation("No versioning system found for {Repository} on {Branch}", repository, branchOrTag);
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using Elastic.Documentation.Configuration.Assembler;
6+
using Elastic.Documentation.Configuration.Products;
7+
using Elastic.Documentation.Configuration.Versions;
8+
using FluentAssertions;
9+
using Microsoft.Extensions.Logging;
10+
using Microsoft.Extensions.Logging.Abstractions;
11+
using MatchResult = Elastic.Documentation.Configuration.Assembler.AssemblyConfiguration.ContentSourceMatch;
12+
13+
namespace Elastic.Documentation.Configuration.Tests;
14+
15+
16+
public class AssemblyConfigurationMatchTests
17+
{
18+
private static ILoggerFactory LoggerFactory => NullLoggerFactory.Instance;
19+
20+
private static MatchResult NoMatch => new(null, null, null, false);
21+
private static MatchResult Speculative => new(null, null, null, true);
22+
23+
private static AssemblyConfiguration CreateConfiguration(Dictionary<string, Repository>? repositories = null)
24+
{
25+
repositories ??= new Dictionary<string, Repository>
26+
{
27+
["test-repo"] = new()
28+
{
29+
Name = "test-repo",
30+
GitReferenceCurrent = "8.0",
31+
GitReferenceNext = "8.1",
32+
GitReferenceEdge = "main"
33+
}
34+
};
35+
36+
var config = new AssemblyConfiguration
37+
{
38+
ReferenceRepositories = repositories,
39+
Narrative = new NarrativeRepository()
40+
};
41+
42+
// Simulate the deserialization process that sets AvailableRepositories
43+
config.GetType().GetProperty("AvailableRepositories")!
44+
.SetValue(config, repositories.Values.Concat([config.Narrative]).ToDictionary(r => r.Name, r => r));
45+
46+
return config;
47+
}
48+
49+
private static Repository CreateRepository(string current = "8.0", string next = "8.1", string edge = "main") =>
50+
new()
51+
{
52+
Name = "test-repo",
53+
GitReferenceCurrent = current,
54+
GitReferenceNext = next,
55+
GitReferenceEdge = edge
56+
};
57+
58+
private static Product CreateProduct(SemVersion currentVersion) =>
59+
new()
60+
{
61+
Id = "test-product",
62+
DisplayName = "Test Product",
63+
VersioningSystem = new VersioningSystem
64+
{
65+
Id = VersioningSystemId.Stack,
66+
Current = currentVersion,
67+
Base = new SemVersion(8, 0, 0)
68+
}
69+
};
70+
71+
[Theory]
72+
[InlineData("test-repo")]
73+
[InlineData("other/test-repo")]
74+
public void InvalidRepositoryFormatReturnsNoMatch(string repository)
75+
{
76+
var config = CreateConfiguration();
77+
78+
var result = config.Match(LoggerFactory, repository, "main", null);
79+
80+
result.Should().BeEquivalentTo(NoMatch);
81+
}
82+
83+
[Theory]
84+
[InlineData("main")]
85+
[InlineData("master")]
86+
[InlineData("8.15")]
87+
public void UnknownElasticRepositoryReturnsSpeculativeForIntegrationBranches(string branch)
88+
{
89+
var config = CreateConfiguration();
90+
91+
var result = config.Match(LoggerFactory, "elastic/unknown-repo", branch, null);
92+
93+
result.Should().BeEquivalentTo(Speculative);
94+
}
95+
96+
[Fact]
97+
public void UnknownElasticRepositoryReturnsNoMatchForFeatureBranches()
98+
{
99+
var config = CreateConfiguration();
100+
101+
var result = config.Match(LoggerFactory, "elastic/unknown-repo", "feature-branch", null);
102+
103+
result.Should().BeEquivalentTo(NoMatch);
104+
}
105+
106+
[Theory]
107+
[InlineData("8.0", ContentSource.Current)]
108+
[InlineData("8.1", ContentSource.Next)]
109+
[InlineData("main", ContentSource.Edge)]
110+
public void MatchesCorrectContentSource(string branch, ContentSource expectedSource)
111+
{
112+
var config = CreateConfiguration();
113+
114+
var result = config.Match(LoggerFactory, "elastic/test-repo", branch, null);
115+
116+
// Version branches set Speculative if they're >= current version (8.0)
117+
var isVersionBranch = branch.Contains('.');
118+
var speculative = isVersionBranch; // Both 8.0 and 8.1 are >= 8.0
119+
120+
var expected = expectedSource switch
121+
{
122+
ContentSource.Current => new MatchResult(ContentSource.Current, null, null, speculative),
123+
ContentSource.Next => new MatchResult(null, ContentSource.Next, null, speculative),
124+
ContentSource.Edge => new MatchResult(null, null, ContentSource.Edge, false),
125+
_ => throw new ArgumentOutOfRangeException(nameof(expectedSource))
126+
};
127+
result.Should().BeEquivalentTo(expected);
128+
}
129+
130+
[Fact]
131+
public void MatchesMultipleContentSourcesWhenBranchMatchesAll()
132+
{
133+
var repositories = new Dictionary<string, Repository>
134+
{
135+
["test-repo"] = CreateRepository(current: "main", next: "main", edge: "main")
136+
};
137+
var config = CreateConfiguration(repositories);
138+
139+
var result = config.Match(LoggerFactory, "elastic/test-repo", "main", null);
140+
141+
result.Should().BeEquivalentTo(new MatchResult(
142+
ContentSource.Current,
143+
ContentSource.Next,
144+
ContentSource.Edge,
145+
false
146+
));
147+
}
148+
149+
[Theory]
150+
[InlineData("8.15", "8.0", true)] // Greater than current
151+
[InlineData("8.15", "8.15", true)] // Equal to current
152+
[InlineData("8.0", "8.15", false)] // Less than current
153+
public void VersionBranchSpeculativeBuildBasedOnCurrentVersion(string branch, string currentVersion, bool shouldBeSpeculative)
154+
{
155+
var repositories = new Dictionary<string, Repository>
156+
{
157+
["test-repo"] = CreateRepository(current: currentVersion, next: "main", edge: "main")
158+
};
159+
var config = CreateConfiguration(repositories);
160+
161+
var result = config.Match(LoggerFactory, "elastic/test-repo", branch, null);
162+
163+
result.Speculative.Should().Be(shouldBeSpeculative);
164+
}
165+
166+
[Theory]
167+
[InlineData("8.16", "8.15", true)] // Greater than product version
168+
[InlineData("8.15", "8.15", true)] // Equal to product version
169+
[InlineData("8.14", "8.15", true)] // Previous minor version
170+
[InlineData("8.13", "8.15", false)] // Less than previous minor
171+
[InlineData("8.0", "8.0", true)] // Edge case: minor version 0
172+
public void VersionBranchSpeculativeBuildBasedOnProductVersion(string branch, string productVersion, bool shouldBeSpeculative)
173+
{
174+
var repositories = new Dictionary<string, Repository>
175+
{
176+
["test-repo"] = CreateRepository(current: "main", next: "main", edge: "main")
177+
};
178+
var config = CreateConfiguration(repositories);
179+
var versionParts = productVersion.Split('.');
180+
var product = CreateProduct(new SemVersion(int.Parse(versionParts[0], null), int.Parse(versionParts[1], null), 0));
181+
182+
var result = config.Match(LoggerFactory, "elastic/test-repo", branch, product);
183+
184+
result.Speculative.Should().Be(shouldBeSpeculative);
185+
}
186+
187+
[Theory]
188+
[InlineData("main")]
189+
[InlineData("master")]
190+
public void FallbackToSpeculativeBuildForMainOrMasterWhenNoMatch(string branch)
191+
{
192+
var repositories = new Dictionary<string, Repository>
193+
{
194+
["test-repo"] = CreateRepository(current: "8.0", next: "8.1", edge: "8.2")
195+
};
196+
var config = CreateConfiguration(repositories);
197+
198+
var result = config.Match(LoggerFactory, "elastic/test-repo", branch, null);
199+
200+
result.Current.Should().BeNull();
201+
result.Next.Should().BeNull();
202+
result.Edge.Should().BeNull();
203+
result.Speculative.Should().BeTrue();
204+
}
205+
206+
[Fact]
207+
public void NoFallbackToSpeculativeBuildForFeatureBranches()
208+
{
209+
var repositories = new Dictionary<string, Repository>
210+
{
211+
["test-repo"] = CreateRepository(current: "8.0", next: "8.1", edge: "8.2")
212+
};
213+
var config = CreateConfiguration(repositories);
214+
215+
var result = config.Match(LoggerFactory, "elastic/test-repo", "feature-branch", null);
216+
217+
result.Should().BeEquivalentTo(NoMatch);
218+
}
219+
220+
[Fact]
221+
public void DoesNotFallbackToSpeculativeWhenContentSourceMatched()
222+
{
223+
var repositories = new Dictionary<string, Repository>
224+
{
225+
["test-repo"] = CreateRepository(current: "main", next: "8.1", edge: "8.2")
226+
};
227+
var config = CreateConfiguration(repositories);
228+
229+
var result = config.Match(LoggerFactory, "elastic/test-repo", "main", null);
230+
231+
result.Current.Should().Be(ContentSource.Current);
232+
}
233+
234+
[Fact]
235+
public void HandlesInvalidVersionBranchGracefully()
236+
{
237+
var config = CreateConfiguration();
238+
239+
var result = config.Match(LoggerFactory, "elastic/test-repo", "8.x", null);
240+
241+
result.Should().NotBeNull();
242+
}
243+
244+
[Fact]
245+
public void ExtractsRepositoryNameFromFullPath()
246+
{
247+
var config = CreateConfiguration();
248+
249+
var result = config.Match(LoggerFactory, "elastic/test-repo", "8.0", null);
250+
251+
result.Current.Should().Be(ContentSource.Current);
252+
}
253+
254+
[Fact]
255+
public void CurrentVersionMatchAlsoSetsSpeculative()
256+
{
257+
var repositories = new Dictionary<string, Repository>
258+
{
259+
["test-repo"] = CreateRepository(current: "8.15", next: "main", edge: "main")
260+
};
261+
var config = CreateConfiguration(repositories);
262+
263+
var result = config.Match(LoggerFactory, "elastic/test-repo", "8.15", null);
264+
265+
result.Current.Should().Be(ContentSource.Current);
266+
result.Speculative.Should().BeTrue();
267+
}
268+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<ProjectReference Include="..\..\src\Elastic.Documentation.Configuration\Elastic.Documentation.Configuration.csproj"/>
9+
</ItemGroup>
10+
11+
</Project>

0 commit comments

Comments
 (0)