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:
@@ -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()
|
||||
_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)
|
||||
|
||||
Reference in New Issue
Block a user