Public Access
Moves the scaffold to src/DodoSSH.Api and establishes the repo conventions the rest
of the milestones build on.
Structure:
- src/{Contracts,Crypto,Domain,Infrastructure,Api}, tests/{Contracts,Crypto,Domain}.Tests
- DodoSSH.slnx rewritten with src/ and tests/ solution folders
Build:
- Directory.Build.props centralises TFM, nullable, deterministic builds and
TreatWarningsAsErrors; Directory.Packages.props pins every version centrally
- packages.lock.json committed so CI restores in locked mode
- NuGet.config clears machine-level sources, which both fixes NU1507 under central
package management and makes restore reproducible off this machine
- Microsoft.OpenApi pinned to 2.11.0: ASP.NET Core 10.0.10 resolves 2.0.0, which is
covered by GHSA-v5pm-xwqc-g5wc (high, patched in 2.7.5)
Analyzers:
- AnalysisLevel is Recommended, not All. With warnings-as-errors, All turns opinionated
naming rules into build breaks and trains people to blanket-suppress.
- BannedSymbols.txt bans DateTime.UtcNow (TimeProvider), Guid.NewGuid (CreateVersion7),
sync-over-async, MD5/SHA1, PBKDF2 and SecureString
- CA1711/CA1724 disabled: both are .NET Framework CAS-era naming rules
- PublicApiAnalyzers on Contracts only, since that assembly is the client's real contract
API:
- weather-forecast template removed
- UseHttpsRedirection removed; TLS terminates at the reverse proxy and redirecting
behind one causes loops
- /healthz/{live,ready,startup}. Liveness deliberately checks no dependencies so a
transient database outage cannot restart the container and kill live SSH sessions.
Notes:
- No coverage collector yet. Microsoft.Testing.Extensions.CodeCoverage pulls an MTP 1.x
MSBuild extension that throws TypeLoadException against the MTP 2.3.x xunit.v3 brings.
Coverage gates are an M3 concern; revisit with an MTP 2.x-aligned version then.
Verified: dotnet build (0 warnings), 17 tests pass, format check clean, API serves
health and OpenAPI endpoints.
48 lines
1.7 KiB
C#
48 lines
1.7 KiB
C#
using System.Reflection;
|
|
using DodoSSH.Contracts;
|
|
|
|
namespace DodoSSH.Contracts.Tests;
|
|
|
|
/// <summary>
|
|
/// Problem codes are part of the public wire contract: the client switches on them.
|
|
/// </summary>
|
|
public sealed class ProblemCodesTests
|
|
{
|
|
private static IReadOnlyList<FieldInfo> CodeFields =>
|
|
[.. typeof(ProblemCodes)
|
|
.GetFields(BindingFlags.Public | BindingFlags.Static)
|
|
.Where(f => f.IsLiteral && f.FieldType == typeof(string))
|
|
.Where(f => !string.Equals(f.Name, nameof(ProblemCodes.TypeBaseUri), StringComparison.Ordinal))];
|
|
|
|
[Fact]
|
|
public void AllCodes_AreUnique()
|
|
{
|
|
// A duplicated value would make two distinct failures indistinguishable to the
|
|
// client, which is the whole point of having codes rather than parsing messages.
|
|
var values = CodeFields.Select(f => (string)f.GetRawConstantValue()!).ToList();
|
|
|
|
values.Distinct(StringComparer.Ordinal).Count().ShouldBe(values.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void AllCodes_AreKebabCase()
|
|
{
|
|
// Codes are appended to TypeBaseUri to form the ProblemDetails type URI, so they
|
|
// must be URL-safe and stylistically consistent.
|
|
foreach (var field in CodeFields)
|
|
{
|
|
var value = (string)field.GetRawConstantValue()!;
|
|
|
|
value.ShouldMatch("^[a-z][a-z0-9]*(-[a-z0-9]+)*$", $"{field.Name} is not kebab-case");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void TypeBaseUri_IsAnAbsoluteHttpsUriEndingInSlash()
|
|
{
|
|
Uri.TryCreate(ProblemCodes.TypeBaseUri, UriKind.Absolute, out var uri).ShouldBeTrue();
|
|
uri!.Scheme.ShouldBe(Uri.UriSchemeHttps);
|
|
ProblemCodes.TypeBaseUri.ShouldEndWith("/");
|
|
}
|
|
}
|