build: bootstrap solution and quality gates (#3)
quality-gate / quality (push) Successful in 58s
quality-gate / quality (push) Successful in 58s
Closes #3
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace FinalFactory.Rendezvous.Tests.Architecture;
|
||||
|
||||
public sealed class DependencyBoundaryTests
|
||||
{
|
||||
[Fact]
|
||||
public void ContractsAreTransportAndEngineIndependent()
|
||||
{
|
||||
string[] references = GetReferences("FinalFactory.Rendezvous.Contracts");
|
||||
|
||||
Assert.DoesNotContain(references, IsGodotAssembly);
|
||||
Assert.DoesNotContain(references, IsServerAssembly);
|
||||
Assert.DoesNotContain(references, IsLiteNetLibAssembly);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClientIsGodotAndServerIndependent()
|
||||
{
|
||||
string[] references = GetReferences("FinalFactory.Rendezvous.Client");
|
||||
|
||||
Assert.DoesNotContain(references, IsGodotAssembly);
|
||||
Assert.DoesNotContain(references, IsServerAssembly);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestClientUsesOnlyPublicRendezvousDependencies()
|
||||
{
|
||||
string[] references = GetReferences("FinalFactory.Rendezvous.TestClient");
|
||||
|
||||
Assert.DoesNotContain(references, IsGodotAssembly);
|
||||
Assert.DoesNotContain(references, IsServerAssembly);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeclaredDependencyGraphMatchesTheArchitecture()
|
||||
{
|
||||
AssertDeclaredDependencies(
|
||||
"src/FinalFactory.Rendezvous.Contracts/FinalFactory.Rendezvous.Contracts.csproj",
|
||||
[],
|
||||
[]);
|
||||
AssertDeclaredDependencies(
|
||||
"src/FinalFactory.Rendezvous.Client/FinalFactory.Rendezvous.Client.csproj",
|
||||
["FinalFactory.Rendezvous.Contracts"],
|
||||
["LiteNetLib"]);
|
||||
AssertDeclaredDependencies(
|
||||
"src/FinalFactory.Rendezvous.Server/FinalFactory.Rendezvous.Server.csproj",
|
||||
["FinalFactory.Rendezvous.Contracts"],
|
||||
["LiteNetLib"]);
|
||||
AssertDeclaredDependencies(
|
||||
"src/FinalFactory.Rendezvous.TestClient/FinalFactory.Rendezvous.TestClient.csproj",
|
||||
["FinalFactory.Rendezvous.Client", "FinalFactory.Rendezvous.Contracts"],
|
||||
["LiteNetLib"]);
|
||||
}
|
||||
|
||||
private static void AssertDeclaredDependencies(
|
||||
string projectPath,
|
||||
string[] expectedProjects,
|
||||
string[] expectedPackages)
|
||||
{
|
||||
XDocument project = XDocument.Load(Path.Combine(FindRepositoryRoot(), projectPath));
|
||||
string[] projects = project
|
||||
.Descendants("ProjectReference")
|
||||
.Select(static element =>
|
||||
Path.GetFileNameWithoutExtension(element.Attribute("Include")?.Value) ?? string.Empty)
|
||||
.Order(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
string[] packages = project
|
||||
.Descendants("PackageReference")
|
||||
.Select(static element => element.Attribute("Include")?.Value ?? string.Empty)
|
||||
.Order(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
Assert.Equal(expectedProjects.Order(StringComparer.Ordinal), projects);
|
||||
Assert.Equal(expectedPackages.Order(StringComparer.Ordinal), packages);
|
||||
}
|
||||
|
||||
private static string FindRepositoryRoot()
|
||||
{
|
||||
DirectoryInfo? directory = new(AppContext.BaseDirectory);
|
||||
|
||||
while (directory is not null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(directory.FullName, "Rendezvous.slnx")))
|
||||
{
|
||||
return directory.FullName;
|
||||
}
|
||||
|
||||
directory = directory.Parent;
|
||||
}
|
||||
|
||||
throw new DirectoryNotFoundException("Could not locate the Rendezvous repository root.");
|
||||
}
|
||||
|
||||
private static string[] GetReferences(string assemblyName) => Assembly
|
||||
.Load(assemblyName)
|
||||
.GetReferencedAssemblies()
|
||||
.Select(static reference => reference.Name ?? string.Empty)
|
||||
.ToArray();
|
||||
|
||||
private static bool IsGodotAssembly(string assemblyName) =>
|
||||
assemblyName.StartsWith("Godot", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static bool IsLiteNetLibAssembly(string assemblyName) =>
|
||||
string.Equals(assemblyName, "LiteNetLib", StringComparison.Ordinal);
|
||||
|
||||
private static bool IsServerAssembly(string assemblyName) =>
|
||||
string.Equals(
|
||||
assemblyName,
|
||||
"FinalFactory.Rendezvous.Server",
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<AssemblyName>FinalFactory.Rendezvous.Tests</AssemblyName>
|
||||
<RootNamespace>FinalFactory.Rendezvous.Tests</RootNamespace>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../src/FinalFactory.Rendezvous.Client/FinalFactory.Rendezvous.Client.csproj" />
|
||||
<ProjectReference Include="../../src/FinalFactory.Rendezvous.Contracts/FinalFactory.Rendezvous.Contracts.csproj" />
|
||||
<ProjectReference Include="../../src/FinalFactory.Rendezvous.Server/FinalFactory.Rendezvous.Server.csproj" />
|
||||
<ProjectReference Include="../../src/FinalFactory.Rendezvous.TestClient/FinalFactory.Rendezvous.TestClient.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1 @@
|
||||
global using Xunit;
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Net;
|
||||
using FinalFactory.Rendezvous.Server.Transport;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace FinalFactory.Rendezvous.Tests.Server;
|
||||
|
||||
public sealed class UdpMediatorServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ServiceBindsAnEphemeralUdpPortAndStopsCleanly()
|
||||
{
|
||||
using CancellationTokenSource timeout = new(TimeSpan.FromSeconds(5));
|
||||
UdpMediatorOptions options = new()
|
||||
{
|
||||
ListenAddress = IPAddress.Loopback.ToString(),
|
||||
Port = 0,
|
||||
};
|
||||
using UdpMediatorService service = new(
|
||||
Options.Create(options),
|
||||
NullLogger<UdpMediatorService>.Instance);
|
||||
|
||||
await service.StartAsync(timeout.Token);
|
||||
|
||||
IPEndPoint? boundEndpoint = service.LocalEndpoint;
|
||||
Assert.NotNull(boundEndpoint);
|
||||
Assert.Equal(IPAddress.Loopback, boundEndpoint.Address);
|
||||
Assert.InRange(boundEndpoint.Port, 1, 65_535);
|
||||
|
||||
await service.StopAsync(timeout.Token);
|
||||
|
||||
Assert.Null(service.LocalEndpoint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net10.0": {
|
||||
"Microsoft.NET.Test.Sdk": {
|
||||
"type": "Direct",
|
||||
"requested": "[18.4.0, )",
|
||||
"resolved": "18.4.0",
|
||||
"contentHash": "w49iZdL4HL6V25l41NVQLXWQ+e71GvSkKVteMrOL02gP/PUkcnO/1yEb2s9FntU4wGmJWfKnyrRAhcMHd9ZZNA==",
|
||||
"dependencies": {
|
||||
"Microsoft.CodeCoverage": "18.4.0",
|
||||
"Microsoft.TestPlatform.TestHost": "18.4.0"
|
||||
}
|
||||
},
|
||||
"xunit": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.9.3, )",
|
||||
"resolved": "2.9.3",
|
||||
"contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
|
||||
"dependencies": {
|
||||
"xunit.analyzers": "1.18.0",
|
||||
"xunit.assert": "2.9.3",
|
||||
"xunit.core": "[2.9.3]"
|
||||
}
|
||||
},
|
||||
"xunit.runner.visualstudio": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.1.5, )",
|
||||
"resolved": "3.1.5",
|
||||
"contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA=="
|
||||
},
|
||||
"Microsoft.CodeCoverage": {
|
||||
"type": "Transitive",
|
||||
"resolved": "18.4.0",
|
||||
"contentHash": "9O0BtCfzCWrkAmK187ugKdq72HHOXoOUjuWFDVc2LsZZ0pOnA9bTt+Sg9q4cF+MoAaUU+MuWtvBuFsnduviJow=="
|
||||
},
|
||||
"Microsoft.TestPlatform.ObjectModel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "18.4.0",
|
||||
"contentHash": "4L6m2kS2pY5uJ9cpeRxzW22opr6ttScIRqsOpMDQpgENp/ZwxkkQCcmc6LRSURo2dFaaSW5KVflQZvroiJ7Wzg=="
|
||||
},
|
||||
"Microsoft.TestPlatform.TestHost": {
|
||||
"type": "Transitive",
|
||||
"resolved": "18.4.0",
|
||||
"contentHash": "gZsCHI+zOmZCcKZieIL4Jg14qKD2OGZOmX5DehuIk1EA9BN6Crm0+taXQNEuajOH1G9CCyBxw8VWR4t5tumcng==",
|
||||
"dependencies": {
|
||||
"Microsoft.TestPlatform.ObjectModel": "18.4.0",
|
||||
"Newtonsoft.Json": "13.0.3"
|
||||
}
|
||||
},
|
||||
"Newtonsoft.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "13.0.3",
|
||||
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
|
||||
},
|
||||
"xunit.abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.0.3",
|
||||
"contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg=="
|
||||
},
|
||||
"xunit.analyzers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.18.0",
|
||||
"contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ=="
|
||||
},
|
||||
"xunit.assert": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.9.3",
|
||||
"contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA=="
|
||||
},
|
||||
"xunit.core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.9.3",
|
||||
"contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
|
||||
"dependencies": {
|
||||
"xunit.extensibility.core": "[2.9.3]",
|
||||
"xunit.extensibility.execution": "[2.9.3]"
|
||||
}
|
||||
},
|
||||
"xunit.extensibility.core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.9.3",
|
||||
"contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
|
||||
"dependencies": {
|
||||
"xunit.abstractions": "2.0.3"
|
||||
}
|
||||
},
|
||||
"xunit.extensibility.execution": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.9.3",
|
||||
"contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
|
||||
"dependencies": {
|
||||
"xunit.extensibility.core": "[2.9.3]"
|
||||
}
|
||||
},
|
||||
"finalfactory.rendezvous.client": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"FinalFactory.Rendezvous.Contracts": "[1.0.0, )",
|
||||
"LiteNetLib": "[2.1.4, )"
|
||||
}
|
||||
},
|
||||
"finalfactory.rendezvous.contracts": {
|
||||
"type": "Project"
|
||||
},
|
||||
"finalfactory.rendezvous.server": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"FinalFactory.Rendezvous.Contracts": "[1.0.0, )",
|
||||
"LiteNetLib": "[2.1.4, )"
|
||||
}
|
||||
},
|
||||
"finalfactory.rendezvous.testclient": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"FinalFactory.Rendezvous.Client": "[1.0.0, )",
|
||||
"FinalFactory.Rendezvous.Contracts": "[1.0.0, )",
|
||||
"LiteNetLib": "[2.1.4, )"
|
||||
}
|
||||
},
|
||||
"LiteNetLib": {
|
||||
"type": "CentralTransitive",
|
||||
"requested": "[2.1.4, )",
|
||||
"resolved": "2.1.4",
|
||||
"contentHash": "KWlxvMw3Urpqj9joD96LRiK+LC62pQNs/zkXRJc+rHnxgkGp+vV703xzDrxRmv+V1YhCFfIGzs5nrVWtREIlyA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user