Skip to content

Commit d8ab255

Browse files
authored
Merge pull request #1280 from makeabilitylab/1279-ci-test-shim-package-split
Add CI + test-settings shim + split tests into a package (#1279)
2 parents 4049075 + 23841a3 commit d8ab255

16 files changed

Lines changed: 1763 additions & 1590 deletions

.github/workflows/test.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Tests
2+
3+
# Runs the Django test suite on every push to master (our test-server deploy
4+
# trigger) and on every pull request. A failing run is a red ✗ + email — it
5+
# reports status only and does not block the push or the deploy.
6+
on:
7+
push:
8+
branches: [master]
9+
pull_request:
10+
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
15+
# Postgres service mirrors the local-dev / server `db` container
16+
# (postgres:16, makeability / admin / password).
17+
services:
18+
postgres:
19+
image: postgres:16
20+
env:
21+
POSTGRES_DB: makeability
22+
POSTGRES_USER: admin
23+
POSTGRES_PASSWORD: password
24+
ports:
25+
- 5432:5432
26+
options: >-
27+
--health-cmd "pg_isready -U admin -d makeability"
28+
--health-interval 10s
29+
--health-timeout 5s
30+
--health-retries 5
31+
32+
env:
33+
# settings_test.py reads these to reach the Postgres service above.
34+
DATABASE_HOST: localhost
35+
DATABASE_PORT: 5432
36+
DJANGO_ENV: DEBUG
37+
38+
steps:
39+
- uses: actions/checkout@v4
40+
41+
# Mirror the Dockerfile's system deps: ImageMagick + Ghostscript power the
42+
# PDF→thumbnail path that Artifact.save() runs (exercised by the Talk
43+
# fixtures); libpq-dev is needed to build psycopg2 from source.
44+
- name: Install system dependencies
45+
run: |
46+
sudo apt-get update
47+
sudo apt-get install -y --no-install-recommends imagemagick ghostscript libpq-dev
48+
sudo cp imagemagick-policy.xml /etc/ImageMagick-6/policy.xml
49+
50+
- name: Set up Python
51+
uses: actions/setup-python@v5
52+
with:
53+
python-version: "3.13"
54+
cache: pip
55+
56+
- name: Install Python dependencies
57+
run: pip install -r requirements.txt
58+
59+
- name: Run tests
60+
run: python manage.py test website --settings=makeabilitylab.settings_test --verbosity=2

CLAUDE.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ A superuser is required to use `/admin` and add content; create one with `python
3737

3838
## Tests and accessibility checks
3939

40-
- Tests: `python manage.py test website` (inside container). The suite has two styles, both in `website/tests.py`:
40+
- Tests: `python manage.py test website --settings=makeabilitylab.settings_test` (inside container). The tests live in the `website/tests/` package (one `test_*.py` per concern; Django auto-discovers them) with shared DB fixtures in `website/tests/base.py`. The suite has two styles:
4141
- **Unit**`SimpleTestCase` + `MagicMock` for pure logic (formatters, BibTeX generation, etc.); no DB, runs in ms.
42-
- **Integration**`DatabaseTestCase` (subclass of Django's `TestCase`) for view / queryset / template regressions; each test runs in a transaction and rolls back. Has fixture helpers `make_person` / `make_publication` / `make_news_item`.
42+
- **Integration**`DatabaseTestCase` (subclass of Django's `TestCase`, in `tests/base.py`) for view / queryset / template regressions; each test runs in a transaction and rolls back. Has fixture helpers `make_person` / `make_publication` / `make_talk` / `make_news_item`.
4343
- When fixing a bug reachable through a real queryset, URL, or view, add a regression test in the matching style before applying the fix (matches the tests-first workflow).
44-
- **Gotcha:** `website/migrations/` is gitignored, so each env has its own history. If `manage.py test` fails at DB creation with `column "..." already exists`, drop the stale test DB with `docker exec makeabilitylabwebsite-db-1 psql -U admin -d postgres -c "DROP DATABASE IF EXISTS test_makeability;"` and re-run. See #1267 for the durable fix.
44+
- **Always use the `--settings=makeabilitylab.settings_test` shim.** It sets `MIGRATION_MODULES = {'website': None}` so the test DB is built directly from the current models, sidestepping the gitignored, per-environment `website/migrations/` history. This is the durable fix for #1267 — without it, a fresh test DB can fail at creation with `column "..." already exists` (old workaround: `docker exec makeabilitylabwebsite-db-1 psql -U admin -d postgres -c "DROP DATABASE IF EXISTS test_makeability;"`).
45+
- **CI:** `.github/workflows/test.yml` runs this same command on every push to `master` and every PR (free/unlimited for this public repo). It reports a green ✓ / red ✗ — it does not block pushes or the deploy. See the testing roadmap in #1278.
4546
- Accessibility (Pa11y CI + Axe, WCAG 2.0 AA): start the site, then `docker-compose -f docker-compose-local-dev.yml --profile testing run --rm a11y`. URLs to scan are configured in `.pa11yci.json`. Run this before submitting UI changes.
4647

4748
## Deployment

CONTRIBUTING.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -249,20 +249,22 @@ A superuser account is required to access the Django admin interface and add con
249249
250250
## Running the Test Suite
251251
252-
The Python test suite lives in `website/tests.py` and runs inside the website container:
252+
The Python test suite lives in the `website/tests/` package (one `test_*.py` module per concern — Django auto-discovers them) and runs inside the website container:
253253
254254
```bash
255-
docker exec makeabilitylabwebsite-website-1 python manage.py test website
255+
docker exec makeabilitylabwebsite-website-1 python manage.py test website --settings=makeabilitylab.settings_test
256256
```
257257
258+
**Always pass `--settings=makeabilitylab.settings_test`** (see [Troubleshooting tests](#troubleshooting-tests) for why). The same command runs automatically in CI on every push to `master` and every PR — see [Continuous integration](#continuous-integration).
259+
258260
The suite has two complementary styles:
259261
260262
| Style | Base class | What it's for |
261263
|---|---|---|
262264
| **Unit** | `SimpleTestCase` + `MagicMock` | Pure logic — formatters, BibTeX generation, single-method behavior. No DB; runs in milliseconds. |
263-
| **Integration** | `DatabaseTestCase` (subclass of Django's `TestCase`) | View, queryset, template, and URL-routing regressions. Each test runs inside a transaction that is rolled back, so tests stay isolated. |
265+
| **Integration** | `DatabaseTestCase` (subclass of Django's `TestCase`, in `website/tests/base.py`) | View, queryset, template, and URL-routing regressions. Each test runs inside a transaction that is rolled back, so tests stay isolated. |
264266
265-
The `DatabaseTestCase` base provides `make_person`, `make_publication`, and `make_news_item` helpers built on plain `Model.objects.create()` — use those rather than hand-rolling fixtures.
267+
The `DatabaseTestCase` base provides `make_person`, `make_publication`, `make_talk`, and `make_news_item` helpers built on plain `Model.objects.create()` — use those rather than hand-rolling fixtures.
266268
267269
### When to add a test
268270
@@ -276,13 +278,19 @@ If a fix is genuinely not unit-testable (FD leaks, `super().save()`-dependent pa
276278
277279
### Troubleshooting tests
278280
279-
`website/migrations/` is **gitignored** — each environment (your laptop, test, production) maintains its own migration history on disk. This sometimes drifts. If `manage.py test` fails at test-DB creation, the symptoms and fixes are:
281+
`website/migrations/` is **gitignored** — each environment (your laptop, test, production) maintains its own migration history on disk, which can drift. The `--settings=makeabilitylab.settings_test` shim is the durable fix (#1267): it sets `MIGRATION_MODULES = {'website': None}`, so the test runner builds the `website` schema directly from the current models instead of replaying that local history. **Use the shim and these symptoms shouldn't appear at all.**
282+
283+
If you forget the shim and the legacy `manage.py test website` fails at test-DB creation:
280284

281-
- **`database "test_makeability" already exists`** — a prior failed run left it half-built. Drop and retry:
285+
- **`database "test_makeability" already exists`** — a prior failed run left it half-built. Drop and retry (or just switch to the shim):
282286
```bash
283287
docker exec makeabilitylabwebsite-db-1 psql -U admin -d postgres -c "DROP DATABASE IF EXISTS test_makeability;"
284288
```
285-
- **`column "..." of relation "..." already exists`** — a local migration file duplicates a field that a later `0001_initial` regeneration already includes. Same fix (drop the test DB) usually clears it; if it persists, the offending migration is a local stale artifact that needs to be edited or removed. See [#1267](https://github.com/makeabilitylab/makeabilitylabwebsite/issues/1267) for the durable fix (test-only settings shim using `MIGRATION_MODULES`).
289+
- **`column "..." of relation "..." already exists`** — a local migration file duplicates a field that a later `0001_initial` regeneration already includes. The shim sidesteps this entirely.
290+
291+
### Continuous integration
292+
293+
`.github/workflows/test.yml` runs the suite (with the test-settings shim, against a Postgres 16 service container) on every push to `master` and every pull request. GitHub Actions is free and unlimited for this public repo. A failing run shows a red ✗ on the commit/PR and emails the author — it **reports** status, it does not block the push or the test-server deploy. The broader testing roadmap (coverage, Pa11y-in-CI, test backfill) is tracked in [#1278](https://github.com/makeabilitylab/makeabilitylabwebsite/issues/1278).
286294

287295
## Accessibility Testing
288296

@@ -317,7 +325,7 @@ Edit `.pa11yci.json` to add or remove URLs to test. The `urls` array lists every
317325

318326
- **One issue per branch**: Keep PRs focused on a single issue for easier review.
319327

320-
- **Run the test suite**: `docker exec makeabilitylabwebsite-website-1 python manage.py test website` should pass before opening a PR. If your fix is reachable through a real queryset, view, or template, add a regression test (see [Running the Test Suite](#running-the-test-suite)).
328+
- **Run the test suite**: `docker exec makeabilitylabwebsite-website-1 python manage.py test website --settings=makeabilitylab.settings_test` should pass before opening a PR (CI runs the same command). If your fix is reachable through a real queryset, view, or template, add a regression test (see [Running the Test Suite](#running-the-test-suite)).
321329

322330
- **Test locally**: Verify your changes work in the browser before submitting.
323331

docs/DEPLOYMENT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ View current and past versions on the [Releases page](https://github.com/makeabi
9797
2. Confirm the Python test suite passes locally:
9898

9999
```bash
100-
docker exec makeabilitylabwebsite-website-1 python manage.py test website
100+
docker exec makeabilitylabwebsite-website-1 python manage.py test website --settings=makeabilitylab.settings_test
101101
```
102102

103103
(See [Running the Test Suite](../CONTRIBUTING.md#running-the-test-suite) in `CONTRIBUTING.md` for what the suite covers and how to add to it.)

makeabilitylab/settings_test.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""
2+
Test-only Django settings.
3+
4+
Run with:
5+
python manage.py test website --settings=makeabilitylab.settings_test
6+
7+
Why this exists
8+
---------------
9+
``website/migrations/`` is gitignored, so every environment (laptop, CI, the
10+
servers) carries its own migration history. That drift intermittently breaks a
11+
fresh test-DB build with ``column "..." already exists`` (see #1267 and
12+
CLAUDE.md). Setting ``MIGRATION_MODULES = {'website': None}`` tells Django to
13+
ignore the website app's migration history entirely and build its tables
14+
directly from the current models during test-DB setup (run_syncdb), which is
15+
both reproducible across environments and the durable fix for that flakiness.
16+
17+
Only the *website* app is affected; third-party apps (admin, auth, ckeditor,
18+
sortedm2m, easy_thumbnails, image_cropping, ...) keep their shipped migrations.
19+
"""
20+
import os
21+
22+
from makeabilitylab.settings import * # noqa: F401,F403
23+
24+
# Build website tables from models instead of replaying gitignored migrations.
25+
MIGRATION_MODULES = {"website": None}
26+
27+
# Let CI point the database at its Postgres service container. Locally (inside
28+
# the website container) these env vars are unset, so we inherit HOST='db' from
29+
# the base settings fallback; CI sets DATABASE_HOST=localhost.
30+
DATABASES["default"]["HOST"] = os.environ.get( # noqa: F405
31+
"DATABASE_HOST", DATABASES["default"]["HOST"] # noqa: F405
32+
)
33+
DATABASES["default"]["PORT"] = os.environ.get( # noqa: F405
34+
"DATABASE_PORT", DATABASES["default"].get("PORT", "5432") # noqa: F405
35+
)
36+
37+
# The base settings wire a RotatingFileHandler to /code/media/debug.log (a
38+
# container path). Django evaluates LOGGING at startup, so on any host without
39+
# that directory — a CI runner, a fresh checkout — django.setup() crashes
40+
# before a single test runs. Swap just the 'file' handler for a no-op; this
41+
# keeps every logger's handler reference valid while never touching disk.
42+
LOGGING["handlers"]["file"] = {"class": "logging.NullHandler"} # noqa: F405
43+
44+
# Speed up the auth tests (Data Health suite creates real superuser rows).
45+
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]

0 commit comments

Comments
 (0)