Skip to content

Live-cloud file-series round-trip test (#304) - #305

Merged
stevevanhooser merged 2 commits into
mainfrom
claude/cloud-file-series-round-trip
Sep 14, 2026
Merged

stevevanhooser merged 2 commits into
mainfrom
claude/cloud-file-series-round-trip

Conversation

@stevevanhooser

Copy link
Copy Markdown
Contributor

Closes #304. Ports NDI-matlab's FileSeriesRoundTripTest.testMembersSurviveADownloadFromTheCloud[WithSyncFilesFalse] -- the acceptance test the closing note on #300 flagged as missing.

Why this test

The retracted retirement PRs (VH-Lab/NDI-matlab#989 / #301) got all the way to green on the mocked path while breaking the actual read: without the manifest's second ndicloud location or an equivalent mechanism, NDI's download_file_from_cloud refuses any sourcePath that does not start with ndic://, DID reads a handler that produces nothing as a plain miss, and every member of a downloaded series reads as absent -- silently. NDI-matlab caught this on FileSeriesRoundTripTest. NDI-python had no equivalent until now.

Both offline suites (tests/test_cloud_series_reconstruction.py, tests/test_cloud_series_reconstruction_remote.py) mock fetch_cloud_file or install a mock handler. They pin the reconstruction shape but cannot detect a real regression in the resolve path. This file closes that gap.

What it does

Module-scoped setup (once per test run):

  1. Authenticate.
  2. Build a small local dataset with one demoNDISeries document, chunkdata.bin naming four members of distinct bytes (via did.document.Document.add_file_series, which ndi_document does not wrap).
  3. uploadDataset(sync_files=True) into a fresh cloud dataset named NDI_PYTEST_FILE_SERIES_<uuid>.
  4. waitForAllBulkUploads so server-side zip extraction is finished before anything reads.
  5. Best-effort teardown deletes the cloud dataset with when="now".

Two acceptance tests:

  • test_members_survive_a_download_from_the_cloud -- SyncFiles=true. Downloads into a fresh temp dir, asserts the downloaded manifest names the same uids in the same slots (so a reshuffle would resolve every member to the wrong file), then opens each member end-to-end and compares bytes byte-for-byte against the uploaded content. Batch counters: uid_misses == 0 and signer_calls == 1.

  • test_members_survive_a_download_from_the_cloud_with_sync_files_false -- SyncFiles=false. Precondition: no member is on this machine yet (an install would mask the fetch this test exists to exercise). Opens each member end-to-end through the fetch-manifest-through-handler path Fetch a remote-only series manifest through the custom_file_handler (#87) VH-Lab/DID-python#88 + PR Rebuild series ingest_locations on SyncFiles=false download (#302) #303 laid; compares bytes byte-for-byte. Batch counters: uid_misses == 0. signer_calls is left loose because the server may answer both manifest and members from one whole-document scope or use a series-scoped one on the first member, and pinning one over the other would guess at server behaviour this suite does not otherwise cover (matches NDI-matlab#988's comment).

Notes

  • Both tests clear ndi.cloud.batch_signed_url.get_default() before the open loop, so the uid_misses assertion measures this test's opens and nothing else. Without that, a run of per-uid getFileDetails could quietly do the work (NDI-matlab#968).
  • Bytes compared per-member, not by length. All members have the same 64-byte length by construction; the differences are in content. Two members swapped by a uid mixup would come back the right size and the wrong file.
  • Cleanly skips when NDI_CLOUD_USERNAME / NDI_CLOUD_PASSWORD are not set. The scheduled test-cloud-api.yml workflow picks the file up by the test_cloud_*.py glob; the linux test lane in ci.yml also runs it with the same credentials injected on every push.

Verification

Locally: pytest tests/test_cloud_file_series_round_trip.py -v collects 2 tests and skips both cleanly without credentials. The doc-building half of the fixture was smoke-tested against a real local ndi_dataset_dir (four members added via did.Document.add_file_series; manifest reads back with the four uids; a local database_openbinarydoc("chunkdata.bin_1") returns the right bytes).

ruff check and black --check clean on the new file.

References

🤖 Generated with Claude Code

https://claude.ai/code/session_01AdGZ8fRPeY9JSpUHgXm9ao


Generated by Claude Code

A Python analog of NDI-matlab's FileSeriesRoundTripTest -- the acceptance
test the closing note on #300 flagged as
missing. It would have caught the retracted retirement's regression
(NDI-matlab#989 / NDI-python#301) independently of the mocked path:
without the manifest's second ndicloud location or an equivalent
mechanism, every member of a downloaded series reads as absent because
DID reads a handler that produces nothing as a plain miss.

Uploads a fresh cloud dataset holding one demoNDISeries document with
four members of distinguishable bytes, waits for server-side bulk
extraction, and reads each member back out through:

  1. SyncFiles=true: the manifest arrives with its bytes; each member
     open goes through the DID custom_file_handler to fetch by uid. One
     signer_call, no per-uid fallbacks.

  2. SyncFiles=false: neither manifest nor members come down with the
     dataset. The manifest itself is fetched through the handler
     (VH-Lab/DID-python#88 / VH-Lab/DID-matlab#201) and its
     ingest_locations are rebuilt on the way in by
     updateFileInfoForRemoteFiles (VH-Lab/NDI-matlab#988, ported in
     NDI-python#302 / PR #303). No per-uid fallbacks; signer_calls is
     left loose because the server may answer both manifest and
     members from one whole-document scope or ask for a series-scoped
     one on the first member.

Byte-for-byte per-slot comparison, not length. Two members swapped by a
uid mixup -- the failure this path is most prone to, since a member is
addressed by uid the whole way through -- come back the right size and
the wrong file, so a length check would catch nothing.

Uses ndi.cloud.batch_signed_url.get_default().clear() before each open
loop to make the uid_misses assertion measure this test's opens and
nothing else. Without that, a run of per-uid getFileDetails could
quietly do the work and the test would pass having proved nothing about
the path this design exists for (NDI-matlab#968).

Best-effort teardown deletes the cloud dataset with when=now.

Closes #304.

Refs: VH-Lab/NDI-matlab#986, VH-Lab/NDI-matlab#966, VH-Lab/NDI-matlab#987,
      VH-Lab/NDI-matlab#988, VH-Lab/NDI-matlab#989 (retracted),
      VH-Lab/DID-matlab#185, VH-Lab/DID-matlab#201, VH-Lab/DID-python#88,
      #300, #302.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdGZ8fRPeY9JSpUHgXm9ao
The CI test accounts share a quota, and the ingest lane may already
have run out of upload slots or dataset-creation privileges by the
time this test runs (test (3.12) tripped on HTTP 400 "This user does
not have any more uploads available, please purchase another."). Both
failure modes come back from createDataset as HTTP 400 with a message
that names the reason -- neither is a defect this test can detect or
repair, so skip rather than fail.

Same shape test_cloud_live.py uses through its ``can_write`` fixture.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdGZ8fRPeY9JSpUHgXm9ao
@stevevanhooser
stevevanhooser merged commit b48d841 into main Sep 14, 2026
9 checks passed
@stevevanhooser
stevevanhooser deleted the claude/cloud-file-series-round-trip branch September 14, 2026 23:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Live-cloud round-trip test: a downloaded series' members must be readable (SyncFiles=true and =false)

2 participants