Compare commits
2
Commits
d02d5db7c3
...
d452dee3f7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d452dee3f7 | ||
|
|
b00218e5c5 |
+19
-19
@@ -1,6 +1,6 @@
|
||||
# Phase 12.1: Fix Nextcloud Root Listing and Sync Visibility — Validation Matrix
|
||||
|
||||
**Status:** PARTIAL external live gate. Credential-free focused suites pass, but CLOUD-01 live root/exact acceptance and live connection-ID browse remain environment-dependent until the three Nextcloud variables are available in the backend container.
|
||||
**Status:** PARTIAL external live gate. Three of four live checks pass using credentials from the ignored `.env`; exact root-manifest acceptance is blocked by one additional live root item pending owner reconciliation.
|
||||
**Updated:** 2026-06-22
|
||||
|
||||
---
|
||||
@@ -9,11 +9,11 @@
|
||||
|
||||
| Metric | Count |
|
||||
|--------|-------|
|
||||
| Gaps found | 1 |
|
||||
| Resolved | 0 |
|
||||
| Gaps found | 2 |
|
||||
| Resolved | 1 |
|
||||
| Escalated to manual-only | 1 |
|
||||
|
||||
Current automated evidence: 236 focused backend tests and 89 focused frontend tests pass. The only outstanding gate is the four-test live Nextcloud suite documented below.
|
||||
Current live evidence: 3 tests pass and 1 exact-manifest test fails safely. The stale read-only assertion was repaired to verify that `list_folder` does not call byte or mutation operations even when later phases expose those capabilities. The only outstanding gate is owner reconciliation of one additional live root item.
|
||||
|
||||
---
|
||||
|
||||
@@ -24,7 +24,7 @@ Current automated evidence: 236 focused backend tests and 89 focused frontend te
|
||||
| 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: Root exact acceptance | Live exact | PARTIAL / manual-only | `test_nextcloud_expected_root_manifest` — exact set and kind equality currently blocks on one additional live item |
|
||||
| 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 |
|
||||
@@ -35,20 +35,21 @@ Current automated evidence: 236 focused backend tests and 89 focused frontend te
|
||||
|
||||
### Nyquist audit result (2026-06-22)
|
||||
|
||||
No additional credential-free behavioral test can validate the remaining gap. Existing synthetic-fixture contract tests can prove parsing, normalization, trusted identity, and no-byte/no-mutation behavior, while mocked API tests can prove connection-ID ownership and response shape. They cannot prove that the external Nextcloud account is reachable, accepts the configured credentials, currently has the owner-confirmed exact root manifest, or traverses the production network path through the connection-ID endpoint.
|
||||
The live credentials are present in the ignored repository-root `.env` and were passed only to the one-off backend test process. Connectivity, metadata listing, credential redaction, connection-ID browse, and owner/admin isolation all pass. Existing synthetic-fixture tests continue to cover parsing, normalization, trusted identity, and credential-free no-byte/no-mutation behavior.
|
||||
|
||||
The live gate therefore remains **manual-only/environment-dependent**, not covered. A fixture-schema test or a mocked replay of the confirmed manifest would be tautological and would not close CLOUD-01 live acceptance.
|
||||
The remaining exact-manifest gap is **manual-only/external-state-dependent**. The live root contains one additional item beyond the owner-confirmed fixture. Its name is intentionally withheld (T-12.1-20). The fixture must not be changed and exact equality must not be weakened until the project owner identifies the item in the Nextcloud UI and confirms whether it belongs in the authoritative manifest.
|
||||
|
||||
Actual command and result:
|
||||
|
||||
```text
|
||||
docker compose exec -T backend pytest -q -rs tests/test_nextcloud_live.py -m live_nextcloud
|
||||
ssss [100%]
|
||||
SKIPPED [4] Live Nextcloud credentials not configured.
|
||||
4 skipped in 0.75s
|
||||
docker compose exec -T -e NEXTCLOUD_URL -e NEXTCLOUD_USER -e NEXTCLOUD_APP_PASSWORD backend pytest -q -rs tests/test_nextcloud_live.py -m live_nextcloud
|
||||
...F [100%]
|
||||
3 passed, 1 failed in 13.49s
|
||||
|
||||
Failure: exact-manifest gate detected 1 additional live root item; name withheld.
|
||||
```
|
||||
|
||||
Required rerun condition: provide `NEXTCLOUD_URL`, `NEXTCLOUD_USER`, and `NEXTCLOUD_APP_PASSWORD` to the backend container through the ignored local environment, then rerun the command above. No credential values belong in commands, logs, fixtures, or this artifact.
|
||||
Required rerun condition: reconcile the additional item in the Nextcloud UI, update the fixture only after explicit owner confirmation if appropriate, then rerun the command above. No credential values belong in commands, logs, fixtures, or this artifact.
|
||||
|
||||
### Marker
|
||||
|
||||
@@ -76,25 +77,24 @@ If any variable is absent, all live tests skip with a message naming only the va
|
||||
|
||||
| Test | Purpose |
|
||||
|------|---------|
|
||||
| `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, and browsing invokes no byte/mutation operation |
|
||||
| `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
|
||||
|
||||
The network guard integrated in the test design blocks these HTTP methods before dispatch:
|
||||
`GET` (byte content), `PUT`, `POST`, `PATCH`, `DELETE`, `MKCOL`, `MOVE`, `COPY`.
|
||||
|
||||
Only `PROPFIND`, `OPTIONS`, and `HEAD` are permitted. Mutation methods are asserted absent
|
||||
on the adapter object. No MinIO, quota, or object-storage change is made.
|
||||
The live adapter test installs fail-closed spies on adapter byte/mutation methods and the
|
||||
underlying WebDAV client's download, upload, create-directory, and delete methods before
|
||||
calling `list_folder`. Later phases may expose these capabilities, but metadata browse must
|
||||
not invoke them. No MinIO, quota, or object-storage change is made.
|
||||
|
||||
### Threat coverage
|
||||
|
||||
| Threat ID | Mitigation | Test |
|
||||
|-----------|-----------|------|
|
||||
| T-12.1-16 | Credentials/full URL excluded from output | `_assert_no_secrets_in_string` on captured output; no values in parametrize IDs, assertions, or exceptions |
|
||||
| T-12.1-17 | No byte download or mutation | `_NetworkMethodGuard`; mutation method absence assertion |
|
||||
| T-12.1-17 | No byte download or mutation | Fail-closed adapter and WebDAV-client spies around `list_folder` |
|
||||
| T-12.1-18 | Designated regular user only; IDOR/admin negatives | `test_nextcloud_connection_id_browse_as_designated_user` |
|
||||
| T-12.1-19 | Count-only acceptance blocked | Exact name gate deferred to Task 2 checkpoint |
|
||||
| T-12.1-20 | Unexpected names never printed | `print(f"Unexpected item count: {unexpected_count} (names withheld)")` |
|
||||
|
||||
@@ -27,6 +27,7 @@ import logging
|
||||
import os
|
||||
import re
|
||||
import uuid as _uuid
|
||||
from contextlib import ExitStack
|
||||
from typing import Optional
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
@@ -199,15 +200,37 @@ async def test_nextcloud_adapter_read_only_root_metadata(capfd):
|
||||
code = _stable_error_code(exc)
|
||||
pytest.skip(f"Adapter construction failed [{code}].")
|
||||
|
||||
# Assert no byte or mutation methods are accessible on the adapter
|
||||
for method_name in ("get_object", "put_object", "delete_object",
|
||||
"upload_to", "download_from", "copy", "move",
|
||||
"create_folder", "rename"):
|
||||
assert not callable(getattr(type(adapter), method_name, None)), (
|
||||
f"T-12.1-17: adapter exposes mutation method {method_name!r}"
|
||||
# Later phases may legitimately add byte and mutation capabilities to the
|
||||
# adapter. Phase 12.1's invariant is behavioral: metadata browsing must not
|
||||
# invoke any of them. Guard both the adapter boundary and its WebDAV client
|
||||
# so an accidental call fails before network dispatch (T-12.1-17).
|
||||
forbidden_adapter_methods = ("get_object", "put_object", "delete_object")
|
||||
forbidden_client_methods = ("download_from", "upload_to", "mkdir", "clean")
|
||||
guards = [
|
||||
patch.object(
|
||||
adapter,
|
||||
method_name,
|
||||
side_effect=AssertionError(
|
||||
f"T-12.1-17: list_folder called forbidden adapter method {method_name!r}"
|
||||
),
|
||||
)
|
||||
for method_name in forbidden_adapter_methods
|
||||
]
|
||||
guards.extend(
|
||||
patch.object(
|
||||
adapter._client,
|
||||
method_name,
|
||||
side_effect=AssertionError(
|
||||
f"T-12.1-17: list_folder called forbidden WebDAV method {method_name!r}"
|
||||
),
|
||||
)
|
||||
for method_name in forbidden_client_methods
|
||||
)
|
||||
|
||||
try:
|
||||
with ExitStack() as stack:
|
||||
for guard in guards:
|
||||
stack.enter_context(guard)
|
||||
listing = await adapter.list_folder(
|
||||
connection_id=connection_id,
|
||||
user_id=user_id,
|
||||
|
||||
Reference in New Issue
Block a user