Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
eb7ea2a
chore(02-01): bootstrap Sources/Tests/Directory.Build.props for share…
Vasar007 May 18, 2026
8d4b51a
feat(02-01): scaffold ProjectV.Tests.Shared with base classes and hel…
Vasar007 May 18, 2026
55035d2
refactor(02-01): retrofit Appraisers.Tests + Common.Tests to AwesomeA…
Vasar007 May 18, 2026
21114c0
chore(02-01): merge executor worktree (worktree-agent-aea059ae3b147d553)
Vasar007 May 18, 2026
56ea65c
docs(02-02): add TEST-01 critical-path test inventory at Docs/Testing…
Vasar007 May 18, 2026
35c599e
docs(02-02): add scenario-tests overview with mermaid architecture di…
Vasar007 May 18, 2026
5168379
chore(02-02): merge executor worktree (worktree-agent-a95e287620e17593b)
Vasar007 May 18, 2026
18b386d
chore(02-03): add Sources/Tests/coverlet.runsettings with D-27 exclus…
Vasar007 May 18, 2026
d6a1381
ci(02-03): split build.yml into four Linux stages + Windows non-Docke…
Vasar007 May 18, 2026
5c774d4
chore(02-03): merge executor worktree (worktree-agent-a05d051cc73513961)
Vasar007 May 18, 2026
872d045
feat(02-04): add Tests.Shared mock builder and value-object generators
Vasar007 May 18, 2026
21bb75e
test(02-04): add ProjectV.Models.Tests project with exception, value-…
Vasar007 May 18, 2026
3a2fb1b
test(02-04): extend Appraisers.Tests with concrete-appraiser and Appr…
Vasar007 May 18, 2026
5fd0f00
chore(02-04): merge executor worktree (worktree-agent-a00de6d32989b3371)
Vasar007 May 18, 2026
cc9013f
feat(02-05): add Shell, ServiceClient, and IO/Crawlers manager mock b…
Vasar007 May 18, 2026
efe5570
test(02-05): add ProjectV.Core.Tests project with Shell + ShellBuilde…
Vasar007 May 18, 2026
e598e1d
test(02-05): add CommunicationServiceClient + Polly retry tests under…
Vasar007 May 18, 2026
703be27
chore(02-05): merge executor worktree (worktree-agent-a4338fbc9e4ceaaf8)
Vasar007 May 18, 2026
a8dbe46
feat(02-06): add DataflowPipeline + 3 crawler mock builders to Tests.…
Vasar007 May 18, 2026
e790f66
test(02-06): add DataPipeline + Crawlers tests covering 3 critical-pa…
Vasar007 May 18, 2026
8e9830f
chore(02-06): merge executor worktree (worktree-agent-ab72eebbaaac8a7a8)
Vasar007 May 18, 2026
1da0323
test(02-07): add Executors + InputProcessing + OutputProcessing unit-…
Vasar007 May 18, 2026
4267bec
chore(02-07): merge executor worktree (worktree-agent-ada0e88c520d77b36)
Vasar007 May 18, 2026
9d3c074
test(02-08): add 7 recorded JSON fixtures for TMDb/OMDb/Steam contrac…
Vasar007 May 18, 2026
caf0396
test(02-08): add WireMock contract tests for TMDb/OMDb/Steam adapters
Vasar007 May 19, 2026
97bdd74
chore(02-08): merge executor worktree (worktree-agent-add6efbecf8635c75)
Vasar007 May 19, 2026
fe0644b
chore(02-09): install dotnet-ef tooling + design-time factory (Task 1…
Vasar007 May 19, 2026
02c6a12
feat(02-09): DbCollection fixture + DAL generators + TestDbHelper (Ta…
Vasar007 May 19, 2026
f05af49
test(02-09): DAL integration tests + Rule 1 production model fixes (T…
Vasar007 May 19, 2026
6bc40b8
chore(02-09): merge executor worktree (worktree-agent-a4ae3c507a7947c57)
Vasar007 May 19, 2026
5bb1e7b
feat(02-10): add TestWebApplicationFactory + TestJwtHelper + WebApiBa…
Vasar007 May 19, 2026
a118479
test(02-10): add JWT scenario tests + per-family base (RED)
Vasar007 May 19, 2026
4bf6f20
feat(02-10): register CommunicationWebService.Tests in sln + format f…
Vasar007 May 19, 2026
0f0095b
chore(02-10): merge executor worktree (worktree-agent-ab9fd1b5455f2e512)
Vasar007 May 19, 2026
cce735d
feat(02-11): add TestTelegramBotClientBuilder to Tests.Shared
Vasar007 May 19, 2026
33842e5
feat(02-11): extend TestWebApplicationFactory with bot-client + comm-…
Vasar007 May 19, 2026
d6450a2
feat(02-11): add Telegram webhook integration tests + family doc
Vasar007 May 19, 2026
300538a
chore(02-11): merge executor worktree (worktree-agent-ac0e56d7fdc66ef70)
Vasar007 May 19, 2026
fdb11c7
feat(02-12): add Telegram polling integration tests + family doc
Vasar007 May 19, 2026
994d7e8
chore(02-12): merge executor worktree (worktree-agent-aeda45007c4344bb4)
Vasar007 May 19, 2026
591076b
fix(02-review): correct nameof + WrappedUserId computation in DAL models
Vasar007 May 19, 2026
ae883e1
test(02-review): apply WR-02/03/04 fixes from phase-02 code review
Vasar007 May 19, 2026
5e8c3af
test(02-review): apply iter-2 audit-team fixes
Vasar007 May 19, 2026
f0b1602
test(02-review): assert TokenHash in FindByUserIdAsync test (iter-3)
Vasar007 May 19, 2026
4dae754
refactor(02-13): hoist NLog test initializer to Tests.Shared and fix …
Vasar007 May 19, 2026
39f929d
refactor(02-13): hoist FakeHttpMessageHandler to Tests.Shared (IN-03)
Vasar007 May 19, 2026
9545514
fix(02-13): guard RefreshTokenDbInfo ctor against Guid.Empty (DA-1)
Vasar007 May 19, 2026
339fb8f
test(02-13): add multi-row filter integration test for FindByUserIdAs…
Vasar007 May 19, 2026
605e607
chore(02-13): merge executor worktree (worktree-agent-a917873f76b4c5dca)
Vasar007 May 19, 2026
b27f57b
docs(02-13-review): clarify EF-materialization + initializer-race com…
Vasar007 May 19, 2026
b770e8f
docs(02-13-review): tighten EF + NLog comments per iter-2 audit-team
Vasar007 May 19, 2026
bf10646
docs(02-13-review): fix updates-routing + domain-guard overclaims (it…
Vasar007 May 19, 2026
d542967
docs(02-review): scrub internal artifact identifiers from committed f…
Vasar007 May 23, 2026
cde3f92
Review round 1: drop dead cross-references to internal-only docs
Vasar007 May 23, 2026
0d93afe
refactor: move InternalsVisibleTo from AssemblyInfo.cs to csproj <Ass…
Vasar007 May 23, 2026
577d371
fix(dal): throw on missing connection string in design-time DbContext…
Vasar007 May 23, 2026
b26115f
refactor(tests): add Fixture to BaseMockTest, relocate stubs, rewrite…
Vasar007 May 23, 2026
be7bbde
refactor(tests): use BaseMockTest.Fixture + per-class builder across …
Vasar007 May 23, 2026
f9449d5
chore(repo): remove redundant .gitkeep placeholders
Vasar007 May 23, 2026
0855044
chore(02-review): cross-cutting scrub of internal-artifact references…
Vasar007 May 23, 2026
de3c3a8
chore(02-review): scrub residual decision-ID reference from Desktop.s…
Vasar007 May 23, 2026
7273f3d
fix(tests): drop unused using directives flagged by dotnet format
Vasar007 May 23, 2026
f112387
docs(scenarios): scrub internal-pipeline references from testing docs
Vasar007 May 24, 2026
1d2ac17
refactor(comments): scrub internal-pipeline references from C# comments
Vasar007 May 24, 2026
eaf7bf6
refactor(comments): drop remaining internal-artifact references from …
Vasar007 May 24, 2026
55f8523
refactor(tests): mock builders take IFixture ctor and use fixture.Cre…
Vasar007 May 24, 2026
d21f96c
refactor(tests): replace inline Fixture.Create<I*> with builder-via-h…
Vasar007 May 24, 2026
45768c0
refactor(tests): consolidate Create* helpers and remove direct builde…
Vasar007 May 24, 2026
c08f99a
refactor: drop references to non-committed architecture overview
Vasar007 May 24, 2026
8a29525
chore(ci): rephrase coverlet settings comment for clarity
Vasar007 May 24, 2026
928e995
refactor(tests): unify Create* helpers with optional params + ApplyIf
Vasar007 May 24, 2026
f42ae1a
refactor(tests): wrap remaining Fixture.Create<I*> calls and drop dup…
Vasar007 May 24, 2026
eb3a403
refactor(tests): add TestHttpClientFactoryBuilder and StubBotService
Vasar007 May 24, 2026
7c9625a
refactor(tests): convert polling scenario to real StubBotService (no …
Vasar007 May 24, 2026
993ab5e
fix(tests): satisfy dotnet format on imports left over from NSubstitu…
Vasar007 May 24, 2026
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
47 changes: 44 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,51 @@ jobs:
- name: Format check
run: dotnet format Sources/ProjectV.sln --severity warn --verify-no-changes

- name: Test (C# projects)
# Linux: four sequential named test stages with Coverlet
# collection on the C# stages (via coverlet.runsettings).
- name: Test (Unit)
if: matrix.os == 'ubuntu-latest'
run: dotnet test Sources/ProjectV.sln --configuration Release --no-build
run: dotnet test Sources/ProjectV.sln --configuration Release --no-build --filter "Category=Unit" --collect:"XPlat Code Coverage" --settings Sources/Tests/coverlet.runsettings

- name: Test (F# ContentDirectories)
- name: Test (Integration)
if: matrix.os == 'ubuntu-latest'
run: dotnet test Sources/ProjectV.sln --configuration Release --no-build --filter "Category=Integration" --collect:"XPlat Code Coverage" --settings Sources/Tests/coverlet.runsettings

- name: Test (Contract)
if: matrix.os == 'ubuntu-latest'
run: dotnet test Sources/ProjectV.sln --configuration Release --no-build --filter "Category=Contract" --collect:"XPlat Code Coverage" --settings Sources/Tests/coverlet.runsettings

# F# stage: explicit fsproj invocation, no Category filter, no coverage collection
# (F# coverage non-essential and the project is tiny).
- name: Test (F#)
if: matrix.os == 'ubuntu-latest'
run: dotnet test Sources/Tests/ProjectV.ContentDirectories.Tests/ProjectV.ContentDirectories.Tests.fsproj --configuration Release --no-build -p:Platform=x64

# Coverage publication: merge per-stage Cobertura outputs into
# one HTML artifact + a Markdown step-summary panel.
- name: Merge coverage reports
if: matrix.os == 'ubuntu-latest'
uses: danielpalme/ReportGenerator-GitHub-Action@5
with:
reports: '**/TestResults/**/coverage.cobertura.xml'
targetdir: 'coverage-report'
reporttypes: 'HtmlInline;MarkdownSummaryGithub'
verbosity: 'Warning'

- name: Upload coverage report artifact
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage-report/

- name: Write coverage summary to Step Summary
if: matrix.os == 'ubuntu-latest'
run: cat coverage-report/SummaryGithub.md >> $GITHUB_STEP_SUMMARY

# Windows: non-Docker tests. Single-quoted filter so PowerShell does not
# interpret `!=` and YAML keeps the string verbatim. Docker-dependent
# Testcontainers tests stay Linux-only via the [Trait("RequiresDocker","true")] tag.
- name: Test (Non-Docker)
if: matrix.os == 'windows-latest'
run: dotnet test Sources/ProjectV.sln --configuration Release --no-build --filter 'RequiresDocker!=true'
126 changes: 126 additions & 0 deletions Docs/Testing/Coverage/test-coverage.md

Large diffs are not rendered by default.

132 changes: 132 additions & 0 deletions Docs/Testing/Scenarios/projectv-jwt-scenarios.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# ProjectV JWT Scenario Tests

Companion to
[`projectv-scenario-tests-overview.md`](./projectv-scenario-tests-overview.md)
and [`../Coverage/test-coverage.md`](../Coverage/test-coverage.md).
This document is the per-family scenario doc for the JWT-authentication
slice of `ProjectV.CommunicationWebService`. Scenarios live under
`Sources/Tests/ProjectV.CommunicationWebService.Tests/Scenarios/Jwt/` and
inherit the conventions described in the overview doc.

## Purpose

Cover the JWT runtime path of `ProjectV.CommunicationWebService` end-to-end
through `WebApplicationFactory<Startup>` — without mocking the authentication
pipeline. The scenarios exercise:

- The bearer-token validation path wired by
`AddJtwAuthentication(jwtConfig)` in
`Sources/WebServices/ProjectV.CommonWebApi/Service/Extensions/ServiceCollectionExtensions.cs`.
- The login round-trip exposed by
`Sources/WebServices/ProjectV.CommunicationWebService/v1/Controllers/UsersController.cs`
(`POST /api/v1/Users/login`).
- The protected entry point in
`Sources/WebServices/ProjectV.CommunicationWebService/v1/Controllers/RequestsController.cs`
(`POST /api/v1/Requests`).

The JWT path uses the in-memory user store
(`InMemoryUserInfoService`) — these tests do NOT require Testcontainers,
so they carry only `[Trait("Category", "Integration")]` (no
`[Trait("RequiresDocker", "true")]`) and run on both the Linux Integration
stage and the Windows Non-Docker stage of CI.

## Audience

- **Test authors** adding new JWT scenarios — for example expired-token,
malformed-Authorization-header, or refresh-token-flow tests. They inherit
from `JwtAuthScenarioBaseTest` and follow the conventions below.
- **Reviewers** scanning the family folder — the class XML doc on each
test file reads like a business-language sentence so a reviewer can scan
the directory and immediately see what behaviour is covered.

## Architecture

Each test class inherits the family base
[`JwtAuthScenarioBaseTest`](../../../Sources/Tests/ProjectV.CommunicationWebService.Tests/Scenarios/Jwt/JwtAuthScenarioBaseTest.cs),
which extends `ProjectV.Tests.Shared.ForTests.WebApiBaseTest<Startup>`. The
base test wires up an in-process
`TestWebApplicationFactory<ProjectV.CommunicationWebService.Startup>` that
injects a deterministic JWT signing key
(`TestJwtConfig.DefaultSecretKeyBase64`) into the host's
`IConfiguration` BEFORE `Startup.ConfigureServices` runs — that timing is
the only seam that lets the test side mint tokens with the same HMAC
SHA-256 secret the production `AddJwtBearer` registration validates against.

```mermaid
flowchart LR
TF[Test Fixture<br/>JwtAuthScenarioBaseTest]
TF --> WAF[TestWebApplicationFactory&lt;Startup&gt;]
WAF --> CAC[ConfigureAppConfiguration<br/>inject JwtOptions:SecretKey,<br/>Issuer, Audience]
WAF --> CTS[ConfigureTestServices<br/>per-scenario DI overrides]
CAC --> HOST[(Hosted CommunicationWebService<br/>real Startup + middleware)]
CTS --> HOST
HOST -->|HTTP loopback| HC[HttpClient<br/>anonymous OR Bearer-decorated]
TF -->|CreateAuthenticatedClient| HC
TF -->|TestJwtHelper| TOK[Signed test JWT<br/>HMAC SHA-256]
TOK --> HC
HC --> CTRL[RequestsController / UsersController]
```

## Scenario Catalog

| Scenario | Test File | Endpoint | Expected Outcome |
|----------|-----------|----------|------------------|
| **JWT-1** — Anonymous request rejected | `JwtAnonymousRequestTests.cs` | `POST /api/v1/Requests` (no `Authorization`) | `401 Unauthorized` |
| **JWT-2** — Authenticated request passes auth | `JwtAuthenticatedRequestTests.cs` | `POST /api/v1/Requests` (valid bearer token) | Status code is NOT 401 / 403 (auth pipeline accepts the token) |
| **JWT-3** — Login issues token | `JwtLoginIssuesTokenTests.cs` | `POST /api/v1/Users/login` (valid in-memory creds) | `200 OK` + `TokenResponse` with non-empty `AccessToken.Token` |

### Scenario JWT-1: Anonymous request rejected

When no `Authorization` header is present on a request to a
`[Authorize]`-decorated controller action, the production JWT bearer
middleware must short-circuit the pipeline with HTTP `401 Unauthorized`.
Verifies that the auth wiring is present at all — a regression that
silently disabled authentication (e.g. removing `app.UseAuthentication()`
or `[Authorize]`) would let the request through with a 400 instead.

### Scenario JWT-2: Authenticated request passes the auth pipeline

A token signed with the same secret / issuer / audience the host was
configured with must pass `TokenValidationParameters` so the request
reaches the controller action. The scenario asserts that the response is
neither 401 nor 403; it does NOT assert on the response body shape
because the request body is intentionally empty (the controller may
short-circuit with 400 for that reason — what matters is that the auth
middleware did NOT short-circuit).

### Scenario JWT-3: Login round-trip issues a token pair

The scenario seeds a single user into the in-memory user store via the
production `IPasswordManager` so the stored salt + hash format match
exactly what `UserService.LoginAsync` expects. It then POSTs credentials
at `/api/v1/Users/login` and asserts the response is `200 OK` with a
non-empty `AccessToken.Token` field. The `ShouldCreateSystemUser` flag is
held OFF to avoid the fire-and-forget seed race in
`UserService`'s constructor — the test owns the entire in-memory store.

## Conventions

JWT scenario tests follow the conventions described in
[`projectv-scenario-tests-overview.md`](./projectv-scenario-tests-overview.md#conventions)
without exception. Two family-specific points:

- **No `[Trait("RequiresDocker", "true")]`** — JWT scenarios use only the
in-memory user store. They run on the Windows Non-Docker stage of CI in
addition to the Linux Integration stage.
- **No `[Collection]` attribute** — JWT scenarios do NOT share a fixture
with the Testcontainers Postgres path used by the DAL integration suite.
Each scenario class spins up its own in-process host via the factory in
`InitializeAsync` and tears it down in `DisposeAsync`.

## Cross-references

- [`Docs/Testing/Coverage/test-coverage.md`](../Coverage/test-coverage.md) —
Infrastructure-Layer rows for the three JWT scenarios.
- [`Docs/Testing/Scenarios/projectv-scenario-tests-overview.md`](./projectv-scenario-tests-overview.md) —
cross-family conventions, architecture diagram, scenario-test pattern.
- [`Sources/Tests/ProjectV.Tests.Shared/Helpers/WebApi/TestWebApplicationFactory.cs`](../../../Sources/Tests/ProjectV.Tests.Shared/Helpers/WebApi/TestWebApplicationFactory.cs) —
generic test host wrapper.
- [`Sources/Tests/ProjectV.Tests.Shared/Helpers/WebApi/TestJwtHelper.cs`](../../../Sources/Tests/ProjectV.Tests.Shared/Helpers/WebApi/TestJwtHelper.cs) —
bearer-token issuance helper.
- [`Sources/Tests/ProjectV.Tests.Shared/ForTests/WebApiBaseTest.cs`](../../../Sources/Tests/ProjectV.Tests.Shared/ForTests/WebApiBaseTest.cs) —
`IAsyncLifetime` base + `CreateAuthenticatedClient`.
181 changes: 181 additions & 0 deletions Docs/Testing/Scenarios/projectv-scenario-tests-overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# ProjectV Scenario Tests — Overview

Companion to
[`Docs/Testing/Coverage/test-coverage.md`](../Coverage/test-coverage.md).
This document is the architecture-diagram baseline for the
`WebApplicationFactory`-based scenario suites covering JWT authentication,
Telegram webhook, and Telegram polling. Per-family scenario docs
(e.g. `projectv-jwt-scenarios.md`, `projectv-telegram-scenarios.md`,
`projectv-tmdb-pipeline-scenarios.md`, …) are added alongside their
respective scenario suites as they land.

## Purpose

Scenario tests in ProjectV are integration tests written one test file per
business scenario:

- One sealed test class per scenario; file name is `<ScenarioShortName>Tests.cs`.
- Class XML doc summarises the scenario in **business** terms (e.g.
"Scenario JWT-1: Anonymous request to `/api/v1/Requests` returns 401"),
not in test-framework terms.
- Class inherits from a per-family base class — e.g. `JwtAuthScenarioBaseTest`,
`TelegramWebhookScenarioBaseTest`, `TmdbPipelineScenarioBaseTest` — which
bundles the `WebApplicationFactory` wiring + scenario-family-wide config knobs.
- Test method bodies use **explicit `// Arrange.` / `// Act.` / `// Assert.`
comment markers**. This convention was introduced during the test-coverage
work tracked in PR #342; new scenario tests follow it without exception.
- Assertions cover production behavior AND stub-side call counts where
relevant — for example, `wireMock.LogEntries.Should().HaveCount(1)` to
verify that the SDK called the external API exactly once after a Polly
retry policy completed.

The point is that the file name, class name, and XML doc together read like
a checklist of business behaviour, so a reviewer can scan the
`Scenarios/<ScenarioFamily>/` directory and see exactly what is being verified
without opening any test method.

## Audience

- **Scenario test authors** — engineers implementing `WebApplicationFactory`-based
integration tests for the JWT authentication
(`Sources/Tests/ProjectV.CommunicationWebService.Tests/Scenarios/Jwt/`),
Telegram webhook
(`Sources/Tests/ProjectV.TelegramBotWebService.Tests/Scenarios/Webhook/`),
or Telegram polling
(`Sources/Tests/ProjectV.TelegramBotWebService.Tests/Scenarios/Polling/`)
suites. They use this overview
to know which base class to inherit from, which Helpers wires up which
external surface, and what shape an Arrange / Act / Assert block should
take inside the scenario file.
- **Future contributors** adding new scenario families — they create a new
per-family base class under
`Sources/Tests/ProjectV.<WebService>.Tests/Scenarios/<ScenarioFamily>/`,
add a per-family doc next to this overview, and follow the conventions
named here.
- **Reviewers** of phase-end PRs — they use the diagram below to confirm
that a new scenario test wires up the real production DI graph (no mocks
on the request path) and that any external dependency lives behind a
WireMock.Net stub.

Scenario tests live under
`Sources/Tests/ProjectV.<WebService>.Tests/Scenarios/<ScenarioFamily>/` —
one directory per scenario family, one file per scenario inside it.

## Architecture

The diagram below shows how a scenario test process drives the system under
test, including the `WebApplicationFactory` integration branch used by the
JWT and Telegram scenario suites.

Key invariants in the diagram:

- The **Real Application** node represents the production DI graph — the
same `Startup` class production runs, the same `ICrawler` / `IAppraiser`
/ `IJobInfoService` registrations, the same EF Core `ProjectVDbContext`.
- The **only test doubles on the request path** are `WireMockServer`
instances for external HTTP APIs (TMDb / OMDb / Steam) and a substituted
`ITelegramBotClient` for the Telegram polling branch. There are no
in-process mocks for the Application or Domain layers in scenario tests
(that is the Unit-test layer's job).
- The **Testcontainers Postgres** node is the single per-test-run
container started by `ICollectionFixture<DbCollectionFixture>`; the same
container is reused across scenario test classes that share the
`DbCollection` `CollectionDefinition`.

```mermaid
flowchart TD
TP[Test Process<br/>xUnit + AwesomeAssertions + NSubstitute]

TP --> UT[Unit Tests<br/>Category=Unit]
UT --> NS[NSubstitute substitutes]
NS --> SUT[Single SUT class]
SUT --> AA1[AwesomeAssertions on return value]

TP --> IT[Integration Tests<br/>Category=Integration]
IT --> WAF[WebApplicationFactory&lt;TStartup&gt;]
WAF --> CTS[ConfigureTestServices<br/>swap connection string &<br/>stub HTTP clients]
CTS --> RA[Real Application DI graph<br/>Startup + EF Core + Polly]
RA --> TC[(Testcontainers<br/>PostgreSqlContainer)]
RA -.->|external HTTP| WMI[WireMockServer<br/>recorded JSON fixtures]

TP --> CT[Contract Tests<br/>Category=Contract]
CT --> WMS[WireMockServer in-process]
WMS -.->|HTTP loopback| HCF[IHttpClientFactory]
HCF --> SDK[Real SDK<br/>TMDbLib / OmdbApiNet / SteamWebApiLib]
SDK --> ADP[Adapter mapper]
ADP --> AA2[AwesomeAssertions on<br/>BasicInfo / MovieInfo / GameInfo]

TP --> FT[F# Tests<br/>separate fsproj invocation,<br/>no Category trait]
FT --> UQ[Unquote quoted-expression<br/>assertions]
UQ --> CF[ContentFinder / PolicyModels]

classDef testDouble stroke-dasharray: 5 5;
class WMI,WMS testDouble;
```

The dashed edges (`-.->`) and dashed-border nodes mark the only places where
a scenario test substitutes a real dependency: HTTP traffic to TMDb / OMDb /
Steam is routed through a local `WireMockServer` instance that serves recorded
JSON fixtures from `Sources/Tests/Fixtures/{Tmdb,Omdb,Steam}/`. Everything
else on the request path is production code running against a real
Testcontainers Postgres.

## Scenario Family Documents

Per-family docs are added by the plan that lands the family's scenario suite,
not up-front:

> Only scenario-family docs that correspond to scenario suites actually
> committed to the repository are created — the overview is mandatory,
> family docs are added as their scenario suites land.

Expected per-family doc filenames:

- `projectv-jwt-scenarios.md` — added alongside the JWT scenario suite
(`Sources/Tests/ProjectV.CommunicationWebService.Tests/Scenarios/Jwt/`).
- `projectv-telegram-scenarios.md` — added alongside the Telegram webhook +
polling scenario suites
(`Sources/Tests/ProjectV.TelegramBotWebService.Tests/Scenarios/Webhook/` and `/Polling/`).
- `projectv-tmdb-pipeline-scenarios.md` — added if/when a TMDb-end-to-end
scenario suite lands; current TMDb coverage is at the contract-test
layer (`Sources/Tests/ProjectV.TmdbService.Tests/TmdbContractTests.cs`).

Family docs follow the same shape as this overview — Purpose, Audience,
Architecture (with a scenario-family-specific mermaid view), Conventions,
and a table that enumerates each scenario file with a one-line description.

## Conventions

- **Class XML doc** summarises the scenario in business terms. Bad:
`"Tests that the controller returns 401 when no Authorization header is
present."` Good: `"Scenario JWT-1: Anonymous request to /api/v1/Requests
returns 401."`
- **Class shape** — `public sealed class <ScenarioShortName>Tests` with an
explicit empty constructor (matches the rest of the ProjectV test stack).
- **Base class** — inherits from a per-family base class (e.g.
`JwtAuthScenarioBaseTest`) that holds the `WebApplicationFactory`
instance + scenario-family-wide config knobs. The base class is what
swaps test-side HttpClients onto WireMock and tells the
`ProjectVDbContext` to point at the Testcontainers Postgres.
- **AAA markers** — every test method body has explicit `// Arrange.`,
`// Act.`, and `// Assert.` comment lines. No exceptions; even one-line
acts include the marker.
- **Assertions** — assert on production behavior AND on stub-side call
counts where the stub-side counts are part of the scenario semantics.
Example: a "Polly retries transient 502 exactly once" scenario asserts
on the final 200 response AND on `wireMockServer.LogEntries.Should()
.HaveCount(2, "Polly should have retried once after the transient
failure")`.
- **Category trait** — every scenario test class is
`[Trait("Category","Integration")]`. Scenarios that hit Testcontainers
Postgres also add `[Trait("RequiresDocker","true")]`. CI filters on these
traits to separate Docker-dependent tests from non-Docker integration tests.
- **xUnit collection** — scenario tests that share the Testcontainers
Postgres declare `[Collection(DbCollection.Name)]` so they run serially
inside the single container session.

## Cross-references

- [`Docs/Testing/Coverage/test-coverage.md`](../Coverage/test-coverage.md) —
critical-path coverage inventory; the scenarios documented here cover the
`WebApplicationFactory` rows in the Infrastructure Layer table.
Loading
Loading