Skip to content

Commit 98fb2e0

Browse files
committed
Implement tests
1 parent 2c8eb8f commit 98fb2e0

24 files changed

Lines changed: 4475 additions & 2 deletions

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
fail-fast: false
1414
matrix:
1515
os: [ubuntu-latest]
16-
python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
16+
python-version: [3.8, "3.12"]
1717
runs-on: ${{ matrix.os }}
1818
steps:
1919
- uses: actions/checkout@v3

.github/workflows/test.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Some things borrowed from Hikari CI workflows
2+
name: test
3+
4+
on: [push, pull_request]
5+
6+
concurrency:
7+
group: ${{ github.workflow }}-${{ github.ref }}
8+
cancel-in-progress: true
9+
10+
jobs:
11+
test:
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
os: [ubuntu-latest]
16+
python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
17+
runs-on: ${{ matrix.os }}
18+
steps:
19+
- uses: actions/checkout@v3
20+
- name: Set up Python
21+
uses: actions/setup-python@v4
22+
with:
23+
python-version: 3.12
24+
- name: Install Python dependencies
25+
run: |
26+
python -m pip install --upgrade pip
27+
pip install -r requirements.txt
28+
pip install -r dev_requirements.txt
29+
- name: Run tests
30+
run: |
31+
pytest

dev_requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
aioresponses==0.7.8
12
black==25.1.0
23
isort==6.0.1
34
pyright==1.1.403
45
mypy==1.17.0
56
mkdocs-material==9.6.15
67
mkdocstrings==0.29.1
78
mkdocstrings[python]==0.29.1
9+
pytest==8.4.1
10+
pytest-asyncio==1.1.0

ravyapi/api/models/guilds.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class GetGuildResponse:
3939

4040
def __init__(self, data: dict[str, Any]) -> None:
4141
self._data: dict[str, Any] = data
42-
self._trust: Trust = data["trust"]
42+
self._trust: Trust = Trust(data["trust"])
4343
self._bans: list[BanEntryResponse] = [
4444
BanEntryResponse(ban) for ban in data["bans"]
4545
]

tests/conftest.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""Fixtures for tests."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any, Generator
6+
from unittest.mock import AsyncMock
7+
8+
import pytest
9+
from aioresponses import aioresponses
10+
11+
from ravyapi.api.endpoints import Avatars, Guilds, KSoft, Tokens, URLs, Users
12+
from ravyapi.client import Client
13+
from ravyapi.http import HTTPClient
14+
15+
16+
@pytest.fixture
17+
def valid_ravy_token() -> str:
18+
"""Valid Ravy token for testing."""
19+
return "abcdefghijklmnopqrstuvwx.1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
20+
21+
22+
@pytest.fixture
23+
def valid_ksoft_token() -> str:
24+
"""Valid KSoft token for testing."""
25+
return "1234567890abcdef1234567890abcdef12345678"
26+
27+
28+
@pytest.fixture
29+
def invalid_token() -> str:
30+
"""Invalid token for testing."""
31+
return "invalid_token"
32+
33+
34+
@pytest.fixture
35+
def sample_check_avatar_response():
36+
"""Sample check avatar response."""
37+
return {
38+
"matched": True,
39+
"key": "test_key",
40+
"similarity": 0.95,
41+
}
42+
43+
44+
@pytest.fixture
45+
def sample_get_token_response():
46+
"""Sample get token response."""
47+
return {
48+
"user": 12345,
49+
"access": ["users", "avatars", "urls"],
50+
"application": 67890,
51+
"token_type": "ravy",
52+
}
53+
54+
55+
@pytest.fixture
56+
def mock_aioresponses() -> Generator[aioresponses, Any, None]:
57+
"""Mock aioresponses fixture."""
58+
with aioresponses() as m:
59+
yield m
60+
61+
62+
@pytest.fixture
63+
def mock_http_client(valid_ravy_token: str) -> HTTPClient:
64+
"""Mock HTTPClient for testing."""
65+
# Create a mock session to avoid the event loop issue
66+
mock_session = AsyncMock()
67+
client = HTTPClient.__new__(HTTPClient)
68+
client._token = valid_ravy_token # type: ignore
69+
client._permissions = None # type: ignore
70+
client._phisherman_token = None # type: ignore
71+
client._headers = { # type: ignore
72+
"Authorization": valid_ravy_token,
73+
"User-Agent": "Test-Agent",
74+
}
75+
client._session = mock_session # type: ignore
76+
return client
77+
78+
79+
@pytest.fixture
80+
def mock_client(valid_ravy_token: str, mock_http_client: HTTPClient) -> Client:
81+
"""Mock Client for testing."""
82+
client = Client.__new__(Client)
83+
client._token = valid_ravy_token # type: ignore
84+
client._http = mock_http_client # type: ignore
85+
client._closed = False # type: ignore
86+
client._avatars = Avatars(mock_http_client) # type: ignore
87+
client._guilds = Guilds(mock_http_client) # type: ignore
88+
client._ksoft = KSoft(mock_http_client) # type: ignore
89+
client._users = Users(mock_http_client) # type: ignore
90+
client._urls = URLs(mock_http_client) # type: ignore
91+
client._tokens = Tokens(mock_http_client) # type: ignore
92+
return client
93+
94+
95+
@pytest.fixture
96+
def mock_avatars_endpoint(mock_http_client: HTTPClient) -> Avatars:
97+
"""Mock Avatars endpoint for testing."""
98+
return Avatars(mock_http_client)
99+
100+
101+
@pytest.fixture
102+
def mock_tokens_endpoint(mock_http_client: HTTPClient) -> Tokens:
103+
"""Mock Tokens endpoint for testing."""
104+
return Tokens(mock_http_client)
105+
106+
107+
@pytest.fixture
108+
def sample_get_guild_response():
109+
"""Sample get guild response."""
110+
return {
111+
"trust": {"level": 3, "label": "Neutral"},
112+
"bans": [
113+
{
114+
"provider": "ravy",
115+
"reason": "Test reason",
116+
"moderator": "987654321",
117+
"reason_key": "test_key",
118+
}
119+
],
120+
}
121+
122+
123+
@pytest.fixture
124+
def sample_get_user_response():
125+
"""Sample get user response."""
126+
return {
127+
"pronouns": "he/him",
128+
"trust": {"level": 3, "label": "Neutral"},
129+
"whitelists": [
130+
{"provider": "ravy", "reason": "Test whitelist", "moderator": "987654321"}
131+
],
132+
"bans": [
133+
{
134+
"provider": "ravy",
135+
"reason": "Test ban",
136+
"moderator": "987654321",
137+
"reason_key": "test_key",
138+
}
139+
],
140+
"rep": [{"provider": "ravy", "reputation": 100, "moderator": "987654321"}],
141+
"sentinel": {
142+
"tracked": True,
143+
"reason": "Test sentinel",
144+
"moderator": "987654321",
145+
},
146+
}
147+
148+
149+
@pytest.fixture
150+
def sample_get_website_response():
151+
"""Sample get website response."""
152+
return {
153+
"is_fraudulent": False,
154+
"message": "Safe website",
155+
"scan_date": "2023-01-01T00:00:00Z",
156+
}
157+
158+
159+
@pytest.fixture
160+
def sample_get_ksoft_ban_response():
161+
"""Sample get KSoft ban response."""
162+
return {
163+
"banned": True,
164+
"reason": "Test ban reason",
165+
"moderator": "987654321",
166+
"appeal": "https://example.com/appeal",
167+
"date": "2023-01-01T00:00:00Z",
168+
}
169+
170+
171+
@pytest.fixture
172+
def mock_guilds_endpoint(mock_http_client: HTTPClient) -> Guilds:
173+
"""Mock Guilds endpoint for testing."""
174+
return Guilds(mock_http_client)
175+
176+
177+
@pytest.fixture
178+
def mock_users_endpoint(mock_http_client: HTTPClient) -> Users:
179+
"""Mock Users endpoint for testing."""
180+
return Users(mock_http_client)
181+
182+
183+
@pytest.fixture
184+
def mock_urls_endpoint(mock_http_client: HTTPClient) -> URLs:
185+
"""Mock URLs endpoint for testing."""
186+
return URLs(mock_http_client)
187+
188+
189+
@pytest.fixture
190+
def mock_ksoft_endpoint(mock_http_client: HTTPClient) -> KSoft:
191+
"""Mock KSoft endpoint for testing."""
192+
return KSoft(mock_http_client)

tests/test_about.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Copyright 2022-Present GoogolGenius
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+
"""Tests for package metadata."""
15+
16+
from __future__ import annotations
17+
18+
from ravyapi._about import (
19+
__all__,
20+
__author__,
21+
__copyright__,
22+
__email__,
23+
__license__,
24+
__maintainer__,
25+
__repository__,
26+
__status__,
27+
__version__,
28+
)
29+
30+
31+
class TestAbout:
32+
"""Test cases for package metadata."""
33+
34+
def test_author(self) -> None:
35+
"""Test __author__ constant."""
36+
assert __author__ == "GoogolGenius"
37+
assert isinstance(__author__, str)
38+
39+
def test_repository(self) -> None:
40+
"""Test __repository__ constant."""
41+
assert __repository__ == "https://github.com/GoogolGenius/RavyAPI.py"
42+
assert isinstance(__repository__, str)
43+
assert __repository__.startswith("https://github.com/")
44+
45+
def test_copyright(self) -> None:
46+
"""Test __copyright__ constant."""
47+
assert __copyright__ == "Copyright 2022-Present GoogolGenius"
48+
assert isinstance(__copyright__, str)
49+
assert "GoogolGenius" in __copyright__
50+
51+
def test_license(self) -> None:
52+
"""Test __license__ constant."""
53+
assert __license__ == "Apache v2"
54+
assert isinstance(__license__, str)
55+
56+
def test_version(self) -> None:
57+
"""Test __version__ constant."""
58+
assert __version__ == "0.1.0a"
59+
assert isinstance(__version__, str)
60+
61+
def test_maintainer(self) -> None:
62+
"""Test __maintainer__ constant."""
63+
assert __maintainer__ == "GoogolGenius"
64+
assert isinstance(__maintainer__, str)
65+
66+
def test_email(self) -> None:
67+
"""Test __email__ constant."""
68+
assert __email__ == "erich.nguyen@outlook.com"
69+
assert isinstance(__email__, str)
70+
assert "@" in __email__
71+
72+
def test_status(self) -> None:
73+
"""Test __status__ constant."""
74+
assert __status__ == "Development"
75+
assert isinstance(__status__, str)
76+
77+
def test_all_exports(self) -> None:
78+
"""Test __all__ contains all expected exports."""
79+
expected_exports = {
80+
"__author__",
81+
"__repository__",
82+
"__copyright__",
83+
"__license__",
84+
"__version__",
85+
"__maintainer__",
86+
"__email__",
87+
"__status__",
88+
}
89+
assert set(__all__) == expected_exports
90+
91+
def test_all_is_tuple(self) -> None:
92+
"""Test __all__ is a tuple."""
93+
assert isinstance(__all__, tuple)
94+
95+
def test_version_format(self) -> None:
96+
"""Test version follows semantic versioning format."""
97+
# Basic check for version format (major.minor.patch with optional alpha/beta)
98+
version_parts = __version__.split(".")
99+
assert len(version_parts) >= 2 # At least major.minor
100+
101+
# Check that the first part is numeric
102+
assert version_parts[0].isdigit()
103+
assert version_parts[1].isdigit()
104+
105+
def test_email_format(self) -> None:
106+
"""Test email follows basic email format."""
107+
assert "@" in __email__
108+
assert "." in __email__
109+
parts = __email__.split("@")
110+
assert len(parts) == 2
111+
assert len(parts[0]) > 0 # Username part
112+
assert len(parts[1]) > 0 # Domain part
113+
114+
def test_repository_format(self) -> None:
115+
"""Test repository URL format."""
116+
assert __repository__.startswith("https://github.com/")
117+
assert __repository__.endswith("/RavyAPI.py")
118+
119+
# Extract username/repo
120+
repo_path = __repository__.replace("https://github.com/", "")
121+
assert "/" in repo_path
122+
username, repo_name = repo_path.split("/")
123+
assert username == "GoogolGenius"
124+
assert repo_name == "RavyAPI.py"
125+
126+
def test_constants_are_strings(self) -> None:
127+
"""Test all constants are strings."""
128+
constants = [
129+
__author__,
130+
__repository__,
131+
__copyright__,
132+
__license__,
133+
__version__,
134+
__maintainer__,
135+
__email__,
136+
__status__,
137+
]
138+
139+
for constant in constants:
140+
assert isinstance(constant, str)
141+
assert len(constant) > 0 # Non-empty strings
142+
143+
def test_author_and_maintainer_consistency(self) -> None:
144+
"""Test author and maintainer are the same."""
145+
assert __author__ == __maintainer__

0 commit comments

Comments
 (0)