Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions .github/agents/tests.agent.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: "Use when developing tests for TorBoxSDK: create or review unit tests, integration tests, and performance tests for the TorBox C# SDK based on changes in the core project."
description: "Use when developing tests for TorBoxSDK: create or review unit tests, integration tests, schema validation tests, and performance tests for the TorBox C# SDK based on changes in the core project."
name: "Tests"
tools: [vscode, execute, read, agent, edit, search, web, browser, 'com.postman/postman-mcp-server/*', 'github/*', 'microsoftdocs/mcp/*', vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/pullRequestStatusChecks, github.vscode-pull-request-github/openPullRequest, todo]
model: Claude Opus 4.6 (copilot)
Expand All @@ -17,6 +17,7 @@ Your job is to build and maintain the test side of the SDK based on what exists
Focus only on test development for TorBoxSDK:
- unit tests
- integration tests
- schema validation tests (static OpenAPI comparison and live field detection)
- performance tests

Your responsibility is to validate the public behavior of the SDK while preserving the project's structure and conventions.
Expand All @@ -43,6 +44,7 @@ Use this agent for:
- adding unit tests for `TorBoxClient`, API clients, and resource clients
- validating request construction, route selection, auth headers, and response mapping
- testing JSON serialization and deserialization behavior
- adding or updating schema validation tests against the TorBox OpenAPI specification (static field/type coverage and live field detection)
- adding integration tests against the live TorBox APIs when appropriate
- adding or updating performance tests for hot serialization or HTTP-client code paths
- reviewing whether current coverage matches changes in the core SDK
Expand All @@ -62,14 +64,15 @@ If a test requires a minimal production-code seam to become testable, keep that

## Workflow

1. Identify whether the task belongs to unit, integration, or performance testing.
1. Identify whether the task belongs to unit, integration, schema validation, or performance testing.
2. Inspect the production behavior under test before writing assertions.
3. Prefer unit tests first, especially for request shape, response mapping, and error handling.
4. Add integration tests only when live API behavior provides real value.
5. Add performance tests only when they answer a concrete throughput or allocation question.
6. If the task requires updating `README.md` or Markdown files under `docs/`, delegate that work to `Docs`.
7. Run relevant tests or validate the test project setup before finishing when possible.
8. Report any remaining gaps in coverage, flaky risks, or missing seams.
4. When models change, update schema validation mappings in `SchemaModelMapping` and add live tests for new endpoints.
5. Add integration tests only when live API behavior provides real value.
6. Add performance tests only when they answer a concrete throughput or allocation question.
7. If the task requires updating `README.md` or Markdown files under `docs/`, delegate that work to `Docs`.
8. Run relevant tests or validate the test project setup before finishing when possible.
9. Report any remaining gaps in coverage, flaky risks, or missing seams.

## Done When

Expand All @@ -87,4 +90,4 @@ When working on a task, prefer to return:
- the production behavior covered
- the strategy used, such as unit vs integration vs performance
- any documentation handoff required for `Docs`
- any remaining coverage gaps, assumptions, or follow-up work
- any remaining coverage gaps, assumptions, or follow-up work
36 changes: 36 additions & 0 deletions .github/instructions/csharp-conventions.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,42 @@ public async Task GetMeAsync_WithRealApiKey_ReturnsUser()
- No I/O in baseline benchmarks unless measuring HTTP overhead
- Do not delete existing benchmarks without justification

## Schema Validation Tests (`TorBoxSDK.SchemaValidationTests`)

Schema tests verify bidirectional consistency between the TorBox OpenAPI specification and SDK model types. The OpenAPI spec is fetched at test time from `https://api.torbox.app/openapi.json` — no local copy is versioned.

### Static Schema Tests (`OpenApi/`)

Static schema tests compare the downloaded OpenAPI specification against SDK model types without calling the API:

- `OpenApiFieldCoverageTests` — verifies all OpenAPI fields are mapped in the SDK and vice-versa
- `OpenApiTypeMappingTests` — verifies C# property types are compatible with OpenAPI type declarations

Key rules:
- Use `OpenApiSchemaReader.ReadFromApi()` for all spec access — never read from a local file
- Register new schema-to-type mappings in `SchemaModelMapping.SchemaToType`
- Record intentional field discrepancies in `SchemaModelMapping.KnownOpenApiFieldsNotInSdk` or `KnownSdkFieldsNotInOpenApi`
- Record intentional type mismatches in `SchemaModelMapping.KnownTypeMismatches`
- Filter static tests in CI with `--filter "Category!=Live"`

### Live Schema Tests (`Live/`)

Live schema tests call real TorBox API endpoints and check for unmapped fields:

- `[Trait("Category", "Live")]` on every live test class
- `[Collection("SchemaLive")]` to share `SchemaLiveTestFixture`
- Always skip gracefully when `TORBOX_API_KEY` is absent via `Skip.If(!Fixture.HasApiKey)`
- Use `SchemaAssert.FindUnmappedFieldsAsync<T>()` for field detection
- Use `EnsureSuccessStatusCode()` and `using` on every `HttpResponseMessage`

### Shared Infrastructure

Infrastructure classes in `Infrastructure/` delegate to `TorBoxSDK.TestUtilities`:
- `ModelReflector` — `[JsonPropertyName]` reflection
- `UnmappedFieldDetector` — recursive unmapped JSON field detection
- `OpenApiSchemaReader` — OpenAPI spec download and parsing (cached per process)
- `SchemaModelMapping` — schema-to-type map with known exclusions and type mismatches

---

# Part 5 — Samples (`samples/**/*.cs`)
Expand Down
1 change: 1 addition & 0 deletions .github/skills/code-review/references/instruction-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ Is the file in tests/?
| DI extensions | `src/TorBoxSDK/Extensions/` | 1 + 2 |
| Unit tests | `tests/TorboxSDK.UnitTests/` | 1 + 4 |
| Integration tests | `tests/TorBoxSDK.IntegrationTests/` | 1 + 4 |
| Schema validation tests | `tests/TorBoxSDK.SchemaValidationTests/` | 1 + 4 |
| Performance tests | `tests/TorBoxSDK.PerformanceTests/` | 1 + 4 |
| Samples | `samples/` | 1 + 5 |
2 changes: 1 addition & 1 deletion .github/skills/dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Endpoint implementation is now owned directly by `/dev` as **J2 — Endpoint** t
|-----|---------|---------------|
| **J1 — Architecture** | Designing or refactoring client structure, DI, namespacing, cross-cutting concerns | `architecture` skill |
| **J2 — Endpoint** | Adding or extending an API endpoint (models + client method + tests) | Internal `/dev` endpoint workflow |
| **J3 — Tests** | Writing unit, integration, or performance tests for existing code | `tests` skill |
| **J3 — Tests** | Writing unit, integration, schema validation, or performance tests for existing code | `tests` skill |
| **J4 — Review** | Reviewing or auditing C# code before merge | `code-review` skill |
| **J5 — Docs & Packaging** | README, samples, XML docs, NuGet metadata, release readiness | `docs` skill |
| **J6 — Foundation** | Scaffolding project infrastructure (csproj, build props, editorconfig, DI setup) | See [foundation jobs reference](./references/dev-jobs.md#j6--foundation-jobs) |
Expand Down
1 change: 1 addition & 0 deletions .github/skills/dev/references/dev-jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ For each endpoint:
|------|-------|-------------|
| Unit tests | After each J2 implementation | `tests/TorboxSDK.UnitTests/` |
| Serialization tests | After each new model (J2 Phase 2) | `tests/TorboxSDK.UnitTests/Models/` |
| Schema validation tests | After each new or modified model | `tests/TorBoxSDK.SchemaValidationTests/` |
| Integration tests | After stabilization of a complete resource client | `tests/TorBoxSDK.IntegrationTests/` |
| Performance tests | On critical paths (serialization, HTTP) | `tests/TorBoxSDK.PerformanceTests/` |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ Use this checklist when executing **J2 — Endpoint** inside `/dev`.
- Add unit tests for success.
- Add unit tests for failure.
- Assert the route, method, and payload.
- Register new models in `SchemaModelMapping.SchemaToType` for schema validation.
- Run static schema tests to verify field and type coverage.
- Update docs if the public API changed.
3 changes: 2 additions & 1 deletion .github/skills/dev/references/skill-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Quick reference to determine which skill to load based on the nature of the work
dev (orchestrator skill)
├── architecture → client structure, DI, namespaces
├── J2 endpoint workflow → models + client method + HTTP wiring
├── tests → unit tests, integration, perf
├── tests → unit tests, schema validation, integration, perf
├── code-review → audit and validation before merge
└── docs → README, docs pages, samples, XML doc, diagrams, NuGet
```
Expand All @@ -33,6 +33,7 @@ The `dev` skill does not replace these skills. It chooses which one to launch, i
| "Extend a resource client interface" | `dev` (J2) | `.github/skills/dev/SKILL.md` |
| "Write unit tests" | `tests` | `.github/skills/tests/SKILL.md` |
| "Write integration tests" | `tests` | `.github/skills/tests/SKILL.md` |
| "Update schema validation tests" | `tests` | `.github/skills/tests/SKILL.md` |
| "Add benchmarks" | `tests` | `.github/skills/tests/SKILL.md` |
| "Review this file" | `code-review` | `.github/skills/code-review/SKILL.md` |
| "Audit src/ before merge" | `code-review` | `.github/skills/code-review/SKILL.md` |
Expand Down
17 changes: 15 additions & 2 deletions .github/skills/tests/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: tests
description: 'Use when writing or reviewing tests for TorBoxSDK: unit tests for HttpClient-based services, integration tests against TorBox, serialization tests, and performance validation.'
description: 'Use when writing or reviewing tests for TorBoxSDK: unit tests for HttpClient-based services, integration tests against TorBox, schema validation tests against the OpenAPI spec, serialization tests, and performance validation.'
argument-hint: 'Describe the service, model, or behavior to test, such as TorrentsService request mapping or TorBoxResponse deserialization.'
user-invocable: true
disable-model-invocation: false
Expand All @@ -18,13 +18,15 @@ Use this skill when you need to:
- write unit tests for a service that uses HttpClient
- validate JSON serialization or deserialization
- add integration tests that hit the real TorBox API
- add or update schema validation tests against the TorBox OpenAPI specification
- review whether a new endpoint implementation is sufficiently tested
- add performance or throughput-oriented validation

## Strategy

Use the lightest test type that gives confidence:
- unit tests for request construction, serialization, and error mapping
- schema validation tests for bidirectional model consistency with the OpenAPI spec
- integration tests for real API behavior and auth-sensitive flows
- performance tests only for code paths where allocation or throughput matters

Expand Down Expand Up @@ -56,7 +58,16 @@ Use integration tests for:
5. Keep integration tests isolated.
Mark them clearly so they can be excluded in default local and CI runs when credentials are unavailable.

6. Add performance tests selectively.
6. Update schema validation tests when models change.
When adding or modifying SDK models:
- add or update mappings in `SchemaModelMapping.SchemaToType`
- register any intentional field or type discrepancies in the appropriate known exclusion sets
- ensure static schema tests pass by running with `--filter "Category!=Live"`
- add live schema tests when a new endpoint is mapped, using `SchemaAssert.FindUnmappedFieldsAsync<T>()`

The OpenAPI specification is fetched from `https://api.torbox.app/openapi.json` at test time — no local file is versioned.

7. Add performance tests selectively.
Use them for:
- heavy serialization paths
- bulk list deserialization
Expand All @@ -68,10 +79,12 @@ A test set is sufficient when:
- the public behavior is validated, not just internals
- success and failure paths both exist
- serialization assumptions are pinned down
- schema validation mappings are current for any new or changed models
- tests remain deterministic unless explicitly integration-based
- the tests follow the repo xUnit conventions

## References

- [HTTP client unit testing patterns](./references/http-client-unit-testing-patterns.md)
- [Integration and performance testing guidance](./references/integration-and-performance-testing-guidance.md)
- [Schema validation testing guidance](./references/schema-validation-testing-guidance.md)
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ Use integration tests only where live behavior matters:
- Avoid brittle assertions on rapidly changing remote state.
- Prefer test accounts or isolated resources when available.

## Schema Validation Tests

Schema validation tests compare SDK models against the TorBox OpenAPI specification. See [schema-validation-testing-guidance.md](./schema-validation-testing-guidance.md) for full details.

Key points:
- The OpenAPI spec is fetched from `https://api.torbox.app/openapi.json` — no local file.
- Static tests (field coverage, type mapping) run without API key.
- Live tests (unmapped field detection) require `TORBOX_API_KEY` and skip gracefully when absent.
- When adding new models, register them in `SchemaModelMapping.SchemaToType`.

## Performance

Use performance tests selectively for:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Schema Validation Testing

## Overview

Schema validation tests verify bidirectional consistency between the TorBox OpenAPI specification and SDK model types. The OpenAPI spec is fetched at test time from `https://api.torbox.app/openapi.json` — no local copy is versioned in the repository.

## Test Categories

### Static Schema Tests (`OpenApi/`)

Compare the downloaded OpenAPI specification against SDK types without calling the API:

- **`OpenApiFieldCoverageTests`** — every OpenAPI field must have a `[JsonPropertyName]` in the SDK, and vice-versa.
- **`OpenApiTypeMappingTests`** — C# property types must be compatible with OpenAPI type declarations.

Static tests run in CI with `--filter "Category!=Live"` and require network access to download the spec.

### Live Schema Tests (`Live/`)

Call real TorBox API endpoints and detect unmapped fields in the JSON response:

- Require `TORBOX_API_KEY` environment variable.
- Skip gracefully when the key is absent.
- Use `SchemaAssert.FindUnmappedFieldsAsync<T>()` for field detection.
- Run in CI via the `integration-tests.yml` workflow with the `testing` environment.

## Adding a New Model

When adding or updating an SDK model:

1. **Register the mapping** in `SchemaModelMapping.SchemaToType` — the key is the OpenAPI schema name, the value is the SDK model type.
2. **Run static tests** with `dotnet test tests/TorBoxSDK.SchemaValidationTests/ --filter "Category!=Live"` to verify field and type coverage.
3. **Add known exclusions** if the SDK intentionally diverges:
- `KnownOpenApiFieldsNotInSdk` — OpenAPI fields the SDK does not map (e.g., deprecated or internal-only fields).
- `KnownSdkFieldsNotInOpenApi` — SDK extensions not in the spec (e.g., computed properties).
- `KnownTypeMismatches` — fields where the OpenAPI type differs from the SDK serialization (e.g., integer enums serialized as strings).
4. **Add a live test** when a new endpoint is mapped, following the pattern in existing `Live/` test files.

## Infrastructure

| Class | Location | Responsibility |
|-------|----------|----------------|
| `OpenApiSchemaReader` | `Infrastructure/` | Downloads and parses the OpenAPI spec (cached per process via `Lazy<T>`) |
| `SchemaModelMapping` | `Infrastructure/` | Schema-to-type map, known exclusions, type mapping |
| `ModelReflector` | `Infrastructure/` | `[JsonPropertyName]` reflection (delegates to `TestUtilities`) |
| `UnmappedFieldDetector` | `Infrastructure/` | Recursive unmapped field detection (delegates to `TestUtilities`) |
| `SchemaLiveTestFixture` | `Infrastructure/` | Shared `HttpClient` with API key for live tests |
| `SchemaTestCollection` | `Infrastructure/` | xUnit collection definition for live test fixture sharing |

## Key Rules

- Never version `openapi.json` locally — always fetch from `https://api.torbox.app/openapi.json`.
- Use `OpenApiSchemaReader.ReadFromApi()` for all spec access.
- Static tests must work without `TORBOX_API_KEY`.
- Live tests must skip gracefully without `TORBOX_API_KEY`.
- All `HttpResponseMessage` in live tests must use `using` and `EnsureSuccessStatusCode()`.
4 changes: 2 additions & 2 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ on:
run_live_schema:
description: 'Run live schema validation tests'
required: false
default: 'true'
default: true
type: boolean
run_integration:
description: 'Run integration tests'
required: false
default: 'true'
default: true
type: boolean

permissions:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,4 @@ MigrationBackup/
.ionide/

# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@ dotnet test tests/TorBoxSDK.IntegrationTests/

Integration tests are designed to skip gracefully when `TORBOX_API_KEY` is not set.

## Versioning

This project follows [Semantic Versioning (SemVer)](https://semver.org). Each release version uses the `MAJOR.MINOR.PATCH` format:

- **MAJOR** — incompatible API changes
- **MINOR** — new functionality added in a backward-compatible manner
- **PATCH** — backward-compatible bug fixes

## License

This repository is available under the [MIT License](LICENSE).
4 changes: 4 additions & 0 deletions TorBoxSDK.slnx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<Solution>
<Folder Name="/items/">
<File Path="Directory.Build.props" />
<File Path="Directory.Packages.props" />
</Folder>
<Folder Name="/src/">
<Project Path="src/TorBoxSDK.Examples/TorBoxSDK.Examples.csproj" />
<Project Path="src/TorBoxSDK/TorBoxSDK.csproj" />
Expand Down
3 changes: 1 addition & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ using TorBoxSDK.Models.Search;
using TorBoxSDK.Models.Torrents;

TorBoxResponse<IReadOnlyList<Torrent>> torrents = await client.Main.Torrents.GetMyTorrentListAsync();
TorBoxResponse<TorrentSearchResponse> results =
await client.Search.SearchTorrentsAsync("ubuntu");
TorBoxResponse<TorrentSearchResponse> results = await client.Search.SearchTorrentsAsync("ubuntu");
```

## What to expect from responses
Expand Down
Loading