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:
- Category is the canonical, per-test classification trait and the primary CI selection key. New routing must select by
Category. - Lane is a coarser grouping (Unit / Contract / Integration / Security / Performance / Live) used to describe CI posture and PR-gating intent.
Lanesurvives only as compatibility metadata during migration and must not be the source of truth for new routing.
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)
| Lane | Purpose | Characteristics | PR Gating |
|---|---|---|---|
| Unit | Fast, isolated tests | No I/O, deterministic, offline | Yes |
| Contract | API contract stability | Schema/OpenAPI validation | Yes |
| Integration | Service and storage tests | Uses Testcontainers (Postgres/Valkey) | Yes |
| Security | Security regression tests | authz, negative cases, injection tests | Yes |
| Performance | Benchmark and perf smoke | Regression thresholds | Optional (scheduled) |
| Live | External connector smoke tests | Requires network, upstream deps | Never (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:
- Category —
[Trait("Category", TestCategories.Unit)]. Constants:src/__Libraries/StellaOps.TestKit/TestCategories.cs(also mirrored as a JSON enum indocs/technical/testing/TEST_MANIFEST.schema.json). The taxonomy table inTEST_TRAIT_TAXONOMY.mddocuments the intended use of each value. - Blast radius (optional) —
[Trait("BlastRadius", TestCategories.BlastRadius.Auth)], for incident-scoped runs. Values live underTestCategories.BlastRadius. - Intent (optional, audit) —
[Intent(TestIntents.Safety, "rationale ...")], which also emits anIntenttrait. Constants:src/__Libraries/StellaOps.TestKit/Traits/TestIntents.cs. - Live — mark with
LiveTestAttribute/LiveTheoryAttribute(env-gated as above):src/__Libraries/StellaOps.TestKit/Connectors/ConnectorLiveSchemaTestBase.cs.
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 baredotnet test --filterwhen 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
- Pure functions, in-memory logic; deterministic time/random via
StellaOps.TestKit.Deterministic(DeterministicTime,DeterministicRandom). - No file I/O (except snapshots), no network, no databases.
Contract lane
- OpenAPI schema validation, response-envelope checks, contract-stability tests.
- No external dependencies.
Integration lane
- Testcontainers for Postgres/Valkey, end-to-end service flows, multi-component interactions. No external/live services.
Security lane
- Authn/authz, input validation (SQL injection, XSS), RBAC scope enforcement, negative cases.
Performance lane
- Benchmarks and perf smoke with regression thresholds. Scheduled, not PR-gating.
Live lane
- External API smoke tests and upstream connector validation. Opt-in only (
STELLAOPS_LIVE_TESTS=true); never PR-gating.
Migrating existing tests
- Classify the test (I/O →
Integration; fast+isolated →Unit; auth/security →Security; external →Live; etc. — full mapping inTEST_TRAIT_TAXONOMY.md). - Add
[Trait("Category", TestCategories.<Value>)](and[Intent(...)]where an audit rationale applies). - Suites without a static
Categorytrait must be tracked indocs/technical/testing/test-trait-migration.csv;validate-test-traits.pyfails ifTEST_MANIFEST.ymluses a category not present inTestCategories.cs. - Verify:
dotnet test --filter "Category=Unit"(or the category runner).
Related Documentation
- Test trait taxonomy (authoritative category list + migration rules):
docs/technical/testing/TEST_TRAIT_TAXONOMY.md - Test manifest + schema:
docs/technical/testing/TEST_MANIFEST.yml,docs/technical/testing/TEST_MANIFEST.schema.json - CI routing:
docs/technical/testing/TEST_CI_ROUTING.md,docs/technical/testing/ci-lane-integration.md - Testing strategy:
docs/technical/testing/testing-strategy-models.md - TestKit usage:
docs/technical/testing/testkit-usage-guide.md,src/__Libraries/StellaOps.TestKit/README.md - Category / intent constants:
src/__Libraries/StellaOps.TestKit/TestCategories.cs,src/__Libraries/StellaOps.TestKit/Traits/TestIntents.cs
