youtrack-cli 0.2.0__tar.gz → 0.2.1__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 (71) hide show
  1. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.claude/settings.local.json +3 -1
  2. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/PKG-INFO +12 -2
  3. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/README.md +11 -1
  4. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/development.rst +18 -1
  5. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/pyproject.toml +3 -2
  6. youtrack_cli-0.2.1/tests/conftest.py +40 -0
  7. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_articles.py +40 -22
  8. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_auth.py +18 -0
  9. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_boards.py +17 -9
  10. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_issues.py +66 -40
  11. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/uv.lock +16 -1
  12. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/articles.py +29 -9
  13. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/auth.py +1 -1
  14. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/boards.py +23 -3
  15. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/issues.py +64 -33
  16. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/main.py +18 -9
  17. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/users.py +8 -4
  18. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.github/dependabot.yml +0 -0
  19. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.github/workflows/ci.yml +0 -0
  20. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.github/workflows/release.yml +0 -0
  21. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.gitignore +0 -0
  22. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.pre-commit-config.yaml +0 -0
  23. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.readthedocs.yaml +0 -0
  24. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/CLAUDE.md +0 -0
  25. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/PUBLISHING.md +0 -0
  26. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/Makefile +0 -0
  27. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/api/index.rst +0 -0
  28. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/changelog.rst +0 -0
  29. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/admin.rst +0 -0
  30. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/articles.rst +0 -0
  31. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/auth.rst +0 -0
  32. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/boards.rst +0 -0
  33. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/config.rst +0 -0
  34. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/index.rst +0 -0
  35. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/issues.rst +0 -0
  36. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/projects.rst +0 -0
  37. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/reports.rst +0 -0
  38. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/time.rst +0 -0
  39. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/users.rst +0 -0
  40. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/conf.py +0 -0
  41. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/configuration.rst +0 -0
  42. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/index.rst +0 -0
  43. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/installation.rst +0 -0
  44. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/learning-path.rst +0 -0
  45. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/quickstart.rst +0 -0
  46. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/requirements.txt +0 -0
  47. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/troubleshooting.rst +0 -0
  48. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/workflows.rst +0 -0
  49. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/youtrack-concepts.rst +0 -0
  50. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/justfile +0 -0
  51. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/package-lock.json +0 -0
  52. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/package.json +0 -0
  53. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/__init__.py +0 -0
  54. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_admin.py +0 -0
  55. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_config.py +0 -0
  56. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_main.py +0 -0
  57. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_projects.py +0 -0
  58. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_reports.py +0 -0
  59. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_time.py +0 -0
  60. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_users.py +0 -0
  61. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tox.ini +0 -0
  62. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/__init__.py +0 -0
  63. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/admin.py +0 -0
  64. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/common.py +0 -0
  65. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/config.py +0 -0
  66. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/exceptions.py +0 -0
  67. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/logging.py +0 -0
  68. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/projects.py +0 -0
  69. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/reports.py +0 -0
  70. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/time.py +0 -0
  71. {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/utils.py +0 -0
@@ -34,7 +34,9 @@
34
34
  "Bash(tree:*)",
35
35
  "Bash(rm:*)",
36
36
  "Bash(yt:*)",
37
- "Bash(git branch:*)"
37
+ "Bash(git branch:*)",
38
+ "Bash(yt:*)",
39
+ "WebFetch(domain:pypi.org)"
38
40
  ],
39
41
  "deny": []
40
42
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: youtrack-cli
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: YouTrack CLI - Command line interface for JetBrains YouTrack issue tracking system
5
5
  Project-URL: Homepage, https://github.com/ryan-murphy/yt-cli
6
6
  Project-URL: Documentation, https://yt-cli.readthedocs.io/
@@ -243,8 +243,10 @@ uv pip install -e .
243
243
 
244
244
  ### Testing
245
245
 
246
+ This project uses `pytest` with randomized test execution to ensure test reliability:
247
+
246
248
  ```bash
247
- # Run tests
249
+ # Run tests (automatically randomized order)
248
250
  uv run pytest
249
251
 
250
252
  # Run tests with coverage
@@ -252,8 +254,16 @@ uv run pytest --cov=yt_cli
252
254
 
253
255
  # Run tests on multiple Python versions
254
256
  uv run tox
257
+
258
+ # Run tests with specific random seed for reproducibility
259
+ uv run pytest --randomly-seed=12345
260
+
261
+ # Disable randomization if needed
262
+ uv run pytest --randomly-dont-shuffle
255
263
  ```
256
264
 
265
+ **Randomized Testing**: Tests run in random order by default using `pytest-randomly` to catch order-dependent bugs and improve test reliability. Each test run displays the random seed used, which can be reused to reproduce specific test failures.
266
+
257
267
  ### Code Quality
258
268
 
259
269
  This project uses comprehensive pre-commit hooks for code quality:
@@ -195,8 +195,10 @@ uv pip install -e .
195
195
 
196
196
  ### Testing
197
197
 
198
+ This project uses `pytest` with randomized test execution to ensure test reliability:
199
+
198
200
  ```bash
199
- # Run tests
201
+ # Run tests (automatically randomized order)
200
202
  uv run pytest
201
203
 
202
204
  # Run tests with coverage
@@ -204,8 +206,16 @@ uv run pytest --cov=yt_cli
204
206
 
205
207
  # Run tests on multiple Python versions
206
208
  uv run tox
209
+
210
+ # Run tests with specific random seed for reproducibility
211
+ uv run pytest --randomly-seed=12345
212
+
213
+ # Disable randomization if needed
214
+ uv run pytest --randomly-dont-shuffle
207
215
  ```
208
216
 
217
+ **Randomized Testing**: Tests run in random order by default using `pytest-randomly` to catch order-dependent bugs and improve test reliability. Each test run displays the random seed used, which can be reused to reproduce specific test failures.
218
+
209
219
  ### Code Quality
210
220
 
211
221
  This project uses comprehensive pre-commit hooks for code quality:
@@ -225,7 +225,9 @@ Tests are organized into categories:
225
225
  Running Tests
226
226
  ~~~~~~~~~~~~~
227
227
 
228
- Run all tests:
228
+ The project uses pytest with randomized test execution to improve test reliability and catch order-dependent bugs.
229
+
230
+ Run all tests (automatically randomized order):
229
231
 
230
232
  .. code-block:: bash
231
233
 
@@ -244,6 +246,21 @@ Run with coverage:
244
246
 
245
247
  uv run pytest --cov=youtrack_cli --cov-report=html
246
248
 
249
+ **Randomized Testing Options:**
250
+
251
+ .. code-block:: bash
252
+
253
+ # Run with specific random seed for reproducibility
254
+ uv run pytest --randomly-seed=12345
255
+
256
+ # Disable randomization if needed
257
+ uv run pytest --randomly-dont-shuffle
258
+
259
+ # Show current random seed
260
+ uv run pytest --randomly-seed=last
261
+
262
+ The test suite uses ``pytest-randomly`` to randomize test execution order. Each test run displays the random seed used, which can be reused to reproduce specific test failures. This helps identify and eliminate order-dependent test bugs.
263
+
247
264
  Multi-version Testing
248
265
  ~~~~~~~~~~~~~~~~~~~~~
249
266
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "youtrack-cli"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "YouTrack CLI - Command line interface for JetBrains YouTrack issue tracking system"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9, <3.14"
@@ -66,7 +66,7 @@ requires = ["hatchling"]
66
66
  build-backend = "hatchling.build"
67
67
 
68
68
  [tool.ruff]
69
- target-version = "py39"
69
+ target-version = "0.2.1"
70
70
  line-length = 88
71
71
 
72
72
  [tool.ruff.lint]
@@ -118,6 +118,7 @@ dev = [
118
118
  "pytest>=8.4.1",
119
119
  "pytest-asyncio>=1.0.0",
120
120
  "pytest-cov>=6.2.1",
121
+ "pytest-randomly>=3.0.0",
121
122
  "ruff>=0.12.1",
122
123
  "tox>=4.27.0",
123
124
  "zizmor>=1.11.0",
@@ -0,0 +1,40 @@
1
+ """Global test configuration and fixtures."""
2
+
3
+ import os
4
+ from typing import Optional
5
+ from unittest.mock import patch
6
+
7
+ import pytest
8
+
9
+
10
+ @pytest.fixture(scope="function", autouse=True)
11
+ def isolate_environment():
12
+ """Ensure test isolation by saving and restoring environment variables."""
13
+ # YouTrack environment variables that could cause test contamination
14
+ youtrack_env_vars = ["YOUTRACK_BASE_URL", "YOUTRACK_TOKEN", "YOUTRACK_USERNAME"]
15
+
16
+ # Store original values
17
+ original_env: dict[str, Optional[str]] = {}
18
+ for key in youtrack_env_vars:
19
+ original_env[key] = os.environ.get(key)
20
+
21
+ # Clear YouTrack environment variables before each test
22
+ for key in youtrack_env_vars:
23
+ if key in os.environ:
24
+ del os.environ[key]
25
+
26
+ yield
27
+
28
+ # Restore original environment after test
29
+ for key, value in original_env.items():
30
+ if value is not None:
31
+ os.environ[key] = value
32
+ elif key in os.environ:
33
+ del os.environ[key]
34
+
35
+
36
+ @pytest.fixture(scope="function", autouse=True)
37
+ def mock_dotenv_loading():
38
+ """Mock dotenv loading to prevent real config files from loading."""
39
+ with patch("youtrack_cli.auth.load_dotenv") as mock_load_dotenv:
40
+ yield mock_load_dotenv
@@ -1,6 +1,6 @@
1
1
  """Tests for article management functionality."""
2
2
 
3
- from unittest.mock import AsyncMock, MagicMock, patch
3
+ from unittest.mock import MagicMock, Mock, patch
4
4
 
5
5
  import pytest
6
6
  from click.testing import CliRunner
@@ -46,9 +46,11 @@ class TestArticleManager:
46
46
  }
47
47
 
48
48
  with patch("httpx.AsyncClient") as mock_client:
49
- mock_resp = AsyncMock()
49
+ mock_resp = Mock()
50
50
  mock_resp.status_code = 200
51
- mock_resp.json = lambda: mock_response
51
+ mock_resp.json.return_value = mock_response
52
+ mock_resp.text = '{"id": "123", "summary": "Test Article"}'
53
+ mock_resp.headers = {"content-type": "application/json"}
52
54
  mock_resp.raise_for_status.return_value = None
53
55
  mock_client.return_value.__aenter__.return_value.post.return_value = (
54
56
  mock_resp # noqa: E501
@@ -67,9 +69,9 @@ class TestArticleManager:
67
69
  async def test_create_article_failure(self, article_manager):
68
70
  """Test article creation failure."""
69
71
  with patch("httpx.AsyncClient") as mock_client:
70
- mock_resp = AsyncMock()
72
+ mock_resp = Mock()
71
73
  mock_resp.status_code = 400
72
- mock_resp.text.return_value = "Bad Request"
74
+ mock_resp.text = "Bad Request"
73
75
  mock_client.return_value.__aenter__.return_value.post.return_value = (
74
76
  mock_resp # noqa: E501
75
77
  )
@@ -99,9 +101,11 @@ class TestArticleManager:
99
101
  ]
100
102
 
101
103
  with patch("httpx.AsyncClient") as mock_client:
102
- mock_resp = AsyncMock()
104
+ mock_resp = Mock()
103
105
  mock_resp.status_code = 200
104
- mock_resp.json = lambda: mock_response
106
+ mock_resp.json.return_value = mock_response
107
+ mock_resp.text = '[{"id": "123", "summary": "Article 1"}]'
108
+ mock_resp.headers = {"content-type": "application/json"}
105
109
  mock_resp.raise_for_status.return_value = None
106
110
  mock_client.return_value.__aenter__.return_value.get.return_value = (
107
111
  mock_resp # noqa: E501
@@ -123,9 +127,11 @@ class TestArticleManager:
123
127
  }
124
128
 
125
129
  with patch("httpx.AsyncClient") as mock_client:
126
- mock_resp = AsyncMock()
130
+ mock_resp = Mock()
127
131
  mock_resp.status_code = 200
128
- mock_resp.json = lambda: mock_response
132
+ mock_resp.json.return_value = mock_response
133
+ mock_resp.text = '{"mock": "response"}'
134
+ mock_resp.headers = {"content-type": "application/json"}
129
135
  mock_resp.raise_for_status.return_value = None
130
136
  mock_client.return_value.__aenter__.return_value.get.return_value = (
131
137
  mock_resp # noqa: E501
@@ -146,9 +152,11 @@ class TestArticleManager:
146
152
  }
147
153
 
148
154
  with patch("httpx.AsyncClient") as mock_client:
149
- mock_resp = AsyncMock()
155
+ mock_resp = Mock()
150
156
  mock_resp.status_code = 200
151
- mock_resp.json = lambda: mock_response
157
+ mock_resp.json.return_value = mock_response
158
+ mock_resp.text = '{"mock": "response"}'
159
+ mock_resp.headers = {"content-type": "application/json"}
152
160
  mock_resp.raise_for_status.return_value = None
153
161
  mock_client.return_value.__aenter__.return_value.post.return_value = (
154
162
  mock_resp # noqa: E501
@@ -176,7 +184,7 @@ class TestArticleManager:
176
184
  async def test_delete_article_success(self, article_manager):
177
185
  """Test successful article deletion."""
178
186
  with patch("httpx.AsyncClient") as mock_client:
179
- mock_resp = AsyncMock()
187
+ mock_resp = Mock()
180
188
  mock_resp.status_code = 200
181
189
  mock_resp.raise_for_status.return_value = None
182
190
  mock_client.return_value.__aenter__.return_value.delete.return_value = (
@@ -198,9 +206,11 @@ class TestArticleManager:
198
206
  }
199
207
 
200
208
  with patch("httpx.AsyncClient") as mock_client:
201
- mock_resp = AsyncMock()
209
+ mock_resp = Mock()
202
210
  mock_resp.status_code = 200
203
- mock_resp.json = lambda: mock_response
211
+ mock_resp.json.return_value = mock_response
212
+ mock_resp.text = '{"mock": "response"}'
213
+ mock_resp.headers = {"content-type": "application/json"}
204
214
  mock_resp.raise_for_status.return_value = None
205
215
  mock_client.return_value.__aenter__.return_value.post.return_value = (
206
216
  mock_resp # noqa: E501
@@ -224,9 +234,11 @@ class TestArticleManager:
224
234
  ]
225
235
 
226
236
  with patch("httpx.AsyncClient") as mock_client:
227
- mock_resp = AsyncMock()
237
+ mock_resp = Mock()
228
238
  mock_resp.status_code = 200
229
- mock_resp.json = lambda: mock_response
239
+ mock_resp.json.return_value = mock_response
240
+ mock_resp.text = '{"mock": "response"}'
241
+ mock_resp.headers = {"content-type": "application/json"}
230
242
  mock_resp.raise_for_status.return_value = None
231
243
  mock_client.return_value.__aenter__.return_value.get.return_value = (
232
244
  mock_resp # noqa: E501
@@ -250,9 +262,11 @@ class TestArticleManager:
250
262
  ]
251
263
 
252
264
  with patch("httpx.AsyncClient") as mock_client:
253
- mock_resp = AsyncMock()
265
+ mock_resp = Mock()
254
266
  mock_resp.status_code = 200
255
- mock_resp.json = lambda: mock_response
267
+ mock_resp.json.return_value = mock_response
268
+ mock_resp.text = '{"mock": "response"}'
269
+ mock_resp.headers = {"content-type": "application/json"}
256
270
  mock_resp.raise_for_status.return_value = None
257
271
  mock_client.return_value.__aenter__.return_value.get.return_value = (
258
272
  mock_resp # noqa: E501
@@ -273,9 +287,11 @@ class TestArticleManager:
273
287
  }
274
288
 
275
289
  with patch("httpx.AsyncClient") as mock_client:
276
- mock_resp = AsyncMock()
290
+ mock_resp = Mock()
277
291
  mock_resp.status_code = 200
278
- mock_resp.json = lambda: mock_response
292
+ mock_resp.json.return_value = mock_response
293
+ mock_resp.text = '{"mock": "response"}'
294
+ mock_resp.headers = {"content-type": "application/json"}
279
295
  mock_resp.raise_for_status.return_value = None
280
296
  mock_client.return_value.__aenter__.return_value.post.return_value = (
281
297
  mock_resp # noqa: E501
@@ -300,9 +316,11 @@ class TestArticleManager:
300
316
  ]
301
317
 
302
318
  with patch("httpx.AsyncClient") as mock_client:
303
- mock_resp = AsyncMock()
319
+ mock_resp = Mock()
304
320
  mock_resp.status_code = 200
305
- mock_resp.json = lambda: mock_response
321
+ mock_resp.json.return_value = mock_response
322
+ mock_resp.text = '{"mock": "response"}'
323
+ mock_resp.headers = {"content-type": "application/json"}
306
324
  mock_resp.raise_for_status.return_value = None
307
325
  mock_client.return_value.__aenter__.return_value.get.return_value = (
308
326
  mock_resp # noqa: E501
@@ -49,6 +49,24 @@ class TestAuthManager:
49
49
  self.config_path = os.path.join(self.temp_dir, ".env")
50
50
  self.auth_manager = AuthManager(self.config_path)
51
51
 
52
+ # Store original environment variables to restore later
53
+ self.original_env = {
54
+ key: os.environ.get(key)
55
+ for key in ["YOUTRACK_BASE_URL", "YOUTRACK_TOKEN", "YOUTRACK_USERNAME"]
56
+ }
57
+
58
+ def teardown_method(self):
59
+ """Clean up test fixtures and restore environment."""
60
+ # Remove any YouTrack environment variables that were set during tests
61
+ for key in ["YOUTRACK_BASE_URL", "YOUTRACK_TOKEN", "YOUTRACK_USERNAME"]:
62
+ if key in os.environ:
63
+ del os.environ[key]
64
+
65
+ # Restore original environment variables
66
+ for key, value in self.original_env.items():
67
+ if value is not None:
68
+ os.environ[key] = value
69
+
52
70
  def test_default_config_path(self):
53
71
  """Test default config path generation."""
54
72
  manager = AuthManager()
@@ -1,6 +1,6 @@
1
1
  """Tests for board management functionality."""
2
2
 
3
- from unittest.mock import AsyncMock, MagicMock, patch
3
+ from unittest.mock import MagicMock, Mock, patch
4
4
 
5
5
  import pytest
6
6
 
@@ -48,9 +48,11 @@ class TestBoardManager:
48
48
  ]
49
49
 
50
50
  with patch("httpx.AsyncClient") as mock_client:
51
- mock_resp = AsyncMock()
51
+ mock_resp = Mock()
52
52
  mock_resp.status_code = 200
53
- mock_resp.json = lambda: mock_response
53
+ mock_resp.json.return_value = mock_response
54
+ mock_resp.text = '{"mock": "response"}'
55
+ mock_resp.headers = {"content-type": "application/json"}
54
56
  mock_resp.raise_for_status.return_value = None
55
57
 
56
58
  mock_client.return_value.__aenter__.return_value.get.return_value = (
@@ -75,9 +77,11 @@ class TestBoardManager:
75
77
  ]
76
78
 
77
79
  with patch("httpx.AsyncClient") as mock_client:
78
- mock_resp = AsyncMock()
80
+ mock_resp = Mock()
79
81
  mock_resp.status_code = 200
80
- mock_resp.json = lambda: mock_response
82
+ mock_resp.json.return_value = mock_response
83
+ mock_resp.text = '{"mock": "response"}'
84
+ mock_resp.headers = {"content-type": "application/json"}
81
85
  mock_resp.raise_for_status.return_value = None
82
86
 
83
87
  mock_client.return_value.__aenter__.return_value.get.return_value = (
@@ -113,9 +117,11 @@ class TestBoardManager:
113
117
  }
114
118
 
115
119
  with patch("httpx.AsyncClient") as mock_client:
116
- mock_resp = AsyncMock()
120
+ mock_resp = Mock()
117
121
  mock_resp.status_code = 200
118
- mock_resp.json = lambda: mock_response
122
+ mock_resp.json.return_value = mock_response
123
+ mock_resp.text = '{"mock": "response"}'
124
+ mock_resp.headers = {"content-type": "application/json"}
119
125
  mock_resp.raise_for_status.return_value = None
120
126
 
121
127
  mock_client.return_value.__aenter__.return_value.get.return_value = (
@@ -148,9 +154,11 @@ class TestBoardManager:
148
154
  }
149
155
 
150
156
  with patch("httpx.AsyncClient") as mock_client:
151
- mock_resp = AsyncMock()
157
+ mock_resp = Mock()
152
158
  mock_resp.status_code = 200
153
- mock_resp.json = lambda: mock_response
159
+ mock_resp.json.return_value = mock_response
160
+ mock_resp.text = '{"mock": "response"}'
161
+ mock_resp.headers = {"content-type": "application/json"}
154
162
  mock_resp.raise_for_status.return_value = None
155
163
 
156
164
  mock_client.return_value.__aenter__.return_value.post.return_value = (