mirror of
https://github.com/ipnet-mesh/meshcore-mqtt.git
synced 2026-08-07 01:13:17 +02:00
test: fix type annotations and code quality issues in rate limiting tests
This commit is contained in:
+101
-63
@@ -2,8 +2,8 @@
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from typing import Any, Dict
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -21,7 +21,7 @@ def test_config() -> Config:
|
||||
address="127.0.0.1",
|
||||
port=12345,
|
||||
message_initial_delay=0.1, # 100ms for faster tests
|
||||
message_send_delay=0.2, # 200ms for faster tests
|
||||
message_send_delay=0.2, # 200ms for faster tests
|
||||
),
|
||||
)
|
||||
|
||||
@@ -60,7 +60,9 @@ def mock_meshcore_worker(test_config: Config) -> MeshCoreWorker:
|
||||
class TestRateLimiting:
|
||||
"""Test message rate limiting functionality."""
|
||||
|
||||
async def test_message_initial_delay(self, mock_meshcore_worker: MeshCoreWorker) -> None:
|
||||
async def test_message_initial_delay(
|
||||
self, mock_meshcore_worker: MeshCoreWorker
|
||||
) -> None:
|
||||
"""Test that initial delay is applied before the first message."""
|
||||
start_time = time.time()
|
||||
|
||||
@@ -69,17 +71,15 @@ class TestRateLimiting:
|
||||
|
||||
# Queue a message
|
||||
command_data = {"destination": "test", "message": "test"}
|
||||
future = asyncio.Future()
|
||||
message_data = {
|
||||
"command_type": "send_msg",
|
||||
"future": future,
|
||||
**command_data
|
||||
}
|
||||
future: asyncio.Future[Any] = asyncio.Future()
|
||||
message_data = {"command_type": "send_msg", "future": future, **command_data}
|
||||
|
||||
await mock_meshcore_worker._message_queue.put(message_data)
|
||||
|
||||
# Start the rate limiter
|
||||
rate_limiter_task = asyncio.create_task(mock_meshcore_worker._message_rate_limiter())
|
||||
rate_limiter_task = asyncio.create_task(
|
||||
mock_meshcore_worker._message_rate_limiter()
|
||||
)
|
||||
|
||||
# Wait for the message to be processed
|
||||
try:
|
||||
@@ -97,9 +97,13 @@ class TestRateLimiting:
|
||||
elapsed_time = time.time() - start_time
|
||||
|
||||
# Should take at least the initial delay time
|
||||
assert elapsed_time >= mock_meshcore_worker.config.meshcore.message_initial_delay
|
||||
assert (
|
||||
elapsed_time >= mock_meshcore_worker.config.meshcore.message_initial_delay
|
||||
)
|
||||
|
||||
async def test_message_send_delay(self, mock_meshcore_worker: MeshCoreWorker) -> None:
|
||||
async def test_message_send_delay(
|
||||
self, mock_meshcore_worker: MeshCoreWorker
|
||||
) -> None:
|
||||
"""Test that delay is applied between consecutive messages."""
|
||||
# Set worker to running state
|
||||
mock_meshcore_worker._running = True
|
||||
@@ -111,17 +115,15 @@ class TestRateLimiting:
|
||||
|
||||
# Queue a message
|
||||
command_data = {"destination": "test", "message": "test"}
|
||||
future = asyncio.Future()
|
||||
message_data = {
|
||||
"command_type": "send_msg",
|
||||
"future": future,
|
||||
**command_data
|
||||
}
|
||||
future: asyncio.Future[Any] = asyncio.Future()
|
||||
message_data = {"command_type": "send_msg", "future": future, **command_data}
|
||||
|
||||
await mock_meshcore_worker._message_queue.put(message_data)
|
||||
|
||||
# Start the rate limiter
|
||||
rate_limiter_task = asyncio.create_task(mock_meshcore_worker._message_rate_limiter())
|
||||
rate_limiter_task = asyncio.create_task(
|
||||
mock_meshcore_worker._message_rate_limiter()
|
||||
)
|
||||
|
||||
# Wait for the message to be processed
|
||||
try:
|
||||
@@ -141,7 +143,9 @@ class TestRateLimiting:
|
||||
# Should take at least the send delay time
|
||||
assert elapsed_time >= mock_meshcore_worker.config.meshcore.message_send_delay
|
||||
|
||||
async def test_no_delay_with_zero_config(self, test_config_zero_delays: Config) -> None:
|
||||
async def test_no_delay_with_zero_config(
|
||||
self, test_config_zero_delays: Config
|
||||
) -> None:
|
||||
"""Test that no delays are applied when configured to zero."""
|
||||
worker = MeshCoreWorker(test_config_zero_delays)
|
||||
|
||||
@@ -158,12 +162,8 @@ class TestRateLimiting:
|
||||
|
||||
# Queue a message
|
||||
command_data = {"destination": "test", "message": "test"}
|
||||
future = asyncio.Future()
|
||||
message_data = {
|
||||
"command_type": "send_msg",
|
||||
"future": future,
|
||||
**command_data
|
||||
}
|
||||
future: asyncio.Future[Any] = asyncio.Future()
|
||||
message_data = {"command_type": "send_msg", "future": future, **command_data}
|
||||
|
||||
await worker._message_queue.put(message_data)
|
||||
|
||||
@@ -188,50 +188,71 @@ class TestRateLimiting:
|
||||
# Should complete quickly with no delays
|
||||
assert elapsed_time < 0.1
|
||||
|
||||
async def test_rate_limited_command_execution(self, mock_meshcore_worker: MeshCoreWorker) -> None:
|
||||
async def test_rate_limited_command_execution(
|
||||
self, mock_meshcore_worker: MeshCoreWorker
|
||||
) -> None:
|
||||
"""Test that rate-limited commands are executed correctly."""
|
||||
# Test send_msg command
|
||||
result = await mock_meshcore_worker._execute_rate_limited_message({
|
||||
"command_type": "send_msg",
|
||||
"destination": "test_user",
|
||||
"message": "Hello",
|
||||
"future": None
|
||||
})
|
||||
await mock_meshcore_worker._execute_rate_limited_message(
|
||||
{
|
||||
"command_type": "send_msg",
|
||||
"destination": "test_user",
|
||||
"message": "Hello",
|
||||
"future": None,
|
||||
}
|
||||
)
|
||||
|
||||
# Verify the command was called
|
||||
mock_meshcore_worker.meshcore.commands.send_msg.assert_called_once_with("test_user", "Hello")
|
||||
assert mock_meshcore_worker.meshcore is not None
|
||||
mock_meshcore_worker.meshcore.commands.send_msg.assert_called_once_with(
|
||||
"test_user", "Hello"
|
||||
)
|
||||
|
||||
# Test send_chan_msg command
|
||||
await mock_meshcore_worker._execute_rate_limited_message({
|
||||
"command_type": "send_chan_msg",
|
||||
"channel": 0,
|
||||
"message": "Hello channel",
|
||||
"future": None
|
||||
})
|
||||
await mock_meshcore_worker._execute_rate_limited_message(
|
||||
{
|
||||
"command_type": "send_chan_msg",
|
||||
"channel": 0,
|
||||
"message": "Hello channel",
|
||||
"future": None,
|
||||
}
|
||||
)
|
||||
|
||||
# Verify the command was called
|
||||
mock_meshcore_worker.meshcore.commands.send_chan_msg.assert_called_once_with(0, "Hello channel")
|
||||
assert mock_meshcore_worker.meshcore is not None
|
||||
mock_meshcore_worker.meshcore.commands.send_chan_msg.assert_called_once_with(
|
||||
0, "Hello channel"
|
||||
)
|
||||
|
||||
async def test_queue_rate_limited_command(self, mock_meshcore_worker: MeshCoreWorker) -> None:
|
||||
async def test_queue_rate_limited_command(
|
||||
self, mock_meshcore_worker: MeshCoreWorker
|
||||
) -> None:
|
||||
"""Test queuing and execution of rate-limited commands."""
|
||||
# Set worker to running state
|
||||
mock_meshcore_worker._running = True
|
||||
|
||||
# Start the rate limiter in the background
|
||||
rate_limiter_task = asyncio.create_task(mock_meshcore_worker._message_rate_limiter())
|
||||
rate_limiter_task = asyncio.create_task(
|
||||
mock_meshcore_worker._message_rate_limiter()
|
||||
)
|
||||
|
||||
try:
|
||||
# Queue a command
|
||||
command_data = {"destination": "test_user", "message": "Hello"}
|
||||
result_task = asyncio.create_task(
|
||||
mock_meshcore_worker._queue_rate_limited_command("send_msg", command_data)
|
||||
mock_meshcore_worker._queue_rate_limited_command(
|
||||
"send_msg", command_data
|
||||
)
|
||||
)
|
||||
|
||||
# Wait for completion
|
||||
result = await asyncio.wait_for(result_task, timeout=2.0)
|
||||
await asyncio.wait_for(result_task, timeout=2.0)
|
||||
|
||||
# Verify the command was executed
|
||||
mock_meshcore_worker.meshcore.commands.send_msg.assert_called_once_with("test_user", "Hello")
|
||||
assert mock_meshcore_worker.meshcore is not None
|
||||
mock_meshcore_worker.meshcore.commands.send_msg.assert_called_once_with(
|
||||
"test_user", "Hello"
|
||||
)
|
||||
|
||||
finally:
|
||||
mock_meshcore_worker._running = False # Stop the rate limiter
|
||||
@@ -241,13 +262,17 @@ class TestRateLimiting:
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
async def test_multiple_messages_rate_limiting(self, mock_meshcore_worker: MeshCoreWorker) -> None:
|
||||
async def test_multiple_messages_rate_limiting(
|
||||
self, mock_meshcore_worker: MeshCoreWorker
|
||||
) -> None:
|
||||
"""Test that multiple messages are properly rate limited."""
|
||||
# Set worker to running state
|
||||
mock_meshcore_worker._running = True
|
||||
|
||||
# Start the rate limiter
|
||||
rate_limiter_task = asyncio.create_task(mock_meshcore_worker._message_rate_limiter())
|
||||
rate_limiter_task = asyncio.create_task(
|
||||
mock_meshcore_worker._message_rate_limiter()
|
||||
)
|
||||
|
||||
try:
|
||||
start_time = time.time()
|
||||
@@ -257,7 +282,9 @@ class TestRateLimiting:
|
||||
for i in range(3):
|
||||
command_data = {"destination": f"user{i}", "message": f"Message {i}"}
|
||||
task = asyncio.create_task(
|
||||
mock_meshcore_worker._queue_rate_limited_command("send_msg", command_data)
|
||||
mock_meshcore_worker._queue_rate_limited_command(
|
||||
"send_msg", command_data
|
||||
)
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
@@ -268,12 +295,13 @@ class TestRateLimiting:
|
||||
|
||||
# Should take at least initial_delay + 2 * send_delay for 3 messages
|
||||
expected_min_time = (
|
||||
mock_meshcore_worker.config.meshcore.message_initial_delay +
|
||||
2 * mock_meshcore_worker.config.meshcore.message_send_delay
|
||||
mock_meshcore_worker.config.meshcore.message_initial_delay
|
||||
+ 2 * mock_meshcore_worker.config.meshcore.message_send_delay
|
||||
)
|
||||
assert elapsed_time >= expected_min_time
|
||||
|
||||
# Verify all commands were executed
|
||||
assert mock_meshcore_worker.meshcore is not None
|
||||
assert mock_meshcore_worker.meshcore.commands.send_msg.call_count == 3
|
||||
|
||||
finally:
|
||||
@@ -284,24 +312,29 @@ class TestRateLimiting:
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
async def test_rate_limiter_error_handling(self, mock_meshcore_worker: MeshCoreWorker) -> None:
|
||||
async def test_rate_limiter_error_handling(
|
||||
self, mock_meshcore_worker: MeshCoreWorker
|
||||
) -> None:
|
||||
"""Test error handling in rate-limited message execution."""
|
||||
# Set worker to running state
|
||||
mock_meshcore_worker._running = True
|
||||
|
||||
# Make the mock command raise an exception - but we need to bypass the retry logic
|
||||
# So we'll make it raise an exception in _execute_rate_limited_message directly
|
||||
# Make the mock command raise an exception - bypass the retry logic
|
||||
# So we'll make it raise an exception in _execute_rate_limited_message
|
||||
original_execute = mock_meshcore_worker._execute_rate_limited_message
|
||||
|
||||
async def mock_execute(message_data):
|
||||
async def mock_execute(message_data: Dict[str, Any]) -> None:
|
||||
future = message_data.get("future")
|
||||
if future and not future.done():
|
||||
future.set_exception(Exception("Test error"))
|
||||
|
||||
mock_meshcore_worker._execute_rate_limited_message = mock_execute
|
||||
# Use setattr to avoid mypy error about method assignment
|
||||
setattr(mock_meshcore_worker, "_execute_rate_limited_message", mock_execute)
|
||||
|
||||
# Start the rate limiter
|
||||
rate_limiter_task = asyncio.create_task(mock_meshcore_worker._message_rate_limiter())
|
||||
rate_limiter_task = asyncio.create_task(
|
||||
mock_meshcore_worker._message_rate_limiter()
|
||||
)
|
||||
|
||||
try:
|
||||
# Queue a command that will fail
|
||||
@@ -309,13 +342,18 @@ class TestRateLimiting:
|
||||
|
||||
with pytest.raises(Exception, match="Test error"):
|
||||
await asyncio.wait_for(
|
||||
mock_meshcore_worker._queue_rate_limited_command("send_msg", command_data),
|
||||
timeout=2.0
|
||||
mock_meshcore_worker._queue_rate_limited_command(
|
||||
"send_msg", command_data
|
||||
),
|
||||
timeout=2.0,
|
||||
)
|
||||
|
||||
finally:
|
||||
mock_meshcore_worker._running = False # Stop the rate limiter
|
||||
mock_meshcore_worker._execute_rate_limited_message = original_execute # Restore original
|
||||
# Restore original method
|
||||
setattr(
|
||||
mock_meshcore_worker, "_execute_rate_limited_message", original_execute
|
||||
)
|
||||
rate_limiter_task.cancel()
|
||||
try:
|
||||
await rate_limiter_task
|
||||
@@ -344,7 +382,7 @@ class TestRateLimiting:
|
||||
connection_type=ConnectionType.TCP,
|
||||
address="127.0.0.1",
|
||||
message_initial_delay=0.0, # Minimum
|
||||
message_send_delay=60.0, # Maximum
|
||||
message_send_delay=60.0, # Maximum
|
||||
),
|
||||
)
|
||||
assert config.meshcore.message_initial_delay == 0.0
|
||||
@@ -369,4 +407,4 @@ class TestRateLimiting:
|
||||
address="127.0.0.1",
|
||||
message_send_delay=61.0, # Invalid: too large
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user