Skip to content

[change:api] Include reset period in UserRadiusUsageView response #669#670

Open
PRoIISHAAN wants to merge 3 commits intoopenwisp:masterfrom
PRoIISHAAN:api-change
Open

[change:api] Include reset period in UserRadiusUsageView response #669#670
PRoIISHAAN wants to merge 3 commits intoopenwisp:masterfrom
PRoIISHAAN:api-change

Conversation

@PRoIISHAAN
Copy link

@PRoIISHAAN PRoIISHAAN commented Jan 17, 2026

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 #669 >.

Description of Changes

Modified API response to return reset attribute in API

@coderabbitai
Copy link

coderabbitai bot commented Jan 17, 2026

📝 Walkthrough

Walkthrough

Adds a reset field to UserGroupCheckSerializer as a SerializerMethodField. Implements get_reset(self, obj) which looks up the counter type via app_settings.CHECK_ATTRIBUTE_COUNTERS_MAP, calls the counter's get_reset_timestamps to obtain an end_time, and returns that timestamp. get_reset handles SkipCheck, ValueError, and KeyError by returning None. Serializer Meta.fields is updated to include "reset". Tests are updated to include and assert the new reset field in serialized outputs.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant View
    participant Serializer
    participant AppSettings as app_settings
    participant CounterModule as Counter

    Client->>View: GET UserRadiusUsage
    View->>Serializer: serialize(checks)
    Serializer->>AppSettings: lookup CHECK_ATTRIBUTE_COUNTERS_MAP[type]
    AppSettings-->>Serializer: counter_class
    Serializer->>Counter: counter_class.get_reset_timestamps(obj)
    Counter-->>Serializer: {start_time, end_time} or raise/None
    Serializer-->>View: serialized data (includes reset)
    View-->>Client: HTTP 200 with JSON (includes reset)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers key requirements but lacks detail on test updates. The checklist shows test updates were not completed, which is concerning for a feature addition. Provide evidence of test coverage for the new reset field or update the checklist item. Add documentation or clarify why test updates were not needed.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly describes the main change: adding reset period to the UserRadiusUsageView API response, with issue reference.
Linked Issues check ✅ Passed The PR adds the reset field to UserGroupCheckSerializer with proper implementation using get_reset method matching issue #669 requirements.
Out of Scope Changes check ✅ Passed Changes are narrowly scoped to adding the reset field to serializer and updating related tests, all aligned with issue #669 objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 17, 2026
Copy link
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.

Thanks @PRoIISHAAN 👍

We need to add a test for this, see also my comment below.

Copy link

@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: 0

Caution

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

⚠️ Outside diff range comments (1)
openwisp_radius/tests/test_api/test_api.py (1)

1094-1115: Test assertions for reset field only verify presence, not correctness.

The assertions use checks[0]["reset"] as the expected value, which is self-referential and only confirms the field exists. Unlike result which is tested with concrete expected values (e.g., 0, 261), the reset field's actual value is never validated.

Consider at minimum:

  1. Asserting that reset is not None when a counter exists
  2. Verifying the type is a valid datetime (e.g., assertIsInstance or regex match for ISO format)
  3. Or mocking time to get deterministic expected values
💡 Example improvement
from datetime import datetime

# Add type assertion for reset field
self.assertIsNotNone(checks[0]["reset"])
# Optionally verify it's a valid ISO datetime string
datetime.fromisoformat(checks[0]["reset"].replace("Z", "+00:00"))
🧹 Nitpick comments (1)
openwisp_radius/api/serializers.py (1)

330-341: LGTM! Consider extracting shared counter instantiation logic.

The implementation correctly mirrors the existing get_result pattern and handles exceptions appropriately. The static analysis hint (TRY300) about moving the return to an else block is a style preference—since get_result uses the same pattern, consistency is more valuable here.

For a future improvement, you could extract the shared counter instantiation logic (lines 307-313 and 332-337) into a helper method to reduce duplication, but this is optional.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e1cf73 and 5d5e786.

📒 Files selected for processing (2)
  • openwisp_radius/api/serializers.py
  • openwisp_radius/tests/test_api/test_api.py
🧰 Additional context used
🧬 Code graph analysis (1)
openwisp_radius/api/serializers.py (3)
openwisp_radius/counters/base.py (2)
  • reset (39-40)
  • get_reset_timestamps (81-89)
openwisp_radius/models.py (1)
  • RadiusGroupCheck (49-52)
openwisp_radius/counters/exceptions.py (1)
  • SkipCheck (13-18)
🪛 Ruff (0.14.13)
openwisp_radius/api/serializers.py

339-339: Consider moving this statement to an else block

(TRY300)

🔇 Additional comments (2)
openwisp_radius/tests/test_api/test_api.py (2)

1064-1075: LGTM!

The test correctly verifies that reset is None when the counter attribute doesn't exist in CHECK_ATTRIBUTE_COUNTERS_MAP.


1245-1265: LGTM!

This test appropriately mirrors the test_user_group_check_serializer_counter_does_not_exist test in TestApi for the transaction test case.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@PRoIISHAAN PRoIISHAAN requested a review from nemesifier January 17, 2026 15:15
@coveralls
Copy link

coveralls commented Jan 17, 2026

Coverage Status

coverage: 88.242% (-3.8%) from 91.995%
when pulling f2976bc on PRoIISHAAN:api-change
into 33bbd55 on openwisp:master.

Copy link

@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.

Caution

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

⚠️ Outside diff range comments (2)
openwisp_radius/tests/test_api/test_api.py (1)

1307-1457: ⚠️ Potential issue | 🟠 Major

Tautological reset assertions don't validate the feature — tests will always pass regardless of the actual value.

"reset": checks[0]["reset"] asserts that a key equals itself, which is trivially true. For Max-Daily-Session and Max-Daily-Session-Traffic (both daily counters), reset should be a non-None ISO 8601 datetime string representing the next midnight. The test suite would pass even if get_reset returned a completely wrong value, or even if it always returned None.

✅ Proposed fix — assert non-None and validate format

Replace the tautological assertions with explicit value checks. For example, for each subtest:

-                "reset": checks[0]["reset"],
+                "reset": checks[0]["reset"],  # keep for dict equality, but add:

Then add explicit assertions after each assertDictEqual:

+            self.assertIsNotNone(checks[0]["reset"])
+            self.assertIsNotNone(checks[1]["reset"])

Or, more thoroughly, validate the format using a datetime.fromisoformat / dateutil.parser.parse check:

from django.utils.dateparse import parse_datetime
self.assertIsNotNone(parse_datetime(checks[0]["reset"]))
self.assertIsNotNone(parse_datetime(checks[1]["reset"]))

Alternatively, compute the expected next-midnight value and assert it directly.

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

In `@openwisp_radius/tests/test_api/test_api.py` around lines 1307 - 1457, The
tests currently assert `"reset": checks[0]["reset"]` which is tautological and
doesn't validate reset values; update the subtests that check `checks[0]` and
`checks[1]` (the entries for "Max-Daily-Session" and
"Max-Daily-Session-Traffic") to instead assert that `checks[i]["reset"]` is not
None and is a valid ISO8601 datetime (e.g., use Django's `parse_datetime` /
`datetime.fromisoformat` to parse and assert parsing succeeded), or compute the
expected next-midnight datetime and assert equality—replace the self-referential
`"reset": checks[i]["reset"]` entries with these explicit assertions.
openwisp_radius/api/serializers.py (1)

309-344: 🧹 Nitpick | 🔵 Trivial

Extract a shared _get_counter(obj) helper to avoid double counter instantiation.

Both get_result (Line 312–316) and get_reset (Line 336–340) independently instantiate the same Counter object for the same obj. Each instantiation may trigger database access (e.g., in consumed() and get_reset_timestamps()), so serializing a single check item fires the counter constructor twice.

♻️ Proposed refactor
+    def _get_counter(self, obj):
+        Counter = app_settings.CHECK_ATTRIBUTE_COUNTERS_MAP[obj.attribute]
+        return Counter(
+            user=self.context["user"],
+            group=self.context["group"],
+            group_check=obj,
+        )
+
     def get_result(self, obj):
         try:
-            Counter = app_settings.CHECK_ATTRIBUTE_COUNTERS_MAP[obj.attribute]
-            counter = Counter(
-                user=self.context["user"],
-                group=self.context["group"],
-                group_check=obj,
-            )
+            counter = self._get_counter(obj)
             consumed = counter.consumed()
             value = int(obj.value)
             if consumed > value:
                 consumed = value
             return consumed
         except (SkipCheck, ValueError, KeyError):
             return None

     def get_reset(self, obj):
         try:
-            Counter = app_settings.CHECK_ATTRIBUTE_COUNTERS_MAP[obj.attribute]
-            counter = Counter(
-                user=self.context["user"],
-                group=self.context["group"],
-                group_check=obj,
-            )
+            counter = self._get_counter(obj)
             _, end_time = counter.get_reset_timestamps()
             return end_time
         except (SkipCheck, ValueError, KeyError):
             return None

Note: _get_counter itself should remain free of a try/except, since callers already handle KeyError and SkipCheck.

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

In `@openwisp_radius/api/serializers.py` around lines 309 - 344, Add a private
helper method _get_counter(self, obj) that looks up the Counter class from
app_settings.CHECK_ATTRIBUTE_COUNTERS_MAP[obj.attribute] and returns
Counter(user=self.context["user"], group=self.context["group"], group_check=obj)
(do not wrap it in try/except). Replace the duplicated Counter instantiation in
get_result(self, obj) and get_reset(self, obj) to call self._get_counter(obj)
and keep the existing exception handling in those methods intact; leave get_type
as-is (it only needs the Counter class lookup).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@openwisp_radius/api/serializers.py`:
- Around line 309-344: Add a private helper method _get_counter(self, obj) that
looks up the Counter class from
app_settings.CHECK_ATTRIBUTE_COUNTERS_MAP[obj.attribute] and returns
Counter(user=self.context["user"], group=self.context["group"], group_check=obj)
(do not wrap it in try/except). Replace the duplicated Counter instantiation in
get_result(self, obj) and get_reset(self, obj) to call self._get_counter(obj)
and keep the existing exception handling in those methods intact; leave get_type
as-is (it only needs the Counter class lookup).

In `@openwisp_radius/tests/test_api/test_api.py`:
- Around line 1307-1457: The tests currently assert `"reset":
checks[0]["reset"]` which is tautological and doesn't validate reset values;
update the subtests that check `checks[0]` and `checks[1]` (the entries for
"Max-Daily-Session" and "Max-Daily-Session-Traffic") to instead assert that
`checks[i]["reset"]` is not None and is a valid ISO8601 datetime (e.g., use
Django's `parse_datetime` / `datetime.fromisoformat` to parse and assert parsing
succeeded), or compute the expected next-midnight datetime and assert
equality—replace the self-referential `"reset": checks[i]["reset"]` entries with
these explicit assertions.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5e786 and f2976bc.

📒 Files selected for processing (2)
  • openwisp_radius/api/serializers.py
  • openwisp_radius/tests/test_api/test_api.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.12 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.10 | 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.13 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0
🧰 Additional context used
🧬 Code graph analysis (1)
openwisp_radius/api/serializers.py (3)
openwisp_radius/counters/base.py (2)
  • reset (39-40)
  • get_reset_timestamps (81-89)
openwisp_radius/models.py (1)
  • RadiusGroupCheck (49-52)
openwisp_radius/counters/exceptions.py (1)
  • SkipCheck (13-18)

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.

[change:api] Include reset period in UserRadiusUsageView response

3 participants