Skip to content

Commit 1a6bf4f

Browse files
committed
Merge branch 'develop' into feature/add-oauth-support
2 parents b0f1c03 + 6a16991 commit 1a6bf4f

7 files changed

Lines changed: 498 additions & 12 deletions

File tree

API.IntegrationTests/API.IntegrationTests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.8" />
10+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.9" />
1111
<PackageReference Include="Testcontainers.PostgreSql" Version="4.7.0" />
1212
<PackageReference Include="Testcontainers.Redis" Version="4.7.0" />
13-
<PackageReference Include="TUnit" Version="0.57.24" />
13+
<PackageReference Include="TUnit" Version="0.57.65" />
1414
</ItemGroup>
1515

1616
<ItemGroup>

Common.Tests/Common.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<PackageReference Include="Testcontainers.PostgreSql" Version="4.7.0" />
77
<PackageReference Include="Testcontainers.Redis" Version="4.7.0" />
88
<PackageReference Include="Bogus" Version="35.6.3" />
9-
<PackageReference Include="TUnit" Version="0.57.24" />
9+
<PackageReference Include="TUnit" Version="0.57.65" />
1010
</ItemGroup>
1111

1212
<ItemGroup>
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
using OpenShock.Common.Utils;
2+
3+
namespace OpenShock.Common.Tests.Utils;
4+
5+
public class DomainValidatorTests
6+
{
7+
[Test]
8+
public async Task NullString_ReturnsFalse()
9+
{
10+
// Act
11+
var result = DomainValidator.IsValidDomain(null);
12+
13+
// Assert
14+
await Assert.That(result).IsFalse();
15+
}
16+
17+
[Test]
18+
public async Task EmptyString_ReturnsFalse()
19+
{
20+
// Act
21+
var result = DomainValidator.IsValidDomain("");
22+
23+
// Assert
24+
await Assert.That(result).IsFalse();
25+
}
26+
27+
[Test]
28+
public async Task NoDot_ReturnsFalse()
29+
{
30+
// Act
31+
var result = DomainValidator.IsValidDomain("example");
32+
33+
// Assert
34+
await Assert.That(result).IsFalse();
35+
}
36+
37+
[Test]
38+
public async Task LeadingDot_ReturnsFalse()
39+
{
40+
// Act
41+
var result = DomainValidator.IsValidDomain(".example.com");
42+
43+
// Assert
44+
await Assert.That(result).IsFalse();
45+
}
46+
47+
[Test]
48+
public async Task TrailingDot_ReturnsFalse()
49+
{
50+
// Act
51+
var result = DomainValidator.IsValidDomain("example.com.");
52+
53+
// Assert
54+
await Assert.That(result).IsFalse();
55+
}
56+
57+
[Test]
58+
public async Task ConsecutiveDots_ReturnsFalse()
59+
{
60+
// Act
61+
var result = DomainValidator.IsValidDomain("a..b.com");
62+
63+
// Assert
64+
await Assert.That(result).IsFalse();
65+
}
66+
67+
[Test]
68+
public async Task LabelTooLong_ReturnsFalse()
69+
{
70+
// Arrange: label with 64 chars (invalid), then ".com"
71+
var tooLong = new string('a', 64) + ".com";
72+
73+
// Act
74+
var result = DomainValidator.IsValidDomain(tooLong);
75+
76+
// Assert
77+
await Assert.That(result).IsFalse();
78+
}
79+
80+
[Test]
81+
public async Task LabelStartingHyphen_ReturnsFalse()
82+
{
83+
// Act
84+
var result = DomainValidator.IsValidDomain("-abc.com");
85+
86+
// Assert
87+
await Assert.That(result).IsFalse();
88+
}
89+
90+
[Test]
91+
public async Task LabelEndingHyphen_ReturnsFalse()
92+
{
93+
// Act
94+
var result = DomainValidator.IsValidDomain("abc-.com");
95+
96+
// Assert
97+
await Assert.That(result).IsFalse();
98+
}
99+
100+
[Test]
101+
public async Task InvalidCharacters_ReturnsFalse()
102+
{
103+
// Act
104+
var r1 = DomainValidator.IsValidDomain("exa_mple.com");
105+
var r2 = DomainValidator.IsValidDomain("examp le.com");
106+
var r3 = DomainValidator.IsValidDomain("exam!ple.com");
107+
108+
// Assert
109+
await Assert.That(r1).IsFalse();
110+
await Assert.That(r2).IsFalse();
111+
await Assert.That(r3).IsFalse();
112+
}
113+
114+
[Test]
115+
public async Task TotalLengthOver253_ReturnsFalse()
116+
{
117+
// Arrange: construct >253 chars with dots
118+
// "a." repeated 200 times yields 400 chars; ensure clearly >253.
119+
var longHost = string.Join('.', Enumerable.Repeat("a", 130)); // 129 dots + 130 a's ~ 259 chars
120+
121+
// Act
122+
var result = DomainValidator.IsValidDomain(longHost);
123+
124+
// Assert
125+
await Assert.That(result).IsFalse();
126+
}
127+
128+
[Test]
129+
public async Task ValidAsciiLDH_ReturnsTrue()
130+
{
131+
// Act
132+
var r1 = DomainValidator.IsValidDomain("example.com");
133+
var r2 = DomainValidator.IsValidDomain("a.b");
134+
var r3 = DomainValidator.IsValidDomain("foo-bar.baz0");
135+
var r4 = DomainValidator.IsValidDomain("xn--d1acufc.xn--p1ai"); // Punycode
136+
137+
// Assert
138+
await Assert.That(r1).IsTrue();
139+
await Assert.That(r2).IsTrue();
140+
await Assert.That(r3).IsTrue();
141+
await Assert.That(r4).IsTrue();
142+
}
143+
144+
[Test]
145+
public async Task HostMatchesCookieDomain_ExactMatch_ReturnsTrue()
146+
{
147+
// Act
148+
var result = DomainValidator.HostMatchesCookieDomain("example.com", "example.com");
149+
150+
// Assert
151+
await Assert.That(result).IsTrue();
152+
}
153+
154+
[Test]
155+
public async Task HostMatchesCookieDomain_SuffixLabelMatch_ReturnsTrue()
156+
{
157+
// Act
158+
var result = DomainValidator.HostMatchesCookieDomain("shop.foo.example.com".AsSpan(), "example.com".AsSpan());
159+
160+
// Assert
161+
await Assert.That(result).IsTrue();
162+
}
163+
164+
[Test]
165+
public async Task HostMatchesCookieDomain_PartialSuffixNoBoundary_ReturnsFalse()
166+
{
167+
// Act
168+
var result = DomainValidator.HostMatchesCookieDomain("badexample.com", "example.com");
169+
170+
// Assert
171+
await Assert.That(result).IsFalse();
172+
}
173+
174+
[Test]
175+
public async Task HostMatchesCookieDomain_CookieLongerThanHost_ReturnsFalse()
176+
{
177+
// Act
178+
var result = DomainValidator.HostMatchesCookieDomain("example.com", "foo.example.com");
179+
180+
// Assert
181+
await Assert.That(result).IsFalse();
182+
}
183+
184+
[Test]
185+
public async Task HostMatchesCookieDomain_InvalidHost_ReturnsFalse()
186+
{
187+
// Act
188+
var result = DomainValidator.HostMatchesCookieDomain("example", "example.com"); // host without dot is invalid
189+
190+
// Assert
191+
await Assert.That(result).IsFalse();
192+
}
193+
194+
[Test]
195+
public async Task HostMatchesCookieDomain_CaseInsensitive_ReturnsTrue()
196+
{
197+
// Act
198+
var result = DomainValidator.HostMatchesCookieDomain("SHOP.Foo.Example.COM", "example.com");
199+
200+
// Assert
201+
await Assert.That(result).IsTrue();
202+
}
203+
204+
// ---- GetBestMatchingCookieDomain ----
205+
206+
[Test]
207+
public async Task GetBestMatchingCookieDomain_PicksMostSpecific()
208+
{
209+
// Arrange
210+
var list = "example.com,foo.example.com,bar.com";
211+
212+
// Act
213+
var best = DomainValidator.GetBestMatchingCookieDomain("shop.foo.example.com", list);
214+
215+
// Assert
216+
await Assert.That(best).IsEqualTo("foo.example.com");
217+
}
218+
219+
[Test]
220+
public async Task GetBestMatchingCookieDomain_ExactMatchBeatsShorterSuffix()
221+
{
222+
// Arrange
223+
var list = "example.com,shop.foo.example.com";
224+
225+
// Act
226+
var best = DomainValidator.GetBestMatchingCookieDomain("shop.foo.example.com", list);
227+
228+
// Assert
229+
await Assert.That(best).IsEqualTo("shop.foo.example.com");
230+
}
231+
232+
[Test]
233+
public async Task GetBestMatchingCookieDomain_EmptySegmentsIgnored()
234+
{
235+
// Arrange
236+
var list = ",,example.com,,foo.example.com,,";
237+
238+
// Act
239+
var best = DomainValidator.GetBestMatchingCookieDomain("shop.foo.example.com", list);
240+
241+
// Assert
242+
await Assert.That(best).IsEqualTo("foo.example.com");
243+
}
244+
245+
[Test]
246+
public async Task GetBestMatchingCookieDomain_InvalidDomainsIgnored()
247+
{
248+
// Arrange: includes invalid ".example.com" and "exa_mple.com"
249+
var list = ".example.com,exa_mple.com,example.com";
250+
251+
// Act
252+
var best = DomainValidator.GetBestMatchingCookieDomain("shop.example.com", list);
253+
254+
// Assert
255+
await Assert.That(best).IsEqualTo("example.com");
256+
}
257+
258+
[Test]
259+
public async Task GetBestMatchingCookieDomain_NoMatch_ReturnsNull()
260+
{
261+
// Arrange
262+
var list = "foo.com,bar.net";
263+
264+
// Act
265+
var best = DomainValidator.GetBestMatchingCookieDomain("example.com", list);
266+
267+
// Assert
268+
await Assert.That(best).IsNull();
269+
}
270+
271+
[Test]
272+
public async Task GetBestMatchingCookieDomain_HostInvalid_ReturnsNull()
273+
{
274+
// Arrange
275+
var list = "example.com,foo.example.com";
276+
277+
// Act
278+
var best = DomainValidator.GetBestMatchingCookieDomain("example", list);
279+
280+
// Assert
281+
await Assert.That(best).IsNull();
282+
}
283+
284+
[Test]
285+
public async Task GetBestMatchingCookieDomain_WhitespaceNotTrimmedInCurrentImplementation_SkipsEntry()
286+
{
287+
// Arrange: first entry has spaces (invalid for current impl), second is valid and more specific.
288+
var list = " example.com ,foo.example.com";
289+
290+
// Use a host that matches *both* example.com and foo.example.com
291+
var best = DomainValidator.GetBestMatchingCookieDomain("shop.foo.example.com", list);
292+
293+
// Assert: because the first is invalid (whitespace not trimmed), the best is foo.example.com
294+
await Assert.That(best).IsEqualTo("foo.example.com");
295+
}
296+
297+
// ---- Additional boundary checks via HostMatchesCookieDomain ----
298+
299+
[Test]
300+
public async Task HostBoundary_ChecksLabelBoundary()
301+
{
302+
// "ample.com" is substring but not a label-suffix of "example.com"
303+
var r1 = DomainValidator.HostMatchesCookieDomain("example.com", "ample.com");
304+
var r2 = DomainValidator.HostMatchesCookieDomain("xample.com", "ample.com");
305+
var r3 = DomainValidator.HostMatchesCookieDomain("fooample.com", "ample.com");
306+
307+
await Assert.That(r1).IsFalse();
308+
await Assert.That(r2).IsFalse();
309+
await Assert.That(r3).IsFalse();
310+
}
311+
}

Common/Common.csproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1414
</PackageReference>
1515
<PackageReference Include="MessagePack" Version="3.1.4" />
16-
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="9.0.8" />
17-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
18-
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.8" />
19-
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.8.0" />
16+
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="9.0.9" />
17+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.9" />
18+
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.9" />
19+
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.9.0" />
2020
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
2121
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
2222
<PackageReference Include="NRedisStack" Version="1.1.0" />
@@ -27,10 +27,10 @@
2727
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
2828
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0" />
2929
<PackageReference Include="Redis.OM" Version="1.0.1" />
30-
<PackageReference Include="Scalar.AspNetCore" Version="2.7.2" />
30+
<PackageReference Include="Scalar.AspNetCore" Version="2.8.0" />
3131
<PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.3.1" />
3232
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
33-
<PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="9.0.8" />
33+
<PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="9.0.9" />
3434
<PackageReference Include="OneOf" Version="3.0.271" />
3535
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
3636
<PackageReference Include="Serilog.Sinks.OpenTelemetry" Version="4.2.0" />

0 commit comments

Comments
 (0)