rxresume-python 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/PKG-INFO +6 -1
  2. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/pyproject.toml +21 -2
  3. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/rxresume_python.egg-info/PKG-INFO +6 -1
  4. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/rxresume_python.egg-info/requires.txt +6 -0
  5. rxresume_python-0.2.0/tests/test_client_async.py +268 -0
  6. rxresume_python-0.2.0/tests/test_client_sync.py +259 -0
  7. rxresume_python-0.1.0/tests/test_client_async.py +0 -107
  8. rxresume_python-0.1.0/tests/test_client_sync.py +0 -113
  9. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/LICENSE +0 -0
  10. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/README.md +0 -0
  11. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/reactive_resume/__init__.py +0 -0
  12. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/reactive_resume/api/__init__.py +0 -0
  13. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/reactive_resume/api/auth.py +0 -0
  14. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/reactive_resume/api/resumes.py +0 -0
  15. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/reactive_resume/core/__init__.py +0 -0
  16. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/reactive_resume/core/async_client.py +0 -0
  17. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/reactive_resume/core/client.py +0 -0
  18. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/reactive_resume/core/exceptions.py +0 -0
  19. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/reactive_resume/models/__init__.py +0 -0
  20. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/reactive_resume/models/resume.py +0 -0
  21. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/reactive_resume/models/user.py +0 -0
  22. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/rxresume_python.egg-info/SOURCES.txt +0 -0
  23. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/rxresume_python.egg-info/dependency_links.txt +0 -0
  24. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/rxresume_python.egg-info/top_level.txt +0 -0
  25. {rxresume_python-0.1.0 → rxresume_python-0.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rxresume-python
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Unofficial Python API client (SDK) for Reactive Resume v4
5
5
  Author: Ata Can Yaymacı
6
6
  License: MIT
@@ -20,6 +20,11 @@ Requires-Dist: ruff>=0.1.0; extra == "dev"
20
20
  Requires-Dist: pre-commit>=3.0.0; extra == "dev"
21
21
  Requires-Dist: bandit>=1.7.0; extra == "dev"
22
22
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
23
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
24
+ Provides-Extra: docs
25
+ Requires-Dist: mkdocs>=1.5.0; extra == "docs"
26
+ Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
27
+ Requires-Dist: mkdocstrings[python]>=0.20.0; extra == "docs"
23
28
  Dynamic: license-file
24
29
 
25
30
  # Reactive Resume Python SDK (`rxresume-python`)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rxresume-python"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Unofficial Python API client (SDK) for Reactive Resume v4"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -21,9 +21,9 @@ dependencies = [
21
21
  "httpx>=0.24.0",
22
22
  "pydantic[email]>=2.0.0",
23
23
  ]
24
-
25
24
  [project.optional-dependencies]
26
25
  dev = [
26
+
27
27
  "pytest>=7.0.0",
28
28
  "pytest-asyncio>=0.20.0",
29
29
  "respx>=0.20.0",
@@ -31,8 +31,15 @@ dev = [
31
31
  "pre-commit>=3.0.0",
32
32
  "bandit>=1.7.0",
33
33
  "pytest-cov>=4.0.0",
34
+ "mypy>=1.0.0",
35
+ ]
36
+ docs = [
37
+ "mkdocs>=1.5.0",
38
+ "mkdocs-material>=9.0.0",
39
+ "mkdocstrings[python]>=0.20.0",
34
40
  ]
35
41
 
42
+
36
43
  [tool.setuptools.packages.find]
37
44
  where = ["."]
38
45
  include = ["reactive_resume*"]
@@ -44,3 +51,15 @@ target-version = "py310"
44
51
  [tool.pytest.ini_options]
45
52
  asyncio_mode = "auto"
46
53
  testpaths = ["tests"]
54
+
55
+ [tool.mypy]
56
+ python_version = "3.10"
57
+ warn_unused_configs = true
58
+ warn_redundant_casts = true
59
+ warn_unused_ignores = true
60
+ strict_equality = true
61
+ extra_checks = true
62
+ check_untyped_defs = true
63
+ disallow_untyped_decorators = true
64
+ disallow_any_generics = true
65
+ plugins = ["pydantic.mypy"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rxresume-python
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Unofficial Python API client (SDK) for Reactive Resume v4
5
5
  Author: Ata Can Yaymacı
6
6
  License: MIT
@@ -20,6 +20,11 @@ Requires-Dist: ruff>=0.1.0; extra == "dev"
20
20
  Requires-Dist: pre-commit>=3.0.0; extra == "dev"
21
21
  Requires-Dist: bandit>=1.7.0; extra == "dev"
22
22
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
23
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
24
+ Provides-Extra: docs
25
+ Requires-Dist: mkdocs>=1.5.0; extra == "docs"
26
+ Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
27
+ Requires-Dist: mkdocstrings[python]>=0.20.0; extra == "docs"
23
28
  Dynamic: license-file
24
29
 
25
30
  # Reactive Resume Python SDK (`rxresume-python`)
@@ -9,3 +9,9 @@ ruff>=0.1.0
9
9
  pre-commit>=3.0.0
10
10
  bandit>=1.7.0
11
11
  pytest-cov>=4.0.0
12
+ mypy>=1.0.0
13
+
14
+ [docs]
15
+ mkdocs>=1.5.0
16
+ mkdocs-material>=9.0.0
17
+ mkdocstrings[python]>=0.20.0
@@ -0,0 +1,268 @@
1
+ import pytest
2
+ import respx
3
+ import httpx
4
+ from httpx import Response
5
+ from reactive_resume import (
6
+ AsyncRxResumeClient,
7
+ AuthenticationError,
8
+ NotFoundError,
9
+ ReactiveResumeAPIError,
10
+ ReactiveResumeError,
11
+ )
12
+ from reactive_resume.models import ResumeImportData, Resume, User
13
+
14
+ BASE_URL = "https://rxresu.me"
15
+ API_KEY = "test-api-key"
16
+
17
+
18
+ @pytest.fixture
19
+ async def async_client():
20
+ async with AsyncRxResumeClient(base_url=BASE_URL, api_key=API_KEY) as c:
21
+ yield c
22
+
23
+
24
+ @pytest.mark.asyncio
25
+ @respx.mock
26
+ async def test_list_resumes_async(async_client):
27
+ mock_resume = {
28
+ "id": "test-resume-id",
29
+ "name": "Test CV",
30
+ "slug": "test-cv",
31
+ "userId": "user-123",
32
+ "visibility": "public",
33
+ "locked": False,
34
+ "data": {
35
+ "basics": {
36
+ "name": "John Doe",
37
+ "headline": "Software Developer",
38
+ "email": "john@example.com",
39
+ "phone": "123456",
40
+ "website": "https://john.me",
41
+ "location": "Earth",
42
+ "picture": "",
43
+ "profiles": [],
44
+ },
45
+ "sections": {},
46
+ },
47
+ "createdAt": "2026-07-13T12:00:00Z",
48
+ "updatedAt": "2026-07-13T12:00:00Z",
49
+ }
50
+
51
+ route = respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
52
+ return_value=Response(200, json=[mock_resume])
53
+ )
54
+
55
+ resumes = await async_client.resumes.list()
56
+ assert len(resumes) == 1
57
+ assert isinstance(resumes[0], Resume)
58
+ assert resumes[0].id == "test-resume-id"
59
+ assert resumes[0].data.basics.name == "John Doe"
60
+ assert route.called
61
+
62
+
63
+ @pytest.mark.asyncio
64
+ @respx.mock
65
+ async def test_get_resume_async(async_client):
66
+ mock_resume = {
67
+ "id": "resume-1",
68
+ "name": "Test CV 1",
69
+ "slug": "test-cv-1",
70
+ "userId": "user-123",
71
+ "data": {"basics": {}, "sections": {}},
72
+ "createdAt": "2026-07-13T12:00:00Z",
73
+ "updatedAt": "2026-07-13T12:00:00Z",
74
+ }
75
+ route = respx.get(f"{BASE_URL}/api/openapi/resume/resume-1").mock(
76
+ return_value=Response(200, json=mock_resume)
77
+ )
78
+
79
+ resume = await async_client.resumes.get("resume-1")
80
+ assert resume.id == "resume-1"
81
+ assert route.called
82
+
83
+
84
+ @pytest.mark.asyncio
85
+ @respx.mock
86
+ async def test_create_resume_async(async_client):
87
+ mock_resume = {
88
+ "id": "new-resume",
89
+ "name": "My New CV",
90
+ "slug": "my-new-cv",
91
+ "userId": "user-123",
92
+ "data": {"basics": {}, "sections": {}},
93
+ "createdAt": "2026-07-13T12:00:00Z",
94
+ "updatedAt": "2026-07-13T12:00:00Z",
95
+ }
96
+ route = respx.post(f"{BASE_URL}/api/openapi/resumes").mock(
97
+ return_value=Response(201, json=mock_resume)
98
+ )
99
+
100
+ import_data = ResumeImportData(title="My New CV")
101
+ resume = await async_client.resumes.import_resume(import_data)
102
+ assert resume.id == "new-resume"
103
+ assert route.called
104
+
105
+
106
+ @pytest.mark.asyncio
107
+ @respx.mock
108
+ async def test_update_resume_async(async_client):
109
+ mock_resume = {
110
+ "id": "resume-1",
111
+ "name": "Updated CV Name",
112
+ "slug": "updated-cv-name",
113
+ "userId": "user-123",
114
+ "data": {"basics": {}, "sections": {}},
115
+ "createdAt": "2026-07-13T12:00:00Z",
116
+ "updatedAt": "2026-07-13T12:00:00Z",
117
+ }
118
+ route = respx.patch(f"{BASE_URL}/api/openapi/resume/resume-1").mock(
119
+ return_value=Response(200, json=mock_resume)
120
+ )
121
+
122
+ resume = await async_client.resumes.update("resume-1", {"name": "Updated CV Name"})
123
+ assert resume.name == "Updated CV Name"
124
+ assert route.called
125
+
126
+
127
+ @pytest.mark.asyncio
128
+ @respx.mock
129
+ async def test_delete_resume_async(async_client):
130
+ route = respx.delete(f"{BASE_URL}/api/openapi/resume/resume-1").mock(return_value=Response(204))
131
+ await async_client.resumes.delete("resume-1")
132
+ assert route.called
133
+
134
+
135
+ @pytest.mark.asyncio
136
+ async def test_get_pdf_url_async(async_client):
137
+ pdf_url = await async_client.resumes.get_pdf_url("resume-1")
138
+ assert pdf_url == f"{BASE_URL}/api/openapi/resume/resume-1/pdf"
139
+
140
+
141
+ @pytest.mark.asyncio
142
+ @respx.mock
143
+ async def test_auth_login_async(async_client):
144
+ mock_user = {
145
+ "id": "user-123",
146
+ "name": "Ata Can",
147
+ "username": "atacan",
148
+ "email": "ata@example.com",
149
+ "provider": "email",
150
+ "createdAt": "2026-07-13T12:00:00Z",
151
+ "updatedAt": "2026-07-13T12:00:00Z",
152
+ }
153
+ route = respx.post(f"{BASE_URL}/api/auth/login").mock(
154
+ return_value=Response(200, json={"token": "fake-jwt-token", "user": mock_user})
155
+ )
156
+
157
+ token, user = await async_client.auth.login("ata@example.com", "password123")
158
+ assert token == "fake-jwt-token"
159
+ assert isinstance(user, User)
160
+ assert user.username == "atacan"
161
+ assert route.called
162
+
163
+
164
+ @pytest.mark.asyncio
165
+ @respx.mock
166
+ async def test_auth_me_async(async_client):
167
+ mock_user = {
168
+ "id": "user-123",
169
+ "name": "Ata Can",
170
+ "username": "atacan",
171
+ "email": "ata@example.com",
172
+ "provider": "email",
173
+ "createdAt": "2026-07-13T12:00:00Z",
174
+ "updatedAt": "2026-07-13T12:00:00Z",
175
+ }
176
+ route = respx.get(f"{BASE_URL}/api/user/me").mock(return_value=Response(200, json=mock_user))
177
+
178
+ user = await async_client.auth.me()
179
+ assert isinstance(user, User)
180
+ assert user.id == "user-123"
181
+ assert route.called
182
+
183
+
184
+ @pytest.mark.asyncio
185
+ @respx.mock
186
+ async def test_authentication_error_async(async_client):
187
+ respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
188
+ return_value=Response(401, json={"message": "Invalid API Key"})
189
+ )
190
+
191
+ with pytest.raises(AuthenticationError):
192
+ await async_client.resumes.list()
193
+
194
+
195
+ @pytest.mark.asyncio
196
+ @respx.mock
197
+ async def test_not_found_error_async(async_client):
198
+ respx.get(f"{BASE_URL}/api/openapi/resume/non-existent").mock(
199
+ return_value=Response(404, json={"message": "Not found"})
200
+ )
201
+
202
+ with pytest.raises(NotFoundError):
203
+ await async_client.resumes.get("non-existent")
204
+
205
+
206
+ @pytest.mark.asyncio
207
+ @respx.mock
208
+ async def test_generic_api_error_500_async(async_client):
209
+ respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
210
+ return_value=Response(500, text="Internal Server Error")
211
+ )
212
+
213
+ with pytest.raises(ReactiveResumeAPIError) as exc_info:
214
+ await async_client.resumes.list()
215
+ assert exc_info.value.status_code == 500
216
+
217
+
218
+ @pytest.mark.asyncio
219
+ @respx.mock
220
+ async def test_network_error_async(async_client):
221
+ respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
222
+ side_effect=httpx.ConnectTimeout("Connection timed out")
223
+ )
224
+
225
+ with pytest.raises(ReactiveResumeError) as exc_info:
226
+ await async_client.resumes.list()
227
+ assert "Network or connection error occurred" in str(exc_info.value)
228
+
229
+
230
+ @pytest.mark.asyncio
231
+ async def test_token_api_key_modifiers_async(async_client):
232
+ async_client.set_token("new-token")
233
+ assert async_client.client.headers["Authorization"] == "Bearer new-token"
234
+ assert "x-api-key" not in async_client.client.headers
235
+
236
+ async_client.set_api_key("new-api-key")
237
+ assert async_client.client.headers["x-api-key"] == "new-api-key"
238
+ assert "Authorization" not in async_client.client.headers
239
+
240
+
241
+ @pytest.mark.asyncio
242
+ async def test_token_initialization_async():
243
+ async with AsyncRxResumeClient(base_url=BASE_URL, token="initial-token") as c:
244
+ assert c.client.headers["Authorization"] == "Bearer initial-token"
245
+ assert "x-api-key" not in c.client.headers
246
+
247
+
248
+ @pytest.mark.asyncio
249
+ @respx.mock
250
+ async def test_list_resumes_pagination_async(async_client):
251
+ mock_resume = {
252
+ "id": "paginated-resume-id",
253
+ "name": "Paginated CV",
254
+ "slug": "paginated-cv",
255
+ "userId": "user-123",
256
+ "visibility": "public",
257
+ "locked": False,
258
+ "data": {"basics": {}, "sections": {}},
259
+ "createdAt": "2026-07-13T12:00:00Z",
260
+ "updatedAt": "2026-07-13T12:00:00Z",
261
+ }
262
+ route = respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
263
+ return_value=Response(200, json={"data": [mock_resume]})
264
+ )
265
+ resumes = await async_client.resumes.list()
266
+ assert len(resumes) == 1
267
+ assert resumes[0].id == "paginated-resume-id"
268
+ assert route.called
@@ -0,0 +1,259 @@
1
+ import pytest
2
+ import respx
3
+ import httpx
4
+ from httpx import Response
5
+ from reactive_resume import (
6
+ RxResumeClient,
7
+ AuthenticationError,
8
+ NotFoundError,
9
+ ReactiveResumeAPIError,
10
+ ReactiveResumeError,
11
+ )
12
+ from reactive_resume.models import ResumeImportData, Resume, User
13
+
14
+ BASE_URL = "https://rxresu.me"
15
+ API_KEY = "test-api-key"
16
+
17
+
18
+ @pytest.fixture
19
+ def client():
20
+ with RxResumeClient(base_url=BASE_URL, api_key=API_KEY) as c:
21
+ yield c
22
+
23
+
24
+ @respx.mock
25
+ def test_list_resumes(client):
26
+ mock_resume = {
27
+ "id": "test-resume-id",
28
+ "name": "Test CV",
29
+ "slug": "test-cv",
30
+ "userId": "user-123",
31
+ "visibility": "public",
32
+ "locked": False,
33
+ "data": {
34
+ "basics": {
35
+ "name": "John Doe",
36
+ "headline": "Software Developer",
37
+ "email": "john@example.com",
38
+ "phone": "123456",
39
+ "website": "https://john.me",
40
+ "location": "Earth",
41
+ "picture": "",
42
+ "profiles": [],
43
+ },
44
+ "sections": {},
45
+ },
46
+ "createdAt": "2026-07-13T12:00:00Z",
47
+ "updatedAt": "2026-07-13T12:00:00Z",
48
+ }
49
+
50
+ route = respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
51
+ return_value=Response(200, json=[mock_resume])
52
+ )
53
+
54
+ resumes = client.resumes.list()
55
+ assert len(resumes) == 1
56
+ assert isinstance(resumes[0], Resume)
57
+ assert resumes[0].id == "test-resume-id"
58
+ assert resumes[0].data.basics.name == "John Doe"
59
+ assert route.called
60
+
61
+
62
+ @respx.mock
63
+ def test_get_resume(client):
64
+ mock_resume = {
65
+ "id": "resume-1",
66
+ "name": "Test CV 1",
67
+ "slug": "test-cv-1",
68
+ "userId": "user-123",
69
+ "data": {"basics": {}, "sections": {}},
70
+ "createdAt": "2026-07-13T12:00:00Z",
71
+ "updatedAt": "2026-07-13T12:00:00Z",
72
+ }
73
+ route = respx.get(f"{BASE_URL}/api/openapi/resume/resume-1").mock(
74
+ return_value=Response(200, json=mock_resume)
75
+ )
76
+
77
+ resume = client.resumes.get("resume-1")
78
+ assert resume.id == "resume-1"
79
+ assert route.called
80
+
81
+
82
+ @respx.mock
83
+ def test_create_resume(client):
84
+ mock_resume = {
85
+ "id": "new-resume",
86
+ "name": "My New CV",
87
+ "slug": "my-new-cv",
88
+ "userId": "user-123",
89
+ "data": {"basics": {}, "sections": {}},
90
+ "createdAt": "2026-07-13T12:00:00Z",
91
+ "updatedAt": "2026-07-13T12:00:00Z",
92
+ }
93
+ route = respx.post(f"{BASE_URL}/api/openapi/resumes").mock(
94
+ return_value=Response(201, json=mock_resume)
95
+ )
96
+
97
+ import_data = ResumeImportData(title="My New CV")
98
+ resume = client.resumes.import_resume(import_data)
99
+ assert resume.id == "new-resume"
100
+ assert route.called
101
+
102
+
103
+ @respx.mock
104
+ def test_update_resume(client):
105
+ mock_resume = {
106
+ "id": "resume-1",
107
+ "name": "Updated CV Name",
108
+ "slug": "updated-cv-name",
109
+ "userId": "user-123",
110
+ "data": {"basics": {}, "sections": {}},
111
+ "createdAt": "2026-07-13T12:00:00Z",
112
+ "updatedAt": "2026-07-13T12:00:00Z",
113
+ }
114
+ route = respx.patch(f"{BASE_URL}/api/openapi/resume/resume-1").mock(
115
+ return_value=Response(200, json=mock_resume)
116
+ )
117
+
118
+ resume = client.resumes.update("resume-1", {"name": "Updated CV Name"})
119
+ assert resume.name == "Updated CV Name"
120
+ assert route.called
121
+
122
+
123
+ @respx.mock
124
+ def test_delete_resume(client):
125
+ route = respx.delete(f"{BASE_URL}/api/openapi/resume/resume-1").mock(return_value=Response(204))
126
+ client.resumes.delete("resume-1")
127
+ assert route.called
128
+
129
+
130
+ def test_get_pdf_url(client):
131
+ pdf_url = client.resumes.get_pdf_url("resume-1")
132
+ assert pdf_url == f"{BASE_URL}/api/openapi/resume/resume-1/pdf"
133
+
134
+
135
+ @respx.mock
136
+ def test_auth_login(client):
137
+ mock_user = {
138
+ "id": "user-123",
139
+ "name": "Ata Can",
140
+ "username": "atacan",
141
+ "email": "ata@example.com",
142
+ "provider": "email",
143
+ "createdAt": "2026-07-13T12:00:00Z",
144
+ "updatedAt": "2026-07-13T12:00:00Z",
145
+ }
146
+ route = respx.post(f"{BASE_URL}/api/auth/login").mock(
147
+ return_value=Response(200, json={"token": "fake-jwt-token", "user": mock_user})
148
+ )
149
+
150
+ token, user = client.auth.login("ata@example.com", "password123")
151
+ assert token == "fake-jwt-token"
152
+ assert isinstance(user, User)
153
+ assert user.username == "atacan"
154
+ assert route.called
155
+
156
+
157
+ @respx.mock
158
+ def test_auth_me(client):
159
+ mock_user = {
160
+ "id": "user-123",
161
+ "name": "Ata Can",
162
+ "username": "atacan",
163
+ "email": "ata@example.com",
164
+ "provider": "email",
165
+ "createdAt": "2026-07-13T12:00:00Z",
166
+ "updatedAt": "2026-07-13T12:00:00Z",
167
+ }
168
+ route = respx.get(f"{BASE_URL}/api/user/me").mock(return_value=Response(200, json=mock_user))
169
+
170
+ user = client.auth.me()
171
+ assert isinstance(user, User)
172
+ assert user.id == "user-123"
173
+ assert route.called
174
+
175
+
176
+ @respx.mock
177
+ def test_authentication_error(client):
178
+ respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
179
+ return_value=Response(401, json={"message": "Invalid API Key"})
180
+ )
181
+
182
+ with pytest.raises(AuthenticationError):
183
+ client.resumes.list()
184
+
185
+
186
+ @respx.mock
187
+ def test_not_found_error(client):
188
+ respx.get(f"{BASE_URL}/api/openapi/resume/non-existent").mock(
189
+ return_value=Response(404, json={"message": "Not found"})
190
+ )
191
+
192
+ with pytest.raises(NotFoundError):
193
+ client.resumes.get("non-existent")
194
+
195
+
196
+ @respx.mock
197
+ def test_generic_api_error_500(client):
198
+ respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
199
+ return_value=Response(500, text="Internal Server Error")
200
+ )
201
+
202
+ with pytest.raises(ReactiveResumeAPIError) as exc_info:
203
+ client.resumes.list()
204
+ assert exc_info.value.status_code == 500
205
+
206
+
207
+ @respx.mock
208
+ def test_network_error(client):
209
+ respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
210
+ side_effect=httpx.ConnectTimeout("Connection timed out")
211
+ )
212
+
213
+ with pytest.raises(ReactiveResumeError) as exc_info:
214
+ client.resumes.list()
215
+ assert "Network or connection error occurred" in str(exc_info.value)
216
+
217
+
218
+ def test_token_api_key_modifiers(client):
219
+ client.set_token("new-token")
220
+ assert client.client.headers["Authorization"] == "Bearer new-token"
221
+ assert "x-api-key" not in client.client.headers
222
+
223
+ client.set_api_key("new-api-key")
224
+ assert client.client.headers["x-api-key"] == "new-api-key"
225
+ assert "Authorization" not in client.client.headers
226
+
227
+
228
+ def test_token_initialization():
229
+ with RxResumeClient(base_url=BASE_URL, token="initial-token") as c:
230
+ assert c.client.headers["Authorization"] == "Bearer initial-token"
231
+ assert "x-api-key" not in c.client.headers
232
+
233
+
234
+ @respx.mock
235
+ def test_list_resumes_pagination(client):
236
+ mock_resume = {
237
+ "id": "paginated-resume-id",
238
+ "name": "Paginated CV",
239
+ "slug": "paginated-cv",
240
+ "userId": "user-123",
241
+ "visibility": "public",
242
+ "locked": False,
243
+ "data": {"basics": {}, "sections": {}},
244
+ "createdAt": "2026-07-13T12:00:00Z",
245
+ "updatedAt": "2026-07-13T12:00:00Z",
246
+ }
247
+ # Mock pagination wrapper {"data": [...]}
248
+ route = respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
249
+ return_value=Response(200, json={"data": [mock_resume]})
250
+ )
251
+ resumes = client.resumes.list()
252
+ assert len(resumes) == 1
253
+ assert resumes[0].id == "paginated-resume-id"
254
+ assert route.called
255
+
256
+
257
+ def test_exceptions_str_no_status_code():
258
+ err = ReactiveResumeAPIError("Something went wrong")
259
+ assert str(err) == "Something went wrong"
@@ -1,107 +0,0 @@
1
- import pytest
2
- import respx
3
- from httpx import Response
4
- from reactive_resume import AsyncRxResumeClient, AuthenticationError
5
- from reactive_resume.models import ResumeImportData, Resume
6
-
7
- BASE_URL = "https://rxresu.me"
8
- API_KEY = "test-api-key"
9
-
10
-
11
- @pytest.fixture
12
- async def async_client():
13
- async with AsyncRxResumeClient(base_url=BASE_URL, api_key=API_KEY) as c:
14
- yield c
15
-
16
-
17
- @pytest.mark.asyncio
18
- @respx.mock
19
- async def test_list_resumes_async(async_client):
20
- mock_resume = {
21
- "id": "test-resume-id",
22
- "name": "Test CV",
23
- "slug": "test-cv",
24
- "userId": "user-123",
25
- "visibility": "public",
26
- "locked": False,
27
- "data": {
28
- "basics": {
29
- "name": "John Doe",
30
- "headline": "Software Developer",
31
- "email": "john@example.com",
32
- "phone": "123456",
33
- "website": "https://john.me",
34
- "location": "Earth",
35
- "picture": "",
36
- "profiles": [],
37
- },
38
- "sections": {},
39
- },
40
- "createdAt": "2026-07-13T12:00:00Z",
41
- "updatedAt": "2026-07-13T12:00:00Z",
42
- }
43
-
44
- route = respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
45
- return_value=Response(200, json=[mock_resume])
46
- )
47
-
48
- resumes = await async_client.resumes.list()
49
- assert len(resumes) == 1
50
- assert isinstance(resumes[0], Resume)
51
- assert resumes[0].id == "test-resume-id"
52
- assert resumes[0].data.basics.name == "John Doe"
53
- assert route.called
54
-
55
-
56
- @pytest.mark.asyncio
57
- @respx.mock
58
- async def test_get_resume_async(async_client):
59
- mock_resume = {
60
- "id": "resume-1",
61
- "name": "Test CV 1",
62
- "slug": "test-cv-1",
63
- "userId": "user-123",
64
- "data": {"basics": {}, "sections": {}},
65
- "createdAt": "2026-07-13T12:00:00Z",
66
- "updatedAt": "2026-07-13T12:00:00Z",
67
- }
68
- route = respx.get(f"{BASE_URL}/api/openapi/resume/resume-1").mock(
69
- return_value=Response(200, json=mock_resume)
70
- )
71
-
72
- resume = await async_client.resumes.get("resume-1")
73
- assert resume.id == "resume-1"
74
- assert route.called
75
-
76
-
77
- @pytest.mark.asyncio
78
- @respx.mock
79
- async def test_create_resume_async(async_client):
80
- mock_resume = {
81
- "id": "new-resume",
82
- "name": "My New CV",
83
- "slug": "my-new-cv",
84
- "userId": "user-123",
85
- "data": {"basics": {}, "sections": {}},
86
- "createdAt": "2026-07-13T12:00:00Z",
87
- "updatedAt": "2026-07-13T12:00:00Z",
88
- }
89
- route = respx.post(f"{BASE_URL}/api/openapi/resumes").mock(
90
- return_value=Response(201, json=mock_resume)
91
- )
92
-
93
- import_data = ResumeImportData(title="My New CV")
94
- resume = await async_client.resumes.import_resume(import_data)
95
- assert resume.id == "new-resume"
96
- assert route.called
97
-
98
-
99
- @pytest.mark.asyncio
100
- @respx.mock
101
- async def test_authentication_error_async(async_client):
102
- respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
103
- return_value=Response(401, json={"message": "Invalid API Key"})
104
- )
105
-
106
- with pytest.raises(AuthenticationError):
107
- await async_client.resumes.list()
@@ -1,113 +0,0 @@
1
- import pytest
2
- import respx
3
- from httpx import Response
4
- from reactive_resume import RxResumeClient, AuthenticationError, NotFoundError
5
- from reactive_resume.models import ResumeImportData, Resume
6
-
7
- BASE_URL = "https://rxresu.me"
8
- API_KEY = "test-api-key"
9
-
10
-
11
- @pytest.fixture
12
- def client():
13
- with RxResumeClient(base_url=BASE_URL, api_key=API_KEY) as c:
14
- yield c
15
-
16
-
17
- @respx.mock
18
- def test_list_resumes(client):
19
- mock_resume = {
20
- "id": "test-resume-id",
21
- "name": "Test CV",
22
- "slug": "test-cv",
23
- "userId": "user-123",
24
- "visibility": "public",
25
- "locked": False,
26
- "data": {
27
- "basics": {
28
- "name": "John Doe",
29
- "headline": "Software Developer",
30
- "email": "john@example.com",
31
- "phone": "123456",
32
- "website": "https://john.me",
33
- "location": "Earth",
34
- "picture": "",
35
- "profiles": [],
36
- },
37
- "sections": {},
38
- },
39
- "createdAt": "2026-07-13T12:00:00Z",
40
- "updatedAt": "2026-07-13T12:00:00Z",
41
- }
42
-
43
- route = respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
44
- return_value=Response(200, json=[mock_resume])
45
- )
46
-
47
- resumes = client.resumes.list()
48
- assert len(resumes) == 1
49
- assert isinstance(resumes[0], Resume)
50
- assert resumes[0].id == "test-resume-id"
51
- assert resumes[0].data.basics.name == "John Doe"
52
- assert route.called
53
-
54
-
55
- @respx.mock
56
- def test_get_resume(client):
57
- mock_resume = {
58
- "id": "resume-1",
59
- "name": "Test CV 1",
60
- "slug": "test-cv-1",
61
- "userId": "user-123",
62
- "data": {"basics": {}, "sections": {}},
63
- "createdAt": "2026-07-13T12:00:00Z",
64
- "updatedAt": "2026-07-13T12:00:00Z",
65
- }
66
- route = respx.get(f"{BASE_URL}/api/openapi/resume/resume-1").mock(
67
- return_value=Response(200, json=mock_resume)
68
- )
69
-
70
- resume = client.resumes.get("resume-1")
71
- assert resume.id == "resume-1"
72
- assert route.called
73
-
74
-
75
- @respx.mock
76
- def test_create_resume(client):
77
- mock_resume = {
78
- "id": "new-resume",
79
- "name": "My New CV",
80
- "slug": "my-new-cv",
81
- "userId": "user-123",
82
- "data": {"basics": {}, "sections": {}},
83
- "createdAt": "2026-07-13T12:00:00Z",
84
- "updatedAt": "2026-07-13T12:00:00Z",
85
- }
86
- route = respx.post(f"{BASE_URL}/api/openapi/resumes").mock(
87
- return_value=Response(201, json=mock_resume)
88
- )
89
-
90
- import_data = ResumeImportData(title="My New CV")
91
- resume = client.resumes.import_resume(import_data)
92
- assert resume.id == "new-resume"
93
- assert route.called
94
-
95
-
96
- @respx.mock
97
- def test_authentication_error(client):
98
- respx.get(f"{BASE_URL}/api/openapi/resumes").mock(
99
- return_value=Response(401, json={"message": "Invalid API Key"})
100
- )
101
-
102
- with pytest.raises(AuthenticationError):
103
- client.resumes.list()
104
-
105
-
106
- @respx.mock
107
- def test_not_found_error(client):
108
- respx.get(f"{BASE_URL}/api/openapi/resume/non-existent").mock(
109
- return_value=Response(404, json={"message": "Not found"})
110
- )
111
-
112
- with pytest.raises(NotFoundError):
113
- client.resumes.get("non-existent")
File without changes