Skip to content

Commit 6bb00a1

Browse files
authored
Merge pull request #12 from agenixframework/feature/refactor-prometheus-config-structure
Refactor Dockerfiles for optimized caching, add label-matching tests,…
2 parents c8f40ff + 0319c3b commit 6bb00a1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2705
-335
lines changed

.github/workflows/publish-nuget-and-containers.yml

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,22 @@ on:
99
tags:
1010
- 'v*.*.*'
1111
- 'v*.*.*-*'
12+
release:
13+
types: [published]
14+
workflow_dispatch:
15+
inputs:
16+
publish_nuget:
17+
description: "Publish to NuGet?"
18+
required: true
19+
type: choice
20+
options: [ "no", "yes" ]
21+
default: "no"
22+
publish_containers:
23+
description: "Publish container images?"
24+
required: true
25+
type: choice
26+
options: [ "no", "yes" ]
27+
default: "no"
1228

1329
permissions:
1430
contents: write
@@ -172,13 +188,16 @@ jobs:
172188
always() &&
173189
needs.build.result == 'success' &&
174190
(needs.security-scan.result == 'success' || needs.security-scan.result == 'skipped') &&
175-
github.event_name == 'push' &&
176191
(
177-
github.ref == 'refs/heads/main' ||
178-
github.ref == 'refs/heads/develop' ||
179-
startsWith(github.ref, 'refs/heads/release/') ||
180-
startsWith(github.ref, 'refs/heads/hotfix/') ||
181-
startsWith(github.ref, 'refs/tags/v')
192+
github.event_name == 'release' ||
193+
(github.event_name == 'workflow_dispatch' && inputs.publish_nuget == 'yes') ||
194+
(github.event_name == 'push' && (
195+
github.ref == 'refs/heads/main' ||
196+
github.ref == 'refs/heads/develop' ||
197+
startsWith(github.ref, 'refs/heads/release/') ||
198+
startsWith(github.ref, 'refs/heads/hotfix/') ||
199+
startsWith(github.ref, 'refs/tags/v')
200+
))
182201
)
183202
184203
environment:
@@ -242,13 +261,16 @@ jobs:
242261
always() &&
243262
needs.build.result == 'success' &&
244263
(needs.security-scan.result == 'success' || needs.security-scan.result == 'skipped') &&
245-
github.event_name == 'push' &&
246264
(
247-
github.ref == 'refs/heads/main' ||
248-
github.ref == 'refs/heads/develop' ||
249-
startsWith(github.ref, 'refs/heads/release/') ||
250-
startsWith(github.ref, 'refs/heads/hotfix/') ||
251-
startsWith(github.ref, 'refs/tags/v')
265+
github.event_name == 'release' ||
266+
(github.event_name == 'workflow_dispatch' && inputs.publish_containers == 'yes') ||
267+
(github.event_name == 'push' && (
268+
github.ref == 'refs/heads/main' ||
269+
github.ref == 'refs/heads/develop' ||
270+
startsWith(github.ref, 'refs/heads/release/') ||
271+
startsWith(github.ref, 'refs/heads/hotfix/') ||
272+
startsWith(github.ref, 'refs/tags/v')
273+
))
252274
)
253275
254276
steps:
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<AssemblyName>Agenix.PlaywrightGrid.Domain.Tests</AssemblyName>
4+
<RootNamespace>Agenix.PlaywrightGrid.Domain.Tests</RootNamespace>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<ProjectReference Include="../Agenix.PlaywrightGrid.Domain/Agenix.PlaywrightGrid.Domain.csproj" />
8+
</ItemGroup>
9+
</Project>
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using NUnit.Framework;
2+
using Agenix.PlaywrightGrid.Domain;
3+
4+
namespace Agenix.PlaywrightGrid.Domain.Tests;
5+
6+
public class LabelKeyTests
7+
{
8+
[Test]
9+
public void TryParse_Valid_MinSegments_Two_AppBrowser_DefaultOptions()
10+
{
11+
var ok = LabelKey.TryParse("MyApp:Chromium", out var lk);
12+
Assert.That(ok, Is.True);
13+
Assert.That(lk, Is.Not.Null);
14+
Assert.That(lk!.Original, Is.EqualTo("MyApp:Chromium"));
15+
Assert.That(lk.Normalized, Is.EqualTo("MyApp:Chromium"));
16+
Assert.That(lk.Segments, Has.Count.EqualTo(2));
17+
Assert.That(lk.App, Is.EqualTo("MyApp"));
18+
Assert.That(lk.Browser, Is.EqualTo("Chromium"));
19+
Assert.That(lk.Env, Is.EqualTo(""));
20+
}
21+
22+
[Test]
23+
public void TryParse_Trims_And_Rejects_LeadingTrailingColons_And_DoubleColons()
24+
{
25+
Assert.That(LabelKey.TryParse(":A:Chromium", out _), Is.False);
26+
Assert.That(LabelKey.TryParse("A:Chromium:", out _), Is.False);
27+
Assert.That(LabelKey.TryParse("A::Chromium", out _), Is.False);
28+
Assert.That(LabelKey.TryParse(" A:Chromium ", out var lk), Is.True);
29+
Assert.That(lk!.Original, Is.EqualTo("A:Chromium"));
30+
Assert.That(lk.Normalized, Is.EqualTo("A:Chromium"));
31+
}
32+
33+
[Test]
34+
public void TryParse_EnforceBrowserSecond_DefaultTrue_Rejects_UnknownBrowser()
35+
{
36+
Assert.That(LabelKey.TryParse("AppX:NotABrowser:UAT", out _), Is.False);
37+
}
38+
39+
[Test]
40+
public void TryParse_EnforceBrowserSecond_False_Allows_Any_Second_Segment()
41+
{
42+
var opts = new LabelKeyParsingOptions { EnforceBrowserSecond = false };
43+
Assert.That(LabelKey.TryParse("AppX:NotABrowser:UAT", out var lk, opts), Is.True);
44+
Assert.That(lk!.Browser, Is.EqualTo("NotABrowser"));
45+
}
46+
47+
[Test]
48+
public void TryParse_CasePolicy_Lower_And_Upper_Are_Applied_To_Normalized()
49+
{
50+
var lower = new LabelKeyParsingOptions { CasePolicy = LabelKeyCasePolicy.Lower, EnforceBrowserSecond = false };
51+
Assert.That(LabelKey.TryParse("MyApp:Chromium:Staging:US", out var lkLower, lower), Is.True);
52+
Assert.That(lkLower!.Normalized, Is.EqualTo("myapp:chromium:staging:us"));
53+
Assert.That(lkLower.Original, Is.EqualTo("MyApp:Chromium:Staging:US"));
54+
55+
var upper = new LabelKeyParsingOptions { CasePolicy = LabelKeyCasePolicy.Upper, EnforceBrowserSecond = false };
56+
Assert.That(LabelKey.TryParse("MyApp:Firefox:Uat:eu", out var lkUpper, upper), Is.True);
57+
Assert.That(lkUpper!.Normalized, Is.EqualTo("MYAPP:FIREFOX:UAT:EU"));
58+
Assert.That(lkUpper.Original, Is.EqualTo("MyApp:Firefox:Uat:eu"));
59+
}
60+
61+
[Test]
62+
public void TryParse_ForbiddenChars_Default_Rejects_Whitespace_In_Segments()
63+
{
64+
// Default ForbiddenChars include whitespace; spaces within segments should be rejected
65+
Assert.That(LabelKey.TryParse("My App:Chromium:UAT", out _), Is.False);
66+
Assert.That(LabelKey.TryParse("MyApp:Chrom ium:UAT", out _), Is.False);
67+
}
68+
69+
[Test]
70+
public void TryParse_SegmentCount_OutOfBounds_Fails()
71+
{
72+
var tooFew = new LabelKeyParsingOptions { MinSegments = 3 };
73+
Assert.That(LabelKey.TryParse("App:Chromium", out _, tooFew), Is.False);
74+
75+
var tooMany = new LabelKeyParsingOptions { MaxSegments = 3 };
76+
Assert.That(LabelKey.TryParse("A:B:C:D", out _, tooMany), Is.False);
77+
}
78+
79+
[Test]
80+
public void Equality_And_HashCode_Use_Normalized()
81+
{
82+
var lower = new LabelKeyParsingOptions { CasePolicy = LabelKeyCasePolicy.Lower, EnforceBrowserSecond = false };
83+
Assert.That(LabelKey.TryParse("MyApp:Chromium:UAT", out var a, lower), Is.True);
84+
Assert.That(LabelKey.TryParse("myapp:chromium:uat", out var b, lower), Is.True);
85+
Assert.That(a!.Equals(b), Is.True);
86+
Assert.That(a.GetHashCode(), Is.EqualTo(b!.GetHashCode()));
87+
88+
// With Keep policy, differing case leads to different Normalized values
89+
var keep = new LabelKeyParsingOptions { CasePolicy = LabelKeyCasePolicy.Keep, EnforceBrowserSecond = false };
90+
Assert.That(LabelKey.TryParse("MyApp:Chromium:UAT", out var c, keep), Is.True);
91+
Assert.That(LabelKey.TryParse("myapp:chromium:uat", out var d, keep), Is.True);
92+
Assert.That(c!.Equals(d), Is.False);
93+
}
94+
95+
[Test]
96+
public void Accessors_App_Browser_Env_Work_With_Missing_Segments()
97+
{
98+
var opts = new LabelKeyParsingOptions { EnforceBrowserSecond = false };
99+
Assert.That(LabelKey.TryParse("OnlyApp:OnlyBrowser", out var two, opts), Is.True);
100+
Assert.That(two!.App, Is.EqualTo("OnlyApp"));
101+
Assert.That(two.Browser, Is.EqualTo("OnlyBrowser"));
102+
Assert.That(two.Env, Is.EqualTo(""));
103+
104+
Assert.That(LabelKey.TryParse("A:B:C", out var three, opts), Is.True);
105+
Assert.That(three!.Env, Is.EqualTo("C"));
106+
}
107+
108+
[Test]
109+
public void TryParse_Null_Or_Empty_Returns_False()
110+
{
111+
Assert.That(LabelKey.TryParse(null, out _), Is.False);
112+
Assert.That(LabelKey.TryParse(" ", out _), Is.False);
113+
}
114+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using NUnit.Framework;
5+
using Agenix.PlaywrightGrid.Domain;
6+
7+
namespace Agenix.PlaywrightGrid.Domain.Tests;
8+
9+
public class LabelMatcherTests
10+
{
11+
private static LabelKey L(string s)
12+
{
13+
var opts = new LabelKeyParsingOptions { EnforceBrowserSecond = false };
14+
if (!LabelKey.TryParse(s, out var lk, opts)) throw new ArgumentException($"Invalid label: {s}");
15+
return lk!;
16+
}
17+
18+
[Test]
19+
public void Exact_Match_Returns_Requested_Label()
20+
{
21+
var requested = L("AppA:Chromium:UAT");
22+
var available = new[] { L("AppA:Chromium:UAT"), L("AppA:Chromium"), L("Other:Firefox:UAT") };
23+
var matcher = new LabelMatcher(new LabelMatchingOptions { WildcardsEnabled = false });
24+
25+
var match = matcher.TryMatch(requested, available);
26+
Assert.That(match, Is.Not.Null);
27+
Assert.That(match!.Normalized, Is.EqualTo("AppA:Chromium:UAT"));
28+
}
29+
30+
[Test]
31+
public void Trailing_Fallback_Drops_To_MinSegments_Default_2()
32+
{
33+
var requested = L("AppA:Chromium:UAT:EU");
34+
var available = new[] { L("AppA:Chromium"), L("AppA:Chromium:UAT") };
35+
var matcher = new LabelMatcher(new LabelMatchingOptions { TrailingFallbackEnabled = true, PrefixExpansionEnabled = false });
36+
37+
var match = matcher.TryMatch(requested, available);
38+
// exact missing; fallback from 4->3 matches 3-segment entry
39+
Assert.That(match, Is.Not.Null);
40+
Assert.That(match!.Normalized, Is.EqualTo("AppA:Chromium:UAT"));
41+
}
42+
43+
[Test]
44+
public void Trailing_Fallback_Respects_MinSegmentsForFallback()
45+
{
46+
var requested = L("AppA:Chromium:UAT");
47+
var available = new[] { L("AppA:Chromium") };
48+
var matcher = new LabelMatcher(new LabelMatchingOptions { TrailingFallbackEnabled = true, MinSegmentsForFallback = 3, PrefixExpansionEnabled = false });
49+
50+
var match = matcher.TryMatch(requested, available);
51+
Assert.That(match, Is.Null, "Should not fallback below 3 segments");
52+
}
53+
54+
[Test]
55+
public void Prefix_Expansion_Picks_Shortest_More_Specific_Then_Lexicographical()
56+
{
57+
var requested = L("AppB:Chromium");
58+
var available = new[] { L("AppB:Chromium:UAT:x"), L("AppB:Chromium:UAT:y"), L("AppB:Chromium:UAT:zz") };
59+
var matcher = new LabelMatcher(new LabelMatchingOptions { PrefixExpansionEnabled = true, TrailingFallbackEnabled = false });
60+
61+
var match = matcher.TryMatch(requested, available);
62+
Assert.That(match, Is.Not.Null);
63+
// Shortest length (3 segments). Among equals, lexicographically first is x
64+
Assert.That(match!.Normalized, Is.EqualTo("AppB:Chromium:UAT:x"));
65+
}
66+
67+
[Test]
68+
public void Wildcards_Disabled_Do_Not_Match_Asterisk_Segments()
69+
{
70+
var requested = L("AppA:*:UAT");
71+
var available = new[] { L("AppA:Firefox:UAT") };
72+
var matcher = new LabelMatcher(new LabelMatchingOptions { WildcardsEnabled = false, PrefixExpansionEnabled = true, TrailingFallbackEnabled = true });
73+
74+
var match = matcher.TryMatch(requested, available);
75+
Assert.That(match, Is.Null);
76+
}
77+
78+
[Test]
79+
public void Wildcards_Enabled_Match_Any_Segment_In_Exact_And_Prefix()
80+
{
81+
var requested = L("AppA:*:UAT");
82+
var available = new[] { L("AppA:Firefox:UAT"), L("AppA:Chromium:UAT:EU") };
83+
var matcher = new LabelMatcher(new LabelMatchingOptions { WildcardsEnabled = true, TrailingFallbackEnabled = false, PrefixExpansionEnabled = true });
84+
85+
var match = matcher.TryMatch(requested, available);
86+
// Exact should win (3 segments) before prefix expansion to 4
87+
Assert.That(match, Is.Not.Null);
88+
Assert.That(match!.Normalized, Is.EqualTo("AppA:Firefox:UAT"));
89+
}
90+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
</Project>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Collections.Generic;
2+
3+
namespace Agenix.PlaywrightGrid.Domain;
4+
5+
/// <summary>
6+
/// Central label matching strategy service.
7+
/// Implements ordered resolution: exact → trailing fallback → prefix expansion → optional wildcards.
8+
/// </summary>
9+
public interface ILabelMatcher
10+
{
11+
/// <summary>
12+
/// Attempts to find the best matching available label for the requested <see cref="LabelKey"/>.
13+
/// Returns null when no match is found according to the configured <see cref="LabelMatchingOptions"/>.
14+
/// </summary>
15+
LabelKey? TryMatch(LabelKey requested, IEnumerable<LabelKey> available);
16+
}

0 commit comments

Comments
 (0)