Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
127 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
29dbd0a
refactor(tests): route all mock creation through Test*Builder helpers
Vasar007 May 31, 2026
64e515d
refactor(tests): replace Test*Builder mocks with concrete stubs in sc…
Vasar007 May 31, 2026
63244ad
fix(tests): clean up stub XML docs and null-guard param names
Vasar007 May 31, 2026
5818581
refactor(tests): remove dead mock builders and factory stub properties
Vasar007 May 31, 2026
8f5bb41
refactor(tests): remove unused OMDb/Steam crawler builder clones
Vasar007 Jul 6, 2026
4308eb0
refactor(tests): drop unused DataflowPipeline test builder
Vasar007 Jul 6, 2026
b664f42
refactor(tests): remove unused TestTimeHelper
Vasar007 Jul 6, 2026
8f2b444
refactor(tests): slim CapturedException and move it to Scenarios/Helpers
Vasar007 Jul 6, 2026
7099406
fix(tests): use thread-safe Random.Shared in test data generators
Vasar007 Jul 6, 2026
bdab019
refactor(tests): expose singleton Instance on test data generators
Vasar007 Jul 6, 2026
fae7b99
refactor(tests): wrap direct generator and creator calls in private h…
Vasar007 Jul 6, 2026
8803e7e
fix(tests): remove stale unused-variable discard in pipeline test
Vasar007 Jul 6, 2026
b3d5f3f
refactor(tests): dedupe job info arrangement in executor tests
Vasar007 Jul 6, 2026
93d69a2
refactor(tests): extract minimal XML configuration helper in shell bu…
Vasar007 Jul 6, 2026
0635ab5
refactor(tests): assert against arranged values instead of repeated l…
Vasar007 Jul 6, 2026
7ad61ba
fix(tests): replace invalid namespace cref with plain code tag in XML…
Vasar007 Jul 6, 2026
e650264
refactor(tests): remove duplicate BasicInfo serialization round-trip …
Vasar007 Jul 6, 2026
36e5ded
refactor(tests): route crawlers manager construction through BuildSut…
Vasar007 Jul 6, 2026
b9433b8
refactor(tests): extract shared reflection probe for input filter pre…
Vasar007 Jul 6, 2026
5fa4edf
fix(tests): rename misspelled appraiser rating test methods
Vasar007 Jul 6, 2026
0b93dde
docs(tests): describe current behaviour instead of authoring history …
Vasar007 Jul 6, 2026
9f7c02c
test(tests): assert webhook scenario actually sends a bot reply
Vasar007 Jul 6, 2026
825709b
docs(tests): update Telegram scenario docs to match concrete stubs
Vasar007 Jul 6, 2026
445b541
test(tests): drive appraisers flow end-to-end in manager dispatch tests
Vasar007 Jul 6, 2026
d9ec666
test(tests): add hand-computed rating expectation for common appraisal
Vasar007 Jul 6, 2026
a2d64b1
fix(deps): pin patched Microsoft.OpenApi and Scriban.Signed to clear …
Vasar007 Jul 7, 2026
ef1ee0c
refactor(tests): adopt canonical constructor null-guard test shape
Vasar007 Jul 7, 2026
2717594
refactor(tests): extract shared base class for GUID wrapper tests
Vasar007 Jul 7, 2026
763fd61
refactor(tests): extract shared Telegram scenario base class
Vasar007 Jul 16, 2026
c8c3649
refactor(tests): route inline model creation through generator-backed…
Vasar007 Jul 16, 2026
fc90924
fix(tests): generate valid random appraiser test data via generator
Vasar007 Jul 16, 2026
016a864
refactor(tests): remove unused fluent methods from shared test builders
Vasar007 Jul 16, 2026
70e5069
refactor(tests): drop unused optional parameters from movie creation …
Vasar007 Jul 16, 2026
3cc598c
test(tests): assert exact normalized rating for middle batch item
Vasar007 Jul 16, 2026
42567b0
refactor(tests): remove unused params overload from expected-value cr…
Vasar007 Jul 16, 2026
385f280
docs(tests): fix grammar in Shell test class remarks
Vasar007 Jul 16, 2026
7611abb
refactor(tests): align minimal shell config helper name with sibling …
Vasar007 Jul 16, 2026
315d210
docs(tests): correct async iterator comment in crawler builder
Vasar007 Jul 16, 2026
751ce88
style(tests): replace fully-qualified type names with using directives
Vasar007 Jul 16, 2026
5a14c32
test(tests): compare fetched title against arranged variable
Vasar007 Jul 16, 2026
f35d31d
test(tests): tighten appraisers manager test structure
Vasar007 Jul 16, 2026
9130a10
refactor(tests): use named arguments in constructor guard factory calls
Vasar007 Jul 17, 2026
f44a252
style(tests): add UTF-8 BOM to Telegram scenario base test files
Vasar007 Jul 17, 2026
8ce31c0
refactor(tests): remove unused dependency-injection test base class
Vasar007 Jul 17, 2026
cdc741c
test(tests): route inline BasicInfo test data through the shared gene…
Vasar007 Jul 17, 2026
9ecbfca
test(tests): route mutated JobInfo copy through the shared generator
Vasar007 Jul 17, 2026
3bebe3b
docs(tests): correct webhook scenario base rationale for hosted-servi…
Vasar007 Jul 17, 2026
1a808ab
docs(tests): fix stale comments in polling update-sequence scenario
Vasar007 Jul 17, 2026
09c82a6
docs(tests): describe TestShellBuilder managers as empty defaults
Vasar007 Jul 17, 2026
b22c6c1
docs(tests): correct inputter comment in dataflow pipeline test
Vasar007 Jul 17, 2026
a21af43
docs(tests): correct WebApiBaseTest remark about Telegram scenario bases
Vasar007 Jul 18, 2026
7a27720
refactor(tests): remove unused tag/type-id knobs from crawler mock bu…
Vasar007 Jul 18, 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