Skip to content

Tests

python tests/run.py              # everything
python tests/run.py multiuser    # one suite

1,250 assertions across twenty-two suites, each in its own process against its own throwaway database. Sample comics are generated on first run into tests/.fixtures/.

Nothing reaches the network. The OAuth suite runs a fake provider, and the Comic Vine and provider suites stub the API. A suite also blanks every LONGBOX_* variable and the provider credential names before the application is imported, including any a .env beside the repository would supply — so running the tests on a configured host cannot reach that host's library or spend its API keys. That was not always true: run on a deployment host, the suites once wrote sample comics into a real upload folder and called Comic Vine for real with the host's key.

requirements-dev.txt adds jsonschema, which validates the OPDS 2.0 output and the Divina manifests against the published schemas vendored under tests/schemas/. The suites run without it, and say on their first line what stopped running rather than skipping quietly.

Suite Covers
import filename parsing, every archive format, the import pipeline, the HTTP surface, uploads
multiuser cross-account access on every route, browse permissions, admin boundaries, sessions, CSRF
admin account setup links, password reset (admin link, self-service, CLI), token expiry/replay/supersession, account-enumeration resistance, every admin action and its permission boundary, the audit trail
reader the manifest carrying URLs rather than bytes, page caching and eviction, progress, reading direction, and that one account cannot fetch another's pages
convert metadata surviving a write-and-reimport round trip, pages compared byte for byte across containers, and a failed rewrite leaving the original intact
identity every ComicInfo field surviving parse, import, storage and editing; portable ids including the migration backfill; integrity hashing catching a byte flipped deep inside a file
backup snapshots taken while another connection is committing, verification, rotation, scheduling, and that backups are not downloadable
migration upgrading a real single-user database, with the v1 schema pinned in tests/schema_v1.sql
oauth the full authorization-code flow against a fake OIDC provider, including PKCE, replay, forged state, wrong audience, wrong issuer and expired tokens
comicvine matching a series to a volume and a file to the right issue, that ambiguity is reported rather than guessed at, and that the issue picker never filters away the issue you are trying to correct to
routes the same match routes answering for either metadata source, the legacy Comic Vine paths redirecting rather than breaking, and cross-account isolation on every one of them
providers the source interface itself: Metron behind it end to end, per-account source order, and falling through sources with gaps filled from a later confident one without ever overwriting the primary's own fields
skins the WCAG AA contrast check refusing a bad palette with the failing pairs named, the lifecycle rules (active/default cannot be deleted), and that the active skin reaches a signed-out page exactly as it reaches a signed-in one
diskusage two configured paths on one physical device collapsing into a single reported figure, a missing or unreadable path failing on its own rather than the whole report, a slow lookup timing out rather than hanging, and both the admin and settings pages showing it
opds Basic and per-account-token authentication answering 401 with a challenge rather than a redirect, token issue/revoke/reissue and their admin_log entries, throttling of guessed tokens, and that each account sees only its own root feed
opds-catalogue the version-agnostic feed model and the query layer under it: pagination edges, a series listing sorting by issue rather than import order, facet active-marking and filter composition, and per-account isolation
opds-atom the OPDS 1.2 serialiser checked by an internal conformance validator that is itself shown to catch broken feeds, and the real /opds routes over HTTP -- filters, search, pagination, the OpenSearch document, and cross-account 404s on the file and cover routes
opds-json the OPDS 2.0 serialiser and the Divina manifests, validated against the published JSON schemas vendored under tests/schemas/ -- including the one deliberate deviation, a feed with no results, asserted as the schema's only complaint so it cannot hide a second bug
formats every page image format IMAGE_EXTENSIONS accepts -- PNG, GIF, BMP, WebP, AVIF, JPEG XL -- reaching the cache under its own extension and being served as its own type, plus covers and thumbnails where a decoder exists, and PDF where pymupdf installs
opds-pse page streaming: the {pageNumber} template counting from 0 while pse:lastRead counts from 1, progress clamped to a re-counted file, and the page route warming the cache once per comic rather than once per page turn
proxy who Longbox believes the caller is behind a reverse proxy, against a real run.py process: a forged X-Forwarded-For ignored from anything but a configured proxy, and a client-prepended entry never outranking the one the proxy appended
collected the two shapes a provider files a collected edition under, and what a volume marker in a filename gets matched to -- including the assertions that record a defect still to be fixed, and the issue rules a fix must not undo

The isolation suite is the one to keep green: it asserts that a second account cannot read another's comic, cover, download, page image, job or search results, and cannot edit, delete or refresh them either.

What the suites cannot do

They import the code they are testing, so they prove the application correct and say nothing about a deployment. tests/live_check.py walks a deployed server's OPDS feed the way a client does, and is hand-run rather than a suite because it is one of only two things under tests/ that touch the network (the other is tests/schemas/update.py).

LONGBOX_CHECK_PASSWORD='...' python tests/live_check.py https://comics.example.com -u reader

A feed whose links all point at http://127.0.0.1:8080 validates perfectly and is useless on a phone. Only this catches that. It also caught a server running last release's code from a current checkout, because install.sh had copied the files without restarting the service.