You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is your feature request related to a problem? Please describe.
Each of sub-issues 01–08 has its own unit tests, which cover the individual pieces well. But four things only make sense as cross-cutting work:
Browser tests. Clicking the is_persistent checkbox, seeing pending render in the live WebSocket UI, filtering for pending, cancelling from the admin. These need Selenium driving a real browser against a running server — they can't be done with the Django test client.
End-to-end persistence loop. Offline device → pending → time advances → Beat scan → retry → success. Spans the failure handler, the retry pipeline, and the WebSocket flow in one test.
Time-mocking helper. Backoff math, Beat scan selection, and reminder cadence all need timezone.now() mocked. A shared helper avoids each test reinventing the pattern.
Coverage parity check.[feature] Persistent Mass Upgrades #379 requires test coverage not to decrease. CI today uploads to coveralls for tracking but doesn't have a hard gate — that's a new CI step I'd add.
Without these, the individual sub-issue tests pass in isolation, but the system's behaviour under realistic flow isn't verified.
Describe the solution I would implement
I would like to add the cross-cutting test coverage that lets us ship persistence with confidence.
Selenium tests extending tests/test_selenium.py (already 1317 lines; new tests follow the existing TestUpgraderMixin, SeleniumTestMixin, StaticLiveServerTestCase pattern):
Confirmation page renders the is_persistent checkbox pre-checked.
Submitting propagates is_persistent correctly to the batch and its children.
WebSocket-driven batch detail page renders the pending status with the new orange indicator instead of the in-progress spinner.
Operations list filter shows "pending" and selecting it returns only pending rows.
Corrected progress display shows "X complete, Y pending" instead of conflating them.
Admin cancel button on a pending row transitions it to cancelled.
End-to-end integration test simulating the full persistence loop:
Create a persistent batch against a fleet with one mocked-online device and one mocked-offline device (offline = a DeviceConnection mock that raises RecoverableFailure).
Assert: online → success; offline → pending with retry_count=1 and a future next_retry_at.
Advance timezone.now() past next_retry_at, call check_pending_upgrades() directly.
Verify the cycle (pending → in-progress → pending if still offline → in-progress → success once the mock is flipped online).
Idempotency cross-check spanning sub-issues 04 and 05: simultaneously dispatch a signal-driven retry and a Beat-driven retry for the same pending op. Assert exactly one upgrade_firmware.delay call thanks to the atomic compare-and-swap inside retry_pending_upgrade.
Seed long-pending batches and failing-pending ops.
Assert the expected notifications fire with the right description/target/?status=pending URL.
Assert per-batch cadence via last_reminder_at (single fire on first run; no fire within the cadence window; new fire after advancing time past the cadence).
A shared time_travel test helper in tests/utils.py (or similar) wrapping freezegun or unittest.mock.patch on django.utils.timezone.now. Used consistently across backoff tests (sub-issue 02), Beat scan tests (sub-issue 04), and reminder tests (sub-issue 06). Saves each test from reinventing the pattern.
Add a CI coverage parity gate. Currently .github/workflows/ci.yml:90–96 uploads coverage to coveralls but there's no --fail-under step. I'd add a coverage report --fail-under=<current> step after runtests, or wire pytest-cov's --cov-fail-under flag — fails the build if per-module coverage drops on base/models.py, tasks.py, admin.py, api/serializers.py, or api/views.py versus master.
Migration-introspection test asserting the db_index=True on next_retry_at from sub-issue 01 produces an index in the generated migration SQL (via MigrationLoader or connection.introspection.get_constraints). Protects against a silent regression if someone removes the annotation later.
All tests run with CELERY_TASK_ALWAYS_EAGER=True (matching the existing convention) and call periodic tasks directly rather than relying on real Beat scheduling.
Is your feature request related to a problem? Please describe.
Each of sub-issues 01–08 has its own unit tests, which cover the individual pieces well. But four things only make sense as cross-cutting work:
is_persistentcheckbox, seeing pending render in the live WebSocket UI, filtering for pending, cancelling from the admin. These need Selenium driving a real browser against a running server — they can't be done with the Django test client.timezone.now()mocked. A shared helper avoids each test reinventing the pattern.Without these, the individual sub-issue tests pass in isolation, but the system's behaviour under realistic flow isn't verified.
Describe the solution I would implement
I would like to add the cross-cutting test coverage that lets us ship persistence with confidence.
Selenium tests extending
tests/test_selenium.py(already 1317 lines; new tests follow the existingTestUpgraderMixin, SeleniumTestMixin, StaticLiveServerTestCasepattern):is_persistentcheckbox pre-checked.is_persistentcorrectly to the batch and its children.cancelled.End-to-end integration test simulating the full persistence loop:
DeviceConnectionmock that raisesRecoverableFailure).success; offline →pendingwithretry_count=1and a futurenext_retry_at.timezone.now()pastnext_retry_at, callcheck_pending_upgrades()directly.Idempotency cross-check spanning sub-issues 04 and 05: simultaneously dispatch a signal-driven retry and a Beat-driven retry for the same pending op. Assert exactly one
upgrade_firmware.delaycall thanks to the atomic compare-and-swap insideretry_pending_upgrade.Reminder + failure-notification integration tests (covers sub-issue 06):
openwisp_notifications.signals.notify.send.?status=pendingURL.last_reminder_at(single fire on first run; no fire within the cadence window; new fire after advancing time past the cadence).A shared
time_traveltest helper intests/utils.py(or similar) wrappingfreezegunorunittest.mock.patchondjango.utils.timezone.now. Used consistently across backoff tests (sub-issue 02), Beat scan tests (sub-issue 04), and reminder tests (sub-issue 06). Saves each test from reinventing the pattern.Add a CI coverage parity gate. Currently
.github/workflows/ci.yml:90–96uploads coverage to coveralls but there's no--fail-understep. I'd add acoverage report --fail-under=<current>step afterruntests, or wirepytest-cov's--cov-fail-underflag — fails the build if per-module coverage drops onbase/models.py,tasks.py,admin.py,api/serializers.py, orapi/views.pyversus master.Migration-introspection test asserting the
db_index=Trueonnext_retry_atfrom sub-issue 01 produces an index in the generated migration SQL (viaMigrationLoaderorconnection.introspection.get_constraints). Protects against a silent regression if someone removes the annotation later.All tests run with
CELERY_TASK_ALWAYS_EAGER=True(matching the existing convention) and call periodic tasks directly rather than relying on real Beat scheduling.