Issue #1: scaffold gateway solution and projects #51

Merged
dohertj2 merged 1 commits from agent-1/issue-1-scaffold-gateway-solution-and-projects into main 2026-04-26 15:52:12 -04:00
18 changed files with 385 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisLevel>latest</AnalysisLevel>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<Deterministic>true</Deterministic>
</PropertyGroup>
</Project>
@@ -0,0 +1,12 @@
namespace MxGateway.Contracts;
/// <summary>
/// Exposes version metadata shared by gateway components before generated
/// protobuf contracts are introduced.
/// </summary>
public static class GatewayContractInfo
{
public const uint WorkerProtocolVersion = 1;
public const string DefaultBackendName = "mxaccess-worker";
}
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
</Project>
@@ -0,0 +1,12 @@
namespace MxGateway.IntegrationTests;
public static class IntegrationTestEnvironment
{
public const string LiveMxAccessVariableName = "MXGATEWAY_RUN_LIVE_MXACCESS_TESTS";
public static bool LiveMxAccessTestsEnabled =>
string.Equals(
Environment.GetEnvironmentVariable(LiveMxAccessVariableName),
"1",
StringComparison.Ordinal);
}
@@ -0,0 +1,12 @@
namespace MxGateway.IntegrationTests;
public sealed class IntegrationTestEnvironmentTests
{
[Fact]
public void LiveMxAccessTests_AreOptInByEnvironmentVariable()
{
Assert.Equal(
"MXGATEWAY_RUN_LIVE_MXACCESS_TESTS",
IntegrationTestEnvironment.LiveMxAccessVariableName);
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MxGateway.Contracts\MxGateway.Contracts.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,40 @@
using MxGateway.Contracts;
namespace MxGateway.Server;
public static class GatewayApplication
{
public static WebApplication Build(string[] args)
{
WebApplicationBuilder builder = CreateBuilder(args);
WebApplication app = builder.Build();
app.MapGatewayEndpoints();
return app;
}
public static WebApplicationBuilder CreateBuilder(string[] args)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();
return builder;
}
public static IEndpointRouteBuilder MapGatewayEndpoints(this IEndpointRouteBuilder endpoints)
{
endpoints.MapGet("/", () => Results.Redirect("/health/live"));
endpoints.MapGet(
"/health/live",
() => Results.Ok(new GatewayHealthReply(
Status: "Healthy",
DefaultBackend: GatewayContractInfo.DefaultBackendName,
WorkerProtocolVersion: GatewayContractInfo.WorkerProtocolVersion)))
.WithName("LiveHealth");
return endpoints;
}
}
@@ -0,0 +1,6 @@
namespace MxGateway.Server;
public sealed record GatewayHealthReply(
string Status,
string DefaultBackend,
uint WorkerProtocolVersion);
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MxGateway.Contracts\MxGateway.Contracts.csproj" />
</ItemGroup>
</Project>
+7
View File
@@ -0,0 +1,7 @@
using MxGateway.Server;
var app = GatewayApplication.Build(args);
app.Run();
public partial class Program;
@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5120",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7121;http://localhost:5120",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
@@ -0,0 +1,18 @@
using MxGateway.Contracts;
namespace MxGateway.Tests.Contracts;
public sealed class GatewayContractInfoTests
{
[Fact]
public void DefaultBackendName_IsMxAccessWorker()
{
Assert.Equal("mxaccess-worker", GatewayContractInfo.DefaultBackendName);
}
[Fact]
public void WorkerProtocolVersion_StartsAtVersionOne()
{
Assert.Equal(1u, GatewayContractInfo.WorkerProtocolVersion);
}
}
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using MxGateway.Server;
namespace MxGateway.Tests.Gateway;
public sealed class GatewayApplicationTests
{
[Fact]
public void Build_MapsLiveHealthEndpoint()
{
WebApplication app = GatewayApplication.Build([]);
RouteEndpoint endpoint = Assert.Single(
((IEndpointRouteBuilder)app).DataSources
.SelectMany(dataSource => dataSource.Endpoints)
.OfType<RouteEndpoint>(),
candidate => candidate.RoutePattern.RawText == "/health/live");
Assert.Equal("LiveHealth", endpoint.Metadata.GetMetadata<IEndpointNameMetadata>()?.EndpointName);
}
}
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MxGateway.Contracts\MxGateway.Contracts.csproj" />
<ProjectReference Include="..\MxGateway.Server\MxGateway.Server.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,64 @@
using System.Xml.Linq;
namespace MxGateway.Tests.ProjectStructure;
public sealed class GatewayProjectReferenceTests
{
[Fact]
public void GatewayProject_TargetsNet10()
{
XDocument project = LoadProject("MxGateway.Server");
Assert.Equal("net10.0", ElementValue(project, "TargetFramework"));
}
[Fact]
public void GatewayProject_DoesNotReferenceMxAccessCom()
{
XDocument project = LoadProject("MxGateway.Server");
IReadOnlyList<string> referenceNames = project
.Descendants()
.Where(element => element.Name.LocalName is "Reference" or "COMReference" or "COMFileReference" or "PackageReference")
.Select(element => (string?)element.Attribute("Include") ?? string.Empty)
.ToArray();
Assert.DoesNotContain(referenceNames, reference =>
reference.Contains("MxAccess", StringComparison.OrdinalIgnoreCase)
|| reference.Contains("ArchestrA.MXAccess", StringComparison.OrdinalIgnoreCase)
|| reference.Contains("LMXProxy", StringComparison.OrdinalIgnoreCase));
}
private static XDocument LoadProject(string projectName)
{
DirectoryInfo repositoryRoot = FindRepositoryRoot();
string projectPath = Path.Combine(repositoryRoot.FullName, projectName, $"{projectName}.csproj");
return XDocument.Load(projectPath);
}
private static string ElementValue(XDocument project, string elementName)
{
return project
.Descendants()
.Single(element => element.Name.LocalName == elementName)
.Value;
}
private static DirectoryInfo FindRepositoryRoot()
{
DirectoryInfo? current = new(AppContext.BaseDirectory);
while (current is not null)
{
if (File.Exists(Path.Combine(current.FullName, "MxGateway.sln")))
{
return current;
}
current = current.Parent;
}
throw new DirectoryNotFoundException("Could not locate src/MxGateway.sln from the test output directory.");
}
}
+76
View File
@@ -0,0 +1,76 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MxGateway.Contracts", "MxGateway.Contracts\MxGateway.Contracts.csproj", "{484053B1-30E8-4411-9ACE-E3AE5EE65EB8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MxGateway.Server", "MxGateway.Server\MxGateway.Server.csproj", "{2752A666-898C-4D2A-A5A6-4F2FD17F64AE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MxGateway.Tests", "MxGateway.Tests\MxGateway.Tests.csproj", "{6E069780-A892-487E-AEED-051E26C829A4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MxGateway.IntegrationTests", "MxGateway.IntegrationTests\MxGateway.IntegrationTests.csproj", "{6D0BDEA5-F3F5-4F7C-9152-040BF88E4F2D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{484053B1-30E8-4411-9ACE-E3AE5EE65EB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{484053B1-30E8-4411-9ACE-E3AE5EE65EB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{484053B1-30E8-4411-9ACE-E3AE5EE65EB8}.Debug|x64.ActiveCfg = Debug|Any CPU
{484053B1-30E8-4411-9ACE-E3AE5EE65EB8}.Debug|x64.Build.0 = Debug|Any CPU
{484053B1-30E8-4411-9ACE-E3AE5EE65EB8}.Debug|x86.ActiveCfg = Debug|Any CPU
{484053B1-30E8-4411-9ACE-E3AE5EE65EB8}.Debug|x86.Build.0 = Debug|Any CPU
{484053B1-30E8-4411-9ACE-E3AE5EE65EB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{484053B1-30E8-4411-9ACE-E3AE5EE65EB8}.Release|Any CPU.Build.0 = Release|Any CPU
{484053B1-30E8-4411-9ACE-E3AE5EE65EB8}.Release|x64.ActiveCfg = Release|Any CPU
{484053B1-30E8-4411-9ACE-E3AE5EE65EB8}.Release|x64.Build.0 = Release|Any CPU
{484053B1-30E8-4411-9ACE-E3AE5EE65EB8}.Release|x86.ActiveCfg = Release|Any CPU
{484053B1-30E8-4411-9ACE-E3AE5EE65EB8}.Release|x86.Build.0 = Release|Any CPU
{2752A666-898C-4D2A-A5A6-4F2FD17F64AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2752A666-898C-4D2A-A5A6-4F2FD17F64AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2752A666-898C-4D2A-A5A6-4F2FD17F64AE}.Debug|x64.ActiveCfg = Debug|Any CPU
{2752A666-898C-4D2A-A5A6-4F2FD17F64AE}.Debug|x64.Build.0 = Debug|Any CPU
{2752A666-898C-4D2A-A5A6-4F2FD17F64AE}.Debug|x86.ActiveCfg = Debug|Any CPU
{2752A666-898C-4D2A-A5A6-4F2FD17F64AE}.Debug|x86.Build.0 = Debug|Any CPU
{2752A666-898C-4D2A-A5A6-4F2FD17F64AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2752A666-898C-4D2A-A5A6-4F2FD17F64AE}.Release|Any CPU.Build.0 = Release|Any CPU
{2752A666-898C-4D2A-A5A6-4F2FD17F64AE}.Release|x64.ActiveCfg = Release|Any CPU
{2752A666-898C-4D2A-A5A6-4F2FD17F64AE}.Release|x64.Build.0 = Release|Any CPU
{2752A666-898C-4D2A-A5A6-4F2FD17F64AE}.Release|x86.ActiveCfg = Release|Any CPU
{2752A666-898C-4D2A-A5A6-4F2FD17F64AE}.Release|x86.Build.0 = Release|Any CPU
{6E069780-A892-487E-AEED-051E26C829A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6E069780-A892-487E-AEED-051E26C829A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6E069780-A892-487E-AEED-051E26C829A4}.Debug|x64.ActiveCfg = Debug|Any CPU
{6E069780-A892-487E-AEED-051E26C829A4}.Debug|x64.Build.0 = Debug|Any CPU
{6E069780-A892-487E-AEED-051E26C829A4}.Debug|x86.ActiveCfg = Debug|Any CPU
{6E069780-A892-487E-AEED-051E26C829A4}.Debug|x86.Build.0 = Debug|Any CPU
{6E069780-A892-487E-AEED-051E26C829A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6E069780-A892-487E-AEED-051E26C829A4}.Release|Any CPU.Build.0 = Release|Any CPU
{6E069780-A892-487E-AEED-051E26C829A4}.Release|x64.ActiveCfg = Release|Any CPU
{6E069780-A892-487E-AEED-051E26C829A4}.Release|x64.Build.0 = Release|Any CPU
{6E069780-A892-487E-AEED-051E26C829A4}.Release|x86.ActiveCfg = Release|Any CPU
{6E069780-A892-487E-AEED-051E26C829A4}.Release|x86.Build.0 = Release|Any CPU
{6D0BDEA5-F3F5-4F7C-9152-040BF88E4F2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6D0BDEA5-F3F5-4F7C-9152-040BF88E4F2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D0BDEA5-F3F5-4F7C-9152-040BF88E4F2D}.Debug|x64.ActiveCfg = Debug|Any CPU
{6D0BDEA5-F3F5-4F7C-9152-040BF88E4F2D}.Debug|x64.Build.0 = Debug|Any CPU
{6D0BDEA5-F3F5-4F7C-9152-040BF88E4F2D}.Debug|x86.ActiveCfg = Debug|Any CPU
{6D0BDEA5-F3F5-4F7C-9152-040BF88E4F2D}.Debug|x86.Build.0 = Debug|Any CPU
{6D0BDEA5-F3F5-4F7C-9152-040BF88E4F2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6D0BDEA5-F3F5-4F7C-9152-040BF88E4F2D}.Release|Any CPU.Build.0 = Release|Any CPU
{6D0BDEA5-F3F5-4F7C-9152-040BF88E4F2D}.Release|x64.ActiveCfg = Release|Any CPU
{6D0BDEA5-F3F5-4F7C-9152-040BF88E4F2D}.Release|x64.Build.0 = Release|Any CPU
{6D0BDEA5-F3F5-4F7C-9152-040BF88E4F2D}.Release|x86.ActiveCfg = Release|Any CPU
{6D0BDEA5-F3F5-4F7C-9152-040BF88E4F2D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal