Skip to content

Commit d49d2cc

Browse files
committed
Add tests
1 parent 7f7211e commit d49d2cc

File tree

7 files changed

+314
-4
lines changed

7 files changed

+314
-4
lines changed

tests/common/test_asyncapi.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import pytest
2+
from pydantic import BaseModel
3+
4+
from common.asyncapi import (
5+
get_schema,
6+
init_asyncapi_info,
7+
register_channel,
8+
register_channel_operation,
9+
register_server,
10+
)
11+
12+
13+
# Test fixtures
14+
@pytest.fixture
15+
def reset_asyncapi_state():
16+
"""Reset all global state between tests"""
17+
from common.asyncapi import _channels, _components_schemas, _operations, _servers
18+
19+
_servers.clear()
20+
_channels.clear()
21+
_operations.clear()
22+
_components_schemas.clear()
23+
yield
24+
_servers.clear()
25+
_channels.clear()
26+
_operations.clear()
27+
_components_schemas.clear()
28+
29+
30+
# Test message models
31+
class TestMessage(BaseModel):
32+
content: str
33+
timestamp: int
34+
35+
36+
class AnotherTestMessage(BaseModel):
37+
status: bool
38+
code: int
39+
40+
41+
# Test cases
42+
def test_init_asyncapi_info():
43+
"""Test initialization of AsyncAPI info"""
44+
title = "Test API"
45+
version = "2.0.0"
46+
47+
init_asyncapi_info(title=title, version=version)
48+
schema = get_schema()
49+
50+
assert schema.info.title == title
51+
assert schema.info.version == version
52+
53+
54+
def test_register_server(reset_asyncapi_state):
55+
"""Test server registration"""
56+
server_id = "test-server"
57+
host = "localhost"
58+
protocol = "ws"
59+
pathname = "/ws"
60+
61+
register_server(id=server_id, host=host, protocol=protocol, pathname=pathname)
62+
63+
schema = get_schema()
64+
assert server_id in schema.servers
65+
assert schema.servers[server_id].host == host
66+
assert schema.servers[server_id].protocol == protocol
67+
assert schema.servers[server_id].pathname == pathname
68+
69+
70+
def test_register_channel(reset_asyncapi_state):
71+
"""Test channel registration"""
72+
channel_id = "test-channel"
73+
address = "test/topic"
74+
description = "Test channel"
75+
title = "Test Channel"
76+
77+
register_channel(address=address, id=channel_id, description=description, title=title)
78+
79+
schema = get_schema()
80+
assert channel_id in schema.channels
81+
assert schema.channels[channel_id].address == address
82+
assert schema.channels[channel_id].description == description
83+
assert schema.channels[channel_id].title == title
84+
85+
86+
def test_register_channel_with_server(reset_asyncapi_state):
87+
"""Test channel registration with server reference"""
88+
server_id = "test-server"
89+
channel_id = "test-channel"
90+
91+
register_server(id=server_id, host="localhost", protocol="ws")
92+
register_channel(address="test/topic", id=channel_id, server_id=server_id)
93+
94+
schema = get_schema()
95+
assert len(schema.channels[channel_id].servers) == 1
96+
assert schema.channels[channel_id].servers[0].ref == f"#/servers/{server_id}"
97+
98+
99+
def test_register_channel_operation(reset_asyncapi_state):
100+
"""Test channel operation registration"""
101+
channel_id = "test-channel"
102+
operation_type = "receive"
103+
104+
register_channel(address="test/topic", id=channel_id)
105+
register_channel_operation(
106+
channel_id=channel_id, operation_type=operation_type, messages=[TestMessage], operation_name="test-operation"
107+
)
108+
109+
schema = get_schema()
110+
assert "test-operation" in schema.operations
111+
assert schema.operations["test-operation"].action == operation_type
112+
assert schema.operations["test-operation"].channel.ref == f"#/channels/{channel_id}"
113+
assert TestMessage.__name__ in schema.components.schemas
114+
115+
116+
def test_register_channel_operation_invalid_channel(reset_asyncapi_state):
117+
"""Test channel operation registration with invalid channel"""
118+
with pytest.raises(ValueError, match="Channel non-existent does not exist"):
119+
register_channel_operation(channel_id="non-existent", operation_type="receive", messages=[TestMessage])
120+
121+
122+
def test_multiple_messages_registration(reset_asyncapi_state):
123+
"""Test registration of multiple messages for an operation"""
124+
channel_id = "test-channel"
125+
126+
register_channel(address="test/topic", id=channel_id)
127+
register_channel_operation(channel_id=channel_id, operation_type="send", messages=[TestMessage, AnotherTestMessage])
128+
129+
schema = get_schema()
130+
assert TestMessage.__name__ in schema.components.schemas
131+
assert AnotherTestMessage.__name__ in schema.components.schemas
132+
assert TestMessage.__name__ in schema.channels[channel_id].messages
133+
assert AnotherTestMessage.__name__ in schema.channels[channel_id].messages

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def anyio_backend():
1212
return "asyncio"
1313

1414

15-
@pytest.fixture(scope="function")
15+
@pytest.fixture(scope="session")
1616
def test_config() -> AppConfig:
1717
return AppConfig(
1818
SQLALCHEMY_CONFIG={},

tests/http_app/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from http_app import create_app
88

99

10-
@pytest.fixture(scope="function")
10+
@pytest.fixture(scope="session")
1111
def testapp(test_config) -> Iterator[FastAPI]:
1212
# We don't need the storage to test the HTTP app
1313
with patch("common.bootstrap.init_storage", return_value=None):

tests/http_app/routes/test_docs_ws.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import json
2+
from unittest.mock import MagicMock, patch
3+
4+
import pytest
5+
from fastapi import FastAPI, status
6+
from fastapi.testclient import TestClient
7+
from pydantic_asyncapi.v3 import AsyncAPI, Info
8+
9+
fake_schema = AsyncAPI(
10+
asyncapi="3.0.0",
11+
info=Info(
12+
title="Some fake schema",
13+
version="1.2.3",
14+
),
15+
)
16+
17+
18+
@patch("http_app.routes.docs_ws.get_schema", return_value=fake_schema)
19+
async def test_asyncapi_json_is_whatever_returned_by_schema(
20+
mock_schema: MagicMock,
21+
testapp: FastAPI,
22+
):
23+
ac = TestClient(app=testapp, base_url="http://test")
24+
response = ac.get(
25+
"/docs/ws/asyncapi.json",
26+
)
27+
28+
assert response.status_code == status.HTTP_200_OK
29+
assert response.text == fake_schema.model_dump_json(exclude_unset=True)
30+
31+
32+
@pytest.mark.parametrize("sidebar", (True, False))
33+
@pytest.mark.parametrize("info", (True, False))
34+
@pytest.mark.parametrize("servers", (True, False))
35+
@pytest.mark.parametrize("operations", (True, False))
36+
@pytest.mark.parametrize("messages", (True, False))
37+
@pytest.mark.parametrize("schema", (True, False))
38+
@pytest.mark.parametrize("errors", (True, False))
39+
@pytest.mark.parametrize("expand_message_examples", (True, False))
40+
async def test_ws_docs_renders_config_based_on_params(
41+
sidebar: bool,
42+
info: bool,
43+
servers: bool,
44+
operations: bool,
45+
messages: bool,
46+
schema: bool,
47+
errors: bool,
48+
expand_message_examples: bool,
49+
testapp: FastAPI,
50+
):
51+
config = json.dumps(
52+
{
53+
"schema": {
54+
"url": "/docs/ws/asyncapi.json",
55+
},
56+
"config": {
57+
"show": {
58+
"sidebar": sidebar,
59+
"info": info,
60+
"servers": servers,
61+
"operations": operations,
62+
"messages": messages,
63+
"schemas": schema,
64+
"errors": errors,
65+
},
66+
"expand": {
67+
"messageExamples": expand_message_examples,
68+
},
69+
"sidebar": {
70+
"showServers": "byDefault",
71+
"showOperations": "byDefault",
72+
},
73+
},
74+
}
75+
)
76+
77+
ac = TestClient(app=testapp, base_url="http://test")
78+
response = ac.get(
79+
"/docs/ws",
80+
params={
81+
"sidebar": sidebar,
82+
"info": info,
83+
"servers": servers,
84+
"operations": operations,
85+
"messages": messages,
86+
"schemas": schema,
87+
"errors": errors,
88+
"expand_message_examples": expand_message_examples,
89+
},
90+
)
91+
assert response.status_code == status.HTTP_200_OK
92+
assert config in response.text

tests/http_app/routes/ws/__init__.py

Whitespace-only changes.

tests/http_app/routes/ws/test_chat.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import pytest
2+
from fastapi.testclient import TestClient
3+
4+
from http_app.routes.ws.chat import ConnectionManager
5+
6+
7+
@pytest.fixture
8+
def test_client(testapp):
9+
return TestClient(testapp)
10+
11+
12+
@pytest.fixture
13+
def connection_manager():
14+
return ConnectionManager()
15+
16+
17+
def test_websocket_connection(test_client):
18+
with test_client.websocket_connect("/ws/chat/1") as websocket:
19+
websocket.send_text("Hello!")
20+
data = websocket.receive_text()
21+
22+
assert data == "You wrote: Hello!"
23+
broadcast = websocket.receive_text()
24+
assert broadcast == "Client #1 says: Hello!"
25+
26+
27+
def test_multiple_clients(test_client):
28+
with test_client.websocket_connect("/ws/chat/1") as websocket1:
29+
with test_client.websocket_connect("/ws/chat/2") as websocket2:
30+
# Client 1 sends message
31+
websocket1.send_text("Hello from client 1")
32+
33+
# Client 1 receives personal message
34+
data1 = websocket1.receive_text()
35+
assert data1 == "You wrote: Hello from client 1"
36+
37+
# Both clients receive broadcast
38+
broadcast1 = websocket1.receive_text()
39+
broadcast2 = websocket2.receive_text()
40+
assert broadcast1 == "Client #1 says: Hello from client 1"
41+
assert broadcast2 == "Client #1 says: Hello from client 1"
42+
43+
44+
def test_client_disconnect(test_client):
45+
with test_client.websocket_connect("/ws/chat/1") as websocket1:
46+
with test_client.websocket_connect("/ws/chat/2") as websocket2:
47+
# Close first client
48+
websocket1.close()
49+
50+
# Second client should receive disconnect message
51+
disconnect_message = websocket2.receive_text()
52+
assert disconnect_message == "Client #1 left the chat"
53+
54+
55+
@pytest.mark.asyncio
56+
async def test_connection_manager():
57+
manager = ConnectionManager()
58+
59+
# Mock WebSocket for testing
60+
class MockWebSocket:
61+
def __init__(self):
62+
self.received_messages = []
63+
64+
async def accept(self):
65+
pass
66+
67+
async def send_text(self, message: str):
68+
self.received_messages.append(message)
69+
70+
# Test connect
71+
websocket = MockWebSocket()
72+
await manager.connect(websocket)
73+
assert len(manager.active_connections) == 1
74+
75+
# Test personal message
76+
await manager.send_personal_message("test message", websocket)
77+
assert websocket.received_messages[-1] == "test message"
78+
79+
# Test broadcast
80+
await manager.broadcast("broadcast message")
81+
assert websocket.received_messages[-1] == "broadcast message"
82+
83+
# Test disconnect
84+
manager.disconnect(websocket)
85+
assert len(manager.active_connections) == 0

tests/http_app/test_dependencies.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from common import AppConfig
22
from http_app import context
3-
from http_app.dependencies import app_config
3+
from http_app.dependencies import get_app_config
44

55

66
def test_app_config_return_context_variable():
77
config = AppConfig(APP_NAME="SomeOtherAppName")
88
context.app_config.set(config)
9-
assert app_config() is config
9+
assert get_app_config() is config

0 commit comments

Comments
 (0)