1+ # Copyright (C) 2026 Kajetan Rachwał
2+ #
3+ # Licensed under the Apache License, Version 2.0 (the "License");
4+ # you may not use this file except in compliance with the License.
5+ # You may obtain a copy of the License at
6+ #
7+ # http://www.apache.org/licenses/LICENSE-2.0
8+ #
9+ # Unless required by applicable law or agreed to in writing, software
10+ # distributed under the License is distributed on an "AS IS" BASIS,
11+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ # See the License for the specific language governing permissions and
13+ # limitations under the License.
14+
115import asyncio
2- import threading
316import time
4- import pytest
517from unittest .mock import AsyncMock , MagicMock , patch
6- from aiohttp import web
718
19+ import pytest
20+ from aiohttp import web
821from rai .communication .http .api import HTTPAPI , HTTPAPIError , HTTPConnectorMode
922
10-
1123# ── Fixtures ──────────────────────────────────────────────────────────────────
1224
25+
1326@pytest .fixture
1427def client_api ():
1528 api = HTTPAPI (mode = HTTPConnectorMode .client )
@@ -36,6 +49,7 @@ def client_server_api():
3649
3750# ── HTTPConnectorMode ─────────────────────────────────────────────────────────
3851
52+
3953class TestHTTPConnectorMode :
4054 def test_client_value (self ):
4155 assert HTTPConnectorMode .client == 1
@@ -58,11 +72,15 @@ def test_server_flag_in_client_server(self):
5872 assert HTTPConnectorMode .server & HTTPConnectorMode .client_server
5973
6074 def test_client_flag_not_in_server_only (self ):
61- assert not (HTTPConnectorMode .client & HTTPConnectorMode .server == HTTPConnectorMode .client_server )
75+ assert not (
76+ HTTPConnectorMode .client & HTTPConnectorMode .server
77+ == HTTPConnectorMode .client_server
78+ )
6279
6380
6481# ── Initialisation ────────────────────────────────────────────────────────────
6582
83+
6684class TestHTTPAPIInit :
6785 def test_default_attributes (self ):
6886 api = HTTPAPI ()
@@ -91,6 +109,7 @@ def test_started_event_initially_unset(self):
91109
92110# ── run() / stop() lifecycle ──────────────────────────────────────────────────
93111
112+
94113class TestLifecycle :
95114 def test_run_sets_started_event (self , client_api ):
96115 assert client_api ._started_event .is_set ()
@@ -118,6 +137,7 @@ def test_double_stop_does_not_raise(self, client_api):
118137
119138# ── add_route() ───────────────────────────────────────────────────────────────
120139
140+
121141class TestAddRoute :
122142 def test_add_route_ignored_in_client_only_mode (self , client_api ):
123143 client_api .add_route ("GET" , "/ignored" , AsyncMock ())
@@ -163,21 +183,20 @@ async def handler(request):
163183
164184# ── send_request() ────────────────────────────────────────────────────────────
165185
186+
166187class TestSendRequest :
167188 def test_raises_in_server_only_mode (self , server_api ):
168189 with pytest .raises (HTTPAPIError , match = "client mode disabled" ):
169190 server_api .send_request (
170- "GET" , "http://example.com" , timeout = 1.0 ,
171- payload = None , headers = None
191+ "GET" , "http://example.com" , timeout = 1.0 , payload = None , headers = None
172192 )
173193
174194 def test_returns_empty_string_and_200_when_no_timeout (self , client_api ):
175195 """Fire-and-forget path (timeout=None) returns immediately."""
176196 with patch .object (client_api , "_request" , new_callable = AsyncMock ) as mock_req :
177197 mock_req .return_value = ("" , 200 )
178198 body , status = client_api .send_request (
179- "GET" , "http://example.com" , timeout = None ,
180- payload = None , headers = None
199+ "GET" , "http://example.com" , timeout = None , payload = None , headers = None
181200 )
182201 assert body == ""
183202 assert status == 200
@@ -244,8 +263,7 @@ def test_future_added_to_unresolved(self, client_api):
244263 mock_req .return_value = ("" , 200 )
245264 before = len (client_api .unresolved_futures )
246265 client_api .send_request (
247- "GET" , "http://example.com" , timeout = None ,
248- payload = None , headers = None
266+ "GET" , "http://example.com" , timeout = None , payload = None , headers = None
249267 )
250268 time .sleep (0.05 )
251269 # future is appended even for fire-and-forget
@@ -264,6 +282,7 @@ def test_404_response(self, client_server_api):
264282
265283# ── shutdown() ────────────────────────────────────────────────────────────────
266284
285+
267286class TestShutdown :
268287 def test_shutdown_clears_unresolved_futures (self , client_api ):
269288 mock_future = MagicMock ()
@@ -299,6 +318,7 @@ def test_shutdown_with_no_futures_is_noop(self, client_api):
299318
300319# ── _request() (internal async helper) ───────────────────────────────────────
301320
321+
302322class TestInternalRequest :
303323 def test_request_returns_text_and_status (self , client_server_api ):
304324 async def handler (request ):
0 commit comments