Skip to content

Commit 71db41c

Browse files
authored
Test/creat test suite (#3)
* test(tests/): create test suite * bump: version 0.2.0 → 0.2.1 * fix(.github/workflows/ci.yml): fix trigger * bump: version 0.2.1 → 0.2.2 * fix(dsd): sdfsf * bump: version 0.2.2 → 0.2.3 --------- Co-authored-by: JD <>
1 parent 460e9ec commit 71db41c

File tree

10 files changed

+700
-510
lines changed

10 files changed

+700
-510
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
paths:
77
- 'src/codesphere/**'
88
- '.github/workflows/ci.yml'
9+
- 'tests/**'
910

1011
permissions:
1112
contents: write
@@ -30,7 +31,7 @@ jobs:
3031

3132
- name: Install dependencies using uv
3233
run: |
33-
uv sync
34+
uv sync --extra dev
3435
shell: bash
3536

3637
- name: Run Bandit security check on backend code

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ wheels/
1616
.venv/
1717
venv/
1818
env/
19+
20+
.pytest_cache

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
## v0.2.3 (2025-07-21)
2+
3+
### Fix
4+
5+
- **dsd**: sdfsf
6+
7+
## v0.2.2 (2025-07-21)
8+
9+
### Fix
10+
11+
- **.github/workflows/ci.yml**: fix trigger
12+
13+
## v0.2.1 (2025-07-21)
14+
115
## v0.2.0 (2025-07-16)
216

317
### Feat

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ help: ## Shows a help message with all available commands
88
install: ## Sets up the development environment
99
@echo ">>> Setting up the development environment..."
1010
@echo "1. Creating virtual environment with uv..."
11-
uv venv
11+
uv venv --python 3.12.9
1212
@echo "2. Installing all dependencies (including 'dev')..."
1313
uv pip install -e '.[dev]'
1414
@echo "3. Installing git hooks with pre-commit..."

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "codesphere"
33

4-
version = "0.2.0"
4+
version = "0.2.3"
55
description = "Use Codesphere within python scripts."
66
readme = "README.md"
77
license = { file="LICENSE" }
@@ -39,6 +39,7 @@ dev = [
3939
"commitizen>=4.8.3",
4040
"pre-commit>=4.2.0",
4141
"pytest>=8.4.0",
42+
"pytest-asyncio",
4243
"pytest-cov>=6.2.1",
4344
"ruff>=0.11.13",
4445
]

tests/resources/test_domain_resources.py

Whitespace-only changes.

tests/resources/test_metadata_resources.py

Whitespace-only changes.

tests/resources/test_workspaces_resources.py

Whitespace-only changes.

tests/test_client.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import pytest
2+
import httpx
3+
from unittest.mock import patch, AsyncMock
4+
from dataclasses import dataclass
5+
from typing import Optional, Any, Type
6+
from pydantic import BaseModel
7+
8+
from codesphere.client import APIHttpClient, AuthenticationError
9+
10+
11+
class DummyModel(BaseModel):
12+
"""Ein einfaches Pydantic-Modell für Testzwecke."""
13+
14+
name: str
15+
value: int
16+
17+
18+
@dataclass
19+
class InitTestCase:
20+
"""Definiert einen Testfall für die Initialisierung des APIHttpClient."""
21+
22+
name: str
23+
token_env_var: Optional[str]
24+
expected_exception: Optional[Type[Exception]] = None
25+
26+
27+
@dataclass
28+
class RequestTestCase:
29+
"""Definiert einen Testfall für die Request-Methoden des APIHttpClient."""
30+
31+
name: str
32+
method: str
33+
use_context_manager: bool
34+
payload: Any = None
35+
mock_status_code: int = 200
36+
expected_exception: Optional[Type[Exception]] = None
37+
38+
39+
init_test_cases = [
40+
InitTestCase(
41+
name="Erfolgreiche Initialisierung mit Token",
42+
token_env_var="secret-token",
43+
expected_exception=None,
44+
),
45+
InitTestCase(
46+
name="Fehlgeschlagene Initialisierung ohne Token",
47+
token_env_var=None,
48+
expected_exception=AuthenticationError,
49+
),
50+
]
51+
52+
request_test_cases = [
53+
RequestTestCase(
54+
name="GET-Request erfolgreich",
55+
method="get",
56+
use_context_manager=True,
57+
),
58+
RequestTestCase(
59+
name="POST-Request mit Pydantic-Modell erfolgreich",
60+
method="post",
61+
use_context_manager=True,
62+
payload=DummyModel(name="test", value=123),
63+
),
64+
RequestTestCase(
65+
name="PUT-Request mit Dictionary erfolgreich",
66+
method="put",
67+
use_context_manager=True,
68+
payload={"key": "value"},
69+
),
70+
RequestTestCase(
71+
name="Request schlägt fehl ohne Context Manager",
72+
method="get",
73+
use_context_manager=False,
74+
expected_exception=RuntimeError,
75+
),
76+
RequestTestCase(
77+
name="Request mit 404-Fehler löst HTTPStatusError aus",
78+
method="get",
79+
use_context_manager=True,
80+
mock_status_code=404,
81+
expected_exception=httpx.HTTPStatusError,
82+
),
83+
RequestTestCase(
84+
name="Request mit 500-Fehler löst HTTPStatusError aus",
85+
method="post",
86+
use_context_manager=True,
87+
mock_status_code=500,
88+
expected_exception=httpx.HTTPStatusError,
89+
),
90+
]
91+
92+
93+
@pytest.mark.parametrize("case", init_test_cases, ids=[c.name for c in init_test_cases])
94+
def test_client_initialization(case: InitTestCase):
95+
"""
96+
Testet die Initialisierungslogik des APIHttpClient.
97+
"""
98+
with patch("os.environ.get", return_value=case.token_env_var):
99+
if case.expected_exception:
100+
with pytest.raises(case.expected_exception):
101+
APIHttpClient()
102+
else:
103+
client = APIHttpClient()
104+
assert client._token == case.token_env_var
105+
106+
107+
@pytest.mark.asyncio
108+
@pytest.mark.parametrize(
109+
"case", request_test_cases, ids=[c.name for c in request_test_cases]
110+
)
111+
async def test_client_requests(case: RequestTestCase):
112+
"""
113+
Testet die verschiedenen Request-Methoden (get, post, etc.) und deren Verhalten.
114+
"""
115+
with patch("os.environ.get", return_value="fake-token"):
116+
client = APIHttpClient()
117+
118+
mock_http_client = AsyncMock(spec=httpx.AsyncClient)
119+
mock_response = AsyncMock(spec=httpx.Response)
120+
mock_response.status_code = case.mock_status_code
121+
122+
if 400 <= case.mock_status_code < 600:
123+
mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
124+
f"{case.mock_status_code} Error",
125+
request=AsyncMock(),
126+
response=mock_response,
127+
)
128+
129+
mock_http_client.request.return_value = mock_response
130+
131+
if case.expected_exception:
132+
with pytest.raises(case.expected_exception):
133+
with patch("httpx.AsyncClient", return_value=mock_http_client):
134+
if case.use_context_manager:
135+
async with client:
136+
await getattr(client, case.method)("/test-endpoint")
137+
else:
138+
await getattr(client, case.method)("/test-endpoint")
139+
return
140+
141+
with patch("httpx.AsyncClient", return_value=mock_http_client):
142+
async with client:
143+
request_func = getattr(client, case.method)
144+
response = await request_func("/test-endpoint", json=case.payload)
145+
146+
mock_http_client.request.assert_awaited_once()
147+
call_args = mock_http_client.request.call_args
148+
assert call_args.args[0] == case.method.upper()
149+
assert call_args.args[1] == "/test-endpoint"
150+
151+
if isinstance(case.payload, BaseModel):
152+
assert call_args.kwargs["json"] == case.payload.model_dump(
153+
exclude_none=True
154+
)
155+
else:
156+
assert call_args.kwargs["json"] == case.payload
157+
158+
assert response.status_code == case.mock_status_code

0 commit comments

Comments
 (0)