Skip to content

Commit e2bea3b

Browse files
authored
Merge pull request #23 from agenixframework/feature/add-playwright-grid-helm-chart
Add Playwright Grid Helm chart with Redis, Ingress templates, and enh…
2 parents 62065cc + b374e0d commit e2bea3b

File tree

110 files changed

+7524
-970
lines changed

Some content is hidden

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

110 files changed

+7524
-970
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#region License
2+
// Copyright (c) 2025 Agenix
3+
//
4+
// SPDX-License-Identifier: Apache-2.0
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// https://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
#endregion
18+
19+
using System.Text.Json;
20+
using System.Text.Json.Serialization;
21+
using NUnit.Framework;
22+
23+
namespace Agenix.PlaywrightGrid.Domain.Tests;
24+
25+
public class DtoSerializationTests
26+
{
27+
private static readonly JsonSerializerOptions WebJson = new(JsonSerializerDefaults.Web)
28+
{
29+
// Explicitly mirror Web defaults: camelCase + ignore null
30+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
31+
};
32+
33+
[Test]
34+
public void BorrowRequestDto_Serializes_With_RunName_When_Present()
35+
{
36+
var dto = new BorrowRequestDto
37+
{
38+
LabelKey = "AppB:Chromium:UAT",
39+
RunId = "rid-123",
40+
RunName = "My Run"
41+
};
42+
43+
var json = JsonSerializer.Serialize(dto, WebJson);
44+
TestContext.WriteLine(json);
45+
// Assert camelCase property names and runName included
46+
Assert.That(json, Does.Contain("\"labelKey\":\"AppB:Chromium:UAT\""));
47+
Assert.That(json, Does.Contain("\"runId\":\"rid-123\""));
48+
Assert.That(json, Does.Contain("\"runName\":\"My Run\""));
49+
}
50+
51+
[Test]
52+
public void BorrowRequestDto_Omits_RunName_When_Null()
53+
{
54+
var dto = new BorrowRequestDto
55+
{
56+
LabelKey = "AppB:Chromium:UAT",
57+
RunId = "rid-456",
58+
RunName = null
59+
};
60+
61+
var json = JsonSerializer.Serialize(dto, WebJson);
62+
TestContext.WriteLine(json);
63+
Assert.That(json, Does.Contain("\"labelKey\":\"AppB:Chromium:UAT\""));
64+
Assert.That(json, Does.Contain("\"runId\":\"rid-456\""));
65+
Assert.That(json, Does.Not.Contain("runName"));
66+
}
67+
68+
[Test]
69+
public void BorrowResponseDto_Serializes_RunName_When_Present_And_Omits_When_Null()
70+
{
71+
var withName = new BorrowResponseDto
72+
{
73+
BrowserId = "b1",
74+
WebSocketEndpoint = "ws://h/ws/b1",
75+
LabelKey = "AppB:Chromium:UAT",
76+
BrowserType = "chromium",
77+
RunName = "Some Display Name"
78+
};
79+
var withNameJson = JsonSerializer.Serialize(withName, WebJson);
80+
TestContext.WriteLine(withNameJson);
81+
Assert.That(withNameJson, Does.Contain("\"browserId\":\"b1\""));
82+
Assert.That(withNameJson, Does.Contain("\"webSocketEndpoint\":\"ws://h/ws/b1\""));
83+
Assert.That(withNameJson, Does.Contain("\"labelKey\":\"AppB:Chromium:UAT\""));
84+
Assert.That(withNameJson, Does.Contain("\"browserType\":\"chromium\""));
85+
Assert.That(withNameJson, Does.Contain("\"runName\":\"Some Display Name\""));
86+
87+
var withoutName = new BorrowResponseDto
88+
{
89+
BrowserId = "b2",
90+
WebSocketEndpoint = "ws://h/ws/b2",
91+
LabelKey = "AppB:Chromium:UAT",
92+
BrowserType = null,
93+
RunName = null
94+
};
95+
var withoutNameJson = JsonSerializer.Serialize(withoutName, WebJson);
96+
TestContext.WriteLine(withoutNameJson);
97+
Assert.That(withoutNameJson, Does.Contain("\"browserId\":\"b2\""));
98+
Assert.That(withoutNameJson, Does.Contain("\"webSocketEndpoint\":\"ws://h/ws/b2\""));
99+
Assert.That(withoutNameJson, Does.Contain("\"labelKey\":\"AppB:Chromium:UAT\""));
100+
Assert.That(withoutNameJson, Does.Not.Contain("browserType"));
101+
Assert.That(withoutNameJson, Does.Not.Contain("runName"));
102+
}
103+
104+
[Test]
105+
public void Run_And_RunSummary_Serialize_With_Optional_RunName()
106+
{
107+
var run = new Run { RunId = "r1", RunName = null };
108+
var runJson = JsonSerializer.Serialize(run, WebJson);
109+
TestContext.WriteLine(runJson);
110+
Assert.That(runJson, Does.Contain("\"runId\":\"r1\""));
111+
Assert.That(runJson, Does.Not.Contain("runName"));
112+
113+
var summary = new RunSummary
114+
{
115+
RunId = "r2",
116+
RunName = "Display Name",
117+
App = "AppA",
118+
Browser = "Chromium",
119+
Env = "UAT",
120+
Status = "Running",
121+
TotalTests = 10,
122+
Passed = 5,
123+
Failed = 1
124+
};
125+
var summaryJson = JsonSerializer.Serialize(summary, WebJson);
126+
TestContext.WriteLine(summaryJson);
127+
Assert.That(summaryJson, Does.Contain("\"runId\":\"r2\""));
128+
Assert.That(summaryJson, Does.Contain("\"runName\":\"Display Name\""));
129+
Assert.That(summaryJson, Does.Contain("\"app\":\"AppA\""));
130+
Assert.That(summaryJson, Does.Contain("\"browser\":\"Chromium\""));
131+
Assert.That(summaryJson, Does.Contain("\"env\":\"UAT\""));
132+
Assert.That(summaryJson, Does.Contain("\"status\":\"Running\""));
133+
}
134+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#region License
2+
// Copyright (c) 2025 Agenix
3+
//
4+
// SPDX-License-Identifier: Apache-2.0
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// https://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
#endregion
18+
19+
using NUnit.Framework;
20+
21+
namespace Agenix.PlaywrightGrid.Domain.Tests;
22+
23+
public class LabelMatcherEdgeTests
24+
{
25+
private static LabelKey L(string s)
26+
{
27+
var opts = new LabelKeyParsingOptions { EnforceBrowserSecond = false };
28+
if (!LabelKey.TryParse(s, out var lk, opts))
29+
{
30+
Assert.Fail($"Invalid test label: {s}");
31+
}
32+
return lk!;
33+
}
34+
35+
[Test]
36+
public void Wildcards_Enabled_Exact_Length_Chooses_Lexicographically_First()
37+
{
38+
var requested = L("AppZ:*:UAT");
39+
var available = new[] { L("AppZ:Chromium:UAT"), L("AppZ:Firefox:UAT") };
40+
var matcher = new LabelMatcher(new LabelMatchingOptions
41+
{
42+
WildcardsEnabled = true,
43+
TrailingFallbackEnabled = false,
44+
PrefixExpansionEnabled = false
45+
});
46+
47+
var match = matcher.TryMatch(requested, available);
48+
Assert.That(match, Is.Not.Null);
49+
// Among exact-length matches, Normalized order picks Chromium before Firefox
50+
Assert.That(match!.Normalized, Is.EqualTo("AppZ:Chromium:UAT"));
51+
}
52+
53+
[Test]
54+
public void Fallback_Beats_PrefixExpansion_When_Both_Enabled()
55+
{
56+
var requested = L("AppA:Chromium:UAT:EU");
57+
var available = new[] { L("AppA:Chromium:UAT"), L("AppA:Chromium:UAT:EU:extra") };
58+
var matcher = new LabelMatcher(new LabelMatchingOptions
59+
{
60+
TrailingFallbackEnabled = true,
61+
PrefixExpansionEnabled = true,
62+
WildcardsEnabled = false
63+
});
64+
65+
var match = matcher.TryMatch(requested, available);
66+
Assert.That(match, Is.Not.Null);
67+
// Trailing fallback to 3 segments should be preferred before expanding to longer prefixes
68+
Assert.That(match!.Normalized, Is.EqualTo("AppA:Chromium:UAT"));
69+
}
70+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#region License
2+
// Copyright (c) 2025 Agenix
3+
//
4+
// SPDX-License-Identifier: Apache-2.0
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// https://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
#endregion
18+
19+
using System;
20+
using Agenix.PlaywrightGrid.Domain;
21+
using NUnit.Framework;
22+
23+
namespace Agenix.PlaywrightGrid.Domain.Tests;
24+
25+
public class RedisKeysTests
26+
{
27+
[Test]
28+
public void Available_InUse_With_LabelKey_AreStable()
29+
{
30+
var key = "AppA:Chromium:UAT"; // Valid label
31+
Assert.That(RedisKeys.Available(key), Is.EqualTo("available:AppA:Chromium:UAT"));
32+
Assert.That(RedisKeys.InUse(key), Is.EqualTo("inuse:AppA:Chromium:UAT"));
33+
}
34+
35+
[Test]
36+
public void Maintenance_Keys_Compose_Correctly()
37+
{
38+
var key = "AppB:Firefox:Prod";
39+
Assert.That(RedisKeys.MaintenanceFlag(key), Is.EqualTo("maintenance:AppB:Firefox:Prod"));
40+
Assert.That(RedisKeys.MaintenanceTarget(key), Is.EqualTo("maintenance:target:AppB:Firefox:Prod"));
41+
Assert.That(RedisKeys.MaintenanceSince(key), Is.EqualTo("maintenance:since:AppB:Firefox:Prod"));
42+
Assert.That(RedisKeys.MaintenanceSnapInuse(key), Is.EqualTo("maintenance:snap_inuse:AppB:Firefox:Prod"));
43+
}
44+
45+
[Test]
46+
public void Node_And_Alive_Sanitize_Id()
47+
{
48+
var raw = "worker 1:region/eu"; // contains space, colon, slash
49+
var node = RedisKeys.Node(raw);
50+
var alive = RedisKeys.NodeAlive(raw);
51+
Assert.That(node, Does.StartWith("node:"));
52+
Assert.That(alive, Does.StartWith("node_alive:"));
53+
// Ensure forbidden chars are replaced with '-'
54+
Assert.That(node.Contains(' '), Is.False);
55+
Assert.That(node.Contains(':'), Is.True); // prefix only
56+
Assert.That(node.LastIndexOf(':'), Is.EqualTo(4)); // only the prefix colon
57+
Assert.That(node, Does.Not.Contain("/"));
58+
Assert.That(alive, Does.Not.Contain("/"));
59+
}
60+
61+
[Test]
62+
public void BorrowTtl_Sanitizes_BrowserId()
63+
{
64+
var raw = "bid:abc xyz";
65+
var key = RedisKeys.BorrowTtl(raw);
66+
Assert.That(key, Does.StartWith("borrow_ttl:"));
67+
Assert.That(key, Does.Not.Contain(" "));
68+
// After prefix, no additional ':' should remain
69+
Assert.That(key.LastIndexOf(':'), Is.EqualTo("borrow_ttl".Length));
70+
}
71+
72+
[Test]
73+
public void Results_Keys_AreStable()
74+
{
75+
var runId = "run_123";
76+
Assert.That(RedisKeys.ResultsRunsByStart(), Is.EqualTo("results:runs:byStart"));
77+
Assert.That(RedisKeys.ResultsRun(runId), Is.EqualTo("results:run:run_123"));
78+
Assert.That(RedisKeys.ResultsRunName(runId), Is.EqualTo("results:runname:run_123"));
79+
Assert.That(RedisKeys.ResultsTests(runId), Is.EqualTo("results:tests:run_123"));
80+
Assert.That(RedisKeys.ResultsCmd(runId), Is.EqualTo("results:cmd:run_123"));
81+
Assert.That(RedisKeys.ResultsCmdCount(runId), Is.EqualTo("results:cmdcount:run_123"));
82+
}
83+
84+
[Test]
85+
public void Audit_Keys_AreStable()
86+
{
87+
Assert.That(RedisKeys.AuditEntries(), Is.EqualTo("audit:entries"));
88+
Assert.That(RedisKeys.AuditSecretsRunnerFingerprint(), Is.EqualTo("audit:secrets:runner:fp"));
89+
Assert.That(RedisKeys.AuditSecretsNodeFingerprint(), Is.EqualTo("audit:secrets:node:fp"));
90+
}
91+
92+
[Test]
93+
public void Available_Throws_On_Invalid_Label()
94+
{
95+
Assert.Throws<ArgumentException>(() => RedisKeys.Available(":invalid"));
96+
Assert.Throws<ArgumentException>(() => RedisKeys.Available(""));
97+
}
98+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#region License
2+
// Copyright (c) 2025 Agenix
3+
//
4+
// SPDX-License-Identifier: Apache-2.0
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// https://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
#endregion
18+
19+
using NUnit.Framework;
20+
21+
namespace Agenix.PlaywrightGrid.Domain.Tests;
22+
23+
public class RunNameRulesTests
24+
{
25+
[Test]
26+
public void Null_Or_Whitespace_Is_Treated_As_Absent()
27+
{
28+
Assert.That(RunNameRules.TryNormalize(null, out var n1, out var e1), Is.True);
29+
Assert.That(n1, Is.Null);
30+
Assert.That(e1, Is.Null);
31+
32+
Assert.That(RunNameRules.TryNormalize(" ", out var n2, out var e2), Is.True);
33+
Assert.That(n2, Is.Null);
34+
Assert.That(e2, Is.Null);
35+
}
36+
37+
[Test]
38+
public void Trims_And_Preserves_Case()
39+
{
40+
Assert.That(RunNameRules.TryNormalize(" My Run.Name-01 ", out var n, out var e), Is.True);
41+
Assert.That(e, Is.Null);
42+
Assert.That(n, Is.EqualTo("My Run.Name-01"));
43+
}
44+
45+
[Test]
46+
public void Rejects_Control_And_Disallowed_Chars()
47+
{
48+
Assert.That(RunNameRules.TryNormalize("Bad@Name", out _, out var e1), Is.False);
49+
Assert.That(e1, Does.Contain("invalid characters"));
50+
51+
Assert.That(RunNameRules.TryNormalize("Bad#Name", out _, out var e2), Is.False);
52+
Assert.That(e2, Does.Contain("invalid characters"));
53+
54+
// control char (tab) should fail
55+
Assert.That(RunNameRules.TryNormalize("Bad\tName", out _, out var e3), Is.False);
56+
Assert.That(e3, Does.Contain("control characters"));
57+
}
58+
59+
[Test]
60+
public void Accepts_Max_Length_And_Rejects_Too_Long()
61+
{
62+
var ok = new string('A', RunNameRules.MaxLength);
63+
Assert.That(RunNameRules.TryNormalize(ok, out var n1, out var e1), Is.True);
64+
Assert.That(n1, Is.EqualTo(ok));
65+
Assert.That(e1, Is.Null);
66+
67+
var tooLong = new string('B', RunNameRules.MaxLength + 1);
68+
Assert.That(RunNameRules.TryNormalize(tooLong, out _, out var e2), Is.False);
69+
Assert.That(e2, Does.Contain("maximum length"));
70+
}
71+
}

0 commit comments

Comments
 (0)