Use when writing or modifying tests, improving coverage, debugging test failures, updating E2E fixtures, or working in tests/** directories.
{Component}Test or {Component}IntegrationTestLoad_many_iterations_of_config)internal sealed classinternal sealed class MyFeatureIntegrationTest : CliIntegrationFixture
{
protected override void RegisterStubsAndMocks(ContainerBuilder builder)
{
// Register custom mocks here
}
}
Mock externals only: Git (LibGit2Sharp), HTTP APIs, filesystem (MockFileSystem).
[AutoMockData]: Basic DI with mocks[InlineAutoMockData(params)]: Parameterized tests[Frozen] or [Frozen(Matching.ImplementedInterfaces)]: Shared mock instances[CustomizeWith(typeof(T))]: Custom configuration[AutoMockData(typeof(TestClass), nameof(Method))]: DI container integrationArrange (setting up dependencies):
dependency.Method().Returns(value);
dependency.Property.ReturnsNull();
dependency.Method(default!).ReturnsForAnyArgs(value);
dependency.Method().Returns([item1, item2]);
Verify.That<T>(x => x.Property.Should().Be(expected));
Argument matching (returns setup and received verification): Prefer
ReceivedWithAnyArgs()/ReturnsForAnyArgs() with default over Arg.Any<T>():
// Good
mock.ReceivedWithAnyArgs().SetStatus(default, default);
mock.Method(default!, default!).ReturnsForAnyArgs(value);
// Bad
mock.Received().SetStatus(Arg.Any<Status>(), Arg.Any<int?>());
mock.Method(Arg.Any<string>(), Arg.Any<int>()).ReturnsForAnyArgs(value);
Should(), run
ctx7 library AwesomeAssertions "<needed behavior>", then query the resolved ID with ctx7 docs.
Check for a direct assertion, selector overload, or chaining API first.Preferred:
result.Should().BeEquivalentTo(expected);
collection.Should().AllSatisfy(x => x.Property.Should().Be(expected));
act.Should().Throw<ExceptionType>().WithMessage("pattern");
collection.Should().HaveCount(n).And.Contain(item);
dict.Should().ContainKey(key).WhoseValue.Should().Be(expected);
Anti-patterns:
dict!["key"]! - use ContainKey().WhoseValue insteadHaveCount() + BeEquivalentTo() - redundant; equivalence checks count.And chainingShould() when an assertion-first equivalent exists. Prefer Contain, OnlyContain,
AllSatisfy, HaveCount, ContainSingle().Which, and selector overloads for clearer failures.
Use LINQ when no assertion API expresses the same outcome.All(predicate) with OnlyContain(predicate) without considering empty collections;
OnlyContain fails when the collection is empty.IntegrationTestFixture: Core library integration testsCliIntegrationFixture: CLI integration with composition rootVerify.That<T>(): NSubstitute matcher with assertionsTestableLogger: Capture log messagesNUnitAnsiConsole: Console output verificationMockFileSystem: Filesystem testing (avoid absolute paths)NewCf, NewConfig, NewQualitySize, NewPlannedCf, etc.New* Classes)One New{DomainType} class per domain type (e.g. NewQualityDefinition, NewCustomFormat). No
grab-bag classes mixing unrelated types.
Rules:
Core.TestLibrary for types in Core; Cli.Tests/Reusable for types in Cli. Hard
constraint from project dependency direction (see REC-90).When to skip: If a domain type is trivial (few properties, no required fields, natural
defaults), direct construction in tests is acceptable. Include a brief justification when skipping.
Avoid absolute paths in MockFileSystem (platform-incompatible):
// Good
Fs.CurrentDirectory().SubDirectory("a", "b").File("c.json")
// Bad
"/absolute/path/file.json"
# Unit and integration tests
dotnet test -v q
# Specific test project
dotnet test -v q tests/Recyclarr.Cli.Tests/
# Single test by name
dotnet test -v q --filter "FullyQualifiedName~TestMethodName"
# E2E tests (requires Docker services)
./scripts/Run-E2ETests.ps1
Use coverage analysis to identify gaps before writing tests.
# Run tests + query uncovered lines (one-shot, preferred)
./scripts/coverage.py --run uncovered Platform Migration
# Run separately only when making multiple queries
./scripts/coverage.py --run
./scripts/coverage.py uncovered Platform
./scripts/coverage.py files Migration
# Find N files with lowest coverage
./scripts/coverage.py --run lowest 10
Patterns are substring matches (case-insensitive), not globs. Multiple patterns match files containing ANY pattern. Examples:
Platform matches src/Recyclarr.Core/Platform/AppPaths.csPlatform Migration matches files containing "Platform" OR "Migration"Output format: path:pct:covered/total[:uncovered_lines]
CRITICAL: --run must succeed before querying. Investigate failures - coverage data is invalid on
failure. Run coverage BEFORE writing tests to understand gaps.
E2E tests run the full Recyclarr CLI against containerized Sonarr/Radarr instances. Tests verify that sync operations produce expected state in the services.
MANDATORY: Use ./scripts/Run-E2ETests.ps1 - never run dotnet test directly for E2E tests.
The script outputs a log file path; use rg to search logs without rerunning tests.
The test uses multiple resource providers to verify different loading mechanisms:
- name: trash-guides-pinned
type: trash-guides
clone_url: https://github.com/TRaSH-Guides/Guides.git
reference: <pinned-sha>
replace_default: true
Purpose: Baseline data that tests real-world compatibility.
Use for: Stable CFs that exist in official guides (e.g., Bad Dual Groups, Obfuscated).
Why pinned: Prevents upstream changes from breaking tests unexpectedly.
- name: sonarr-cfs-local
type: custom-formats
service: sonarr
path: <local-path>
Purpose: Tests type: custom-formats provider behavior specifically.
Use for: CFs that need controlled structure or don't exist in official guides.
- name: radarr-override
type: trash-guides
path: <local-path>
Purpose: Tests override/layering behavior (higher precedence than official guides).
Use for:
Fixtures/
recyclarr.yml # Test configuration
settings.yml # Resource provider definitions
custom-formats-sonarr/ # type: custom-formats provider (Sonarr)
custom-formats-radarr/ # type: custom-formats provider (Radarr)
trash-guides-override/ # type: trash-guides provider (override layer)
metadata.json # Defines paths for each resource type
docs/
Radarr/
cf/ # Custom formats
cf-groups/ # CF groups
quality-profiles/ # Quality profiles
Sonarr/
cf/
cf-groups/
quality-profiles/
e2e00000000000000000000000000001 - E2E test Radarr quality profilee2e00000000000000000000000000002 - E2E test Sonarr quality profilee2e00000000000000000000000000003 - E2E test Sonarr guide-only profilee2e00000000000000000000000000010 - E2E test Sonarr CF groupe2e00000000000000000000000000011 - E2E test Radarr CF groupcf000000000000000000000000000001 through cf000000000000000000000000000008 - Local test CFsConvention: Local test trash IDs use a cf prefix so YAML treats them as strings without
quoting. Never use all-numeric trash IDs in fixtures.
custom-formats-* or trash-guides-override/docs/*/cf/trash-guides-override/docs/*/quality-profiles/trash-guides-override/docs/*/cf-groups/RecyclarrSyncTests.csThe metadata.json file tells Recyclarr where to find each resource type:
{
"json_paths": {
"radarr": {
"custom_formats": ["docs/Radarr/cf"],
"qualities": [],
"naming": [],
"custom_format_groups": ["docs/Radarr/cf-groups"],
"quality_profiles": ["docs/Radarr/quality-profiles"]
},
"sonarr": { "..." }
}
}
Important: Paths must not contain spaces. Use cf instead of Custom Formats.
Use CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON.
Search and analyze your own session logs (older/parent conversations) using jq.
Start voice calls via the OpenClaw voice-call plugin.
BluOS CLI (blu) for discovery, playback, grouping, and volume.
Delegate coding tasks to Codex, Claude Code, or Pi agents via background process. Use when: (1) building/creating new features or apps, (2) reviewing PRs (spawn in temp dir), (3) refactoring large codebases, (4) iterative coding that needs file exploration. NOT for: simple one-liner fixes (just edit), reading code (use read tool), thread-bound ACP harness requests in chat (for example spawn/run Codex or Claude Code in a Discord thread; use sessions_spawn with runtime:"acp"), or any work in ~/clawd workspace (never spawn agents here). Claude Code: use --print --permission-mode bypassPermissions (no PTY). Codex/Pi/OpenCode: pty:true required.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Category:developer