CI Lane Filters and Test Traits

This document describes how StellaOps categorizes tests so CI can select and gate them. It is a narrative overview; the authoritative definitions live in source and are pointed to below — do not re-enumerate them here.

Lanes vs. categories

Two concepts are easy to conflate:

See docs/technical/testing/TEST_TRAIT_TAXONOMY.md for the binding statement of this rule and src/__Libraries/StellaOps.TestKit/TestCategories.cs for the canonical category constants.

Lane posture (PR-gating intent)

LanePurposeCharacteristicsPR Gating
UnitFast, isolated testsNo I/O, deterministic, offlineYes
ContractAPI contract stabilitySchema/OpenAPI validationYes
IntegrationService and storage testsUses Testcontainers (Postgres/Valkey)Yes
SecuritySecurity regression testsauthz, negative cases, injection testsYes
PerformanceBenchmark and perf smokeRegression thresholdsOptional (scheduled)
LiveExternal connector smoke testsRequires network, upstream depsNever (opt-in only)

Live tests are skipped by default and require STELLAOPS_LIVE_TESTS=true; live drift detection is a dedicated CI lane prerequisite, not a default-suite gate.

Tagging tests

Add a StellaOps.TestKit project reference to your test project, then tag with the real trait surface:

Example shape (categories and intents abbreviated — see the source for the full set):

using StellaOps.TestKit;
using StellaOps.TestKit.Traits;
using Xunit;

public class MyTests
{
    [Fact]
    [Trait("Category", TestCategories.Unit)]
    public void FastIsolatedTest() { /* no I/O, deterministic */ }

    [Fact]
    [Trait("Category", TestCategories.Integration)]
    [Trait("BlastRadius", TestCategories.BlastRadius.Persistence)]
    public async Task DatabaseTest() { /* Testcontainers PostgreSQL */ }

    [Fact]
    [Trait("Category", TestCategories.Security)]
    [Intent(TestIntents.Safety, "Prevents SQL injection per OWASP A03:2021")]
    public void UnauthorizedRequestIsRejected() { /* RBAC / scope enforcement */ }
}

Running tests by category

# Filter by the Category trait (VSTest projects honour --filter):
dotnet test --filter "Category=Unit"
dotnet test --filter "Category=Integration"
dotnet test --filter "Category!=Live"

# Repo runner: sweeps all test projects for a category
.gitea/scripts/test/run-test-category.sh Unit

Caveat: xUnit v3 under Microsoft.Testing.Platform (MTP) ignores --filter; VSTest-based projects honour it. The repo runner accounts for both — prefer it over a bare dotnet test --filter when sweeping. See .gitea/scripts/test/run-test-category.sh.

CI workflows

CI jobs invoke the category runner (or dotnet test --filter "Category=...") per lane, collect TRX, and publish results. For the concrete workflow wiring and current per-lane posture, see the CI routing docs in docs/technical/testing/ (e.g. TEST_CI_ROUTING.md) and docs/technical/testing/ci-lane-integration.md.

Lane best practices

Unit lane

Contract lane

Integration lane

Security lane

Performance lane

Live lane

Migrating existing tests

  1. Classify the test (I/O → Integration; fast+isolated → Unit; auth/security → Security; external → Live; etc. — full mapping in TEST_TRAIT_TAXONOMY.md).
  2. Add [Trait("Category", TestCategories.<Value>)] (and [Intent(...)] where an audit rationale applies).
  3. Suites without a static Category trait must be tracked in docs/technical/testing/test-trait-migration.csv; validate-test-traits.py fails if TEST_MANIFEST.yml uses a category not present in TestCategories.cs.
  4. Verify: dotnet test --filter "Category=Unit" (or the category runner).