docs: fix config table, test-location claims

This commit is contained in:
2026-10-25 23:07:14 +01:00
parent 80072eb463
commit 7c6dff7f70
6 changed files with 30 additions and 22 deletions

View File

@@ -56,7 +56,14 @@ crates/application/tests/diary/log_review_test.rs
crates/application/tests/support/ # shared contexts and command builders crates/application/tests/support/ # shared contexts and command builders
``` ```
Adapters follow the same layout (`crates/adapters/sqlite/tests/repositories/diary_repository_test.rs`). The only tests left beside source are small mapper tests in `crates/adapters/http-axum/src/**/tests.rs`, which need crate-private items; their shared fixtures live in `http-axum/src/test_support.rs`. Route smoke, API and handler tests run against the real composition and live in `crates/bootstrap/tests/`. Adapters follow the same layout (`crates/adapters/sqlite/tests/repositories/diary_repository_test.rs`). Route smoke, API and handler tests run against the real composition and live in `crates/bootstrap/tests/`; http-axum also has integration tests in `crates/adapters/http-axum/tests/` (`log_review_form_test.rs`, `openapi_contract_test.rs`).
Inline `#[cfg(test)]` tests remain only where they must reach private items:
- `crates/adapters/http-axum`: the `api/social`, `federation/blocklist_api` and `html/social` mapper tests (`tests.rs` beside each; shared fixtures in `src/test_support.rs`) and the rate-limit tests in `router.rs`
- `crates/application/src/users/diary_filter.rs`
- `crates/adapters/nats/src/subject.rs`
- `crates/tui/src/client.rs`
## Naming ## Naming

View File

@@ -69,7 +69,7 @@ Cargo features: `federation` (default; ActivityPub) and `nats`. `sqlite` and `sq
### Tests ### Tests
Tests live in `crates/<crate>/tests/`, never in the production file (see `CODE_STYLE.md`). Build fixtures with the `domain::testing` factories and use the cascading `InMemoryStore` and `Refusing*` fakes. Route smoke, API and handler tests over the real wiring are in `crates/bootstrap/tests/`. Tests live in `crates/<crate>/tests/`, not in the production file. A few inline tests remain where they need private items; `CODE_STYLE.md` lists them. Build fixtures with the `domain::testing` factories and use the cascading `InMemoryStore` and `Refusing*` fakes. Route smoke, API and handler tests over the real wiring are in `crates/bootstrap/tests/`.
### Database adapters ### Database adapters

View File

@@ -92,10 +92,10 @@ check-dockerfile-manifests:
copied=$$(sed -n 's/^COPY \([^[:space:]]*\)\/Cargo\.toml[[:space:]].*/\1/p' Dockerfile | grep -v '^\(Cargo\.toml\|\.\)$$' || true); \ copied=$$(sed -n 's/^COPY \([^[:space:]]*\)\/Cargo\.toml[[:space:]].*/\1/p' Dockerfile | grep -v '^\(Cargo\.toml\|\.\)$$' || true); \
bad=""; \ bad=""; \
for m in $$members; do \ for m in $$members; do \
echo "$$copied" | grep -qx "$$m" || bad="$$bad missing-COPY:$$m"; \ echo "$$copied" | grep -qxF "$$m" || bad="$$bad missing-COPY:$$m"; \
done; \ done; \
for c in $$copied; do \ for c in $$copied; do \
echo "$$members" | grep -qx "$$c" || bad="$$bad not-a-member:$$c"; \ echo "$$members" | grep -qxF "$$c" || bad="$$bad not-a-member:$$c"; \
done; \ done; \
if [ -n "$$bad" ]; then \ if [ -n "$$bad" ]; then \
echo "❌ Dockerfile manifest COPY lines out of sync with workspace members:$$bad"; \ echo "❌ Dockerfile manifest COPY lines out of sync with workspace members:$$bad"; \

View File

@@ -95,7 +95,7 @@ config — typed configuration (defaults, config.toml, legacy env n
bootstrap — composition root: the only crate that names adapters; builds the server router and the worker service bootstrap — composition root: the only crate that names adapters; builds the server router and the worker service
event-payload — serde payloads for domain events, shared by the NATS and SQLite-queue adapters event-payload — serde payloads for domain events, shared by the NATS and SQLite-queue adapters
domain — pure types and CQRS port traits (MovieCommand/MovieQuery, WatchEventCommand/WatchEventQuery, GoalCommand/GoalQuery, DiaryQuery, PersonCommand/PersonQuery, SearchCommand/SearchPort, SocialCommand/SocialQuery, ImageFetcher, RssFeedRenderer), no external deps except serde domain — pure types and CQRS port traits (MovieCommand/MovieQuery, WatchEventCommand/WatchEventQuery, GoalCommand/GoalQuery, DiaryQuery, PersonCommand/PersonQuery, SearchCommand/SearchPort, SocialCommand/SocialQuery, ImageFetcher, RssFeedRenderer), no external deps except serde
application — use cases (commands + queries), business logic orchestration; handlers delegate here for all domain logic; modules: auth, diary, goals, import, integrations, movies, person, search, social, users, watchlist, wrapup application — use cases (commands + queries), business logic orchestration; handlers delegate here for all domain logic; modules: auth, diary, goals, import, insights, integrations, jobs, movies, person, search, social, users, watchlist, wrapup
server — thin HTTP binary (dotenv, config, bootstrap::build_server, axum::serve); features (federation, nats) forward to bootstrap server — thin HTTP binary (dotenv, config, bootstrap::build_server, axum::serve); features (federation, nats) forward to bootstrap
worker — thin worker binary (bootstrap::build_worker, run until ctrl-c) worker — thin worker binary (bootstrap::build_worker, run until ctrl-c)
adapters/ adapters/
@@ -134,35 +134,35 @@ Settings are layered, later sources winning:
2. `config.toml` in the working directory, or the file named by `CONFIG_FILE` (a named file that does not exist is an error; the default `config.toml` is optional) 2. `config.toml` in the working directory, or the file named by `CONFIG_FILE` (a named file that does not exist is an error; the default `config.toml` is optional)
3. environment variables (including those from `.env`), under the names in the table below 3. environment variables (including those from `.env`), under the names in the table below
Copy `.env.example` to `.env` and set the values you need. Malformed values (a non-numeric `PORT`, an unknown `EVENT_BUS_BACKEND`) fail startup with a message naming the setting. An empty value (`PORT=`) counts as unset, so the default applies. The variable names are unchanged from earlier releases; every one maps to a `section.field` key in `config.toml` (for example `PORT` is `server.port`, `JWT_SECRET` is `auth.jwt_secret`). Copy `.env.example` to `.env` and set the values you need. Malformed values (a non-numeric `PORT`, an unknown `EVENT_BUS_BACKEND`) fail startup with a message naming the setting. An empty value (`PORT=`) counts as unset, so the default applies. The variable names are unchanged from earlier releases; every one except `RUST_LOG` and `CONFIG_FILE` maps to a `section.field` key in `config.toml` (for example `PORT` is `server.port`, `JWT_SECRET` is `auth.jwt_secret`).
| Variable | Default | Required | Description | | Variable | Default | Required | Description |
|---|---|---|---| |---|---|---|---|
| `DATABASE_URL` | `sqlite://movies.db` | Yes | SQLite connection string | | `DATABASE_URL` | — | Yes | SQLite connection string (e.g. `sqlite://movies.db`); the only value the config loader itself requires |
| `JWT_SECRET` | — | Yes | Secret for JWT signing — use a long random string | | `JWT_SECRET` | — | Server | Secret for JWT signing — use a long random string. The server refuses to start without it (the worker does not need it) |
| `OMDB_API_KEY` | — | Yes | [OMDb](https://www.omdbapi.com/apikey.aspx) key for movie metadata | | `OMDB_API_KEY` | — | One of the two | [OMDb](https://www.omdbapi.com/apikey.aspx) key for movie metadata. Server and worker refuse to start unless `TMDB_API_KEY` or `OMDB_API_KEY` is set |
| `TMDB_API_KEY` | — | No | [TMDb](https://www.themoviedb.org/settings/api) key — enables cast, crew, genres, enrichment | | `TMDB_API_KEY` | — | One of the two | [TMDb](https://www.themoviedb.org/settings/api) key — used for metadata when set (takes precedence over OMDb); enables cast, crew, genres, enrichment |
| `BASE_URL` | — | Yes | Public URL of your instance (used for ActivityPub actor URLs) | | `BASE_URL` | `http://localhost:3000` | No | Public URL of your instance (used for ActivityPub actor URLs); set it for any real deployment |
| `IMAGE_STORAGE_BACKEND` | `local` | No | `local` or `s3` | | `IMAGE_STORAGE_BACKEND` | `local` | No | `local` or `s3` |
| `IMAGE_STORAGE_PATH` | `./images` | No | Path for local image storage | | `IMAGE_STORAGE_PATH` | `./images` | No | Path for local image storage |
| `MINIO_ENDPOINT` | — | S3 only | S3-compatible endpoint (e.g. `http://localhost:9000`) | | `MINIO_ENDPOINT` | — | S3 only | S3-compatible endpoint (e.g. `http://localhost:9000`) |
| `MINIO_BUCKET` | — | S3 only | Bucket name | | `MINIO_BUCKET` | — | S3 only | Bucket name |
| `MINIO_REGION` | — | S3 only | Region (e.g. `minio`) | | `MINIO_REGION` | `minio` | No | Region for S3 storage |
| `MINIO_ACCESS_KEY_ID` | — | S3 only | Access key ID | | `MINIO_ACCESS_KEY_ID` | — | S3 only | Access key ID |
| `MINIO_SECRET_ACCESS_KEY` | — | S3 only | Secret access key | | `MINIO_SECRET_ACCESS_KEY` | — | S3 only | Secret access key |
| `IMAGE_CONVERSION_ENABLED` | `false` | No | Convert stored images to AVIF or WebP | | `IMAGE_CONVERSION_ENABLED` | `false` | No | Set `true` to convert stored images to AVIF or WebP (only the value `true` enables it) |
| `IMAGE_CONVERSION_FORMAT` | `avif` | No | `avif` or `webp` | | `IMAGE_CONVERSION_FORMAT` | — | When conversion is enabled | `avif` or `webp`; startup fails if conversion is enabled without it |
| `HOST` | `0.0.0.0` | No | Bind address | | `HOST` | `0.0.0.0` | No | Bind address |
| `PORT` | `3000` | No | HTTP port | | `PORT` | `3000` | No | HTTP port |
| `RATE_LIMIT` | `60` | No | Requests per minute per IP | | `RATE_LIMIT` | `60` | No | Requests per minute per IP |
| `ALLOW_REGISTRATION` | `true` | No | Set `false` to disable new sign-ups | | `ALLOW_REGISTRATION` | `false` | No | Set `true` (or `1`) to allow new sign-ups |
| `SECURE_COOKIES` | `true` | No | Must be `true` when serving over HTTPS | | `SECURE_COOKIES` | `false` | No | Set `true` when serving over HTTPS (only the value `true` enables it) |
| `RUST_LOG` | — | No | Log verbosity (e.g. `server=info,worker=info`) | | `RUST_LOG` | — | No | Log verbosity (e.g. `server=info,worker=info`); read by the logger, not part of `config.toml` |
| `CORS_ORIGINS` | `*` | No | Comma-separated allowed origins for SPA dev | | `CORS_ORIGINS` | empty | No | Comma-separated allowed origins for the API; empty or `*` allows any origin |
| `EVENT_BUS_BACKEND` | `db` | No | `db` (default) or `nats` | | `EVENT_BUS_BACKEND` | `db` | No | `db` (default) or `nats` |
| `NATS_URL` | — | NATS only | NATS connection URL (e.g. `nats://localhost:4222`) | | `NATS_URL` | — | NATS only | NATS connection URL (e.g. `nats://localhost:4222`) |
Required fields must be set (in either source) before the server will start. `DATABASE_URL` is the only setting the loader requires; the other "required" entries are enforced when the corresponding adapter is built, and failing there also stops startup. Further optional variables (`CONFIG_FILE`, `JWT_TTL_SECONDS`, `REFRESH_TTL_SECONDS`, `POSTER_FETCH_TIMEOUT_SECONDS`, `EVENT_CHANNEL_BUFFER`, `EVENT_QUEUE_*`, `NATS_MODE`, `NATS_STREAM_NAME`, `NATS_SUBJECT_PREFIX`, `NATS_CONSUMER_NAME`, `FEDERATION_DEBUG`, `WRAPUP_*`) are read by `crates/config`; see `crates/config/src/` for defaults.
### Cargo features ### Cargo features
@@ -245,7 +245,7 @@ cargo test -p bootstrap # route smoke, API and handler tests over the real
cargo llvm-cov -p application -p domain # line coverage report (requires cargo-llvm-cov) cargo llvm-cov -p application -p domain # line coverage report (requires cargo-llvm-cov)
``` ```
Each crate keeps its tests in `crates/<crate>/tests/`. The `application` and `domain` crates have 400+ tests covering all use case modules (auth, diary, goals, import, integrations, movies, person, search, users, watchlist, wrapup) backed by the in-memory fakes and factories in `domain::testing` (the `test-helpers` feature). They are the fastest feedback loop for business logic changes. Tests live in `crates/<crate>/tests/`; the exceptions are listed in `CODE_STYLE.md`. The `application` and `domain` crates have 400+ tests covering all use case modules (auth, diary, goals, import, insights, integrations, movies, person, search, users, watchlist, wrapup) backed by the in-memory fakes and factories in `domain::testing` (the `test-helpers` feature). They are the fastest feedback loop for business logic changes.
## Docker ## Docker

View File

@@ -138,7 +138,8 @@ graph TB
UC_WRAPUP -->|"delegates to"| DS_WRAPUP UC_WRAPUP -->|"delegates to"| DS_WRAPUP
Adapters -.->|"implements"| Ports Adapters -.->|"implements"| Ports
Messaging -.->|"serialises with"| EventPayload A_NATS -.->|"serialises with"| EventPayload
A_SQLITE -.->|"event queue serialises with"| EventPayload
%% Key data flows %% Key data flows
A_HTTP ===|"HTTP"| DTO A_HTTP ===|"HTTP"| DTO

View File

@@ -4,7 +4,7 @@
//! //!
//! **Error policy (deliberately no internal tolerance):** this function //! **Error policy (deliberately no internal tolerance):** this function
//! propagates every error from `user` and `follow_graph` with `?`. It does not //! propagates every error from `user` and `follow_graph` with `?`. It does not
//! itself decide to degrade — that's a http-adapter policy //! itself decide to degrade — that's an http-adapter policy
//! (`build_page_context` logs a warning and falls back to a zeroed `PageViewer` //! (`build_page_context` logs a warning and falls back to a zeroed `PageViewer`
//! on `Err`). Page chrome degrading is a rendering decision, not a domain one. //! on `Err`). Page chrome degrading is a rendering decision, not a domain one.