From 7c6dff7f709040eda2f705579fb381cc2dea0689 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 25 Oct 2026 23:07:14 +0100 Subject: [PATCH] docs: fix config table, test-location claims --- CODE_STYLE.md | 9 +++++- CONTRIBUTING.md | 2 +- Makefile | 4 +-- README.md | 32 +++++++++---------- architecture.mmd | 3 +- .../application/src/users/get_page_viewer.rs | 2 +- 6 files changed, 30 insertions(+), 22 deletions(-) diff --git a/CODE_STYLE.md b/CODE_STYLE.md index 9d44b5d2..b0f021d2 100644 --- a/CODE_STYLE.md +++ b/CODE_STYLE.md @@ -56,7 +56,14 @@ crates/application/tests/diary/log_review_test.rs 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8f3ba768..f4884241 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,7 +69,7 @@ Cargo features: `federation` (default; ActivityPub) and `nats`. `sqlite` and `sq ### Tests -Tests live in `crates//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//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 diff --git a/Makefile b/Makefile index b018db21..3479d34c 100644 --- a/Makefile +++ b/Makefile @@ -92,10 +92,10 @@ check-dockerfile-manifests: copied=$$(sed -n 's/^COPY \([^[:space:]]*\)\/Cargo\.toml[[:space:]].*/\1/p' Dockerfile | grep -v '^\(Cargo\.toml\|\.\)$$' || true); \ bad=""; \ 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; \ 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; \ if [ -n "$$bad" ]; then \ echo "❌ Dockerfile manifest COPY lines out of sync with workspace members:$$bad"; \ diff --git a/README.md b/README.md index 1c43fac4..6df55ec0 100644 --- a/README.md +++ b/README.md @@ -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 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 -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 worker — thin worker binary (bootstrap::build_worker, run until ctrl-c) 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) 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 | |---|---|---|---| -| `DATABASE_URL` | `sqlite://movies.db` | Yes | SQLite connection string | -| `JWT_SECRET` | — | Yes | Secret for JWT signing — use a long random string | -| `OMDB_API_KEY` | — | Yes | [OMDb](https://www.omdbapi.com/apikey.aspx) key for movie metadata | -| `TMDB_API_KEY` | — | No | [TMDb](https://www.themoviedb.org/settings/api) key — enables cast, crew, genres, enrichment | -| `BASE_URL` | — | Yes | Public URL of your instance (used for ActivityPub actor URLs) | +| `DATABASE_URL` | — | Yes | SQLite connection string (e.g. `sqlite://movies.db`); the only value the config loader itself requires | +| `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` | — | 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` | — | 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` | `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_PATH` | `./images` | No | Path for local image storage | | `MINIO_ENDPOINT` | — | S3 only | S3-compatible endpoint (e.g. `http://localhost:9000`) | | `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_SECRET_ACCESS_KEY` | — | S3 only | Secret access key | -| `IMAGE_CONVERSION_ENABLED` | `false` | No | Convert stored images to AVIF or WebP | -| `IMAGE_CONVERSION_FORMAT` | `avif` | No | `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` | — | When conversion is enabled | `avif` or `webp`; startup fails if conversion is enabled without it | | `HOST` | `0.0.0.0` | No | Bind address | | `PORT` | `3000` | No | HTTP port | | `RATE_LIMIT` | `60` | No | Requests per minute per IP | -| `ALLOW_REGISTRATION` | `true` | No | Set `false` to disable new sign-ups | -| `SECURE_COOKIES` | `true` | No | Must be `true` when serving over HTTPS | -| `RUST_LOG` | — | No | Log verbosity (e.g. `server=info,worker=info`) | -| `CORS_ORIGINS` | `*` | No | Comma-separated allowed origins for SPA dev | +| `ALLOW_REGISTRATION` | `false` | No | Set `true` (or `1`) to allow new sign-ups | +| `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`); read by the logger, not part of `config.toml` | +| `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` | | `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 @@ -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) ``` -Each crate keeps its tests in `crates//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//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 diff --git a/architecture.mmd b/architecture.mmd index c12ede18..eff20264 100644 --- a/architecture.mmd +++ b/architecture.mmd @@ -138,7 +138,8 @@ graph TB UC_WRAPUP -->|"delegates to"| DS_WRAPUP Adapters -.->|"implements"| Ports - Messaging -.->|"serialises with"| EventPayload + A_NATS -.->|"serialises with"| EventPayload + A_SQLITE -.->|"event queue serialises with"| EventPayload %% Key data flows A_HTTP ===|"HTTP"| DTO diff --git a/crates/application/src/users/get_page_viewer.rs b/crates/application/src/users/get_page_viewer.rs index 483ec281..df3f8c6e 100644 --- a/crates/application/src/users/get_page_viewer.rs +++ b/crates/application/src/users/get_page_viewer.rs @@ -4,7 +4,7 @@ //! //! **Error policy (deliberately no internal tolerance):** this function //! 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` //! on `Err`). Page chrome degrading is a rendering decision, not a domain one.