Skip to content

Sync develop → master (post-v0.17.0)#467

Merged
nitrobass24 merged 45 commits into
masterfrom
develop
May 6, 2026
Merged

Sync develop → master (post-v0.17.0)#467
nitrobass24 merged 45 commits into
masterfrom
develop

Conversation

@nitrobass24
Copy link
Copy Markdown
Owner

@nitrobass24 nitrobass24 commented May 5, 2026

Brings 35 commits from develop to master. No new user-facing features beyond what shipped in v0.17.0 — this batch is dominated by test coverage, CI rework, image slimming, and dependency bumps.

⚠️ Pre-merge checklist (per CLAUDE.md release process):

  • Bump version in src/angular/package.json (currently still 0.17.0)
  • Add a [X.Y.Z] - YYYY-MM-DD entry in CHANGELOG.md summarizing the changes below
  • Update MODERNIZATION_PLAN.md if applicable
  • Tag vX.Y.Z after merge so CI publishes the multi-arch image

Highlights

User-facing

Test coverage expansion

CI / infrastructure

Bug fixes

Dependency bumps

Full commit log

35 commits — see the Commits tab. Notable PR numbers above.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Expanded Settings UI (Staging, Extraction, Integrity, Notifications, Sonarr/Radarr, API key auth); restart prompts now shown only for options that require it.
    • LFTP tuning can be reloaded at runtime; auto-queue patterns now persist across restarts.
  • Chores

    • CI/tooling updated (Python 3.13); Playwright E2E moved to pinned container runs; legacy test Docker stages removed.
    • Version bumped to 0.18.0 and various dependency updates.
  • Tests

    • Large additions of unit, integration, and Playwright E2E coverage.
  • Documentation

    • README, badges, volumes table, and CHANGELOG updated.

nitrobass24 and others added 30 commits April 30, 2026 19:13
Delete src/docker/test/ directory (python/ and angular/ subdirs).
These Dockerfiles are not referenced by CI, Makefile, or any script.
The python Dockerfile references a non-existent base image
(seedsync/run/python/devenv). Same cleanup as f7d654f which removed
the e2e test directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch from python:3.12-slim-bookworm (Debian) to python:3.13-alpine
so the test image matches the production Alpine base. Replace apt-get
with apk, groupadd/useradd with addgroup/adduser.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…erfiles

Remove stale Docker test infrastructure
Merge the python-deps and runtime stages into a single
python:3.13-alpine stage, eliminating the separate alpine:3.21 base
and the COPY --from=python-deps step.

- Upgrade Python 3.12 → 3.13
- Remove 6 dead stdlib cleanup entries (distutils, lib2to3, audioop,
  ossaudiodev, nis, _crypt — all removed from CPython)
- Replace pip self-uninstall with direct rm -rf of pip/setuptools
- Update CI python-version from 3.12 to 3.13 across all jobs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrite test-image Dockerfile as Alpine with Python 3.13
…lpine

Collapse Dockerfile to 2 stages with Python 3.13-alpine
* Use RUN --mount for uv to avoid 49 MB ghost layer

Mount the uv binary from ghcr.io/astral-sh/uv:0.11 during RUN instead
of COPY-ing it into a layer. The COPY+delete pattern left a 49.4 MB
layer in the image even though uv was removed afterward. The mount
approach keeps uv out of all committed layers entirely.

Image size: 114 MB → 64 MB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Restore 3-stage build and use RUN --mount for uv

The 2-stage collapse in PR 3 caused a size regression (45 MB → 114 MB)
because stdlib cleanup in a RUN layer can't reclaim space from the base
image's Python layer. Restore the intermediate python-deps stage so
COPY --from only brings stripped files into the final alpine image.

Combined with RUN --mount for uv (avoids 49 MB ghost layer), the final
image is 48 MB — smaller than the original 45 MB thanks to the Alpine
3.23 upgrade and Python 3.13 stdlib removals.

Image size: 114 MB → 48 MB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Strip additional unused Python artifacts from runtime image

Remove C headers (/usr/local/include, 2.4 MB), build config
(config-3.13-*, 284 KB), REPL enhancements (_pyrepl, 240 KB),
decimal fallback (_pydecimal.py, 224 KB), test extension modules
(xx*.so, 204 KB), and ~100 unused encoding codepages (~1.2 MB).

Keeps only UTF-8, ASCII, Latin-1, UTF-16/32, charmap, IDNA, and
unicode escape encodings — all that LC_ALL=C.UTF-8 needs.

Image size: 48 MB → 44 MB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Remove apk and unused SSH binaries from runtime image

Strip the package manager (apk, libapk, /etc/apk, /lib/apk) and
unused openssh binaries (ssh-keyscan, ssh-add, ssh-agent,
ssh-pkcs11-helper, ssh-copy-id) plus utility binaries (scanelf,
c_rehash, iconv, getconf, ssl_client, findssl.sh) from the final
image. The app only needs ssh, scp, and sftp.

Prevents installing packages inside a running container and reduces
attack surface.

Image size: 44 MB → 42.5 MB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Remove ssh moduli, exclude ruff cache from build context

Remove /etc/ssh/moduli (568 KB) — only needed by sshd (server), not
the ssh client. Add .ruff_cache to .dockerignore to exclude 1.3 MB of
linter cache from COPY src/python/.

Image size: 42.5 MB → 41.5 MB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Exclude dev artifacts from runtime image

Add .pytest_cache, pyrightconfig.json to .dockerignore. Remove
pyproject.toml and uv.lock from /app/python/ after COPY (they're
needed in the python-deps stage but not at runtime).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Exclude typings/ stubs from runtime image

The typings/ directory contains .pyi type stubs used only by Pyright
during development. Not needed at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Remove missed .so modules, venv, and libapk from runtime

Add to python-deps cleanup: _tkinter.so (tkinter dir was removed but
not the .so), _xxtestfuzz.so (test module), _lsprof.so (profiler),
_interp*.so (sub-interpreters), and venv/ directory.

Add /usr/lib/libapk.so* to runtime cleanup (missed in apk removal).

Image size: 41.3 MB → 40.9 MB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…t-reload (#433)

* Add requiresRestart flag to differentiate config restart notifications

Only show the "restart required" notification for settings that truly
need a restart (server, staging, extract, scan intervals, autoqueue
core, logging, web port/API key). Settings like notifications,
validation, exclude patterns, verbose logging, LFTP tuning, and
auto_delete_remote take effect immediately and no longer trigger
the restart banner.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Flush configs to disk immediately after REST mutations (#427 Phase 1)

Add file paths to Context and pass them through WebAppBuilder to each
REST handler. After every successful mutation (config set, path pair
CRUD, integration CRUD, auto-queue add/remove), the handler now calls
to_file() to persist immediately instead of waiting for the 30-second
timer. The periodic flush remains as a safety net for ControllerPersist.

Test infrastructure updated to create a temp directory for flush paths
and clean up in tearDown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add LFTP hot-reload: reapply tuning settings without restart (#427 Phase 3)

Add threading.Event reconfigure flag to Controller with a public
request_lftp_reconfigure() method. In process(), when the flag is set,
clear it and re-call _configure_lftp() on all pair contexts.

ConfigHandler gets an optional on_lftp_config_change callback and a
_LFTP_TUNING_KEYS set (14 keys covering parallelism, connections, rate
limits, timeouts, etc.). After a successful set_property on an lftp
tuning key, the callback fires to signal reconfiguration.

WebAppBuilder wires controller.request_lftp_reconfigure into
ConfigHandler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Use (section, key) tuples for LFTP tuning keys; add verbose and xfer_verify

Change _LFTP_TUNING_KEYS from a flat set of key names to a frozenset
of (section, key) tuples so the lookup is section-aware. Add
("general", "verbose") and ("validate", "xfer_verify") which also
feed into configure_lftp via the Controller.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix Pyright errors and add validate.algorithm to LFTP hot-reload keys

- Assert context paths are non-None in WebAppBuilder to satisfy Pyright
  str vs str|None type check
- Add ("validate", "algorithm") to _LFTP_TUNING_KEYS since it affects
  xfer:verify-command in configure_lftp()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Replace assert with explicit RuntimeError for context path checks

Asserts are stripped with python -O. Explicit RuntimeError with
descriptive messages is more robust and Pyright can narrow the types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add E2E tests for file actions and error states

- file-actions.spec.ts: 11 tests covering file row selection, action
  button visibility, disabled states, delete confirmation double-click
  pattern, selection switching, and bulk action bar presence
- error-states.spec.ts: 5 tests covering no-enabled-pairs warning
  notification, notification text/level, restart notification on
  config change, and re-enabling a pair clears the warning

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix E2E test failures and address CodeRabbit review comments

- error-states: Extract disableAllPairs/restorePairs helpers that always
  ensure a disabled pair exists (fixes all 4 failures -- the backend only
  sets noEnabledPairs when pairs exist but none are enabled)
- error-states: Assert res.ok on every API setup/teardown call
- error-states: Move temp pair cleanup into finally via restorePairs helper
- error-states: Scope restart notification locator to #header .alert
- settings: Remove separate clear() before fill() to avoid Angular signal
  re-render race; add blur() and toBeEnabled wait; increase poll timeout
- settings.page: Scope getRestartNotification() to #header .alert
- file-actions: Assert .actions container is hidden after deselection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Skip noEnabledPairs E2E tests when server is not fully configured

The noEnabledPairs notification requires status.server.up === true,
which only happens when the server has valid config (remote address,
paths, etc.). CI containers start with incomplete config so the
controller never evaluates pair state. Skip these tests when the
server reports as not up, matching the existing skip pattern used
in dashboard.spec.ts for file-dependent tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add Python integration tests to CI

Add a new CI job that runs the web handler integration tests
(tests/integration/test_web). These use webtest.TestApp with mocks
and don't require external dependencies (lftp, SSH).

The heavier integration tests (test_lftp, test_controller) that
require real lftp/SSH are excluded — they need the Docker test
container which is a separate concern.

Gate the publish job on this new job passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix pre-existing integration test failures exposed by CI

- test_auto_queue: use resp.text/resp.json instead of resp.html
  (handlers now return text/plain and application/json, not text/html)
- test_stream_*: increase Timer delays from 0.5s to 2.0s to prevent
  race conditions where the stop timer fires before update timers
  on slow CI runners

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover all four security subsystems in web/security.py:
- CSRF protection: safe method exemption, loopback bypass, origin/referer
  matching, port normalization, proxy header detection
- Rate limiting: per-IP tracking, limit enforcement, stale sweep, SSE
  exemption, disable flag, X-Forwarded-For extraction
- Security headers: nosniff, X-Frame-Options, CSP, Referrer-Policy
- API key auth: enabled/disabled states, exempt paths, SSE query param,
  header validation

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover four previously untested modules:
- pair_context: PairContext init state, validate_config (missing fields,
  extract path logic), configure_lftp (mandatory/optional settings, xfer_verify)
- active_scanner: empty scan, set_active_files, queue draining, missing
  file debug logging, unexpected error warning logging
- local_scanner: missing path handling, successful scan, ScannerError,
  temp file suffix
- delete_process: local file/dir deletion, path traversal blocking,
  remote SSH command construction, tilde path escaping

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add web app job and context unit tests (14 tests)

Cover two previously untested modules:
- web_app_job: setup creates server and starts thread, execute calls
  process, cleanup stops server and joins thread, never-initialized
  server logs warning, request logging middleware logs method/path/status
- context: create_child_context returns copy with child logger,
  print_to_log redacts sensitive fields, handles present/absent path
  pairs, Args.as_dict serializes all fields as strings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix assertLogs/assertRaises nesting in error logging test

assertLogs must be the outer context so it can validate captured log
records on normal exit. With assertRaises as the outer context, the
RuntimeError propagated through assertLogs preventing its validation
from running — making the log assertion a no-op.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Expand handler integration tests (+15 tests)

Add integration tests to three existing handler test files:
- test_server (+5): response body text, 200 status, state transition
  False->True, restart idempotency, state persistence
- test_status (+5): full response structure (server + controller sections),
  null scan times initially, no_enabled_pairs reflects actual state
- test_stream_log (+5): QueueLogHandler attach/remove, multiple handlers
  independent operation, CachedQueueLogHandler delivers history, cache=0
  sends no historical records

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix ruff format: add missing blank line between classes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add missing status assertions and fix stream_log docstring

- test_status: assert latest_remote_scan_failed and
  latest_remote_scan_error are present in controller response
  (both are emitted by the serializer but were not checked)
- test_stream_log: fix docstring that incorrectly described
  QueueLogHandler connection behavior

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Test NameFilterCriteria (null/empty match-all, case-insensitive substring,
space-dot fuzzy matching), StatusFilterCriteria (null match-all, specific
status filtering), AndFilterCriteria (both-match/either-fails), and
service integration (option changes trigger setFilterCriteria, redundant
values suppressed).

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- integrations.page.ts: Page object scoped to .integrations container
  with helpers for form filling, instance rows, edit/delete/test buttons
- integrations.spec.ts: 10 tests covering empty state, add form with
  kind pre-selection, validation error on empty name, create/edit/delete
  flows with API verification, double-click delete confirmation, test
  connection, enable/disable toggle, and cancel without creating

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AutoQueueService: 14 tests covering connect/disconnect pattern fetch,
empty/whitespace/duplicate pattern rejection, successful add with
double-encoding, server rejection preserving state, remove filtering,
and graceful degradation on GET failure.

PathPairsService: 11 tests covering connect/disconnect pair fetch,
create POST with append, 409 conflict rethrow, update PUT with replace,
remove DELETE with filter, and error handling.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…448)

FileOptionsComponent: 8 tests covering status filter enablement based
on file list contents, reactive updates, and delegation to
ViewFileOptionsService.

IntegrationsComponent: 12 tests covering add/edit/cancel forms, empty
name validation, service create delegation, double-click delete
confirmation with 3-second timeout reset, ngOnDestroy timer cleanup,
and error handling (null response, 409 conflict).

OptionComponent: 9 tests covering debounce pipe logic (1000ms delay,
rapid changes emit only final value, distinctUntilChanged), password
REDACTED_SENTINEL suppression, and effectiveChoices computed signal.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HeaderComponent: 12 tests covering server-down DANGER notification,
recovery hiding, waiting-for-scan INFO, remote-scan-failure WARNING,
no-enabled-pairs WARNING, text replacement, duplicate suppression,
rule coexistence, and cleanup on recovery.

VersionCheckService: 8 tests covering newer/same/older version
detection, v-prefix stripping, network error graceful degradation,
malformed JSON handling, and URL inclusion in notification text.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Cache npm deps via setup-node cache option
- Use npm ci instead of npm install for deterministic installs
- Cache Playwright browser binaries (~150 MB) in ~/.cache/ms-playwright
- On cache hit, only install system deps (apt packages) since browsers
  are already cached
- Cache key includes package-lock.json hash so it invalidates on
  Playwright version bumps

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Playwright caching added in #452 references src/e2e-playwright/package-lock.json, but the root .gitignore was filtering all package-lock.json files except the angular and website ones. With no lockfile in the repo, actions/setup-node failed at "Some specified paths were not resolved, unable to cache dependencies" and the npm ci step would have failed too.

Add an allow-list entry for src/e2e-playwright and check in the generated lockfile.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add static badges showing test counts: 828 Python (697 unit + 131
integration), 412 Angular, 95 E2E. Update Python badge from 3.12
to 3.13 to match the Docker image upgrade.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Expand E2E settings tests with 8 new tests

Add coverage for previously untested settings sections:
- Staging Directory: path field save, use staging checkbox toggle
- Archive Extraction: extract in downloads directory toggle
- Integrity Check: verify checkbox + algorithm select presence, algorithm save
- Connections: max parallel downloads field save
- Notifications: Discord/Telegram webhook field presence
- Logging: log level dropdown persistence after page reload

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix E2E: use apiSetConfig for cleanup instead of page interaction

The hash algorithm select test's finally block used UI interaction
(selectOption) to restore the original value, but the page/browser
may already be torn down by that point. Switch to apiSetConfig which
makes a direct HTTP call and doesn't depend on the page being open.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix E2E: enable integrity check before testing algorithm select

The Hash Algorithm select is disabled when xfer_verify is false
(the default). Enable it via API before testing, and restore the
original state in the finally block.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix E2E: set xfer_verify before navigation, use waitForStream

Set the xfer_verify config via API BEFORE navigating to the settings
page, so the page loads with the select already enabled. Use
waitForStream to ensure SSE config delivery before interacting.
Increase toBeEnabled timeout to 10s for CI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix E2E: wait for config value before asserting select enabled

The algorithm select is disabled when its value is null/undefined
(option.component.html). Wait for the SSE config delivery to populate
the select value before asserting it's enabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Updates the Features list with file integrity validation, Discord/Telegram/webhook notifications, Sonarr/Radarr integration, staging directory, and API key auth. Moves the hard-link Recommended Workflow up to follow Quick Start, adds Configuration subsections for the new settings, and lists /staging in the volumes table.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix flaky Hash Algorithm select wait condition

The Integrity Check select test waited with not.toHaveValue("") for the
config to arrive via SSE, but Angular renders selects bound to a null
model with a non-empty "? null:null ?" placeholder option. That caused
the assertion to pass while the model was still null, after which
toBeEnabled would deterministically fail because value() === null still
disables the select.

Wait on a real algorithm value (md5|sha1|sha256) instead. The
toBeEnabled check is left in place as an explicit confirmation of the
gate the comment describes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Wait for SSE-delivered value before asserting persisted log level

After reloading the settings page, the test asserted on the Log Level
select's value with only the default 5 s expect timeout. Recent CI runs
showed this test finishing at ~4.2 s, leaving little headroom — and the
margin shrinks once Playwright workers go up.

settings.goto() only waits for Angular's sidebar render (before SSE
config delivery), so toBeEnabled is the right gate: the disable
condition is `value() === null || value() === undefined`, so an enabled
select implies the model has received the SSE value. This mirrors the
pattern used in the Hash Algorithm test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Use validate.enabled to unlock Hash Algorithm select in test

The original setup set validate.xfer_verify = True under the assumption
that this enabled the algorithm select. It does not. The disable rule
lives in buildValidateContext() in settings-page.component.ts and is
gated on config.validate.enabled (post-download validation), not on
xfer_verify (inline transfer verification). So the select stayed
disabled regardless of the SSE-delivered model value, and toBeEnabled
deterministically failed.

Switch the setup to toggle validate.enabled, and reorder the assertions
so toBeEnabled (the slower wait, since it depends on both SSE delivery
AND the dynamic context rebuild) gates the toHaveValue check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Run Build and Test on develop pushes and bump Playwright workers to 5

Two CI throughput improvements rolled up.

1. Drop the develop-push skip on Build and Test (amd64). With it in place
   the job never ran on develop merges, so the actions/cache step never
   populated a develop-scoped Playwright browser cache. PRs targeting
   develop fall back to looking at their own branch (empty for fresh
   PRs) and at develop (also empty), and pay the full browser+system-deps
   install cost every run. Letting the job run on develop pushes warms
   the cache once per merge so subsequent PRs hit it.

2. Set workers: 5 in playwright.config.ts. Default on a 4-vCPU runner is
   ~2 workers, which the user observed as "two tests at a time". Bumping
   to 5 lets five spec files run in parallel. fullyParallel stays false,
   so within a file tests still run serially.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Update playwright.config.ts

* Move pair-disable test to path-pairs.spec.ts to avoid cross-file race

settings.spec.ts:176 created and deleted a temporary path pair to
verify the Server Directory field becomes disabled. With workers > 2
that test ran in parallel with path-pairs.spec.ts, whose beforeEach
and afterEach delete every existing pair to start from a clean slate.
The result was a 404 on cleanup ("Failed to delete temp pair … 404")
and intermittently the assertion itself, depending on which spec's
hook fired first.

Move the test into path-pairs.spec.ts so it shares the same per-file
serialized cleanup. No other spec creates pairs from outside its own
describe block, which removes the cross-file pair race entirely.
integrations.spec.ts has the same nuclear-cleanup pattern but no other
spec creates integrations, so it isn't affected today — flagging it
as a pattern to watch as we add more cross-cutting tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Drop Playwright workers from 10 to 4 to match runner CPU count

ubuntu-latest has 4 vCPUs. With workers: 10 we ran 10 chromium
processes plus the SeedSync container on those 4 cores, ~2.5x CPU
oversubscription. The suite ran slower overall — context-switching
overhead and contention on the shared backend dominated any
parallelism gain. With fullyParallel: false the per-file serialization
also caps useful parallelism at the spec count, and the longest spec
(settings) sets the floor regardless of worker count.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Drop Playwright workers from 4 to 3

Leave one of the runner's 4 vCPUs free for the SeedSync Docker
container and runner overhead instead of saturating all four with
chromium worker processes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Run E2E in official Playwright container; drop workers to 2

Replace the host-installed Playwright pipeline with a single docker run
of mcr.microsoft.com/playwright:v<lockfile-version>-noble. That image
ships chromium plus all system deps prebaked, so we eliminate:

  - Set up Node.js (cache: npm)
  - Install Playwright dependencies (npm ci on host)
  - Cache Playwright browsers (actions/cache)
  - Install Playwright browsers (npx playwright install)
  - Install Playwright system deps (apt install fonts/xfonts)

The version pin is read from package-lock.json with jq so a future
@playwright/test bump auto-bumps the image tag — the chromium binary
in the image must match the @playwright/test API expectations.

Container-to-SUT networking uses --network host so localhost:8800
still resolves to the seedsync test-container started in the previous
step.

Also drop workers from 3 to 2: with the Playwright image now running
as another process group on the same 4-vCPU runner, the previous
"leave one core for runner overhead" math becomes "leave two cores
for runner + Playwright container coordination."

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Bump Playwright workers back to 4 for the new container baseline

Trying 4 (one per vCPU) now that the system-deps install is gone and
chromium runs inside the official Playwright image instead of on the
host. If we see contention or test races we can drop again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Test setUp methods across the suite added a fresh StreamHandler to a
shared logger (mostly the root logger) and never removed it. Because
loggers are singletons, by test N the logger held N handlers and each
log line printed N times. Test correctness wasn't affected — no
assertion depends on handler count — only output noise.

Use unittest's addCleanup hook to remove the handler after each test.
addCleanup runs even if setUp partially fails after the registration,
so the cleanup is robust against initialization errors.

Touches 10 setUp methods across 8 files (TestAppProcess /
TestAppOneShotProcess and TestDeleteLocalProcess /
TestDeleteRemoteProcess each have their own setUp).

Closes #450

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps [@docusaurus/preset-classic](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-preset-classic) from 3.10.0 to 3.10.1.
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.1/packages/docusaurus-preset-classic)

---
updated-dependencies:
- dependency-name: "@docusaurus/preset-classic"
  dependency-version: 3.10.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@docusaurus/faster](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-faster) from 3.10.0 to 3.10.1.
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.1/packages/docusaurus-faster)

---
updated-dependencies:
- dependency-name: "@docusaurus/faster"
  dependency-version: 3.10.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…463)

Bumps the angular group in /src/angular with 10 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/cdk](https://github.com/angular/components) | `21.2.8` | `21.2.9` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.2.10` | `21.2.11` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.2.10` | `21.2.11` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.2.10` | `21.2.11` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.2.10` | `21.2.11` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.2.10` | `21.2.11` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.2.10` | `21.2.11` |
| [@angular/build](https://github.com/angular/angular-cli) | `21.2.8` | `21.2.9` |
| [@angular/cli](https://github.com/angular/angular-cli) | `21.2.8` | `21.2.9` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `21.2.10` | `21.2.11` |


Updates `@angular/cdk` from 21.2.8 to 21.2.9
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](angular/components@v21.2.8...v21.2.9)

Updates `@angular/common` from 21.2.10 to 21.2.11
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/common)

Updates `@angular/compiler` from 21.2.10 to 21.2.11
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/compiler)

Updates `@angular/core` from 21.2.10 to 21.2.11
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/core)

Updates `@angular/forms` from 21.2.10 to 21.2.11
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/forms)

Updates `@angular/platform-browser` from 21.2.10 to 21.2.11
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/platform-browser)

Updates `@angular/router` from 21.2.10 to 21.2.11
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/router)

Updates `@angular/build` from 21.2.8 to 21.2.9
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](angular/angular-cli@v21.2.8...v21.2.9)

Updates `@angular/cli` from 21.2.8 to 21.2.9
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](angular/angular-cli@v21.2.8...v21.2.9)

Updates `@angular/compiler-cli` from 21.2.10 to 21.2.11
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.11/packages/compiler-cli)

---
updated-dependencies:
- dependency-name: "@angular/cdk"
  dependency-version: 21.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/common"
  dependency-version: 21.2.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/compiler"
  dependency-version: 21.2.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/core"
  dependency-version: 21.2.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/forms"
  dependency-version: 21.2.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/platform-browser"
  dependency-version: 21.2.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/router"
  dependency-version: 21.2.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/build"
  dependency-version: 21.2.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/cli"
  dependency-version: 21.2.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/compiler-cli"
  dependency-version: 21.2.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
dependabot Bot added 6 commits May 3, 2026 19:04
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.59.0 to 8.59.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.59.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [jsdom](https://github.com/jsdom/jsdom) from 29.0.2 to 29.1.1.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](jsdom/jsdom@v29.0.2...v29.1.1)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-version: 29.1.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@docusaurus/module-type-aliases](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases) from 3.10.0 to 3.10.1.
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.1/packages/docusaurus-module-type-aliases)

---
updated-dependencies:
- dependency-name: "@docusaurus/module-type-aliases"
  dependency-version: 3.10.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
)

Bumps [eslint](https://github.com/eslint/eslint) from 10.2.1 to 10.3.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](eslint/eslint@v10.2.1...v10.3.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@docusaurus/types](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-types) from 3.10.0 to 3.10.1.
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.1/packages/docusaurus-types)

---
updated-dependencies:
- dependency-name: "@docusaurus/types"
  dependency-version: 3.10.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…462)

Bumps [@docusaurus/core](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus) from 3.10.0 to 3.10.1.
- [Release notes](https://github.com/facebook/docusaurus/releases)
- [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/docusaurus/commits/v3.10.1/packages/docusaurus)

---
updated-dependencies:
- dependency-name: "@docusaurus/core"
  dependency-version: 3.10.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 755ac84b-5028-477f-af50-76213abbe3d7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

This PR upgrades Python CI from 3.12 to 3.13, implements persistent configuration with hot-reload for LFTP settings via Context/handler path parameters and Controller signaling, removes legacy Docker test images, migrates E2E tests to Docker-based Playwright execution, expands Angular/Python test coverage with unit/integration tests, adds requiresRestart flag to settings, and updates Angular/Docusaurus dependencies.

Changes

Configuration Persistence & Runtime LFTP Reconfiguration

Layer / File(s) Summary
Data Shape
src/python/common/context.py
Context constructor accepts optional path parameters (config_path, path_pairs_path, integrations_path, auto_queue_persist_path, controller_persist_path) and stores them as instance attributes.
Core Reconfiguration
src/python/controller/controller.py
Controller adds __needs_lftp_reconfigure threading.Event and request_lftp_reconfigure() public method; process() checks the event, clears it, and reapplies LFTP configuration to all pair contexts on each cycle.
Handler Persistence
src/python/web/handler/config.py, src/python/web/handler/path_pairs.py, src/python/web/handler/integrations.py, src/python/web/handler/auto_queue.py
Each handler constructor accepts explicit *_path parameters; after successful create/update/delete operations, handlers persist updated configs via .to_file(...) before returning responses. Config handler also conditionally invokes on_lftp_config_change() callback for LFTP tuning settings.
Wiring & Integration
src/python/web/web_app_builder.py, src/python/seedsync.py
WebAppBuilder validates required context path attributes and wires handlers with explicit paths and ConfigHandler callback (controller.request_lftp_reconfigure); Seedsync populates context path fields during service construction.
Settings UI
src/angular/src/app/pages/settings/options-list.ts, src/angular/src/app/pages/settings/settings-page.component.ts, src/angular/src/app/pages/settings/settings-page.component.html
IOption interface adds optional requiresRestart?: boolean field; multiple server/autoqueue/staging/logging options marked with requiresRestart: true; SettingsPage conditionally shows restart notification only when flag is truthy.
Tests & Validation
src/python/tests/integration/test_web/test_web_app.py, src/python/tests/integration/test_web/test_handler/test_server.py, src/python/tests/integration/test_web/test_handler/test_status.py
Integration tests verify config/path-pairs/integrations/auto-queue endpoint behavior, status response structure with new no_enabled_pairs field, and restart request handling.

Testing Infrastructure, Coverage & CI Modernization

Layer / File(s) Summary
CI/Docker Foundation
.github/workflows/ci.yml, src/docker/build/docker-image/Dockerfile, src/docker/build/test-image/Dockerfile, src/docker/build/docker-image/Dockerfile.dockerignore
Python upgraded from 3.12 to 3.13 across lint/typecheck/test jobs; new python-integration-test job runs pytest tests/integration/test_web; final runtime base switched to alpine:3.23; test image uses Alpine + mocked uv; Playwright moved to Docker-based execution with version-pinned container tag from lockfile; removed legacy src/docker/test/angular and src/docker/test/python build targets.
Playwright E2E Expansion
src/e2e-playwright/playwright.config.ts, src/e2e-playwright/tests/error-states.spec.ts, src/e2e-playwright/tests/file-actions.spec.ts, src/e2e-playwright/tests/integrations.spec.ts, src/e2e-playwright/tests/settings.spec.ts, src/e2e-playwright/tests/path-pairs.spec.ts, src/e2e-playwright/tests/pages/integrations.page.ts, src/e2e-playwright/tests/pages/settings.page.ts, .gitignore
Added workers: 4 config; new error-states, file-actions, and integrations test suites; expanded settings tests (Staging, Extraction, Integrity Check, Connections, Notifications, Logging); new IntegrationsPage object; refined SettingsPage locator scoping; unignored src/e2e-playwright/package-lock.json.
Angular Component Tests
src/angular/src/app/pages/files/file-options.component.spec.ts, src/angular/src/app/pages/main/header.component.spec.ts, src/angular/src/app/pages/settings/integrations.component.spec.ts, src/angular/src/app/pages/settings/option.component.spec.ts
New Vitest suites validating FileOptionsComponent filter enablement, HeaderComponent notification precedence/lifecycle, IntegrationsComponent CRUD/confirmation UI, OptionComponent debounce/redaction behavior.
Angular Service Tests
src/angular/src/app/services/autoqueue/autoqueue.service.spec.ts, src/angular/src/app/services/files/view-file-filter.service.spec.ts, src/angular/src/app/services/settings/path-pairs.service.spec.ts, src/angular/src/app/services/utils/version-check.service.spec.ts
New Vitest suites testing AutoQueueService state/persistence, ViewFileFilterService name/status/combined filtering, PathPairsService CRUD/HTTP error handling, VersionCheckService version comparison and notification dispatch.
Python Unit Tests
src/python/tests/unittests/test_common/test_context.py, src/python/tests/unittests/test_common/test_app_process.py, src/python/tests/unittests/test_controller/test_pair_context.py, src/python/tests/unittests/test_controller/test_delete/test_delete_process.py, src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py, src/python/tests/unittests/test_controller/test_scan/test_local_scanner.py, src/python/tests/unittests/test_lftp/test_lftp.py, src/python/tests/unittests/test_ssh/test_sshcp.py, src/python/tests/unittests/test_web/test_security.py, src/python/tests/unittests/test_web/test_web_app_job.py
New test modules covering Context creation/logging/serialization, PairContext initialization/validation/LFTP config, DeleteLocalProcess/DeleteRemoteProcess filesystem/SSH operations, ActiveScanner/LocalScanner file enumeration, comprehensive web security (CSRF, rate limiting, headers, API key auth), WebAppJob threading/server setup.
Python Integration Tests
src/python/tests/integration/test_controller/test_controller.py, src/python/tests/integration/test_lftp/test_lftp.py, src/python/tests/integration/test_web/test_handler/test_auto_queue.py, src/python/tests/integration/test_web/test_handler/test_stream_log.py, src/python/tests/integration/test_web/test_handler/test_stream_model.py, src/python/tests/integration/test_web/test_handler/test_stream_status.py
Added/updated integration test handler cleanup, response parsing (JSON vs. HTML), stream server timing adjustments (0.5s → 2.0s), CachedQueueLogHandler history behavior, multi-handler independence.
Test Cleanup Infrastructure
src/python/tests/integration/test_controller/test_controller.py, src/python/tests/integration/test_lftp/test_lftp.py, src/python/tests/unittests/test_common/test_app_process.py, src/python/tests/unittests/test_lftp/test_lftp.py, src/python/tests/unittests/test_ssh/test_sshcp.py, src/python/tests/integration/test_web/test_web_app.py
Standardized logging handler cleanup via self.addCleanup(logger.removeHandler, handler) and temp-directory cleanup in web app tests to prevent handler accumulation and resource leaks.

Dependency Updates & Documentation

Layer / File(s) Summary
Angular Dependency Bumps
src/angular/package.json
Angular runtime (@angular/*) bumped from 21.2.8/21.2.10 to 21.2.9/21.2.11; dev tools (@angular/build, @angular/cli, @angular/compiler-cli) updated similarly; eslint 10.0.3→10.3.0, typescript-eslint 8.59.0→8.59.1, jsdom 29.0.1→29.1.1.
Docusaurus & Documentation
website/package.json, README.md
Docusaurus packages (@docusaurus/core, @docusaurus/preset-classic, faster, module-type-aliases, types) bumped from 3.10.0 to 3.10.1; README badges updated for Python 3.13, test counts; Features section expanded; Configuration docs added for Notifications, Sonarr/Radarr, Staging, API Key auth; Volumes table updated for /staging mount.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • nitrobass24/seedsync#456: Modifies CI workflow build-test condition and Playwright worker config, directly overlaps on gating and E2E orchestration.
  • nitrobass24/seedsync#433: Implements the same config persistence feature (Context paths, handler file-writing, requiresRestart UI, Controller hot-reload) with matching code-level changes.
  • nitrobass24/seedsync#444: Adds identical Python unit test modules for PairContext, ActiveScanner, LocalScanner, and DeleteProcess behavior validation.

Suggested labels

infrastructure, testing, documentation, angular, python, ci

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/angular/package.json (1)

3-3: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Version field must be bumped before merge.

The version field is still 0.17.0. The pre-merge checklist in this PR explicitly requires a version bump here, and the coding guidelines require this field to reflect the release version (used on the About page).

🔖 Proposed fix
-  "version": "0.17.0",
+  "version": "0.18.0",

(Adjust the target version to whatever this release is being tagged as.)

As per coding guidelines: "Update the version in src/angular/package.json to match the release version for display on the About page."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/angular/package.json` at line 3, The package.json "version" field is
still set to "0.17.0"; update that "version" property in package.json to the
correct release version (the value this PR will be tagged with) so the About
page shows the proper release number—replace "0.17.0" with the new release
string (e.g., "0.18.0" or whatever the target release is).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/angular/src/app/pages/settings/option.component.spec.ts`:
- Around line 15-16: Export the existing private debounce constant from
option.component.ts as a top-level exported const named DEBOUNCE_TIME_MS
(alongside existing exports like OptionType and OptionValue), then update this
spec to import DEBOUNCE_TIME_MS instead of redefining it; ensure the component
uses the exported const internally (replace the private constant usage in the
component with the exported symbol) so the test and component share a single
source of truth.
- Around line 90-125: Tests that call vi.useFakeTimers() inline can leak fake
timers if an assertion fails; add a suite-level cleanup by adding afterEach(()
=> vi.useRealTimers()) to this spec so real timers are always restored, and
remove the per-test vi.useRealTimers() calls inside the three it blocks that
currently call vi.useFakeTimers(); keep the existing vi.useFakeTimers() calls in
the tests ('should suppress REDACTED_SENTINEL for password type', 'should pass
real password value through to Subject', 'should not suppress REDACTED_SENTINEL
for non-password types') and leave component.onChange and OptionType usage
unchanged.

In `@src/angular/src/app/services/settings/path-pairs.service.spec.ts`:
- Around line 197-212: Rename the spec to reflect actual behavior: change the
test name from 'should preserve existing list when refresh fails' to something
like 'should clear pairs when refresh fails (catchError emits empty array)';
update any inline comment to remove the contradictory wording and reference the
implemented behavior in PathPairsService (the refresh() call and its
catchError(of([])) path that leads to pairsSubject.next([]) and the pairs$
observable); ensure the assertion still checks expect(result).toEqual([]) and
that the test continues to subscribe to service.pairs$ to capture the emitted
empty array.

In `@src/angular/src/app/services/utils/version-check.service.spec.ts`:
- Around line 66-77: The test in the spec uses a hardcoded release string;
update the “should not show notification when release is same version” test to
import packageJson and pass the actual app version to makeGithubResponse (use
'v' + packageJson.version) when stubbing mockRestService.sendRequest so the test
continues to validate "same version" behavior; locate the test around the
makeGithubResponse call and the
createService()/notificationService.notifications$ usage and swap the hardcoded
'v0.17.0' for the dynamic 'v'+packageJson.version import.

In `@src/docker/build/docker-image/Dockerfile`:
- Line 17: The FROM line uses the floating tag "python:3.13-alpine" which can
pull a different Alpine minor than the runtime; update the Dockerfile's builder
stage reference (the FROM instruction currently using python:3.13-alpine) to the
pinned image "python:3.13-alpine3.23" so the Python builder and the pinned
alpine:3.23 runtime share the same Alpine minor version.

In `@src/docker/build/test-image/Dockerfile`:
- Around line 6-9: The Dockerfile RUN line uses the floating image tag
ghcr.io/astral-sh/uv:0.11 which can change with future 0.11.x patches; update
that reference in the RUN
--mount=from=ghcr.io/astral-sh/uv:0.11,source=/uv,target=/tmp/uv to a pinned
patch release (e.g., ghcr.io/astral-sh/uv:0.11.8) or, for maximum
reproducibility, replace it with the image digest (SHA256) form so the mounted
/tmp/uv content is deterministic during builds.

In `@src/e2e-playwright/tests/integrations.spec.ts`:
- Around line 8-17: The setup currently continues when the list or delete API
calls fail; change test.beforeEach (and the similar beforeAll/other hook) to
fail fast by checking response.ok after the GET to "/server/integrations" and
after each DELETE to "/server/integrations/${inst.id}" and throwing an Error
(including status/code and body or statusText) when a response is not ok so the
test stops immediately instead of proceeding with stale state; locate the logic
in test.beforeEach, the apiFetch calls, and the loop over inst to add these
checks.

In `@src/e2e-playwright/tests/settings.spec.ts`:
- Around line 222-225: Replace the race-prone clear()+fill() sequence with the
stable pattern: await field.toBeEnabled(); await field.fill(testValue); await
field.blur(); i.e., remove calls to field.clear() and instead ensure the input
is enabled then fill and blur to trigger save; apply this change to both places
where field.clear(); field.fill(...) is used (the new occurrence and the other
use around the same test in settings.spec.ts).

In `@src/python/tests/integration/test_web/test_handler/test_status.py`:
- Around line 15-55: Replace instances of json.loads(str(resp.html)) with
resp.json in the tests (e.g., in test_full_response_structure,
test_remote_scan_time_null_initially, test_local_scan_time_null_initially,
test_no_enabled_pairs_reflects_state, test_no_enabled_pairs_true_when_set) so
each GET /server/status call uses resp.json to parse the response; after
updating all occurrences, remove the now-unused module-level import json if it
has no other uses.

In `@src/python/tests/unittests/test_common/test_context.py`:
- Around line 13-15: The helper _make_context() is leaking StreamHandler
instances by calling logging.getLogger("test_context") then creating and adding
handler; remove the creation/attachment of handler (the three lines creating
handler and logger.addHandler(handler)) so the function does not modify the
shared logger, or if you must attach, register cleanup with logger.removeHandler
via self.addCleanup(logger.removeHandler, handler); also remove the now-unused
import sys; reference _make_context(), logging.getLogger("test_context"),
handler, and TestCreateChildContext when locating the change.

In `@src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py`:
- Around line 26-30: Tests create ActiveScanner instances (e.g., scanner =
ActiveScanner("/local")) but only call scanner.close() at the end, so failures
can leak resources; immediately register cleanup after construction using the
test framework's cleanup mechanism (call self.addCleanup(scanner.close) right
after each ActiveScanner(...) creation) so close() runs even if assertions
fail—apply this change to every test in this file that constructs ActiveScanner
(instances referenced by symbol scanner in the tests at the shown locations).

In `@src/python/tests/unittests/test_web/test_web_app_job.py`:
- Around line 14-24: The _make_context method attaches a StreamHandler to the
shared logger "test_web_app_job" and never removes it, leaking handlers across
tests; modify _make_context to register cleanup that removes the handler after
each test (e.g., call self.addCleanup(logger.removeHandler, handler) or
self.addCleanup(lambda: logger.removeHandler(handler)) immediately after
logger.addHandler(handler)), or alternatively use logger.handlers manipulation
to ensure the handler is removed in tearDown; reference the _make_context
function, the logger variable and handler so the handler is removed after each
test run.

In `@src/python/web/handler/auto_queue.py`:
- Around line 47-49: The code mutates state via
self.__auto_queue_persist.add_pattern/remove_pattern and then writes with
self.__auto_queue_persist.to_file(self.__persist_path) but does not catch IO
errors; wrap the persistence call in a try/except that catches Exception (or
specific IO errors), log the exception (including the error object) and return a
controlled HTTP 5xx response (e.g., HTTPResponse(status=500, body="failed to
persist auto-queue")) instead of letting the exception bubble; apply this change
around the places that call add_pattern + to_file and remove_pattern + to_file
so both mutating endpoints handle persistence failures gracefully.

In `@src/python/web/handler/config.py`:
- Around line 76-79: Do not mutate self.__config before persistence; instead
create a copy of the section/config (referencing inner_config and
self.__config), apply inner_config.set_property(key, value) on the copy, attempt
to write the updated config to disk via
self.__config.to_file(self.__config_path) (or write to a temp file and
atomically replace), and only if the write succeeds replace self.__config with
the updated config and then call self.__on_lftp_config_change() if present;
ensure exceptions during to_file prevent in-memory changes and avoid calling the
LFTP callback.

In `@src/python/web/handler/integrations.py`:
- Around line 113-117: The code detaches the integration via
self.__path_pairs_config.detach_arr_target(instance_id) but then writes
self.__config.to_file before persisting self.__path_pairs_config.to_file,
risking dangling references if the process crashes; change the save order so you
call self.__path_pairs_config.to_file(self.__path_pairs_path) immediately after
detach_arr_target (persisting the detached path-pairs), then update/write the
integrations config via self.__config.to_file(self.__integrations_path) and
return the HTTPResponse; keep the same methods
(__path_pairs_config.detach_arr_target, __path_pairs_config.to_file, and
__config.to_file) and paths (__path_pairs_path, __integrations_path).

In `@src/python/web/handler/path_pairs.py`:
- Around line 117-118: The persistence calls using
self.__config.to_file(self.__path_pairs_path) (invoked in the
create/update/delete handlers that return
HTTPResponse(body=json.dumps(pair.to_dict()), status=201, ...) and similar
returns) must be wrapped in try/except to catch failures and return a
deterministic 5xx response; modify the handlers that call self.__config.to_file
(and the code paths that currently return status 201 or 204) to try to write,
and on exception log the error and return HTTPResponse(status=500,
body=json.dumps({"error":"failed to persist path pairs"}),
headers={"Content-Type":"application/json"}) so that persistence failures do not
surface as unhandled request errors.

---

Outside diff comments:
In `@src/angular/package.json`:
- Line 3: The package.json "version" field is still set to "0.17.0"; update that
"version" property in package.json to the correct release version (the value
this PR will be tagged with) so the About page shows the proper release
number—replace "0.17.0" with the new release string (e.g., "0.18.0" or whatever
the target release is).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 76f0d793-ceb4-424d-b10f-063ed4ceca7d

📥 Commits

Reviewing files that changed from the base of the PR and between af22c75 and 660f99b.

⛔ Files ignored due to path filters (3)
  • src/angular/package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
  • src/e2e-playwright/package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
  • website/package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
📒 Files selected for processing (60)
  • .github/workflows/ci.yml
  • .gitignore
  • README.md
  • src/angular/package.json
  • src/angular/src/app/pages/files/file-options.component.spec.ts
  • src/angular/src/app/pages/main/header.component.spec.ts
  • src/angular/src/app/pages/settings/integrations.component.spec.ts
  • src/angular/src/app/pages/settings/option.component.spec.ts
  • src/angular/src/app/pages/settings/options-list.ts
  • src/angular/src/app/pages/settings/settings-page.component.html
  • src/angular/src/app/pages/settings/settings-page.component.ts
  • src/angular/src/app/services/autoqueue/autoqueue.service.spec.ts
  • src/angular/src/app/services/files/view-file-filter.service.spec.ts
  • src/angular/src/app/services/settings/path-pairs.service.spec.ts
  • src/angular/src/app/services/utils/version-check.service.spec.ts
  • src/docker/build/docker-image/Dockerfile
  • src/docker/build/docker-image/Dockerfile.dockerignore
  • src/docker/build/test-image/Dockerfile
  • src/docker/test/angular/Dockerfile
  • src/docker/test/angular/compose.yml
  • src/docker/test/python/Dockerfile
  • src/docker/test/python/compose.yml
  • src/docker/test/python/entrypoint.sh
  • src/e2e-playwright/playwright.config.ts
  • src/e2e-playwright/tests/error-states.spec.ts
  • src/e2e-playwright/tests/file-actions.spec.ts
  • src/e2e-playwright/tests/integrations.spec.ts
  • src/e2e-playwright/tests/pages/integrations.page.ts
  • src/e2e-playwright/tests/pages/settings.page.ts
  • src/e2e-playwright/tests/path-pairs.spec.ts
  • src/e2e-playwright/tests/settings.spec.ts
  • src/python/common/context.py
  • src/python/controller/controller.py
  • src/python/seedsync.py
  • src/python/tests/integration/test_controller/test_controller.py
  • src/python/tests/integration/test_lftp/test_lftp.py
  • src/python/tests/integration/test_web/test_handler/test_auto_queue.py
  • src/python/tests/integration/test_web/test_handler/test_server.py
  • src/python/tests/integration/test_web/test_handler/test_status.py
  • src/python/tests/integration/test_web/test_handler/test_stream_log.py
  • src/python/tests/integration/test_web/test_handler/test_stream_model.py
  • src/python/tests/integration/test_web/test_handler/test_stream_status.py
  • src/python/tests/integration/test_web/test_web_app.py
  • src/python/tests/unittests/test_common/test_app_process.py
  • src/python/tests/unittests/test_common/test_context.py
  • src/python/tests/unittests/test_controller/test_delete/__init__.py
  • src/python/tests/unittests/test_controller/test_delete/test_delete_process.py
  • src/python/tests/unittests/test_controller/test_pair_context.py
  • src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py
  • src/python/tests/unittests/test_controller/test_scan/test_local_scanner.py
  • src/python/tests/unittests/test_lftp/test_lftp.py
  • src/python/tests/unittests/test_ssh/test_sshcp.py
  • src/python/tests/unittests/test_web/test_security.py
  • src/python/tests/unittests/test_web/test_web_app_job.py
  • src/python/web/handler/auto_queue.py
  • src/python/web/handler/config.py
  • src/python/web/handler/integrations.py
  • src/python/web/handler/path_pairs.py
  • src/python/web/web_app_builder.py
  • website/package.json
💤 Files with no reviewable changes (5)
  • src/docker/test/python/entrypoint.sh
  • src/docker/test/python/compose.yml
  • src/docker/test/angular/Dockerfile
  • src/docker/test/python/Dockerfile
  • src/docker/test/angular/compose.yml

Comment thread src/angular/src/app/pages/settings/option.component.spec.ts Outdated
Comment thread src/angular/src/app/services/settings/path-pairs.service.spec.ts Outdated
Comment thread src/angular/src/app/services/utils/version-check.service.spec.ts
Comment thread src/docker/build/docker-image/Dockerfile Outdated
Comment thread src/docker/build/test-image/Dockerfile
Comment thread src/python/tests/unittests/test_web/test_web_app_job.py
Comment thread src/python/web/handler/auto_queue.py
Comment thread src/python/web/handler/config.py
Comment thread src/python/web/handler/integrations.py
Comment thread src/python/web/handler/path_pairs.py
nitrobass24 and others added 2 commits May 4, 2026 23:14
* Address CodeRabbit test-quality findings on develop

All test-side hardening only — no production code changes. The bigger
production findings (auto_queue/path_pairs/config/integrations error
handling and ordering) are deferred for individual PRs with proper test
coverage rather than bundled here.

- option.component.spec.ts: add suite-level afterEach(useRealTimers) so
  a failing assertion inside vi.useFakeTimers() can't leak fake timers
  into the next test. Removed the per-test useRealTimers() calls that
  the new afterEach makes redundant.

- path-pairs.service.spec.ts: rename "preserve existing list when refresh
  fails" to "clear pairs when refresh fails (catchError emits empty
  array)" — the test body always asserted [] so the old name was
  contradictory.

- version-check.service.spec.ts: import packageJson and use
  'v'+packageJson.version in the same-version test instead of
  hardcoding 'v0.17.0'. Future version bumps no longer break this test.

- integrations.spec.ts: fail fast on API errors in beforeEach and
  afterEach (GET + each DELETE). Stops the test with a clear error
  instead of silently proceeding against unknown state.

- settings.spec.ts: drop redundant field.clear() calls (3 sites). fill()
  already clears before typing; calling clear() separately races with
  Angular's signal-driven re-render. Pattern matches the existing fix
  in "text field change saves to backend".

- test_context.py / test_web_app_job.py: stop attaching a StreamHandler
  in _make_context. assertLogs() in the tests installs its own capture
  handler, so the addHandler call only leaked across tests (loggers are
  singletons). Drops the now-unused import sys too.

- test_active_scanner.py: register self.addCleanup(scanner.close) right
  after each ActiveScanner construction so multiprocessing resources
  always get closed even if assertions raise. Trailing scanner.close()
  calls left in place — Queue.close() is idempotent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Bump version to 0.18.0 and add CHANGELOG entry

Minor bump for the develop → master sync. New user-facing functionality
(LFTP hot-reload + differentiated restart notifications) and a
non-trivial operational improvement (image size from 114 MB to 64 MB)
warrant the minor over a patch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/e2e-playwright/tests/integrations.spec.ts`:
- Around line 349-353: The test silently swallows API errors by turning a failed
response into an empty array — change the logic around apiFetch so the test
fails if the GET to "/server/integrations" is not successful: check res.ok (the
response from apiFetch) and if false throw or assert fail with a clear message
before attempting to parse JSON and inspect instances; update the local symbols
referenced (res, instances, and the call to apiFetch("/server/integrations")) so
the test does not proceed when the HTTP request failed.
- Around line 132-142: The POST fixture setup calls using apiFetch (e.g.,
apiFetch("/server/integrations", { method: "POST", ... })) currently ignore the
HTTP response; capture the response (const res = await apiFetch(...)) and assert
it succeeded before continuing — for example check res.ok or res.status is
200/201 and optionally verify the returned JSON contains the created
resource/id; if the assertion fails, surface a clear error (throw or test
expect) so the test stops with "fixture creation failed" rather than later UI
failures; apply this pattern to every POST-based fixture setup that uses
apiFetch in the spec.

In `@src/e2e-playwright/tests/settings.spec.ts`:
- Around line 472-498: The test calls select.selectOption(newValue) on the Log
Level select before SSE-delivered model may be ready; add an explicit guard by
awaiting expect(select).toBeEnabled({ timeout: 10_000 }) (same pattern used for
the Hash Algorithm test) immediately after obtaining select from
settings.getSelect("Log Level") and before calling
select.selectOption(newValue), keeping the existing finally block that restores
originalLevel.

In `@src/python/tests/unittests/test_common/test_context.py`:
- Around line 71-77: Replace the direct private attribute assignment to
PathPairsConfig._pairs with the public property setter to respect encapsulation
and thread-safety: locate where PathPairsConfig (ppc) is created and a PathPair
instance is appended (the lines using PathPairsConfig and PathPair and the
assignment ppc._pairs = [pair]) and change it to use the pairs property
(ppc.pairs) so the class's locking logic is used instead of touching the private
_pairs field directly.

In `@src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py`:
- Around line 26-31: Tests register scanner.close via self.addCleanup but also
call scanner.close() explicitly at the end, causing double-close of
ActiveScanner (potentially non-idempotent). Remove the redundant explicit
scanner.close() calls from the test methods (e.g., in test_scan and the other
occurrences) and rely solely on self.addCleanup(scanner.close) so
ActiveScanner.close() is invoked once by the test framework; search for
ActiveScanner instantiations and lines with both self.addCleanup(scanner.close)
and a trailing scanner.close() and delete the explicit trailing close calls.

In `@src/python/tests/unittests/test_web/test_web_app_job.py`:
- Around line 105-118: Remove the dead captured_status dictionary and simplify
the start_response closure since captured_status is never asserted; update the
test in test_web_app_job.py by deleting captured_status and changing the
start_response function used with middleware(environ, start_response) to a no-op
(e.g., an empty closure that only accepts status, headers, *args) or add an
assertion that explicitly checks captured_status["status"]; reference the test's
captured_status and start_response to locate the change and ensure the rest of
the assertions against log_ctx.output and middleware result remain unchanged.
- Around line 114-118: The test is brittle because it inspects log_ctx.output[0]
directly; change the three assertions to use an any(...) scan over
log_ctx.output so we check that some log line contains "GET", "/test/path", and
"200" together. Replace the three self.assertIn(...) calls that reference
log_output = log_ctx.output[0] with a single assertion like
self.assertTrue(any("GET" in o and "/test/path" in o and "200" in o for o in
log_ctx.output)) (or equivalent three any(...) checks) to mirror the safer
pattern used elsewhere and reference the same log_ctx.output sequence.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0cf4b05b-2256-4dac-ba22-5f7ffbd98a0c

📥 Commits

Reviewing files that changed from the base of the PR and between 660f99b and 04d6320.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • src/angular/package.json
  • src/angular/src/app/pages/settings/option.component.spec.ts
  • src/angular/src/app/services/settings/path-pairs.service.spec.ts
  • src/angular/src/app/services/utils/version-check.service.spec.ts
  • src/e2e-playwright/tests/integrations.spec.ts
  • src/e2e-playwright/tests/settings.spec.ts
  • src/python/tests/unittests/test_common/test_context.py
  • src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py
  • src/python/tests/unittests/test_web/test_web_app_job.py

Comment thread src/e2e-playwright/tests/integrations.spec.ts Outdated
Comment thread src/e2e-playwright/tests/integrations.spec.ts
Comment thread src/e2e-playwright/tests/settings.spec.ts
Comment thread src/python/tests/unittests/test_common/test_context.py
Comment thread src/python/tests/unittests/test_web/test_web_app_job.py Outdated
Comment thread src/python/tests/unittests/test_web/test_web_app_job.py Outdated
* Address second round of CodeRabbit test-quality findings

All 7 findings from this round are valid; all fixed. Test-only — no
production code changes (the production findings tracked in #469 are
still unaddressed in this PR by design).

- integrations.spec.ts: GET on the "should not exist" assertion now
  fails fast instead of returning [] when the API errors. Five
  POST-fixture sites now check `res.ok` via a small `expectFixtureOk`
  helper so a 4xx/5xx surfaces as "fixture creation failed" instead of
  cascading into a confusing UI failure.

- settings.spec.ts: Log Level select-change test waits for the select
  to be enabled before calling selectOption — same SSE-delivery race
  the Hash Algorithm test had.

- test_context.py: use the public PathPairsConfig.pairs property setter
  (which acquires the internal lock) instead of touching ._pairs.

- test_active_scanner.py: drop the redundant trailing scanner.close()
  calls now that addCleanup handles the close path. Six sites — one
  cleanup mechanism instead of two.

- test_web_app_job.py: drop the unused captured_status dict from the
  middleware logging test, and replace `log_ctx.output[0]` indexing
  with an `any(...)` scan over all captured lines so other loggers
  emitting first don't break the test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ruff format: collapse assertTrue onto a single line

Round-2 commit wrapped the new any(...) assertion across three lines;
ruff format prefers it on one. No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 30-46: The "Tests" section in CHANGELOG.md is non-conforming;
either move the entire bulleted block into the "Added" section of the new
top-version entry (renaming it to e.g. "Added: expanded unit and E2E test
coverage" and keeping the individual bullets) or remove it if tests are
considered internal, and ensure the top-level changelog entry follows the
project's semver/date and allowed sections (Changed, Added, Fixed, Removed,
Security); update the heading and section names accordingly so there is no
standalone "Tests" section.
- Around line 5-24: Reorder the topmost release entry in CHANGELOG.md so the
section headings follow the required order: move the "### Changed" block to
appear before the "### Added" block (ensuring the sequence is Changed, Added,
Fixed, Removed, Security as applicable); update the release header to include
the new semver version and YYYY-MM-DD date if missing and verify headings "###
Changed", "### Added", "### Fixed" remain intact and unchanged otherwise.

In `@src/angular/src/app/services/settings/path-pairs.service.spec.ts`:
- Around line 31-34: The snapshot() helper creates a lasting subscription to
service.pairs$ and never tears it down; change it to consume a single emission
and unsubscribe immediately (e.g., use an RxJS one-shot approach such as piping
service.pairs$ through take(1) or use firstValueFrom, or explicitly store the
Subscription and call unsubscribe) so snapshot() returns the latest PathPair[]
without accumulating live subscribers; update the snapshot() function
accordingly.

In `@src/angular/src/app/services/utils/version-check.service.spec.ts`:
- Around line 151-163: The test dereferences notifications[0] without first
asserting any notification was emitted; update the spec (around the
notificationService.notifications$.subscribe and createService() call) to assert
that notifications has at least one item before indexing (for example add
expect(notifications.length).toBeGreaterThan(0) or
expect(notifications).toHaveLength(1)) so the failure is clear and safe when
checking notifications[0].text; keep references to
notificationService.notifications$, notifications variable, and createService()
when adding the assertion.

In `@src/e2e-playwright/tests/integrations.spec.ts`:
- Around line 255-267: The poll callback used with expect.poll (the async
function calling apiFetch("/server/integrations") and returning
instances.find(...) for the Delete Me check) returns undefined when the fetch
response is non-OK, which falsely satisfies .toBeUndefined(); change the
callback so that on non-ok responses it does NOT return undefined — either throw
an error or return a non-undefined sentinel (e.g. `error:${res.status}`) so the
poll keeps retrying until the integration is actually absent; update the same
anonymous async callback used with expect.poll and keep the final assertion as
.toBeUndefined().

In `@src/e2e-playwright/tests/settings.spec.ts`:
- Line 185: The assertion await expect(field).toBeEnabled() omits an explicit
timeout and can race with SSE-delivered config; update these guards to use the
same explicit timeout used elsewhere in this spec for SSE-gated inputs (e.g.,
await expect(field).toBeEnabled({ timeout: <sameTimeoutValue> })), applying the
change for the occurrences that currently lack it (the asserts using field at
the three noted locations) so they match the established SSE-wait pattern.

In `@src/python/tests/unittests/test_web/test_web_app_job.py`:
- Around line 116-136: The test test_logs_even_on_app_error currently only
checks that something was logged; update it to assert the log content like the
happy-path test: use the same assertLogs("test_request_logging_error",
level="DEBUG") context to capture the log output from the
_RequestLoggingMiddleware invocation and then assert the captured log contains
the HTTP method "POST", the path "/fail", and the status "500" (e.g., via
self.assertIn checks on the logged messages) so the middleware's error branch is
verified for the expected fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5c8ad347-4ea0-43f4-834b-525d4d99f07a

📥 Commits

Reviewing files that changed from the base of the PR and between 660f99b and a958f6a.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • src/angular/package.json
  • src/angular/src/app/pages/settings/option.component.spec.ts
  • src/angular/src/app/services/settings/path-pairs.service.spec.ts
  • src/angular/src/app/services/utils/version-check.service.spec.ts
  • src/e2e-playwright/tests/integrations.spec.ts
  • src/e2e-playwright/tests/settings.spec.ts
  • src/python/tests/unittests/test_common/test_context.py
  • src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py
  • src/python/tests/unittests/test_web/test_web_app_job.py

Comment thread CHANGELOG.md Outdated
Comment thread CHANGELOG.md Outdated
Comment thread src/angular/src/app/services/settings/path-pairs.service.spec.ts
Comment thread src/angular/src/app/services/utils/version-check.service.spec.ts
Comment thread src/e2e-playwright/tests/integrations.spec.ts
Comment thread src/e2e-playwright/tests/settings.spec.ts Outdated
Comment thread src/python/tests/unittests/test_web/test_web_app_job.py
All 6 findings verified valid; all fixed. Test/changelog only.

- CHANGELOG.md: Reorder 0.18.0 sections to Changed → Added → Fixed
  per CLAUDE.md's documented section order. Fold the "Tests" block
  into Added as a single sub-bulleted item — test additions are
  additive content, and the standalone heading wasn't a recognized
  section name.

- path-pairs.service.spec.ts: snapshot() now pipes through take(1)
  before subscribing so the helper doesn't accumulate live
  subscribers on service.pairs$ across test runs.

- version-check.service.spec.ts: Assert notifications.toHaveLength(1)
  before indexing notifications[0].text. A 0-length array would
  otherwise throw a confusing "cannot read property 'text' of
  undefined".

- integrations.spec.ts: poll callback for the "Delete Me" check now
  throws on a non-OK response instead of returning undefined.
  Returning undefined was falsely satisfying toBeUndefined() and
  masking real API failures.

- settings.spec.ts: Three SSE-gated toBeEnabled() calls now use an
  explicit { timeout: 5000 } matching the established text-field
  pattern at line 43, instead of relying on the implicit default.

- test_web_app_job.py: error-branch test now asserts the log line
  contains POST, /fail, and 500 — mirrors the happy-path assertion
  added in round 2 so the middleware's exception path is also
  verified for the expected fields.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The runtime stage is `FROM alpine:3.23` and copies /usr/local from the
python-deps builder verbatim. Leaving the builder on the floating
`python:3.13-alpine` tag risks compiling Python + site-packages C
extensions against a newer Alpine's musl while running on alpine:3.23
— a real ABI-mismatch hazard, not just a reproducibility nit.

Tag verified to exist on Docker Hub via `docker manifest inspect`.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (1)
src/e2e-playwright/tests/settings.spec.ts (1)

114-117: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an explicit enabled wait before changing Log Format.

At Line 117, selectOption("json") is called without a readiness guard. This is the same SSE timing class that previously flaked for other selects in this file. Please gate it with await expect(select).toBeEnabled({ timeout: 10_000 }) before the first selectOption.

#!/bin/bash
# Verify this select interaction currently lacks a readiness guard.
rg -n -C3 'getSelect\("Log Format"\)|selectOption\("json"\)|toBeEnabled\(\{ timeout: 10_000 \}\)' src/e2e-playwright/tests/settings.spec.ts
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/e2e-playwright/tests/settings.spec.ts` around lines 114 - 117, The select
for "Log Format" is being changed before ensuring it is enabled; add a readiness
guard by awaiting expect(select).toBeEnabled({ timeout: 10_000 }) immediately
before the call to select.selectOption("json") so the getSelect("Log Format")
interaction waits for the control to be enabled; target the variable select and
the selectOption("json") call when inserting this check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/docker/build/docker-image/Dockerfile`:
- Around line 24-32: The Dockerfile copies uv.lock but then calls the uv pip
interface which ignores the lockfile (using "uv pip install ... -r
/tmp/pyproject.toml"); change the build step to use a lock-aware flow: either
run "uv sync --locked --no-dev --system" to install the locked environment into
system Python, or run "uv export --frozen -o /tmp/requirements.txt" and then
"pip install --system --no-cache -r /tmp/requirements.txt"; ensure the uv.lock
and /tmp/uv.lock copy remains and remove the temporary requirements file and
uv.lock after install to preserve the original cleanup behavior.

In `@src/e2e-playwright/tests/integrations.spec.ts`:
- Around line 12-14: The shared IntegrationsPage instance and global cleanup in
the describe block cause races under workers: 4; either make the suite run
serially by adding test.describe.configure({ mode: "serial" }) at the top of the
"Integrations CRUD" describe, or make each test create and track a unique
integration name (e.g., append a UUID in beforeEach) and change the afterEach
cleanup to delete only that test's integration instead of wiping all
integrations; update references in the describe block, beforeEach, afterEach and
any uses of integrations to use the per-test name or the serial configuration
accordingly.
- Around line 116-133: In the three poll callbacks used in the "fill and save",
"edit", and "toggle" tests replace the current silent `return undefined` when
`res.ok` is false with throwing an Error (include response.status and a snippet
of response.text or statusText) so the poll surfaces the real API failure
instead of timing out; mimic the delete-test poll behavior by throwing on non-OK
responses in the anonymous async callbacks passed to `.poll(...)` that call
`apiFetch("/server/integrations")`, `apiFetch(...)` in the edit test, and the
one in the toggle test so failing backend calls produce clear error messages.

In `@src/e2e-playwright/tests/settings.spec.ts`:
- Around line 441-451: The test currently only checks visibility for the secret
notification inputs (discordField and telegramTokenField); change each assertion
to verify the input type is masked by adding an assertion that the locator has
attribute type="password" (e.g. replace or augment await
expect(discordField).toBeVisible() and await
expect(telegramTokenField).toBeVisible() with await
expect(discordField).toHaveAttribute("type","password") and await
expect(telegramTokenField).toHaveAttribute("type","password"), ensuring the test
fails if those fields are not rendered as password inputs.

In `@src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py`:
- Around line 80-84: The test test_missing_file_logged_at_debug currently only
checks for the message text and not the log level; update it to assert the
missing-file message is logged specifically at DEBUG by inspecting
log_ctx.output entries (formatted as "LEVEL:logger:message") for an entry that
starts with "DEBUG:ActiveScanner:" and contains "does not exist" (or split each
entry and assert the first token is "DEBUG" and the logger token is
"ActiveScanner" with the message containing the missing-file text) so the
assertion enforces DEBUG-level logging from ActiveScanner.

---

Duplicate comments:
In `@src/e2e-playwright/tests/settings.spec.ts`:
- Around line 114-117: The select for "Log Format" is being changed before
ensuring it is enabled; add a readiness guard by awaiting
expect(select).toBeEnabled({ timeout: 10_000 }) immediately before the call to
select.selectOption("json") so the getSelect("Log Format") interaction waits for
the control to be enabled; target the variable select and the
selectOption("json") call when inserting this check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f6da0f70-f200-410e-9fd8-5931f5df3542

📥 Commits

Reviewing files that changed from the base of the PR and between 660f99b and 7cc5de4.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • src/angular/package.json
  • src/angular/src/app/pages/settings/option.component.spec.ts
  • src/angular/src/app/services/settings/path-pairs.service.spec.ts
  • src/angular/src/app/services/utils/version-check.service.spec.ts
  • src/docker/build/docker-image/Dockerfile
  • src/e2e-playwright/tests/integrations.spec.ts
  • src/e2e-playwright/tests/settings.spec.ts
  • src/python/tests/unittests/test_common/test_context.py
  • src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py
  • src/python/tests/unittests/test_web/test_web_app_job.py

Comment thread src/docker/build/docker-image/Dockerfile Outdated
Comment thread src/e2e-playwright/tests/integrations.spec.ts
Comment thread src/e2e-playwright/tests/integrations.spec.ts
Comment thread src/e2e-playwright/tests/settings.spec.ts Outdated
Comment thread src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py Outdated
Five of six findings actioned; one skipped. Test + Dockerfile only.

- Dockerfile: `uv pip install -r pyproject.toml` was ignoring uv.lock,
  so transitive dep versions could drift between identical rebuilds.
  Now run `uv export --frozen --no-dev` to materialize the lockfile
  into a requirements.txt and install from that. Cleanup includes
  the temp requirements file.

- integrations.spec.ts: Three poll callbacks ("fill and save",
  "edit", "toggle") returned undefined on a non-OK response, which
  would silently mask backend failures and either time out or pass
  on stale data. Now throw with status + statusText, matching the
  pattern already used in the "Delete Me" poll.

- settings.spec.ts (Notifications): Discord and Telegram token
  fields previously only asserted visibility. Added explicit
  toHaveAttribute("type", "password") so a regression flipping the
  input back to type="text" is caught.

- settings.spec.ts (Log Format): Added the same SSE-readiness guard
  used for the other selects — toBeEnabled({ timeout: 10_000 })
  before selectOption("json").

- test_active_scanner.py: test_missing_file_logged_at_debug now
  asserts the "DEBUG:ActiveScanner:" prefix on the captured log
  output, not just the message text. assertLogs(level="DEBUG")
  captures records >= DEBUG, so a plain message check could pass
  even if the log was emitted at WARNING/ERROR.

Skipped: workers:4 race claim on integrations.spec.ts. With
fullyParallel:false, tests within a file run serially, and a grep
confirmed no other spec file touches /server/integrations. The
"global cleanup races at workers:4" premise isn't current state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (1)
src/angular/src/app/pages/settings/option.component.spec.ts (1)

15-16: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Re-declared DEBOUNCE_TIME_MS still drifts from the component's private constant.

The component's private DEBOUNCE_TIME_MS = 1000 is mirrored here. If the component's value changes, this suite continues to pass while exercising the wrong period. Export the constant from option.component.ts and import it here so both stay in sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/angular/src/app/pages/settings/option.component.spec.ts` around lines 15
- 16, The test re-declares DEBOUNCE_TIME_MS, which can drift from the
component's private constant; export the constant from option.component.ts (e.g.
export const DEBOUNCE_TIME_MS) and update option.component.spec.ts to import
that exported DEBOUNCE_TIME_MS instead of declaring its own, then remove the
duplicate declaration so the spec uses the component's single source of truth
(referencing OptionComponent and DEBOUNCE_TIME_MS).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/angular/src/app/pages/settings/option.component.spec.ts`:
- Around line 96-125: Remove the unnecessary vi.useFakeTimers() setup calls from
the three tests that exercise component.onChange for OptionType.Password and
OptionType.Text; these tests call newValue.next(...) synchronously, so drop the
vi.useFakeTimers() invocations at the start of the "should suppress
REDACTED_SENTINEL for password type", "should pass real password value through
to Subject", and "should not suppress REDACTED_SENTINEL for non-password types"
specs and keep the existing spies on (component as unknown as { newValue:
Subject<OptionValue> }).newValue and the assertions that check nextSpy behavior
for REDACTED_SENTINEL and real-password.

In `@src/angular/src/app/services/settings/path-pairs.service.spec.ts`:
- Around line 212-214: The test creates a lingering subscription by calling
service.pairs$.subscribe(...) without unsubscribing; change that assertion to
consume only one emission (e.g., use RxJS take(1) on service.pairs$ or call the
existing snapshot() helper if available) so the subscriber is torn down
automatically; update the expectation to read from service.pairs$.pipe(take(1))
(or from service.snapshot()) and assert the resulting value equals [] to avoid
leaving a live subscriber.

In `@src/docker/build/docker-image/Dockerfile`:
- Around line 29-32: Replace the inline shell cd usage in the Dockerfile RUN
that calls /tmp/uv (the RUN line invoking "cd /tmp && /tmp/uv export ... &&
/tmp/uv pip install ...") by setting a WORKDIR /tmp before that RUN (or
otherwise running /uv with absolute paths), i.e., add a preceding WORKDIR /tmp
and remove the "cd /tmp &&" so the commands using "uv export" and "uv pip
install" run from /tmp and avoid the Hadolint DL3003 / Trivy DS-0013 lint.

In `@src/e2e-playwright/tests/settings.spec.ts`:
- Around line 358-392: The test mutates validate.enabled before the try block
causing the finally cleanup to be skipped if setup throws; move the
apiSetConfig("validate", "enabled", "True") call inside the try that begins
before interacting with SettingsPage so that the corresponding finally always
runs and restores validate.enabled via apiSetConfig("validate", "enabled",
"False") and apiSetConfig("validate", "algorithm", String(originalAlgorithm)).
Ensure the sequence around SettingsPage(page).goto(), waitForStream(page),
settings.getSelect("Hash Algorithm"), and the expect checks remains inside the
same try so any thrown error triggers the finally cleanup.

In `@src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py`:
- Around line 101-105: Update the test_unexpected_error_logged_at_warning to
assert the WARNING prefix explicitly: when calling scanner.scan() inside
assertLogs("ActiveScanner", level="WARNING"), check that log_ctx.output contains
an entry that starts with "WARNING:ActiveScanner:" and includes "Unexpected scan
error" (similar to how test_missing_file_logged_at_debug asserts the DEBUG
prefix). Locate the test by name (test_unexpected_error_logged_at_warning) and
modify the final assertion to verify the "WARNING:ActiveScanner:" prefix plus
the message instead of only checking the message text.

In `@src/python/tests/unittests/test_web/test_web_app_job.py`:
- Around line 10-64: The TestWebAppJobSetup class mixes setup, execute, and
cleanup lifecycle tests; split them into separate test classes named to match
the lifecycle (e.g., TestWebAppJobSetup, TestWebAppJobExecute,
TestWebAppJobCleanup) so each only contains the corresponding test method (move
test_setup_creates_server_and_starts_thread into TestWebAppJobSetup,
test_execute_calls_process into TestWebAppJobExecute, and
test_cleanup_stops_server_and_joins_thread into TestWebAppJobCleanup); keep or
refactor the shared _make_context helper into a small base TestCase (or
duplicate it) so each class can call it, and ensure the patch decorators and
references to WebAppJob, setup, execute, cleanup, Thread, and MyWSGIRefServer
remain unchanged.

---

Duplicate comments:
In `@src/angular/src/app/pages/settings/option.component.spec.ts`:
- Around line 15-16: The test re-declares DEBOUNCE_TIME_MS, which can drift from
the component's private constant; export the constant from option.component.ts
(e.g. export const DEBOUNCE_TIME_MS) and update option.component.spec.ts to
import that exported DEBOUNCE_TIME_MS instead of declaring its own, then remove
the duplicate declaration so the spec uses the component's single source of
truth (referencing OptionComponent and DEBOUNCE_TIME_MS).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 65d462d0-b8f7-4f4d-83df-64813594c354

📥 Commits

Reviewing files that changed from the base of the PR and between 660f99b and a317805.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • src/angular/package.json
  • src/angular/src/app/pages/settings/option.component.spec.ts
  • src/angular/src/app/services/settings/path-pairs.service.spec.ts
  • src/angular/src/app/services/utils/version-check.service.spec.ts
  • src/docker/build/docker-image/Dockerfile
  • src/e2e-playwright/tests/integrations.spec.ts
  • src/e2e-playwright/tests/settings.spec.ts
  • src/python/tests/unittests/test_common/test_context.py
  • src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py
  • src/python/tests/unittests/test_web/test_web_app_job.py

Comment thread src/angular/src/app/pages/settings/option.component.spec.ts
Comment thread src/angular/src/app/services/settings/path-pairs.service.spec.ts Outdated
Comment thread src/e2e-playwright/tests/settings.spec.ts Outdated
Comment thread src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py Outdated
Comment thread src/python/tests/unittests/test_web/test_web_app_job.py Outdated
All 7 findings actioned. Test/Dockerfile/single-line component edit only.

- option.component.spec.ts (3 onChange tests): drop unnecessary
  vi.useFakeTimers() — onChange pushes to the Subject synchronously
  before any debounceTime delay, so the spy on next() observes it
  immediately. Fake timers were a leftover from when these tests
  inspected the debounced output instead of the Subject.

- option.component.ts / spec: export DEBOUNCE_TIME_MS as a top-level
  const. Spec now imports it instead of redeclaring 1000 — single
  source of truth, no drift if the debounce window ever changes.

- path-pairs.service.spec.ts (last assertion in catchError test):
  reuse the snapshot() helper (which already pipes through take(1))
  instead of an ad-hoc subscribe that left a live subscriber.

- Dockerfile: replace `cd /tmp && ...` with `WORKDIR /tmp` to silence
  Hadolint DL3003 / Trivy DS-0013. The python-deps stage's working
  directory after this RUN doesn't affect the runtime stage (which
  only copies /usr/local).

- settings.spec.ts (Hash Algorithm test): move the
  `apiSetConfig("validate", "enabled", "True")` setup inside the try
  block so the finally cleanup always runs, even if the apiSetConfig
  itself or any subsequent step (goto, getSelect, expect) throws.

- test_active_scanner.py (test_unexpected_error_logged_at_warning):
  pin the level explicitly via the "WARNING:ActiveScanner:" prefix,
  mirroring the DEBUG check in test_missing_file_logged_at_debug.
  assertLogs(level="WARNING") captures records >= WARNING, so the
  prior message-only check could also pass on ERROR/CRITICAL.

- test_web_app_job.py: split the misnamed TestWebAppJobSetup class
  into TestWebAppJobSetup, TestWebAppJobExecute, and
  TestWebAppJobCleanup, each containing only the lifecycle method
  it tests. Shared _make_context() lifted into _WebAppJobBase so
  the helper isn't duplicated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/angular/src/app/services/utils/version-check.service.spec.ts`:
- Around line 95-107: The test "should strip v prefix before comparing versions"
currently duplicates the "should show notification when a newer release is
available" scenario; update it to verify stripping by exercising both prefixed
and non‑prefixed responses and asserting identical behavior: call
mockRestService.sendRequest to return makeGithubResponse('v99.0.0') for one run
and makeGithubResponse('99.0.0') for the other, subscribe to
notificationService.notifications$ and compare the resulting notifications
arrays are equal (or both length === 1), and ensure you reset the test module
between runs using TestBed.resetTestingModule() before re-configuring and
calling createService() so you don't get a cached singleton; keep using
createService(), mockRestService.sendRequest, makeGithubResponse and
notificationService.notifications$ to locate the code to change.

In `@src/e2e-playwright/tests/settings.spec.ts`:
- Around line 469-521: The test captures apiGet("/server/config/get") and
constructs originalLevel/newValue and calls expect(select).toBeEnabled() outside
the try block which is fine functionally but inconsistent with the Hash
Algorithm test; to tighten setup and ensure consistency, move the retrieval of
originalLevel, computation of newValue and the await
expect(select).toBeEnabled({ timeout: 10_000 }) into the try block that contains
select.selectOption(newValue) (referencing SettingsPage, getSelect("Log Level"),
select.selectOption, and the try/finally cleanup) so all state-read/setup that
precedes mutation lives inside the try and the finally still restores
originalLevel via select.selectOption(String(originalLevel)) and the apiGet
polls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2287afff-1b2c-442b-96c0-ca256b387e31

📥 Commits

Reviewing files that changed from the base of the PR and between 660f99b and e0f7ee6.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • src/angular/package.json
  • src/angular/src/app/pages/settings/option.component.spec.ts
  • src/angular/src/app/pages/settings/option.component.ts
  • src/angular/src/app/services/settings/path-pairs.service.spec.ts
  • src/angular/src/app/services/utils/version-check.service.spec.ts
  • src/docker/build/docker-image/Dockerfile
  • src/e2e-playwright/tests/integrations.spec.ts
  • src/e2e-playwright/tests/settings.spec.ts
  • src/python/tests/unittests/test_common/test_context.py
  • src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py
  • src/python/tests/unittests/test_web/test_web_app_job.py

Comment thread src/angular/src/app/services/utils/version-check.service.spec.ts Outdated
Comment thread src/e2e-playwright/tests/settings.spec.ts
- version-check.service.spec.ts: the "should strip v prefix" test was
  a structural duplicate of "should show notification when a newer
  release is available" — both ran "v99.0.0" and asserted length === 1,
  exercising no v-prefix logic. Rewrote it to run the same scenario
  twice (with "v99.0.0" and with "99.0.0"), reset TestBed between runs,
  and assert the resulting notifications are equivalent. Now actually
  verifies the stripping behavior the test name implies.

- settings.spec.ts (Log Level test): move toBeEnabled and the newValue
  computation inside the try block so any throw before the mutation
  still hits the finally cleanup. Mirrors the structure of the Hash
  Algorithm test from round 5. originalLevel and select stay at
  function scope (finally references originalLevel).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/angular/src/app/pages/settings/option.component.spec.ts`:
- Around line 34-72: The tests hardcode 1000ms in vi.advanceTimersByTime and in
one test title; replace all literal 1000 usages in the spec with the imported
DEBOUNCE_TIME_MS and update the test description to reference DEBOUNCE_TIME_MS
(or a non-magic wording) so the specs use the same debounce constant as the
component; search for vi.advanceTimersByTime(1000) and the string '1000ms' in
this file and change them to use DEBOUNCE_TIME_MS and adjust the test name
accordingly to avoid brittle magic numbers.

In `@src/angular/src/app/services/settings/path-pairs.service.spec.ts`:
- Around line 160-171: Add a second spec for update() that mirrors the non-409
coverage of create(): after emitting connectedSubject.next(true) and seeding the
list via httpMock.expectOne('/server/pathpairs').flush([makePair()]), call
service.update(makePair({ name: 'other' })) and stub the PUT/patch request to
'/server/pathpairs/pair-1' to return a non-409 error (e.g., flush('error', {
status: 500, statusText: 'Server Error' })), then assert the subscriber receives
null (same expectation create() uses) to ensure update() falls back to null on
non-409 errors; reference service.update, connectedSubject, httpMock.expectOne,
and makePair() when adding the test.

In `@src/angular/src/app/services/utils/version-check.service.spec.ts`:
- Around line 51-65: The inline comment that hardcodes the old current version
is stale; update or remove it so it no longer references "Current version is
0.17.0". In the test around mockRestService.sendRequest /
makeGithubResponse('v99.0.0') and the notification assertions
(notificationService.notifications$ subscription and createService()), replace
the comment with a neutral note like "99.0.0 chosen because it is always newer
than the current version" or remove it entirely to avoid future confusion when
the package version bumps.

In `@src/docker/build/docker-image/Dockerfile`:
- Around line 110-116: The Dockerfile currently deletes apk tooling and DB in
the rm -rf line (removing /sbin/apk, /etc/apk, /var/cache/apk, /lib/apk, etc.),
which breaks downstream vulnerability scanners and prevents users from running
apk in derived images; either stop removing the apk binaries/DB in that rm -rf
command so tools like Trivy/Grype can enumerate installed Alpine packages and
downstream RUN apk add works, or if you must keep the smaller image, add a clear
note in the README/CHANGELOG security section for ghcr.io/nitrobass24/seedsync
explaining that the final image strips apk and that consumers must scan the
runtime base layers via image-history/SBOM (and mention affected packages like
lftp, openssh-client, ca-certificates, libstdc++).

In `@src/e2e-playwright/tests/settings.spec.ts`:
- Around line 214-522: Several describe blocks repeat the same setup lines
"const settings = new SettingsPage(page); await settings.goto();" in each test;
extract this by adding a beforeEach inside each sibling describe (e.g., inside
the "Settings — Staging Directory", "Archive Extraction", "Integrity Check",
"Connections", "Notifications", and "Logging" describe blocks) that constructs
SettingsPage(page) and calls settings.goto(), then expose settings to tests via
a shared variable or by extending the test fixture (e.g., add a fixture that
returns settings: SettingsPage). Update tests to use the injected/shared
settings reference and remove the duplicated constructor/goto calls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a2def33b-25e0-42ca-8863-b90e854e9236

📥 Commits

Reviewing files that changed from the base of the PR and between 660f99b and 1c3241c.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • src/angular/package.json
  • src/angular/src/app/pages/settings/option.component.spec.ts
  • src/angular/src/app/pages/settings/option.component.ts
  • src/angular/src/app/services/settings/path-pairs.service.spec.ts
  • src/angular/src/app/services/utils/version-check.service.spec.ts
  • src/docker/build/docker-image/Dockerfile
  • src/e2e-playwright/tests/integrations.spec.ts
  • src/e2e-playwright/tests/settings.spec.ts
  • src/python/tests/unittests/test_common/test_context.py
  • src/python/tests/unittests/test_controller/test_scan/test_active_scanner.py
  • src/python/tests/unittests/test_web/test_web_app_job.py

Comment thread src/angular/src/app/pages/settings/option.component.spec.ts Outdated
Comment thread src/angular/src/app/services/settings/path-pairs.service.spec.ts
Comment thread src/angular/src/app/services/utils/version-check.service.spec.ts
Comment on lines +110 to +116
&& rm -rf /sbin/apk /etc/apk /usr/share/apk /lib/apk /var/cache/apk \
/lib/libapk.so* /usr/lib/libapk.so* /usr/bin/scanelf \
/usr/bin/ssh-keyscan /usr/bin/ssh-add /usr/bin/ssh-agent \
/usr/bin/ssh-pkcs11-helper /usr/bin/ssh-copy-id \
/usr/bin/findssl.sh /usr/bin/c_rehash \
/usr/bin/iconv /usr/bin/getconf /usr/bin/ssl_client \
/etc/ssh/moduli
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Removing apk and the apk DB blocks downstream CVE scanning and image patching.

Stripping /sbin/apk, /etc/apk, /lib/apk, and /var/cache/apk is a real size win, but it also:

  • Prevents Trivy / Grype / Docker Scout from enumerating installed Alpine packages by querying the apk database — vulnerability scans of ghcr.io/nitrobass24/seedsync will under-report OS-level CVEs in lftp, openssh-client, ca-certificates, libstdc++, etc.
  • Removes the ability for downstream users to layer RUN apk add ... on this image (e.g., for debugging or custom builds) without first reinstalling apk.

Both are deliberate trade-offs for the documented 114→64 MB win, but worth calling out in the README/CHANGELOG security section so users running scanners against the published image know they need to scan the runtime base layers via image-history/SBOM rather than the final image alone.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/docker/build/docker-image/Dockerfile` around lines 110 - 116, The
Dockerfile currently deletes apk tooling and DB in the rm -rf line (removing
/sbin/apk, /etc/apk, /var/cache/apk, /lib/apk, etc.), which breaks downstream
vulnerability scanners and prevents users from running apk in derived images;
either stop removing the apk binaries/DB in that rm -rf command so tools like
Trivy/Grype can enumerate installed Alpine packages and downstream RUN apk add
works, or if you must keep the smaller image, add a clear note in the
README/CHANGELOG security section for ghcr.io/nitrobass24/seedsync explaining
that the final image strips apk and that consumers must scan the runtime base
layers via image-history/SBOM (and mention affected packages like lftp,
openssh-client, ca-certificates, libstdc++).

Comment thread src/e2e-playwright/tests/settings.spec.ts
All 5 findings actioned. Test + CHANGELOG only.

- option.component.spec.ts: replace 4 `vi.advanceTimersByTime(1000)`
  literals with the imported DEBOUNCE_TIME_MS constant. Update the
  one test name that hardcoded "1000ms" to the more durable "the full
  debounce window". The 200ms literals stay — those are intentionally
  sub-debounce, not the debounce window.

- path-pairs.service.spec.ts: add the missing non-409 fallback test
  for update(). The service does `return of(null)` on any non-409
  error from PUT; create() already had the matching test, so the
  coverage gap was just on update().

- version-check.service.spec.ts: drop the stale "Current version is
  0.17.0" comment. Replaced with a version-agnostic note about why
  99.0.0 was chosen (always newer than current package.json).

- CHANGELOG.md (0.18.0): add a Security note documenting that the
  runtime image strips apk and the apk DB. In-image scanners can't
  enumerate Alpine packages from a running container; consumers
  scanning the published image should use SBOM/image-history tools.
  Reviewer accepted documentation as an alternative to keeping apk
  in the image (which would re-balloon the runtime).

- settings.spec.ts: extract the repeated `const settings = new
  SettingsPage(page); await settings.goto();` to a per-describe
  beforeEach in 6 sibling describes (Staging Directory, Archive
  Extraction, Integrity Check, Connections, Notifications, Logging).
  Mirrors the existing pattern in the top-level "Settings Page"
  describe (line 5). Removes 8 inline constructions; tests destructure
  only the fixtures they actually need now. Hash Algorithm test keeps
  an explicit `await settings.goto()` inside its try block — beforeEach
  loads the page before validate.enabled is applied, and the test
  needs the reload + waitForStream so buildValidateContext() observes
  the new state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@nitrobass24 nitrobass24 merged commit 686d6f2 into master May 6, 2026
33 checks 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.

1 participant