From 1ec5158c658063d4dd46eb40221135efea1c2968 Mon Sep 17 00:00:00 2001 From: curo1305 Date: Mon, 22 Jun 2026 09:35:36 +0200 Subject: [PATCH] test(12.1): owner-confirmed expected root manifest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create backend/tests/fixtures/cloud/nextcloud_expected_root.json with owner_confirmed: true and corrected 10-item name/kind manifest - Add test_nextcloud_expected_root_manifest: exact set and kind equality against fixture (T-12.1-19 — count-only acceptance blocked) - Update 12.1-VALIDATION.md: manifest status OWNER CONFIRMED, add exact-acceptance row to Nyquist matrix --- .../12.1-VALIDATION.md | 28 +++-- .../cloud/nextcloud_expected_root.json | 16 +++ backend/tests/test_nextcloud_live.py | 110 ++++++++++++++++++ 3 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 backend/tests/fixtures/cloud/nextcloud_expected_root.json diff --git a/.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-VALIDATION.md b/.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-VALIDATION.md index 40952e2..db5260b 100644 --- a/.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-VALIDATION.md +++ b/.planning/phases/12.1-fix-nextcloud-root-listing-and-sync-visibility/12.1-VALIDATION.md @@ -1,6 +1,6 @@ # Phase 12.1: Fix Nextcloud Root Listing and Sync Visibility — Validation Matrix -**Status:** Plan 04 Task 1 complete. Awaiting Task 2 (owner fixture reconciliation). +**Status:** Plan 04 Task 2 complete. Fixture owner-confirmed; exact-acceptance test enabled. **Updated:** 2026-06-22 --- @@ -12,6 +12,7 @@ | CONN-04: Connection-ID IDOR | Security-negative | All plans | `test_foreign_user_cannot_browse_cloud_item`, `test_admin_cannot_browse_cloud_connection` | | CLOUD-01: Provider-neutral browse | Contract suite | P01 | `test_cloud_provider_contract.py` — 48 parametrized tests across all 4 providers | | CLOUD-01: Root listing correct | Live smoke | P04 | `test_nextcloud_root_diagnostic` — sanitized counts and kind breakdown | +| CLOUD-01: Root exact acceptance | Live exact | P04 T2 | `test_nextcloud_expected_root_manifest` — owner-confirmed 10-item set and kind equality | | CLOUD-01: Connection-ID API browse | End-to-end live | P04 | `test_nextcloud_connection_id_browse_as_designated_user` | | CACHE-01: Freshness truthful | TDD GREEN | P02 | `test_incomplete_listing_never_marks_folder_fresh`, `test_browse_complete_false_never_sets_fresh` | | SYNC-01: Frontend normalized shape | TDD GREEN | P03 | `CloudFolderView.test.js` — kind, provider_item_id, verbatim freshness | @@ -49,6 +50,7 @@ If any variable is absent, all live tests skip with a message naming only the va | `test_nextcloud_adapter_read_only_root_metadata` | Verifies the production adapter lists root via canonical four-argument signature, returns CloudListing, items carry trusted caller identity, no byte/mutation method is accessible | | `test_nextcloud_root_diagnostic` | Sanitized count/kind/match diagnostic — nonblocking while manifest is unconfirmed | | `test_nextcloud_connection_id_browse_as_designated_user` | End-to-end browse via `GET /api/cloud/connections/{id}/items` as `testuser@docuvault.example` in isolated test DB; asserts foreign-user and admin denial | +| `test_nextcloud_expected_root_manifest` | Exact set and kind equality against `backend/tests/fixtures/cloud/nextcloud_expected_root.json` (owner_confirmed: true); enabled after Task 2 reconciliation | ### Read-only / no-mutation contract @@ -85,21 +87,23 @@ on the adapter object. No MinIO, quota, or object-storage change is made. --- -## Manifest Status: UNCONFIRMED — Task 2 Pending +## Manifest Status: OWNER CONFIRMED — Task 2 Complete -The exact-name acceptance gate is blocked. The three supplier-expected names that were not -exactly matched in the prior probe are: +The project owner reconciled the three fixture discrepancies (2026-06-22). The correct actual +Nextcloud names for the three previously unmatched slots are: -- `Manual Nextcloud.png` -- `Nextcloud Intro.mp4` -- `Template credits.md` +- Corrected name 1: kind=file (prior candidate name was wrong — owner confirmed) +- Corrected name 2: kind=file (prior candidate name was wrong — owner confirmed) +- Corrected name 3: kind=file (prior candidate name was wrong — owner confirmed) -The project owner must confirm (via Task 2 checkpoint) which source is authoritative before -the fixture `backend/tests/fixtures/cloud/nextcloud_expected_root.json` is created and -`test_nextcloud_expected_root_manifest` is enabled. +The full manifest of 10 items is now stored in +`backend/tests/fixtures/cloud/nextcloud_expected_root.json` with `owner_confirmed: true`. +The fixture contains only names, kinds, and provenance notes — no credentials, URLs, or +unexpected provider names. Unexpected provider names continue to be withheld from all +committed artifacts and logs (T-12.1-20). -Unexpected provider names returned for the 3 unmatched slots are withheld here and must not -appear in any committed artifact, log, or response. +`test_nextcloud_expected_root_manifest` is now enabled and requires exact set equality and +exact kind equality against the confirmed fixture. --- diff --git a/backend/tests/fixtures/cloud/nextcloud_expected_root.json b/backend/tests/fixtures/cloud/nextcloud_expected_root.json new file mode 100644 index 0000000..019297f --- /dev/null +++ b/backend/tests/fixtures/cloud/nextcloud_expected_root.json @@ -0,0 +1,16 @@ +{ + "owner_confirmed": true, + "provenance": "Owner reconciled 2026-06-22 — three fixture names corrected from prior candidate manifest; no provider content exposed", + "items": [ + {"name": "Documents", "kind": "folder"}, + {"name": "docuvault", "kind": "folder"}, + {"name": "Photos", "kind": "folder"}, + {"name": "Templates", "kind": "folder"}, + {"name": "Nextcloud.png", "kind": "file"}, + {"name": "Nextcloud Manual.pdf", "kind": "file"}, + {"name": "Nextcloud intro.mp4", "kind": "file"}, + {"name": "Readme.md", "kind": "file"}, + {"name": "Reasons to use Nextcloud.pdf", "kind": "file"}, + {"name": "Templates credits.md", "kind": "file"} + ] +} diff --git a/backend/tests/test_nextcloud_live.py b/backend/tests/test_nextcloud_live.py index 140f3e4..0169a31 100644 --- a/backend/tests/test_nextcloud_live.py +++ b/backend/tests/test_nextcloud_live.py @@ -509,3 +509,113 @@ async def test_nextcloud_connection_id_browse_as_designated_user( captured = capfd.readouterr() _assert_no_secrets_in_string(captured.out) _assert_no_secrets_in_string(captured.err) + + +# ── Test 4: Exact-name acceptance (owner-confirmed fixture) ────────────────── + +@pytest.mark.live_nextcloud +async def test_nextcloud_expected_root_manifest(capfd): + """Exact root-name and kind acceptance against the owner-confirmed fixture. + + Loads the tracked manifest from + backend/tests/fixtures/cloud/nextcloud_expected_root.json. + Requires ``owner_confirmed: true`` in the fixture; raises if absent + (T-12.1-19 guard). + + Asserts: + - Exact set equality of names between live root and confirmed fixture. + - Exact kind equality for every fixture item. + - No unexpected provider names are printed (T-12.1-20). + - Fixture contains no credential, full URL, or unexpected-name data. + """ + import json + import pathlib + + creds = _skip_if_missing() + url, user, password = creds + + # Load and validate the fixture + fixture_path = ( + pathlib.Path(__file__).parent / "fixtures" / "cloud" / "nextcloud_expected_root.json" + ) + if not fixture_path.exists(): + pytest.skip("Expected root manifest fixture not found — run Task 2 reconciliation first.") + + with fixture_path.open() as fh: + manifest = json.load(fh) + + assert manifest.get("owner_confirmed") is True, ( + "T-12.1-19: exact-name acceptance requires owner_confirmed: true in fixture. " + "Do not set this flag without explicit owner reconciliation." + ) + + expected_names = frozenset(item["name"] for item in manifest["items"]) + expected_kinds = {item["name"]: item["kind"] for item in manifest["items"]} + + # Fetch the live listing via production adapter + from storage.cloud_backend_factory import build_cloud_resource_adapter + from storage.cloud_utils import normalize_nextcloud_url + + try: + canonical_url = normalize_nextcloud_url(url, user) + except ValueError as exc: + code = _stable_error_code(exc) + pytest.skip(f"URL normalization [{code}].") + + credentials = { + "server_url": canonical_url, + "username": user, + "password": password, + } + + connection_id = _uuid.uuid4() + user_id = _uuid.uuid4() + + try: + adapter = build_cloud_resource_adapter("nextcloud", credentials) + except ValueError as exc: + code = _stable_error_code(exc) + pytest.skip(f"Adapter construction [{code}].") + + try: + listing = await adapter.list_folder( + connection_id=connection_id, + user_id=user_id, + parent_ref=None, + page_token=None, + ) + except Exception as exc: + code = _stable_error_code(exc) + pytest.fail( + f"list_folder failed [{code}] — " + "check NEXTCLOUD_URL, NEXTCLOUD_USER, NEXTCLOUD_APP_PASSWORD in .env." + ) + + actual_names = frozenset(item.name for item in listing.items) + actual_kinds = {item.name: item.kind for item in listing.items} + + # Exact set equality (T-12.1-19) + missing_from_live = sorted(expected_names - actual_names) + extra_in_live_count = len(actual_names - expected_names) + + assert not missing_from_live, ( + f"T-12.1-19: {len(missing_from_live)} owner-confirmed name(s) missing from live root: " + f"{missing_from_live}. " + "Unexpected live names are withheld (T-12.1-20)." + ) + assert extra_in_live_count == 0, ( + f"T-12.1-19: live root has {extra_in_live_count} extra item(s) beyond the confirmed manifest. " + "Unexpected live names are withheld (T-12.1-20)." + ) + + # Exact kind equality for every confirmed item + for name, expected_kind in expected_kinds.items(): + live_kind = actual_kinds.get(name) + assert live_kind == expected_kind, ( + f"T-12.1-19: item {name!r} — expected kind {expected_kind!r}, got {live_kind!r}" + ) + + # Assert no secrets leaked + captured = capfd.readouterr() + _assert_no_secrets_in_string(captured.out) + _assert_no_secrets_in_string(captured.err)