# mumble — recipe-maintainer → cc-ci parity (Phase 2 P2)

Source corpus: `references/recipe-maintainer/recipe-info/mumble/tests/` (`/srv/recipe-maintainer/
recipe-info/mumble/`). mumble is a TLS **voice** server (port 64738), not an HTTP app, plus an
optional `mumble-web` HTTP client overlay. cc-ci deploys it with
`COMPOSE_FILE=compose.yml:compose.mumbleweb.yml:compose.host-ports.yml` (recipe_meta.EXTRA_ENV):
the web overlay gives the generic harness an HTTP readiness/serving signal; the host-ports overlay
publishes 64738 on the cc-ci host so the on-host (cc-ci-run) protocol tests connect to
127.0.0.1:64738. Both overlays are shipped by the upstream recipe (documented deployment mode).

## Parity port mapping (P2 — every recipe-maintainer test has a comparable cc-ci test)

| recipe-maintainer test (`recipe-info/mumble/tests/`) | what it verifies | cc-ci test | same thing? |
|---|---|---|---|
| `health_check.py` | mumble server listening on TCP 64738 | `custom/test_tcp_health.py` | yes — TCP connect to 64738 (host-published) |
| `mumble_connect.py` | full TLS protocol handshake: TLS connect, server Version, auth accepted (no Reject), channel list present, ServerSync handshake completes, welcome text | `custom/test_protocol_handshake.py` (+ `custom/_mumble_proto.py`, adapted from the corpus's stdlib protobuf/protocol code) | yes — same handshake; asserts tls_connect + version + auth_accepted + channel presence + ServerSync |
| `web_client.py` | mumble-web client reachable over HTTPS, HTTP 200, page contains `Mumble` + `config.js`, valid HTML | `custom/test_web_client.py` | yes — same 200 + body markers (`Mumble`, `config.js`, `<!DOCTYPE html>`) |

No recipe-maintainer mumble test is omitted — all three are ported. No `DECISIONS.md` non-port
entry is needed for mumble.

## Recipe-specific functional tests (P3 — ≥2 beyond parity)

mumble has no REST "create-an-object" API; its characteristic behaviour is the voice-server control
protocol and its deploy-time server configuration. Both new tests are **config round-trips** that
prove our deploy-time configuration propagated into the running murmur server and is enforced/
delivered over the real protocol (version-independent — they assert OUR configured markers, not
hard-coded upstream values):

1. `custom/test_welcome_text_roundtrip.py` — deploys with a unique `WELCOME_TEXT` marker
   (`recipe_meta.EXTRA_ENV` → `MUMBLE_CONFIG_WELCOMETEXT`); asserts that exact marker surfaces in the
   server's ServerSync `welcome_text` delivered to a connecting client. (create config → read back.)
2. `custom/test_server_config_limits.py` — deploys with a distinctive non-default `USERS=42`
   (max-users cap → `MUMBLE_CONFIG_USERS`); asserts the server's ServerConfig message reports
   `max_users == 42` (and a well-formed `allow_html`), proving the recipe wires deploy-time
   server-capacity policy into the running server.

## Backup data-integrity (P4 — real, recipe-aware)

`ops.py` + `test_backup.py` + `test_restore.py`: seed a `ci_marker` row into the recipe's own state
DB `/data/mumble-server.sqlite` (the exact file the recipe's backupbot hooks `.backup`/restore),
back up, drop the marker (mutate), restore, and assert the row returns as `original`. Writes use
`PRAGMA busy_timeout` to wait out the running murmur server's transient sqlite locks. The upgrade
tier seeds `upgrade-survives` and asserts it survives the prev→PR-head chaos crossover.

## Browser flow (P6)

Not applicable as a primary UX: mumble's core UX is the native desktop client over the voice
protocol (covered by the protocol handshake tests). The mumble-web HTTP UI is asserted via
`test_web_client.py` (HTTP, no interactive flow to drive). No Playwright test.
