Normalize base address in Node class to ensure consistent URI formatting
CI / Backend (build + test) (push) Successful in 1m25s
CI / Frontend (lint + typecheck + build) (push) Successful in 33s

- Implemented `NormalizeBaseAddress` method to add a trailing slash to the `BaseAddress` when necessary, preventing issues with relative path merging in 3x-ui deployments.
- Updated `Register` and `UpdateAddress` methods to utilize the normalization logic, ensuring that both methods handle base address input consistently.
- Added unit tests to verify the normalization behavior for various input scenarios, enhancing the reliability of node address management.
- Updated domain model documentation to reflect the new normalization behavior for `BaseAddress`.
This commit is contained in:
Leonid Pershin
2026-07-23 10:55:17 +03:00
parent c4bc6ff04b
commit 2f6bb26e97
3 changed files with 41 additions and 2 deletions
+11 -2
View File
@@ -34,7 +34,7 @@ public sealed class Node : Entity
{
Id = Guid.NewGuid(),
Name = name,
BaseAddress = baseAddress,
BaseAddress = NormalizeBaseAddress(baseAddress),
Credentials = credentials,
Location = location,
Status = NodeStatus.Unknown,
@@ -54,9 +54,18 @@ public sealed class Node : Entity
if (!baseAddress.IsAbsoluteUri)
throw new DomainException("Адрес ноды должен быть абсолютным URI.");
BaseAddress = baseAddress;
BaseAddress = NormalizeBaseAddress(baseAddress);
}
/// <summary>3x-ui часто разворачивается за нестандартным base path (`webBasePath`, напр.
/// `/benis`) — без завершающего слэша относительное объединение пути (RFC 3986 merge) отбрасывает
/// последний сегмент базы вместо добавления к нему, роняя `/benis` из итогового URL. Нормализуем
/// один раз при сохранении, чтобы админ мог вводить адрес и с завершающим слэшем, и без него.</summary>
private static Uri NormalizeBaseAddress(Uri baseAddress) =>
baseAddress.AbsolutePath.EndsWith('/')
? baseAddress
: new UriBuilder(baseAddress) { Path = baseAddress.AbsolutePath + "/" }.Uri;
public void UpdateCredentials(NodeCredentials credentials) => Credentials = credentials;
public void Enable() => IsEnabled = true;
@@ -35,6 +35,29 @@ public class NodeTests
);
}
[Theory]
[InlineData("https://host.example.com/benis", "https://host.example.com/benis/")]
[InlineData("https://host.example.com/benis/", "https://host.example.com/benis/")]
[InlineData("https://host.example.com", "https://host.example.com/")]
public void Register_NormalizesBaseAddressToTrailingSlash(string input, string expected)
{
var node = Node.Register("Node", new Uri(input), Credentials, null);
Assert.Equal(expected, node.BaseAddress.ToString());
}
[Theory]
[InlineData("https://host.example.com/benis", "https://host.example.com/benis/")]
[InlineData("https://host.example.com/benis/", "https://host.example.com/benis/")]
public void UpdateAddress_NormalizesBaseAddressToTrailingSlash(string input, string expected)
{
var node = Node.Register("Node", new Uri("https://old.example.com"), Credentials, null);
node.UpdateAddress(new Uri(input));
Assert.Equal(expected, node.BaseAddress.ToString());
}
[Fact]
public void UpdateDetails_ChangesNameAndLocation()
{