Skip to content

[tests] Add test for CommandTimeoutException in launch_command task#1261

Open
iamavichal-geek wants to merge 1 commit intoopenwisp:masterfrom
iamavichal-geek:tests/launch-command-timeout-exception
Open

[tests] Add test for CommandTimeoutException in launch_command task#1261
iamavichal-geek wants to merge 1 commit intoopenwisp:masterfrom
iamavichal-geek:tests/launch-command-timeout-exception

Conversation

@iamavichal-geek
Copy link

@iamavichal-geek iamavichal-geek commented Mar 7, 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

No existing issue.

While exploring the command execution flow, I noticed that the
CommandTimeoutException branch in the launch_command task
was not covered by tests.

Description of Changes

The launch_command task in connection/tasks.py handles multiple
exception paths during command execution.

The CommandTimeoutException case represents a device-side timeout
raised by the SSH connector when a command exceeds the configured
timeout. This is different from SoftTimeLimitExceeded, which is
triggered by Celery when the background task itself exceeds its time limit.

This PR adds a test (test_launch_command_ssh_timeout) in
tests/test_tasks.py to verify that when CommandTimeoutException
is raised:

  • the command status is set to failed
  • the output contains the timeout message and exception details

The test follows the structure of the existing
test_launch_command_timeout test to keep the testing style consistent
with the current codebase.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 04a01970-e7bf-47be-bf52-f94f73fba88d

📥 Commits

Reviewing files that changed from the base of the PR and between 14751ed and ec2828c.

📒 Files selected for processing (1)
  • openwisp_controller/connection/tests/test_tasks.py
📜 Recent review details
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.

Applied to files:

  • openwisp_controller/connection/tests/test_tasks.py
📚 Learning: 2026-02-17T19:13:10.088Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Applied to files:

  • openwisp_controller/connection/tests/test_tasks.py
📚 Learning: 2026-01-15T15:07:17.354Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Applied to files:

  • openwisp_controller/connection/tests/test_tasks.py
🔇 Additional comments (2)
openwisp_controller/connection/tests/test_tasks.py (2)

12-12: LGTM!

Import path is correct relative to the test file location and matches the exception class location confirmed in openwisp_controller/connection/connectors/exceptions.py.


71-93: LGTM!

Well-structured test that correctly covers the CommandTimeoutException branch in launch_command. Key observations:

  • Follows the established pattern of test_launch_command_timeout for consistency
  • Correctly mocks execute() to raise CommandTimeoutException, which propagates to the explicit handler in tasks.py (lines 78-80 in context snippet 1)
  • Uses exact assertion with assertEqual for the output string, including the trailing newline from _add_output(), which properly catches any formatting regressions
  • Clearly distinguishes device-side SSH timeout (CommandTimeoutException) from Celery task timeout (SoftTimeLimitExceeded)

📝 Walkthrough

Walkthrough

Adds a new test method test_launch_command_ssh_timeout to openwisp_controller/connection/tests/test_tasks.py. The test imports CommandTimeoutException from openwisp_controller.connection.connectors.exceptions, patches command execution to raise that exception while allowing the connection to succeed, and asserts the Command model is marked as failed with the timeout-specific output message. The existing timeout test using SoftTimeLimitExceeded remains unchanged. Changes are limited to test code; no production code or public API signatures were modified.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding a test for CommandTimeoutException in the launch_command task.
Description check ✅ Passed The description covers all required sections from the template, includes a thorough explanation of changes, and demonstrates understanding of the distinction between CommandTimeoutException and SoftTimeLimitExceeded.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

@kilo-code-bot
Copy link

kilo-code-bot bot commented Mar 7, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Overview

This PR adds a regression test for the CommandTimeoutException handling in the launch_command task. The test properly verifies that when an SSH connection timeout occurs, the command status is set to failed and the output contains an appropriate error message.

What was reviewed:

  • Test addition follows the existing codebase patterns
  • Import of CommandTimeoutException is correct
  • Test assertions match the expected behavior in tasks.py (lines 80-83)
  • Test correctly mocks the execute method to simulate the timeout scenario
Files Reviewed (1 file)
  • openwisp_controller/connection/tests/test_tasks.py - Clean test addition, no issues

Copy link
Contributor

@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

🤖 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_controller/connection/tests/test_tasks.py`:
- Around line 89-91: Replace the two loose substring checks on command.output
with a single exact assertion to lock the formatting: keep the status check on
command.status == "failed" and change the output assertions to assert that
command.output (use .strip() if trailing newline possible) equals the exact
deterministic line written by the branch, e.g. compare command.output to "The
command took longer than expected: connection timed out after 30s" so formatting
regressions are caught; update the test in test_tasks.py where command.status
and command.output are asserted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 81afb611-0d5a-4273-88ea-a45990116ba5

📥 Commits

Reviewing files that changed from the base of the PR and between ffbefa5 and b9c2c4c.

📒 Files selected for processing (1)
  • openwisp_controller/connection/tests/test_tasks.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.2.0
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.10 | 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~=4.2.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-01-15T15:05:49.557Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/management/commands/clear_last_ip.py:38-42
Timestamp: 2026-01-15T15:05:49.557Z
Learning: In Django projects, when using select_related() to traverse relations (for example, select_related("organization__config_settings")), the traversed relation must not be deferred. If you also use .only() in the same query, include the relation name or FK field (e.g., "organization" or "organization_id") in the .only() list to avoid the error "Field X cannot be both deferred and traversed using select_related at the same time." Apply this guideline to Django code in openwisp_controller/config/management/commands/clear_last_ip.py and similar modules by ensuring any select_related with an accompanying only() includes the related field names to prevent deferred/traversed conflicts.

Applied to files:

  • openwisp_controller/connection/tests/test_tasks.py
📚 Learning: 2026-02-17T19:13:10.088Z
Learnt from: nemesifier
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/config/whois/commands.py:0-0
Timestamp: 2026-02-17T19:13:10.088Z
Learning: In reviews for the openwisp/openwisp-controller repository, do not propose changes based on Ruff warnings. The project does not use Ruff as its linter; ignore Ruff-related suggestions and follow the repository’s established linting and configuration rules. This guidance applies to all Python files under the openwisp_controller directory.

Applied to files:

  • openwisp_controller/connection/tests/test_tasks.py
📚 Learning: 2026-01-15T15:07:17.354Z
Learnt from: DragnEmperor
Repo: openwisp/openwisp-controller PR: 1175
File: openwisp_controller/geo/estimated_location/tests/tests.py:172-175
Timestamp: 2026-01-15T15:07:17.354Z
Learning: In this repository, flake8 enforces E501 (line too long) via setup.cfg (max-line-length = 88) while ruff ignores E501 via ruff.toml. Therefore, use '# noqa: E501' on lines that intentionally exceed 88 characters to satisfy flake8 without affecting ruff checks. This applies to Python files across the project (any .py) and is relevant for tests as well. Use sparingly and only where breaking lines is not feasible without hurting readability or functionality.

Applied to files:

  • openwisp_controller/connection/tests/test_tasks.py
🔇 Additional comments (1)
openwisp_controller/connection/tests/test_tasks.py (1)

71-88: Good coverage of the dedicated SSH-timeout branch.

This mirrors the existing timeout test closely and exercises the CommandTimeoutException path in launch_command without broadening the test scope.

@openwisp-companion
Copy link

::warning::API Error (Max retries reached or fatal error): 429 RESOURCE_EXHAUSTED. {'error': {'code': 429, 'message': 'You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit. \n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 250000, model: gemini-2.5-flash-lite\nPlease retry in 6.658183856s.', 'status': 'RESOURCE_EXHAUSTED', 'details': [{'@type': 'type.googleapis.com/google.rpc.Help', 'links': [{'description': 'Learn more about Gemini API quotas', 'url': 'https://ai.google.dev/gemini-api/docs/rate-limits'}]}, {'@type': 'type.googleapis.com/google.rpc.QuotaFailure', 'violations': [{'quotaMetric': 'generativelanguage.googleapis.com/generate_content_free_tier_input_token_count', 'quotaId': 'GenerateContentInputTokensPerModelPerMinute-FreeTier', 'quotaDimensions': {'location': 'global', 'model': 'gemini-2.5-flash-lite'}, 'quotaValue': '250000'}]}, {'@type': 'type.googleapis.com/google.rpc.RetryInfo', 'retryDelay': '6s'}]}}

@iamavichal-geek iamavichal-geek force-pushed the tests/launch-command-timeout-exception branch from b9c2c4c to 7eb924b Compare March 7, 2026 04:31
@openwisp-companion
Copy link

::warning::API Error (Max retries reached or fatal error): 429 RESOURCE_EXHAUSTED. {'error': {'code': 429, 'message': 'You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit. \n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 250000, model: gemini-2.5-flash-lite\nPlease retry in 57.594596374s.', 'status': 'RESOURCE_EXHAUSTED', 'details': [{'@type': 'type.googleapis.com/google.rpc.Help', 'links': [{'description': 'Learn more about Gemini API quotas', 'url': 'https://ai.google.dev/gemini-api/docs/rate-limits'}]}, {'@type': 'type.googleapis.com/google.rpc.QuotaFailure', 'violations': [{'quotaMetric': 'generativelanguage.googleapis.com/generate_content_free_tier_input_token_count', 'quotaId': 'GenerateContentInputTokensPerModelPerMinute-FreeTier', 'quotaDimensions': {'model': 'gemini-2.5-flash-lite', 'location': 'global'}, 'quotaValue': '250000'}]}, {'@type': 'type.googleapis.com/google.rpc.RetryInfo', 'retryDelay': '57s'}]}}

@iamavichal-geek iamavichal-geek force-pushed the tests/launch-command-timeout-exception branch from 7eb924b to 7cac0e1 Compare March 7, 2026 05:00
@openwisp-companion
Copy link

::warning::API Error (Max retries reached or fatal error): 429 RESOURCE_EXHAUSTED. {'error': {'code': 429, 'message': 'You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit. \n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 250000, model: gemini-2.5-flash-lite\nPlease retry in 26.951940311s.', 'status': 'RESOURCE_EXHAUSTED', 'details': [{'@type': 'type.googleapis.com/google.rpc.Help', 'links': [{'description': 'Learn more about Gemini API quotas', 'url': 'https://ai.google.dev/gemini-api/docs/rate-limits'}]}, {'@type': 'type.googleapis.com/google.rpc.QuotaFailure', 'violations': [{'quotaMetric': 'generativelanguage.googleapis.com/generate_content_free_tier_input_token_count', 'quotaId': 'GenerateContentInputTokensPerModelPerMinute-FreeTier', 'quotaDimensions': {'model': 'gemini-2.5-flash-lite', 'location': 'global'}, 'quotaValue': '250000'}]}, {'@type': 'type.googleapis.com/google.rpc.RetryInfo', 'retryDelay': '26s'}]}}

@iamavichal-geek iamavichal-geek force-pushed the tests/launch-command-timeout-exception branch 2 times, most recently from 5f8ccf2 to 14751ed Compare March 7, 2026 05:52
@openwisp-companion
Copy link

::warning::API Error (Max retries reached or fatal error): 429 RESOURCE_EXHAUSTED. {'error': {'code': 429, 'message': 'You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit. \n* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 250000, model: gemini-2.5-flash-lite\nPlease retry in 35.919468138s.', 'status': 'RESOURCE_EXHAUSTED', 'details': [{'@type': 'type.googleapis.com/google.rpc.Help', 'links': [{'description': 'Learn more about Gemini API quotas', 'url': 'https://ai.google.dev/gemini-api/docs/rate-limits'}]}, {'@type': 'type.googleapis.com/google.rpc.QuotaFailure', 'violations': [{'quotaMetric': 'generativelanguage.googleapis.com/generate_content_free_tier_input_token_count', 'quotaId': 'GenerateContentInputTokensPerModelPerMinute-FreeTier', 'quotaDimensions': {'model': 'gemini-2.5-flash-lite', 'location': 'global'}, 'quotaValue': '250000'}]}, {'@type': 'type.googleapis.com/google.rpc.RetryInfo', 'retryDelay': '35s'}]}}

Added test_launch_command_ssh_timeout to verify that when
CommandTimeoutException is raised during command execution,
the command status is set to 'failed' and the output contains
the expected timeout message.
@iamavichal-geek iamavichal-geek force-pushed the tests/launch-command-timeout-exception branch from 14751ed to ec2828c Compare March 7, 2026 19:25
@iamavichal-geek iamavichal-geek changed the title [connection] add test for CommandTimeoutException in launch_command task [tests] Add test for CommandTimeoutException in launch_command task Mar 7, 2026
@coveralls
Copy link

Coverage Status

coverage: 98.712% (+0.04%) from 98.672%
when pulling ec2828c on iamavichal-geek:tests/launch-command-timeout-exception
into ffbefa5 on openwisp:master.

@iamavichal-geek
Copy link
Author

Hi @nemesifier, Avichal (Avi) this side. I have been going through the codebaseI particularly AbstractCommand, the launch_command Celery task, the DeviceAdmin action pattern, and the WebSocket pipeline in commands.js.

I noticed the CommandTimeoutException branch in launch_command had no test coverage hence opening a small PR to address it : https://github.com/openwisp/openwisp-controller/pull/1261

I opened the PR directly since it was a small, self-contained test addition. Happy to open an issue first for future contributions if that is preferred. All CI checks are passing. Would appreciate any feedback from your side.

@asmodehn
Copy link
Contributor

asmodehn commented Mar 9, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 9, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

3 participants