test(12.1): owner-confirmed expected root manifest
- 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
This commit is contained in:
+16
-12
@@ -1,6 +1,6 @@
|
|||||||
# Phase 12.1: Fix Nextcloud Root Listing and Sync Visibility — Validation Matrix
|
# 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
|
**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` |
|
| 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: 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 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` |
|
| 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` |
|
| 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 |
|
| 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_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_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_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
|
### 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
|
The project owner reconciled the three fixture discrepancies (2026-06-22). The correct actual
|
||||||
exactly matched in the prior probe are:
|
Nextcloud names for the three previously unmatched slots are:
|
||||||
|
|
||||||
- `Manual Nextcloud.png`
|
- Corrected name 1: kind=file (prior candidate name was wrong — owner confirmed)
|
||||||
- `Nextcloud Intro.mp4`
|
- Corrected name 2: kind=file (prior candidate name was wrong — owner confirmed)
|
||||||
- `Template credits.md`
|
- 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 full manifest of 10 items is now stored in
|
||||||
the fixture `backend/tests/fixtures/cloud/nextcloud_expected_root.json` is created and
|
`backend/tests/fixtures/cloud/nextcloud_expected_root.json` with `owner_confirmed: true`.
|
||||||
`test_nextcloud_expected_root_manifest` is enabled.
|
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
|
`test_nextcloud_expected_root_manifest` is now enabled and requires exact set equality and
|
||||||
appear in any committed artifact, log, or response.
|
exact kind equality against the confirmed fixture.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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"}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -509,3 +509,113 @@ async def test_nextcloud_connection_id_browse_as_designated_user(
|
|||||||
captured = capfd.readouterr()
|
captured = capfd.readouterr()
|
||||||
_assert_no_secrets_in_string(captured.out)
|
_assert_no_secrets_in_string(captured.out)
|
||||||
_assert_no_secrets_in_string(captured.err)
|
_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)
|
||||||
|
|||||||
Reference in New Issue
Block a user