Skip to content

test: add unit test suite and CI for S3 backup sidecar - #24

Merged
DynamiteC merged 7 commits into
mainfrom
claude/test-coverage-analysis-3LvhZ
Jun 7, 2026
Merged

DynamiteC merged 7 commits into
mainfrom
claude/test-coverage-analysis-3LvhZ

Conversation

@DynamiteC

Copy link
Copy Markdown
Owner

Introduce the first automated tests in the repo, covering the sidecar
sync logic in src/sidecar/main.py (88% line coverage):

  • upload_file: DRY_RUN gating, S3 argument contract, missing-client
    no-op, and exception swallowing
  • run_sync_cycle: manifest+segment upload, cross-cycle dedup, new
    segment detection, prefix-collision isolation (match1 vs match10),
    multi-app isolation, non-directory skipping, and dedup-set pruning

Adds pytest config, dev requirements, and a GitHub Actions workflow
that runs the suite with coverage on pushes and PRs.

claude added 7 commits June 6, 2026 21:08
Introduce the first automated tests in the repo, covering the sidecar
sync logic in src/sidecar/main.py (88% line coverage):

- upload_file: DRY_RUN gating, S3 argument contract, missing-client
  no-op, and exception swallowing
- run_sync_cycle: manifest+segment upload, cross-cycle dedup, new
  segment detection, prefix-collision isolation (match1 vs match10),
  multi-app isolation, non-directory skipping, and dedup-set pruning

Adds pytest config, dev requirements, and a GitHub Actions workflow
that runs the suite with coverage on pushes and PRs.
SRS emits both DASH and HLS, but the sidecar only backed up .mpd/.m4s,
leaving HLS streams with no disaster-recovery coverage.

Generalize run_sync_cycle to scan both packaging formats, keeping
segment matching type-aware so a .mpd manifest claims only .m4s
segments and a .m3u8 manifest only .ts segments (even when both share
a stream-key prefix). The cross-cycle dedup prune now also tracks .ts
files so HLS segments aren't re-uploaded every cycle.

Adds tests for HLS manifest+segment upload, DASH/HLS coexistence
without cross-claiming, and HLS dedup across cycles.
Segments were added to the uploaded_files dedup set *before* the upload
ran, and upload_file swallowed all exceptions. A transient S3 error
therefore marked a segment permanently uploaded, so it was never
retried -- silent gaps in the backup.

upload_file now returns success/failure, and run_sync_cycle records a
segment as uploaded only after its upload actually succeeds; failures
are retried on the next cycle. Adds a regression test plus return-value
assertions for the DRY_RUN, no-client, and exception paths.
get_playback_urls returned bare URLs, but the nginx edge enforces
secure_link and returned 403 for every playback request -- streams
never played.

Add streaming_console.secure_link (Frappe-free, unit-tested) to mint
nginx-compatible tokens and have get_playback_urls append token/expires
to the HLS and DASH URLs. The secure_link hash is bound only to the
expiry, not $uri or $remote_addr: segmented-media players don't carry
the manifest's per-URI signature onto segment requests, so one token
must authorise the whole session, and dropping $remote_addr keeps links
valid behind a CDN and across client IP changes.

- nginx.conf: secure_link_md5 now hashes "$secure_link_expires SECRET"
- docker-compose: pass SECRET_KEY to control-plane so it matches the edge
- studio/stream.html: re-apply the token to video.js segment requests
- tests: secure_link suite with an nginx golden-vector token; wire the
  control-plane package into pytest with scoped coverage
SRS can fire on_publish more than once per session (reconnects,
retries) while on_unpublish fires once, so current_load ratcheted
upward and never returned to zero -- eventually get_best_node saw every
node at capacity and threw 'No capacity available' on an idle fleet.

- on_publish now only transitions a stream into Live (and increments
  load) on the first call; repeat calls are a no-op.
- on_unpublish only decrements on the first transition out of Live.
- Both hooks share _adjust_node_load(), a single atomic UPDATE clamped
  with GREATEST(0, ...), replacing the inconsistent raw-SQL increment vs
  get_doc().save() decrement.
STOP_EVENT existed but nothing ever set it, so a container stop killed
the process (and any in-flight uploads) immediately. Register SIGTERM/
SIGINT handlers that set STOP_EVENT, and drain the executor on exit so
in-progress uploads finish before the process leaves.

Adds tests for the signal handler and the loop's stop-and-drain path.
- Add healthchecks for redis, timescaledb, control-plane, and nginx.
- Gate startup on readiness: control-plane waits for redis/timescaledb
  to be healthy, and srs waits for the control-plane to be healthy so
  the first on_publish webhook isn't a connection-refused.
- .env.example: replace the shippable-looking default SECRET_KEY with an
  obvious change_me placeholder and document how to generate one.
@DynamiteC
DynamiteC merged commit 93cd3cc into main Jun 7, 2026
1 check passed
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.

2 participants