diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-24 14:33:20 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-24 14:33:20 +0200 |
| commit | 0b0da86f1f9d6f0b1a27b5e1e1658c42de9f356a (patch) | |
| tree | ba8daf5dcf050d44b7b0e766babbfda8fadac59f /packages/meshbay-node/tests/test_wizard_apps_endpoint.py | |
| parent | 6af05abf410bbd038ce7fa6915a659defc509071 (diff) | |
| download | meshbay-0b0da86f1f9d6f0b1a27b5e1e1658c42de9f356a.tar.gz | |
feat(node,hub): season-specific overviews, manual TMDB match correction, and wizard polish
Two operator-facing fixes for a real 3-season show whose automatic TMDB
match was wrong at the show level: per-season overview/air_date tabs in the
detail modal (falling back to the show-level text when a season's own is
empty), and a "Fix match…" search-and-correct affordance that re-resolves
every file sharing the corrected show's display_title. New signed op
OP_TMDB_OVERRIDE and two read-only pairs (season_meta_req/resp,
tmdb_search_req/resp), MNP_VERSION 0.5 -> 0.6.
Also: the create-group wizard gets a spinning indexing indicator and an
app-selection step, group settings default the TMDB language to the
operator's own locale (never as a global default), and a file renamed
mid-session now re-triggers title parsing instead of being silently
skipped by the enrichment dedup guard.
Fixes two bugs found during this work: the search overlay's z-index lost
to the base video-overlay class and rendered invisibly, and season_meta's
own empty overview didn't fall back to the show-level one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAmyXtc6dAADsH23ydXQpY
Diffstat (limited to 'packages/meshbay-node/tests/test_wizard_apps_endpoint.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_wizard_apps_endpoint.py | 76 |
1 files changed, 76 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_wizard_apps_endpoint.py b/packages/meshbay-node/tests/test_wizard_apps_endpoint.py new file mode 100644 index 0000000..10ab489 --- /dev/null +++ b/packages/meshbay-node/tests/test_wizard_apps_endpoint.py @@ -0,0 +1,76 @@ +""" +Create Group wizard: choosing which apps a brand-new group offers, before +the (potentially long) initial scan — see app.js's CreateGroupWizard. This +is a loopback-only, operator-authenticated endpoint (11.5.3), same shape as +the existing member-upload one: a thin adapter over `ops.set_enabled_apps`, +with only the validation `_do_apps_enabled` (the signed MNP front door) +already does client-side in the wizard, but worth enforcing at this front +door too since nothing else would. +""" + +from pathlib import Path + +import pytest +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +from fastapi.testclient import TestClient + +from conftest import one_root +from meshbay_node.indexer.group_index import GroupIndex +from meshbay_node.roster import Roster +from meshbay_node.ui.app import create_ui_app + +pytestmark = pytest.mark.asyncio + + +async def _client(tmp_path: Path): + shared = tmp_path / "shared" + shared.mkdir(exist_ok=True) + index = GroupIndex(group_id="g" * 32, sk_node=Ed25519PrivateKey.generate()) + roster = Roster(db_path=tmp_path / "roster.db") + await roster.open() + state = { + "status": "running", + "groups_ctx": {"g" * 32: {"index": index, "roots": one_root(shared)}}, + "indexes": {"g" * 32: index}, + "roster": roster, + } + app = create_ui_app(state) + return TestClient(app), roster + + +async def test_narrowing_the_apps_persists_to_the_roster(tmp_path): + client, roster = await _client(tmp_path) + try: + resp = client.put(f"/api/groups/{'g' * 32}/apps", json={"apps": ["files", "video"]}) + assert resp.status_code == 200, resp.text + assert sorted(resp.json()["apps"]) == ["files", "video"] + assert sorted(await roster.enabled_apps("g" * 32)) == ["files", "video"] + finally: + await roster.close() + + +async def test_empty_apps_list_is_refused(tmp_path): + client, roster = await _client(tmp_path) + try: + resp = client.put(f"/api/groups/{'g' * 32}/apps", json={"apps": []}) + assert resp.status_code == 400 + finally: + await roster.close() + + +async def test_missing_apps_field_is_refused(tmp_path): + client, roster = await _client(tmp_path) + try: + resp = client.put(f"/api/groups/{'g' * 32}/apps", json={}) + assert resp.status_code == 400 + finally: + await roster.close() + + +async def test_unhosted_group_is_refused(tmp_path): + client, roster = await _client(tmp_path) + try: + resp = client.put("/api/groups/" + "z" * 32 + "/apps", json={"apps": ["files"]}) + assert resp.status_code == 404 + finally: + await roster.close() |