Files
cc-ci/tests/ghost/PARITY.md
autonomic-bot b4d03ccafe feat(2): ghost P4 data-integrity overlay (MySQL ci_marker) + §4.3 create-post round-trip
- ops.py + test_{upgrade,backup,restore}.py: seed ci_marker into the MySQL `ghost` DB (db service)
  via the mysql CLI; rides the recipe's mysqldump --tab backup. recipe is MySQL not sqlite (stale
  comment fixed). Expect restore RED -> recipe-PR (no backupbot.restore hook; immich/mattermost class).
- functional/_ghost.py: cookie-aware Ghost Admin API client (stdlib http.cookiejar; Origin CSRF hdr).
- functional/test_post_roundtrip.py: §4.3 create published post + read back (unique marker, non-vacuous);
  closes the DEFERRED ghost create-post item.
- PARITY.md + recipe_meta.py updated. Authored node-free; full-lifecycle run next, NOT yet claimed.
2026-05-30 04:14:13 +01:00

3.3 KiB

Parity — ghost

The recipe-maintainer corpus has no recipe-info/ghost/tests/ directory — ghost was not in their parity suite. This PARITY.md documents the Phase-2 health_check (parity-aligned baseline)

  • recipe-specific tests beyond.

Recipe-specific tests (Phase-2 P3, ≥2 beyond parity)

Ghost is a publishing platform with a public themed site at /, an admin UI at /ghost/, and a JSON Content/Admin API at /ghost/api/*. Defining behaviors exercised:

cc-ci file what's verified rationale
tests/ghost/functional/test_content_api.py GETs /ghost/api/content/settings/; asserts 200 with {"settings": {...}} envelope OR 401/403 with a Ghost error envelope. Distinguishes "the ghost-server JS process is up + emitting its API" from "a static themed page is served at /." A wedged Ghost backend → 5xx; misrouted nginx → 404.
tests/ghost/functional/test_admin_redirect.py GETs /ghost/; asserts 200 or 302 + Ghost branding/SPA references in the response (or a redirect to /ghost/#/setup on fresh deploy). Proves the admin route is wired through the nginx proxy. Distinguishes "admin SPA bound" from "404 (route missing)" or "5xx (broken)."

Two specific tests + parity health_check = ≥2 floor met.

Plan §4.3 prescribed deeper test — AUTHORED (closes DEFERRED ghost create-post)

§4.3 named "create-a-post round-trip" for ghost. Implemented in tests/ghost/functional/test_post_roundtrip.py (helper functional/_ghost.py):

  1. Wait for the Admin API healthcheck (GET /ghost/api/admin/site/ → 200).
  2. Setup the Ghost owner (POST /ghost/api/admin/authentication/setup/, fresh deploy) + establish an admin session cookie (POST /ghost/api/admin/session/) — cookie-aware stdlib opener, version-negotiated (no /v3/ in the path; recipe-versioned).
  3. POST /ghost/api/admin/posts/?source=html to create a published post with a unique marker in title + body.
  4. GET /ghost/api/admin/posts/<id>/?formats=html to read it back; assert title + body marker round-trip intact (unique-per-run → non-vacuous).

Admin creds are class-B run-scoped (destroyed at teardown with the app).

Backup data-integrity (P4) — AUTHORED

ops.py + test_install-free lifecycle overlays (test_upgrade.py / test_backup.py / test_restore.py) seed a deterministic ci_marker row into the MySQL ghost DB (the recipe's real state store) via the mysql CLI in the db service. The recipe's backupbot pre-hook (mysqldump ghost --tab) dumps that table into the backed-up path, so the marker rides backup→restore the way a real post's row would. pre_restore drops the table (divergence); the restore overlay asserts it returned.

Expected RED until a recipe-PR lands: the ghost recipe has a logical mysqldump backup but no backupbot.restore.* hook (and the mysql data volume itself isn't backupbot-labelled), so a file-level restore never reimports the dump — same defect class fixed in immich#1 / mattermost-lts#1. If test_restore_returns_state goes RED, the durable fix is a recipe-PR adding a mysqldump-reimport restore post-hook. (See test_restore.py docstring + DECISIONS.md.)

Playwright (P6)

Not yet authored. Ghost's admin UI is an Ember SPA; a Playwright flow would exercise the setup wizard + post creation. Q4 follow-up.