Trax.Core.Testing

The base package of Trax's architecture guards: the options and result types every guard shares, the helpers that find a repository's files, and three sets of guards of its own. The other guard packages (Trax.Effect.Data.Testing, Trax.Mediator.Testing, Trax.Api.GraphQL.Testing) build on it. See Architecture Guards for how they fit together.

Each guard is available two ways: as a static checker that returns a GuardResult you assert on with any framework, and, for the hygiene and repo-convention guards, as an NUnit fixture you subclass.

ArchitectureGuardOptions

namespace Trax.Core.Testing;
 
public sealed record ArchitectureGuardOptions
{
    public string? RepoRootOverride { get; init; }
    public IReadOnlyList<string> TestScanRoots { get; init; }          // ["tests"]
    public IReadOnlyList<string> SourceScanRoots { get; init; }        // ["src"]
    public IReadOnlySet<string> NoIgnoreKnownExceptions { get; init; }
    public IReadOnlySet<string> FixedDelayKnownExceptions { get; init; }
    public string ExpectedDirectoryBuildPropsVersion { get; init; }    // "1.99.99"
    public string TraxPackagePrefix { get; init; }                     // "Trax."
    public IReadOnlySet<string> CrossRepoPackageKnownExceptions { get; init; }
}
PropertyDefaultDescription
RepoRootOverridenullThe root to scan instead of the detected one, mainly for testing a guard against a synthetic tree
TestScanRoots["tests"]Top-level folders holding test code, scanned by the hygiene guards
SourceScanRoots["src"]Top-level folders holding production code
NoIgnoreKnownExceptionsemptyRepo-relative files exempt from the no-[Ignore] guard
FixedDelayKnownExceptionsemptyRepo-relative files exempt from the no-fixed-delay guard
ExpectedDirectoryBuildPropsVersion"1.99.99"The <Version> the root Directory.Build.props must declare
TraxPackagePrefix"Trax."Package ids that count as cross-repo Trax references
CrossRepoPackageKnownExceptionsemptyRepo-relative project files exempt from the cross-repo package guard

Allowlist paths are repo-relative with forward slashes.

GuardResult

public sealed record GuardResult(IReadOnlyList<string> Offenders, int Inspected, string FailureMessage)
{
    public bool Passed { get; }
}
MemberDescription
OffendersOne description per violation, usually path:line (reason)
InspectedHow many files or items the guard examined. A guard that inspected nothing checked nothing; assert it is above zero.
FailureMessageThe rule, how to fix a violation, and the offender list
PassedOffenders is empty

Infrastructure

TypeMembersDescription
RepoRoot (Trax.Core.Testing.Infrastructure)Path, Combine(params string[]), Relative(string)The repository root: the first directory above the test assembly that contains a *.slnx file. Throws InvalidOperationException when none is found, so a repository with only a .sln must set RepoRootOverride.
SourceFilesCSharp, Projects, Markdown, CSharpUnder, ProjectsUnderEnumerate *.cs, *.csproj and *.md files under the root, or under an explicit root
SourceTextStripCommentsAndStrings(string), MatchingLines(string, Regex)Blank out comments and string literals so a pattern matches code only; list the matching lines with their numbers

Hygiene guards

Checker (HygieneGuards)Flags, in TestScanRoots
NoIgnoreAttribute(options)[Ignore] in any form, and Ignore = or IgnoreReason = on a TestCase, TestCaseSource or fixture attribute
NoLegacyAsserts(options)NUnit's own asserts: Assert.That, Assert.AreEqual and the rest of the classic family, ClassicAssert, CollectionAssert, StringAssert. The convention is one assertion library.
NoFixedDelays(options)Task.Delay( and Thread.Sleep(, unless the line or one of the three above it carries a comment with determinism:, allowed-delay:, measuring-interval: or negative-wait:

HygieneGuardFixture (Trax.Core.Testing.Fixtures) runs all three as NUnit tests. Subclass it, override Options if the defaults do not fit, and the inherited tests run in your assembly. Each test fails first if the guard inspected nothing, then on any offender.

Repo-convention guards

Checker (RepoConventionGuards)Checks
DirectoryBuildPropsVersion(options)The root Directory.Build.props exists and its <Version> equals ExpectedDirectoryBuildPropsVersion
CrossRepoPackageVersions(options)Every PackageReference whose id starts with TraxPackagePrefix carries no Version or VersionOverride and has a <PackageVersion> pin in the root Directory.Packages.props

RepoConventionGuardFixture runs both as NUnit tests.

These encode the Trax repositories' own build conventions. The version check in particular expects the local-development placeholder 1.99.99 that the Trax repositories keep in Directory.Build.props. A consumer repository that versions differently either sets ExpectedDirectoryBuildPropsVersion to its own value or does not use this fixture.

Vocabulary guard

public sealed record ForeignVocabulary(string Library, IReadOnlyList<string> Attributes, string Replacement);
 
public static class VocabularyGuards
{
    public static GuardResult TraxVocabularyIsUsed(
        IReadOnlyList<ForeignVocabulary> banned,
        ArchitectureGuardOptions? options = null,
        IReadOnlyDictionary<string, string>? allowed = null
    );
}

Reports every use of a listed third-party attribute where Trax has its own, in both SourceScanRoots and TestScanRoots.

ParameterDescription
bannedThe vocabularies to refuse. Library is a token that must appear in a file (normally the library's root namespace) before its attributes count, unless a global or project-level using imports it. Attributes are names without the Attribute suffix. Replacement is what to write instead.
optionsScan roots and repo root; defaults apply when null
allowedRepo-relative paths exempt from the scan, each mapped to the reason: the translation layer that has to speak the library's language, and the tests that write the refused attribute on purpose

Comments and string literals are ignored.

[Test]
public void Resolvers_use_Trax_authorization_attributes()
{
    var result = VocabularyGuards.TraxVocabularyIsUsed(
        [new ForeignVocabulary("HotChocolate", ["Authorize", "AllowAnonymous"],
            "[TraxAuthorize] or [TraxAllowAnonymous]")]);
 
    result.Inspected.Should().BeGreaterThan(0);
    result.Offenders.Should().BeEmpty(result.FailureMessage);
}

Package

dotnet add package Trax.Core.Testing

It references NUnit, for the fixtures.