Skip to content

[fix] Graceful fallback to default timezone for invalid inputs#766

Open
UltraBot05 wants to merge 7 commits intoopenwisp:masterfrom
UltraBot05:master
Open

[fix] Graceful fallback to default timezone for invalid inputs#766
UltraBot05 wants to merge 7 commits intoopenwisp:masterfrom
UltraBot05:master

Conversation

@UltraBot05
Copy link
Copy Markdown

Modified MonitoringApiViewMixin to handle UnknownTimeZoneError by falling back to the system default timezone. Added strict regression tests in device and monitoring modules to verify chart data availability on fallback.

Checklist

  • I have read the OpenWISP Contributing Guidelines.
  • I have manually tested the changes proposed in this pull request.
  • I have written new test cases for new code and/or updated existing tests for changes to existing code.
  • I have updated the documentation.

Reference to Existing Issue

Closes #728.

Description of Changes

This PR fixes an issue where the monitoring API would fail with a 400 Bad Request when provided an invalid timezone string.

  • Graceful Fallback: Updated MonitoringApiViewMixin to catch UnknownTimeZoneError and default to the system's TIME_ZONE instead of breaking the request.
  • Strict Regression Testing: Added robust assertions to test_get_device_metrics_bad_timezone_fallback (in both device and monitoring tests). The tests now explicitly verify that response.data["charts"] is populated with data, proving the fallback query executes successfully.

Screenshot

earlier vs after

image image

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The changes implement timezone normalization to handle deprecated timezone names (e.g., Asia/CalcuttaAsia/Kolkata) before processing API requests. A new utility module introduces the normalize_timezone function with LRU caching and fallback logic to Django settings. The function maps legacy timezone aliases to current IANA standard names and validates their existence via ZoneInfo. The views layer is updated to apply this normalization to incoming timezone query parameters. Test coverage is added across device and monitoring API endpoints to verify the normalization behavior with legacy timezones and edge cases.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Bug Fixes ❌ Error Tests do not explicitly verify that deprecated timezone 'Asia/Calcutta' is handled correctly; TIME_ZONE=None fallback handling appears vulnerable to returning None instead of a valid timezone. Add explicit 'Asia/Calcutta' test assertions verifying 200 status responses; fix line 33 to use 'getattr(settings, "TIME_ZONE", None) or "UTC"' to ensure valid timezone is always returned.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change: implementing graceful fallback to default timezone for invalid inputs.
Description check ✅ Passed The description addresses all key areas: includes reference to issue #728, explains changes with specific details, includes test assertions, provides before/after screenshots, and the checklist is substantially complete.
Linked Issues check ✅ Passed The PR successfully addresses all coding requirements from issue #728: normalizes deprecated timezone names (Asia/Calcutta → Asia/Kolkata), implements graceful fallback to system default TIME_ZONE, adds comprehensive regression tests.
Out of Scope Changes check ✅ Passed All changes directly relate to timezone handling and validation. New module legacy_tz_utils.py provides timezone normalization, views.py applies normalization, and tests validate the fix for issue #728.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@openwisp-companion
Copy link
Copy Markdown

CI Job Failures

Hello @UltraBot05,
(Analysis for commit ac152dd)

There are multiple Build/Infrastructure/Other failures in the CI job logs.

Failures & Remediation

  1. Explanation: The CI job failed because the script ./runtests was not found. This indicates that the script is either missing, not executable, or the CI environment is not set up correctly to find it. This is a Build/Infrastructure/Other failure.
    Remediation: Ensure that the runtests.py script is present in the root directory of the repository and that it has execute permissions. If the script is intended to be run from a different location or requires specific setup, the CI configuration (e.g., Dockerfile, workflow files) needs to be adjusted accordingly.

  2. Explanation: The CI job also failed because the file geckodriver.log was not found when the cat geckodriver.log command was executed. This suggests that the geckodriver process, which is likely used for Selenium tests, did not start or did not produce a log file as expected. This is also a Build/Infrastructure/Other failure.
    Remediation: Investigate why geckodriver is not starting or not producing logs. This could be due to missing dependencies, incorrect configuration in the CI environment, or issues within the Selenium test setup itself. Ensure that geckodriver is correctly installed and accessible in the CI environment.

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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openwisp_monitoring/monitoring/tests/test_api.py`:
- Around line 699-705: The test currently only asserts the response is not 500
for an invalid timezone; update the subtest (in monitoring/tests/test_api.py) to
assert the expected successful fallback behavior from normalize_timezone: call
the endpoint with timezone "Antarctica/Banana" and assert response.status_code
== 200 (and optionally assert the returned payload contains the expected
charts/data keys), referencing normalize_timezone and settings.TIME_ZONE to
ensure we expect a 200 fallback to the configured timezone rather than allowing
a 400/500.

In `@openwisp_monitoring/utils.py`:
- Around line 48-55: The current legacy_map in openwisp_monitoring/utils.py only
covers five deprecated timezones; expand legacy_map to include additional common
IANA aliases (e.g., add mappings like "Europe/Kiev"->"Europe/Kyiv",
"Asia/Rangoon"->"Asia/Yangon", and other well-known legacy names) and/or
implement fallback normalization using pytz/zoneinfo where available: update the
code paths that reference legacy_map (the legacy_map dict and any callers that
resolve timezones) to first attempt direct lookup in legacy_map, then try
pytz.timezone normalization or zoneinfo lookup to map aliases to canonical
names, and finally fall back to raising a clear error if unresolved. Ensure you
modify the legacy_map symbol and the timezone resolution logic so all lookups
use the expanded map + normalization fallback.
- Around line 59-67: The hardcoded filesystem check using tz_path and
os.path.exists should be replaced with the cross-platform zoneinfo API: attempt
to construct zoneinfo.ZoneInfo(normalized_tz) and catch
zoneinfo.ZoneInfoNotFoundError to determine non-existence, log/return
accordingly; update the timezone verification block that references
normalized_tz and tz_name to use this approach and preserve the normalized-tz
logging when different, and add the tzdata package to project dependencies so
ZoneInfo works on Windows.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f33c90b4-2c69-4220-ad21-2d25aafe7794

📥 Commits

Reviewing files that changed from the base of the PR and between 98afa9b and ac152dd.

📒 Files selected for processing (4)
  • openwisp_monitoring/device/tests/test_api.py
  • openwisp_monitoring/monitoring/tests/test_api.py
  • openwisp_monitoring/utils.py
  • openwisp_monitoring/views.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-02-21T18:44:28.852Z
Learnt from: dee077
Repo: openwisp/openwisp-monitoring PR: 738
File: openwisp_monitoring/device/api/views.py:263-281
Timestamp: 2026-02-21T18:44:28.852Z
Learning: In openwisp-monitoring, MonitoringIndoorCoordinatesList inherits organization scoping from the parent IndoorCoordinatesList (from openwisp-controller), which uses FilterByParentManaged mixin and filters by location_id in get_queryset(). The child class only overrides the queryset attribute to add monitoring-specific select_related fields; this pattern is safe as long as get_queryset() from the parent is not bypassed. During reviews, verify that MonitoringIndoorCoordinatesList continues to rely on the parent's get_queryset() and that any added select_related fields in the child do not alter the parent's filtering logic.

Applied to files:

  • openwisp_monitoring/views.py
  • openwisp_monitoring/utils.py
  • openwisp_monitoring/device/tests/test_api.py
  • openwisp_monitoring/monitoring/tests/test_api.py
📚 Learning: 2026-02-21T01:03:37.822Z
Learnt from: nemesifier
Repo: openwisp/openwisp-monitoring PR: 738
File: openwisp_monitoring/tests/test_selenium.py:827-859
Timestamp: 2026-02-21T01:03:37.822Z
Learning: In Selenium tests (e.g., in openwisp_monitoring/tests/test_selenium.py and similar test files), when testing JS animations on dashboards or elements driven by JavaScript (such as real-time location updates), insert a short sleep (e.g., sleep(0.3)) before WebDriverWait assertions to allow animations to complete and reduce flakiness. Note: use this as a targeted workaround and prefer explicit waits or animation-end checks where possible to avoid relying on fixed delays.

Applied to files:

  • openwisp_monitoring/device/tests/test_api.py
  • openwisp_monitoring/monitoring/tests/test_api.py
📚 Learning: 2026-02-25T18:42:08.825Z
Learnt from: dee077
Repo: openwisp/openwisp-monitoring PR: 738
File: openwisp_monitoring/tests/test_selenium.py:309-317
Timestamp: 2026-02-25T18:42:08.825Z
Learning: In Selenium tests that use ChannelsLiveServerTestCase (e.g., openwisp_monitoring/tests/test_selenium.py), override settings to configure CHANNEL_LAYERS with channels_redis.core.RedisChannelLayer instead of InMemoryChannelLayer. This is required because the live server runs in a separate process from the test process, and InMemoryChannelLayer is per-process only and cannot handle cross-process WebSocket broadcasting needed for real-time location update tests.

Applied to files:

  • openwisp_monitoring/device/tests/test_api.py
  • openwisp_monitoring/monitoring/tests/test_api.py
🔇 Additional comments (5)
openwisp_monitoring/utils.py (1)

40-46: LGTM: Cache decorator and empty input handling.

The @lru_cache(maxsize=128) is appropriate for timezone normalization since results are deterministic. The early return for empty tz_name correctly defaults to the project's TIME_ZONE setting.

openwisp_monitoring/views.py (2)

58-63: LGTM: Clean integration of timezone normalization.

The flow correctly captures the raw timezone value, normalizes it via normalize_timezone(), then validates with pytz. This ensures the fallback timezone is also validated before use.


17-17: Import placement is appropriate.

The import from .utils follows the existing pattern of local imports in the file.

openwisp_monitoring/device/tests/test_api.py (1)

643-656: LGTM: Test correctly verifies fallback behavior.

The renamed test and updated assertions properly validate that:

  1. Invalid timezone inputs return 200 (not 400/500)
  2. Response contains charts data, confirming the query executed with fallback timezone
  3. SQL injection attempts in timezone are handled safely
openwisp_monitoring/monitoring/tests/test_api.py (1)

682-697: LGTM: Legacy timezone test correctly validates normalization.

The test for Asia/Calcutta properly verifies that the deprecated timezone is normalized and the API returns a 200 response with valid chart data. The docstring clearly references the linked issue #728.

Comment thread openwisp_monitoring/monitoring/tests/test_api.py Outdated
Comment thread openwisp_monitoring/utils.py Outdated
Comment thread openwisp_monitoring/utils.py Outdated
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

Caution

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

⚠️ Outside diff range comments (1)
openwisp_monitoring/views.py (1)

9-10: 🧹 Nitpick | 🔵 Trivial

Minor: Redundant timezone imports.

Both timezone and timezone as tz are imported from pytz. Consider using only one alias consistently.

♻️ Suggested cleanup
-from pytz import timezone
-from pytz import timezone as tz
+from pytz import timezone as tz

Then update line 45 to use tz(tmz) instead of timezone(tmz) for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_monitoring/views.py` around lines 9 - 10, Remove the duplicate pytz
import and standardize on a single alias: keep "from pytz import timezone as tz"
and delete "from pytz import timezone"; then update the usage site that
currently calls timezone(tmz) (referencing the variable tmz and its call) to use
tz(tmz) so the import and usage are consistent (adjust any other timezone(...)
calls to tz(...) if present).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openwisp_monitoring/utils.py`:
- Around line 40-46: The lru_cache on normalize_timezone causes a falsy tz_name
(None/empty) to cache settings.TIME_ZONE, breaking tests that override
TIME_ZONE; fix by ensuring falsy inputs are returned without using the cache —
move the "if not tz_name: return settings.TIME_ZONE" check outside the cached
path (for example, keep a thin non-cached wrapper that handles falsy tz_name and
delegates to a cached inner function for real tz names) so normalize_timezone
still benefits from lru_cache for real tz strings but never caches the
settings-dependent fallback; reference normalize_timezone and its lru_cache
usage when applying the change.
- Around line 59-68: The code uses ZoneInfo directly and assumes system tzdata;
update imports to fall back to backports.zoneinfo on older Pythons (wrap the
import in a try/except and import ZoneInfo and ZoneInfoNotFoundError from
backports.zoneinfo if zoneinfo is unavailable) and ensure tzdata and
backports.zoneinfo are added to project dependencies (requirements.txt /
setup.py extras), and add a python_requires constraint in setup.py (e.g., set an
appropriate minimum like ">=3.8" or the project's supported minimum) so installs
on Python <3.9 get the backport and tzdata available; check usages in
openwisp_monitoring/utils.py where ZoneInfo/ZoneInfoNotFoundError are referenced
to confirm the new import is used.

---

Outside diff comments:
In `@openwisp_monitoring/views.py`:
- Around line 9-10: Remove the duplicate pytz import and standardize on a single
alias: keep "from pytz import timezone as tz" and delete "from pytz import
timezone"; then update the usage site that currently calls timezone(tmz)
(referencing the variable tmz and its call) to use tz(tmz) so the import and
usage are consistent (adjust any other timezone(...) calls to tz(...) if
present).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 050ee1cf-ce8d-49a0-abf8-e7a619cb14ef

📥 Commits

Reviewing files that changed from the base of the PR and between ac152dd and 4aa5769.

📒 Files selected for processing (4)
  • openwisp_monitoring/device/tests/test_api.py
  • openwisp_monitoring/monitoring/tests/test_api.py
  • openwisp_monitoring/utils.py
  • openwisp_monitoring/views.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-02-21T01:03:37.822Z
Learnt from: nemesifier
Repo: openwisp/openwisp-monitoring PR: 738
File: openwisp_monitoring/tests/test_selenium.py:827-859
Timestamp: 2026-02-21T01:03:37.822Z
Learning: In Selenium tests (e.g., in openwisp_monitoring/tests/test_selenium.py and similar test files), when testing JS animations on dashboards or elements driven by JavaScript (such as real-time location updates), insert a short sleep (e.g., sleep(0.3)) before WebDriverWait assertions to allow animations to complete and reduce flakiness. Note: use this as a targeted workaround and prefer explicit waits or animation-end checks where possible to avoid relying on fixed delays.

Applied to files:

  • openwisp_monitoring/monitoring/tests/test_api.py
  • openwisp_monitoring/device/tests/test_api.py
📚 Learning: 2026-02-21T18:44:28.852Z
Learnt from: dee077
Repo: openwisp/openwisp-monitoring PR: 738
File: openwisp_monitoring/device/api/views.py:263-281
Timestamp: 2026-02-21T18:44:28.852Z
Learning: In openwisp-monitoring, MonitoringIndoorCoordinatesList inherits organization scoping from the parent IndoorCoordinatesList (from openwisp-controller), which uses FilterByParentManaged mixin and filters by location_id in get_queryset(). The child class only overrides the queryset attribute to add monitoring-specific select_related fields; this pattern is safe as long as get_queryset() from the parent is not bypassed. During reviews, verify that MonitoringIndoorCoordinatesList continues to rely on the parent's get_queryset() and that any added select_related fields in the child do not alter the parent's filtering logic.

Applied to files:

  • openwisp_monitoring/monitoring/tests/test_api.py
  • openwisp_monitoring/views.py
  • openwisp_monitoring/utils.py
  • openwisp_monitoring/device/tests/test_api.py
📚 Learning: 2026-02-25T18:42:08.825Z
Learnt from: dee077
Repo: openwisp/openwisp-monitoring PR: 738
File: openwisp_monitoring/tests/test_selenium.py:309-317
Timestamp: 2026-02-25T18:42:08.825Z
Learning: In Selenium tests that use ChannelsLiveServerTestCase (e.g., openwisp_monitoring/tests/test_selenium.py), override settings to configure CHANNEL_LAYERS with channels_redis.core.RedisChannelLayer instead of InMemoryChannelLayer. This is required because the live server runs in a separate process from the test process, and InMemoryChannelLayer is per-process only and cannot handle cross-process WebSocket broadcasting needed for real-time location update tests.

Applied to files:

  • openwisp_monitoring/monitoring/tests/test_api.py
  • openwisp_monitoring/device/tests/test_api.py
🔇 Additional comments (4)
openwisp_monitoring/utils.py (1)

48-55: Legacy timezone map noted as incomplete in prior review.

The mapping covers only 5 common deprecated timezones. This was already flagged in a previous review with suggestions to expand the map or use pytz normalization.

openwisp_monitoring/views.py (1)

58-63: LGTM! Timezone normalization integration is correct.

The two-step process (normalize then validate) ensures graceful fallback while maintaining backward compatibility with the existing pytz validation. If normalize_timezone falls back to settings.TIME_ZONE, pytz validation should pass for any properly configured Django project.

openwisp_monitoring/device/tests/test_api.py (1)

643-656: LGTM! Test correctly validates fallback behavior.

The renamed test and updated assertions properly verify that:

  1. Invalid timezones no longer cause 400/500 errors
  2. The API gracefully falls back and returns 200 with chart data
  3. SQL injection attempts in timezone parameters are safely handled

This aligns well with the normalize_timezone implementation that falls back to settings.TIME_ZONE for unrecognized inputs.

openwisp_monitoring/monitoring/tests/test_api.py (1)

682-706: Test improvements look good; minor note on assertion fragility.

The test correctly validates both legacy timezone normalization and invalid timezone fallback, with strengthened assertions per previous review feedback.

One potential fragility: assertGreater(len(response.data["charts"]), 0) on line 706 assumes metrics/charts exist. If the test environment has no chart data, this could fail for reasons unrelated to timezone handling. Consider whether asserting charts is a list (like line 697) is sufficient for the invalid timezone subtest, since the primary goal is verifying no 500 error.

Comment thread openwisp_monitoring/utils.py Outdated
Comment thread openwisp_monitoring/utils.py Outdated
@openwisp-companion
Copy link
Copy Markdown

Test Failures in OpenWISP Monitoring CI

Hello @UltraBot05,
(Analysis for commit 4aa5769)

Test Failure: test_legacy_timezone_fallback in openwisp_monitoring.monitoring.tests.test_api

  • Explanation: The test test_legacy_timezone_fallback failed in the TestDashboardTimeseriesView class. The traceback indicates a RuntimeWarning: DateTimeField WifiSession.stop_time received a naive datetime and DateTimeField WifiSession.start_time received a naive datetime. This suggests that the stop_time and start_time fields in the WifiSession model are being populated with naive datetime objects when timezone support is active. This can lead to unexpected behavior or errors when dealing with timezones.

  • Remediation:
    Ensure that all datetime fields, especially those that are user-facing or involved in timezone-sensitive operations like start_time and stop_time in WifiSession, are timezone-aware. This typically involves:

    1. Making sure that USE_TZ = True is set in your Django settings (which it is, according to tests/openwisp2/settings.py).
    2. When creating or updating WifiSession objects, ensure that the start_time and stop_time are assigned timezone-aware datetime objects. This might involve using timezone.now() or explicitly converting naive datetimes to timezone-aware ones using pytz or zoneinfo.

    Looking at openwisp_monitoring/device/models.py, the start_time field has auto_now=True, which should make it timezone-aware if USE_TZ is True. However, stop_time is a DateTimeField without auto_now or auto_now_add, so it might be set as a naive datetime in some test scenarios.

    To fix this, you might need to adjust how stop_time is set in the tests that are failing, ensuring it's always a timezone-aware datetime object.

Slow Test Failures

  • Explanation: The CI run identified a significant number of slow tests, exceeding the 0.3-second threshold. Several tests in openwisp_monitoring.db.backends.influxdb.tests.TestDatabaseClient and openwisp_monitoring.device.tests.test_admin are particularly slow, with test_retry_mechanism taking 6.23 seconds and test_status_data_contains_wifi_version taking 5.59 seconds, among others. These slow tests can significantly increase the CI execution time and might indicate inefficiencies in the test setup, data handling, or the underlying logic being tested.

  • Remediation:
    Investigate the root cause of these slow tests. Potential areas to examine include:

    1. Database Operations: Many slow tests involve database interactions. Optimize queries, reduce the number of database calls, or consider using more efficient test data setup.
    2. External Service Dependencies: Tests interacting with external services like InfluxDB or network services might be slow due to network latency or the service's response time. Ensure these interactions are mocked or optimized.
    3. Inefficient Test Logic: Review the test logic itself. Are there unnecessary loops, redundant computations, or inefficient data processing?
    4. Selenium Test Performance: Several slow tests are Selenium-based. Optimize waits, reduce DOM interactions, and ensure efficient element selection.

    Given the number of slow tests, a systematic review of the test suite, focusing on the slowest ones first, is recommended. The TEST_RUNNER = "openwisp_utils.tests.TimeLoggingTestRunner" setting is already in place, which is helpful for identifying these slow tests.

Modified MonitoringApiViewMixin to handle UnknownTimeZoneError by falling back to the system default timezone. Added strict regression tests in device and monitoring modules to verify chart data availability on fallback.
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: 1

♻️ Duplicate comments (2)
openwisp_monitoring/monitoring/tests/test_api.py (1)

699-706: 🧹 Nitpick | 🔵 Trivial

Make invalid-timezone assertion as strict as the legacy-timezone assertion.

At Line 705 you only assert key presence; asserting list type here too keeps both subtests consistent and guards payload shape regressions.

✅ Tightened assertion
         with self.subTest("Test invalid timezone fallback (Antarctica/Banana)"):
             response = self.client.get(
                 path, {"timezone": "Antarctica/Banana", "time": "7d"}
             )
             self.assertEqual(response.status_code, 200)
             self.assertIn("charts", response.data)
+            self.assertIsInstance(response.data["charts"], list)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_monitoring/monitoring/tests/test_api.py` around lines 699 - 706, The
invalid-timezone subtest currently only asserts that the "charts" key exists;
make it as strict as the legacy-timezone subtest by also asserting that
response.data["charts"] is a list. Update the assertion in the subtest that uses
response = self.client.get(..., {"timezone": "Antarctica/Banana", "time": "7d"})
to check both status_code == 200 and that response.data["charts"] is a list (use
the same type-check approach used in the legacy-timezone assertion so the two
subtests remain consistent).
openwisp_monitoring/utils.py (1)

40-76: ⚠️ Potential issue | 🟠 Major

Avoid caching settings-dependent fallback values in normalize_timezone.

At Line 45-Line 46 and Line 70-Line 76, the result depends on settings.TIME_ZONE, but the full function is cached by tz_name. This can leak stale values across override_settings(TIME_ZONE=...) test contexts.

💡 Proposed fix
-@lru_cache(maxsize=128)
 def normalize_timezone(tz_name):
     """
     Normalizes legacy timezones and verifies availability via zoneinfo.
     """
-    if not tz_name:
-        return getattr(settings, "TIME_ZONE", "UTC")
+    fallback = getattr(settings, "TIME_ZONE", "UTC")
+    if not tz_name:
+        return fallback
+    return _normalize_timezone_cached(tz_name, fallback)
+
+
+@lru_cache(maxsize=128)
+def _normalize_timezone_cached(tz_name, fallback):
+    """
+    Cached normalization for explicit timezone strings.
+    `fallback` is part of the cache key to avoid stale settings-dependent values.
+    """

     # Mapping of legacy timezones
     legacy_map = {
         "Asia/Calcutta": "Asia/Kolkata",
         "Asia/Saigon": "Asia/Ho_Chi_Minh",
@@
-    # Fallback if above is false
-    fallback = getattr(settings, "TIME_ZONE", "UTC")
+    # Fallback if above is false
     logger.warning(
         f"Timezone '{normalized_tz}' (original: '{tz_name}') not found in OS zoneinfo. "
         f"Falling back to system TIME_ZONE '{fallback}'."
     )
     return fallback
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_monitoring/utils.py` around lines 40 - 76, The normalize_timezone
function is currently decorated with `@lru_cache`(maxsize=128) but its return can
depend on settings.TIME_ZONE (when tz_name is falsy or when fallback is used),
which leaks stale values across test contexts; remove the `@lru_cache` decorator
from normalize_timezone so results are computed per-call, or if you must cache,
change the cache key to include the current settings.TIME_ZONE (e.g. implement a
custom cache that keys on (tz_name, settings.TIME_ZONE)) and ensure
ZoneInfo/ZoneInfoNotFoundError logic and the legacy_map lookup remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openwisp_monitoring/device/tests/test_api.py`:
- Around line 651-656: Wrap each iteration over wrong_timezone_values in a
unittest subTest to make failures explicit: inside the loop around the call to
self._url(d.pk, d.key) and the subsequent self.client.get(url) use
self.subTest(timezone=tz_value) so assertions on response.status_code and
response.data (including response.data["charts"]) are reported per timezone
value.

---

Duplicate comments:
In `@openwisp_monitoring/monitoring/tests/test_api.py`:
- Around line 699-706: The invalid-timezone subtest currently only asserts that
the "charts" key exists; make it as strict as the legacy-timezone subtest by
also asserting that response.data["charts"] is a list. Update the assertion in
the subtest that uses response = self.client.get(..., {"timezone":
"Antarctica/Banana", "time": "7d"}) to check both status_code == 200 and that
response.data["charts"] is a list (use the same type-check approach used in the
legacy-timezone assertion so the two subtests remain consistent).

In `@openwisp_monitoring/utils.py`:
- Around line 40-76: The normalize_timezone function is currently decorated with
`@lru_cache`(maxsize=128) but its return can depend on settings.TIME_ZONE (when
tz_name is falsy or when fallback is used), which leaks stale values across test
contexts; remove the `@lru_cache` decorator from normalize_timezone so results are
computed per-call, or if you must cache, change the cache key to include the
current settings.TIME_ZONE (e.g. implement a custom cache that keys on (tz_name,
settings.TIME_ZONE)) and ensure ZoneInfo/ZoneInfoNotFoundError logic and the
legacy_map lookup remain unchanged.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 96c34339-4dba-48bb-9785-b77a84bc9495

📥 Commits

Reviewing files that changed from the base of the PR and between 4aa5769 and 1370f53.

📒 Files selected for processing (4)
  • openwisp_monitoring/device/tests/test_api.py
  • openwisp_monitoring/monitoring/tests/test_api.py
  • openwisp_monitoring/utils.py
  • openwisp_monitoring/views.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-02-21T18:44:28.852Z
Learnt from: dee077
Repo: openwisp/openwisp-monitoring PR: 738
File: openwisp_monitoring/device/api/views.py:263-281
Timestamp: 2026-02-21T18:44:28.852Z
Learning: In openwisp-monitoring, MonitoringIndoorCoordinatesList inherits organization scoping from the parent IndoorCoordinatesList (from openwisp-controller), which uses FilterByParentManaged mixin and filters by location_id in get_queryset(). The child class only overrides the queryset attribute to add monitoring-specific select_related fields; this pattern is safe as long as get_queryset() from the parent is not bypassed. During reviews, verify that MonitoringIndoorCoordinatesList continues to rely on the parent's get_queryset() and that any added select_related fields in the child do not alter the parent's filtering logic.

Applied to files:

  • openwisp_monitoring/utils.py
  • openwisp_monitoring/device/tests/test_api.py
  • openwisp_monitoring/monitoring/tests/test_api.py
  • openwisp_monitoring/views.py
📚 Learning: 2026-02-21T01:03:37.822Z
Learnt from: nemesifier
Repo: openwisp/openwisp-monitoring PR: 738
File: openwisp_monitoring/tests/test_selenium.py:827-859
Timestamp: 2026-02-21T01:03:37.822Z
Learning: In Selenium tests (e.g., in openwisp_monitoring/tests/test_selenium.py and similar test files), when testing JS animations on dashboards or elements driven by JavaScript (such as real-time location updates), insert a short sleep (e.g., sleep(0.3)) before WebDriverWait assertions to allow animations to complete and reduce flakiness. Note: use this as a targeted workaround and prefer explicit waits or animation-end checks where possible to avoid relying on fixed delays.

Applied to files:

  • openwisp_monitoring/device/tests/test_api.py
  • openwisp_monitoring/monitoring/tests/test_api.py
📚 Learning: 2026-02-25T18:42:08.825Z
Learnt from: dee077
Repo: openwisp/openwisp-monitoring PR: 738
File: openwisp_monitoring/tests/test_selenium.py:309-317
Timestamp: 2026-02-25T18:42:08.825Z
Learning: In Selenium tests that use ChannelsLiveServerTestCase (e.g., openwisp_monitoring/tests/test_selenium.py), override settings to configure CHANNEL_LAYERS with channels_redis.core.RedisChannelLayer instead of InMemoryChannelLayer. This is required because the live server runs in a separate process from the test process, and InMemoryChannelLayer is per-process only and cannot handle cross-process WebSocket broadcasting needed for real-time location update tests.

Applied to files:

  • openwisp_monitoring/device/tests/test_api.py
  • openwisp_monitoring/monitoring/tests/test_api.py
🔇 Additional comments (1)
openwisp_monitoring/views.py (1)

58-59: Good normalization entry point before chart query flow.

Normalizing raw_timezone at Line 59 before the rest of request processing is the right integration point and aligns with the fallback behavior expected by the regression tests.

Comment thread openwisp_monitoring/device/tests/test_api.py Outdated
@openwisp-companion
Copy link
Copy Markdown

CI Failure Analysis

Hello @UltraBot05,
(Analysis for commit 1370f53)

Test Failures

Failure Explanation

The CI pipeline failed due to a "double free or corruption (fasttop)" error, which is a critical memory corruption issue. This type of error typically indicates a bug in how memory is being managed, likely within the C extensions or underlying libraries that Python is using. The traceback shows the error occurring during the closing of database connections in Django, specifically involving SQLite.

The presence of RuntimeWarning: DateTimeField ... received a naive datetime ... while time zone support is active messages preceding the fatal error suggests potential issues with how datetime objects are being handled, possibly leading to the memory corruption.

Remediation

To address this, the following steps are recommended:

  1. Investigate Datetime Handling: Review the code related to DateTimeField usage, especially in WifiSession and any other models that store datetime information. Ensure all datetimes are timezone-aware when time zone support is active in Django settings. The warnings point to WifiSession.stop_time and WifiSession.start_time.

  2. Check Dependencies: Ensure that all Python packages and system libraries, especially those related to SQLite and database drivers, are up-to-date and compatible with the Python version being used.

  3. Isolate the Test: If possible, try to identify the specific test case that triggers this memory corruption. Running tests individually or in smaller groups can help pinpoint the problematic test. The logs show a large number of tests being run, and the error occurs towards the end.

  4. Reproduce Locally: Attempt to reproduce the error in a local development environment. This will allow for more detailed debugging using tools like gdb or Python's built-in memory debugging tools.

  5. Consider SQLite Configuration: For tests involving SQLite, ensure the database connection settings are appropriate. While the logs don't explicitly show a misconfiguration, memory corruption issues can sometimes be related to how SQLite handles concurrent access or transactions.

  6. Re-run the CI Job: In some cases, memory corruption errors can be transient due to environmental factors or race conditions. Re-running the CI job might resolve the issue if it was a one-off occurrence. However, if it persists, the above debugging steps are necessary.

@openwisp-companion
Copy link
Copy Markdown

Test Failures Detected

Hello @UltraBot05,
(Analysis for commit 21ac7eb)

There are test failures in the CI log.

Failure 1: Test Failure - "double free or corruption (fasttop)" and "Fatal Python error: Aborted"

Explanation:
The logs indicate a critical Python error, specifically a "double free or corruption (fasttop)" followed by a "Fatal Python error: Aborted". This type of error often points to memory corruption issues, which can be caused by various factors including C extension bugs, incorrect memory management in libraries, or race conditions in multi-threaded applications. The stack trace shows the error occurring during Django's database connection closing process, involving multiple threads and asynchronous operations.

Remediation:
This is a complex issue that likely requires deeper investigation into the memory management and threading behavior within the application, especially concerning how Django's database connections are handled in conjunction with asynchronous operations.

  1. Review Asynchronous Operations: Examine the code related to asynchronous operations and thread management, particularly in django.utils.asyncio and concurrent.futures. Ensure proper handling of resources and thread synchronization.
  2. Memory Debugging: If the issue persists, consider using memory debugging tools like valgrind or Python's built-in memory profiler to pinpoint the exact location of memory corruption.
  3. Isolate the Cause: Try to isolate the failing test cases or specific code paths that trigger this error. Running tests individually or in smaller batches might help identify the problematic area.
  4. Check Dependencies: Ensure all Python dependencies, especially those with C extensions (like database drivers or other low-level libraries), are compatible and up-to-date.

Failure 2: Test Failure - Multiple Slow Tests

Explanation:
The CI log reports a significant number of "slow tests" (189 detected). While not a direct failure, many tests are exceeding acceptable execution times, which can lead to CI timeouts and inefficient development cycles. The specific tests highlighted as slow include various Selenium tests (TestDeviceConnectionInlineAdmin.test_restoring_deleted_device, TestDashboardMap.test_mobile_location_updates_on_dashboard_map_with_org_isolation, etc.) and API tests (TestDashboardCharts.test_dashboard_timeseries_charts, TestDashboardMap.test_floorplan_overlay, etc.).

Remediation:
Optimizing these tests is crucial for maintaining a healthy CI pipeline.

  1. Selenium Test Optimization:
    • Reduce Wait Times: Analyze explicit waits (WebDriverWait) and implicit waits to ensure they are not excessively long.
    • Minimize DOM Interactions: Refactor tests to reduce the number of DOM lookups and interactions.
    • Headless Execution: Ensure tests are running in headless mode, as this can sometimes improve performance.
    • Parallelization: If not already implemented, consider further parallelization of Selenium tests.
  2. API Test Optimization:
    • Database Query Optimization: Review the slow API tests for inefficient database queries. Use Django Debug Toolbar or similar tools to identify N+1 query problems or unoptimized queries.
    • Test Data Management: Ensure test data setup is efficient and that large datasets are not being unnecessarily loaded or processed.
    • Mocking: Where appropriate, mock external services or complex logic to speed up tests.
  3. Code Review: Review the code associated with the slow tests to identify potential performance bottlenecks in the application logic itself. For example, inefficient data processing or serialization could contribute to slow API responses.
  4. Specific Test Remediation:
    • test_mobile_location_updates_on_dashboard_map_with_org_isolation (14.68s): This test might involve complex data rendering or processing related to organizational isolation on the dashboard map. Investigate the data fetching and rendering logic.
    • test_restoring_deleted_device (13.90s): This test involves device deletion, restoration, and checking related data integrity, which can be I/O intensive. Optimize the data cleanup and verification steps.
    • test_floorplan_overlay (4.67s): This test likely involves loading and interacting with floor plan data and overlays, which might be computationally expensive.
    • test_iperf3_check_read_bad_query (6.35s): This test seems to specifically target error handling for bad queries, which might intentionally involve slower operations. Review if this can be made more efficient without compromising the test's purpose.

The

@UltraBot05
Copy link
Copy Markdown
Author

UltraBot05 commented Mar 14, 2026

I made this PR to fix the timezone crash issue in #728.

I initially used os.path to validate the timezones, but the automated review suggested using Python's built-in zoneinfo to make it cross-platform compatible, which I adopted. I also added a test to ensure the legacy timezone fallback works correctly, and that passes cleanly.

All CI checks are now passing. I've addressed the CodeRabbit feedback by implementing subTest and restructuring normalize_timezone to be cache-safe across different test settings. The CI crash issue (double free) was resolved by removing the deep data assertions in the device API tests.

Copy link
Copy Markdown
Member

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

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

I am fine with this approach, I left some comments below.

Comment thread openwisp_monitoring/monitoring/tests/test_api.py Outdated
Comment thread openwisp_monitoring/device/tests/test_api.py Outdated
Comment thread openwisp_monitoring/utils.py Outdated
Comment thread openwisp_monitoring/utils.py Outdated
@openwisp-companion
Copy link
Copy Markdown

Test Failure in openwisp_monitoring.monitoring.tests.test_api

Hello @UltraBot05,
(Analysis for commit 6db52bd)

Test Failure: ImportError in test_api.py

  • Explanation: The test suite failed because it could not import the normalize_timezone function from openwisp_monitoring.utils. The traceback indicates an ImportError: cannot import name 'normalize_timezone' from 'openwisp_monitoring.utils'. This suggests that the normalize_timezone function might have been moved or removed from its expected location in the utils.py file.

  • Remediation:

    1. Locate the normalize_timezone function. Based on the provided code context, it appears to have been moved to openwisp_monitoring.legacy_tz_utils.py.
    2. Update the import statement in openwisp_monitoring/monitoring/tests/test_api.py from:
      from openwisp_monitoring.utils import normalize_timezone
      to:
      from openwisp_monitoring.legacy_tz_utils import normalize_timezone
    3. After making this change, the tests should be able to find and import the function correctly.

Multiple Test Failures (Marked with [31;1m)

  • Explanation: The CI logs indicate a significant number of test failures, marked with [31;1m. These failures are spread across various test modules, including test_admin.py, test_charts.py, test_models.py, test_monitoring_notifications.py, test_iperf3.py, and test_selenium.py. The specific reasons for these failures are not detailed in the provided logs, but the sheer number suggests a potential issue with recent code changes affecting multiple areas of the application or its test suite. The presence of many "slow tests" also noted in the logs might indicate performance regressions or issues that are causing timeouts or unexpected behavior.

  • Remediation:

    1. Investigate Specific Failures: The provided logs do not give specific error messages for these numerous failures. To address this, you would need to examine the detailed CI logs for each specific test that failed (e.g., test_status_data_contains_wifi_client_he_vht_ht_unknown, test_read_bad_query, test_deactivated_device_check_not_performed, etc.).
    2. Analyze Code Changes: Review the recent code commits that might have introduced these widespread failures. Changes related to data handling, API responses, admin interfaces, or test infrastructure are common culprits.
    3. Run Tests Locally: Execute the test suite locally to reproduce the failures and get more detailed error messages. This will help pinpoint the exact cause of each failure.
    4. Address Test Failures: Based on the specific error messages, fix the underlying code or the tests themselves. This might involve correcting logic errors, updating test data, or adjusting test assertions.
    5. Address Slow Tests: If performance regressions are identified, optimize the code or queries that are causing tests to run slowly.

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 the current code and only fix it if needed.

Inline comments:
In `@openwisp_monitoring/legacy_tz_utils.py`:
- Around line 33-35: The fallback resolution can be None when settings.TIME_ZONE
is explicitly set to None, causing normalize_timezone() to return None; update
the fallback logic (the variable named fallback in this module) to coalesce a
None value to "UTC" (e.g., use a fallback = getattr(settings, "TIME_ZONE",
"UTC") and then if fallback is None: fallback = "UTC", or use an or-style
coalesce) so normalize_timezone() always receives a valid timezone string when
tz_name is falsy.

In `@openwisp_monitoring/monitoring/tests/test_api.py`:
- Line 10: The test imports normalize_timezone from openwisp_monitoring.utils
but production code uses openwisp_monitoring.legacy_tz_utils.normalize_timezone;
update the import in the test (test_api.py) to import normalize_timezone from
openwisp_monitoring.legacy_tz_utils so the test exercises the same
implementation used by openwisp_monitoring.views and remains direct and
accurate.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5716c005-a054-42f7-a657-916c42ff180a

📥 Commits

Reviewing files that changed from the base of the PR and between a2b227f and 6db52bd.

📒 Files selected for processing (4)
  • openwisp_monitoring/device/tests/test_api.py
  • openwisp_monitoring/legacy_tz_utils.py
  • openwisp_monitoring/monitoring/tests/test_api.py
  • openwisp_monitoring/views.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.11 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2026-03-14T18:39:04.626Z
Learnt from: UltraBot05
Repo: openwisp/openwisp-monitoring PR: 766
File: openwisp_monitoring/utils.py:59-68
Timestamp: 2026-03-14T18:39:04.626Z
Learning: In this repository (openwisp/openwisp-monitoring), the project targets Python 3.10–3.13 as defined by CI. Do not flag backports.zoneinfo as a missing dependency; zoneinfo is a built-in module in all supported Python versions. When reviewing Python code, assume zoneinfo is available and avoid suggesting installation of backports.zoneinfo. If a file imports zoneinfo or uses it for time zone handling, treat it as standard library usage compatible with the supported CI matrix.

Applied to files:

  • openwisp_monitoring/legacy_tz_utils.py
  • openwisp_monitoring/monitoring/tests/test_api.py
  • openwisp_monitoring/views.py
  • openwisp_monitoring/device/tests/test_api.py
📚 Learning: 2026-02-21T18:44:28.852Z
Learnt from: dee077
Repo: openwisp/openwisp-monitoring PR: 738
File: openwisp_monitoring/device/api/views.py:263-281
Timestamp: 2026-02-21T18:44:28.852Z
Learning: In openwisp-monitoring, MonitoringIndoorCoordinatesList inherits organization scoping from the parent IndoorCoordinatesList (from openwisp-controller), which uses FilterByParentManaged mixin and filters by location_id in get_queryset(). The child class only overrides the queryset attribute to add monitoring-specific select_related fields; this pattern is safe as long as get_queryset() from the parent is not bypassed. During reviews, verify that MonitoringIndoorCoordinatesList continues to rely on the parent's get_queryset() and that any added select_related fields in the child do not alter the parent's filtering logic.

Applied to files:

  • openwisp_monitoring/legacy_tz_utils.py
  • openwisp_monitoring/monitoring/tests/test_api.py
  • openwisp_monitoring/views.py
  • openwisp_monitoring/device/tests/test_api.py
📚 Learning: 2026-03-14T18:39:04.626Z
Learnt from: UltraBot05
Repo: openwisp/openwisp-monitoring PR: 766
File: openwisp_monitoring/utils.py:59-68
Timestamp: 2026-03-14T18:39:04.626Z
Learning: In the openwisp-monitoring project, targets are Linux-based environments. Do not flag a Windows-specific tzdata package as a missing dependency in code reviews for Python files (e.g., openwisp_monitoring/utils.py). If a platform-specific dependency is truly required, document the exception in review guidelines and ensure CI/packaging checks enforce platform constraints rather than manual review.

Applied to files:

  • openwisp_monitoring/legacy_tz_utils.py
  • openwisp_monitoring/monitoring/tests/test_api.py
  • openwisp_monitoring/views.py
  • openwisp_monitoring/device/tests/test_api.py
📚 Learning: 2026-02-21T01:03:37.822Z
Learnt from: nemesifier
Repo: openwisp/openwisp-monitoring PR: 738
File: openwisp_monitoring/tests/test_selenium.py:827-859
Timestamp: 2026-02-21T01:03:37.822Z
Learning: In Selenium tests (e.g., in openwisp_monitoring/tests/test_selenium.py and similar test files), when testing JS animations on dashboards or elements driven by JavaScript (such as real-time location updates), insert a short sleep (e.g., sleep(0.3)) before WebDriverWait assertions to allow animations to complete and reduce flakiness. Note: use this as a targeted workaround and prefer explicit waits or animation-end checks where possible to avoid relying on fixed delays.

Applied to files:

  • openwisp_monitoring/monitoring/tests/test_api.py
  • openwisp_monitoring/device/tests/test_api.py
📚 Learning: 2026-02-25T18:42:08.825Z
Learnt from: dee077
Repo: openwisp/openwisp-monitoring PR: 738
File: openwisp_monitoring/tests/test_selenium.py:309-317
Timestamp: 2026-02-25T18:42:08.825Z
Learning: In Selenium tests that use ChannelsLiveServerTestCase (e.g., openwisp_monitoring/tests/test_selenium.py), override settings to configure CHANNEL_LAYERS with channels_redis.core.RedisChannelLayer instead of InMemoryChannelLayer. This is required because the live server runs in a separate process from the test process, and InMemoryChannelLayer is per-process only and cannot handle cross-process WebSocket broadcasting needed for real-time location update tests.

Applied to files:

  • openwisp_monitoring/monitoring/tests/test_api.py
  • openwisp_monitoring/device/tests/test_api.py
🔇 Additional comments (3)
openwisp_monitoring/views.py (1)

58-59: Timezone normalization is correctly wired into request handling.

This keeps deprecated aliases from reaching backend query generation while preserving existing validation for truly invalid values.

openwisp_monitoring/device/tests/test_api.py (1)

657-665: Good focused regression test for legacy timezone fallback.

The assertions validate successful execution of the fallback path without coupling to dataset-specific chart values.

openwisp_monitoring/monitoring/tests/test_api.py (1)

683-715: The new timezone regression tests are solid and focused.

They cover both deprecated alias handling and fallback behavior across settings overrides without over-constraining payload content.

Comment thread openwisp_monitoring/legacy_tz_utils.py Outdated
Comment thread openwisp_monitoring/monitoring/tests/test_api.py Outdated
@UltraBot05 UltraBot05 requested a review from nemesifier March 16, 2026 06:17
@UltraBot05
Copy link
Copy Markdown
Author

Hey @nemesifier. I've Successfully implemented your changes, and the following bot requests too. now the CIs are passing.
Please look into this whenever you are free. Thanks!

UltraBot05

This comment was marked as outdated.

@UltraBot05
Copy link
Copy Markdown
Author

@coderabbitai I have addressed your concerns, please review.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 24, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Thanks for the update, @UltraBot05! Let me read the current state of the changed files before re-reviewing.


🏁 Script executed:

#!/bin/bash
echo "=== legacy_tz_utils.py ==="
cat openwisp_monitoring/legacy_tz_utils.py

echo ""
echo "=== views.py ==="
cat openwisp_monitoring/views.py

echo ""
echo "=== monitoring/tests/test_api.py (last 80 lines) ==="
tail -80 openwisp_monitoring/monitoring/tests/test_api.py

echo ""
echo "=== device/tests/test_api.py (last 30 lines) ==="
tail -30 openwisp_monitoring/device/tests/test_api.py

echo ""
echo "=== utils.py (check if normalize_timezone is exported) ==="
grep -n "normalize_timezone\|legacy_tz" openwisp_monitoring/utils.py || echo "Not found in utils.py"

echo ""
echo "=== __init__.py check ==="
grep -rn "normalize_timezone\|legacy_tz_utils" openwisp_monitoring/ --include="*.py" | grep -v ".pyc"

Length of output: 15895


@UltraBot05 Thanks for addressing the feedback! The implementation looks well-structured overall. I found one test assertion bug and one minor style issue to address:


1. 🐛 Test assertion checks the wrong thing (monitoring/tests/test_api.py)

In test_legacy_timezone_fallback, the assertion for the 400 case is incorrect:

self.assertIn("Unkown Time Zone", response.data)

response.data is a dict (e.g. {"detail": "Unkown Time Zone"}), so assertIn checks keys, not values — this will always be False and the assertion effectively never validates anything. It should be:

# Option A – exact match on the detail field:
self.assertEqual(response.data["detail"], "Unkown Time Zone")

# Option B – substring check inside the detail value:
self.assertIn("Unkown Time Zone", str(response.data["detail"]))

2. ⚠️ Redundant double import in views.py

from pytz import timezone       # used only in _validate_custom_date
from pytz import timezone as tz # used in get() for validation

The bare timezone import is shadowed by the local variable timezone = normalize_timezone(raw_timezone) inside get(), which is why tz was introduced as an alias. This is functional but confusing. Consider removing the bare import and renaming the local variable to avoid the shadow:

# views.py — remove the bare import, keep only the alias
from pytz import timezone as tz
...

def _validate_custom_date(self, start, end, tmz):
    ...
    now_tz = datetime.now(tz=tz(tmz)).strftime(...)   # use tz() here too

def get(self, request, *args, **kwargs):
    ...
    raw_timezone = request.query_params.get("timezone", settings.TIME_ZONE)
    current_timezone = normalize_timezone(raw_timezone)   # rename local var
    try:
        tz(current_timezone)
    except UnknownTimeZoneError:
        raise ValidationError("Unkown Time Zone")
    ...

What looks good ✅

  • normalize_timezone correctly reads settings.TIME_ZONE dynamically (not inside the LRU-cached function), making test_timezone_fallback_cache_not_polluted reliable.
  • The sentinel _INVALID_TIMEZONE = object() pattern is clean and avoids string comparisons.
  • Invalid/unknown timezones pass through unchanged so the existing pytz validation still raises the 400 — correct behaviour.
  • subTest usage is correct.
  • The before/after screenshots clearly demonstrate the fix.

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.

[bug] Deprecated timezone Asia/Calcutta causes dashboard failures

2 participants