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.
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.claude/settings.local.json +3 -1
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/PKG-INFO +12 -2
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/README.md +11 -1
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/development.rst +18 -1
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/pyproject.toml +3 -2
- youtrack_cli-0.2.1/tests/conftest.py +40 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_articles.py +40 -22
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_auth.py +18 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_boards.py +17 -9
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_issues.py +66 -40
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/uv.lock +16 -1
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/articles.py +29 -9
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/auth.py +1 -1
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/boards.py +23 -3
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/issues.py +64 -33
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/main.py +18 -9
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/users.py +8 -4
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.github/dependabot.yml +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.github/workflows/ci.yml +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.github/workflows/release.yml +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.gitignore +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.pre-commit-config.yaml +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/.readthedocs.yaml +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/CLAUDE.md +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/PUBLISHING.md +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/Makefile +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/api/index.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/changelog.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/admin.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/articles.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/auth.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/boards.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/config.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/index.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/issues.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/projects.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/reports.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/time.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/commands/users.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/conf.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/configuration.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/index.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/installation.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/learning-path.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/quickstart.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/requirements.txt +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/troubleshooting.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/workflows.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/docs/youtrack-concepts.rst +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/justfile +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/package-lock.json +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/package.json +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/__init__.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_admin.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_config.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_main.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_projects.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_reports.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_time.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tests/test_users.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/tox.ini +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/__init__.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/admin.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/common.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/config.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/exceptions.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/logging.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/projects.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/reports.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/time.py +0 -0
- {youtrack_cli-0.2.0 → youtrack_cli-0.2.1}/youtrack_cli/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: youtrack-cli
|
|
3
|
-
Version: 0.2.
|
|
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
|
-
|
|
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.
|
|
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 = "
|
|
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
|
|
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 =
|
|
49
|
+
mock_resp = Mock()
|
|
50
50
|
mock_resp.status_code = 200
|
|
51
|
-
mock_resp.json =
|
|
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 =
|
|
72
|
+
mock_resp = Mock()
|
|
71
73
|
mock_resp.status_code = 400
|
|
72
|
-
mock_resp.text
|
|
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 =
|
|
104
|
+
mock_resp = Mock()
|
|
103
105
|
mock_resp.status_code = 200
|
|
104
|
-
mock_resp.json =
|
|
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 =
|
|
130
|
+
mock_resp = Mock()
|
|
127
131
|
mock_resp.status_code = 200
|
|
128
|
-
mock_resp.json =
|
|
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 =
|
|
155
|
+
mock_resp = Mock()
|
|
150
156
|
mock_resp.status_code = 200
|
|
151
|
-
mock_resp.json =
|
|
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 =
|
|
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 =
|
|
209
|
+
mock_resp = Mock()
|
|
202
210
|
mock_resp.status_code = 200
|
|
203
|
-
mock_resp.json =
|
|
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 =
|
|
237
|
+
mock_resp = Mock()
|
|
228
238
|
mock_resp.status_code = 200
|
|
229
|
-
mock_resp.json =
|
|
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 =
|
|
265
|
+
mock_resp = Mock()
|
|
254
266
|
mock_resp.status_code = 200
|
|
255
|
-
mock_resp.json =
|
|
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 =
|
|
290
|
+
mock_resp = Mock()
|
|
277
291
|
mock_resp.status_code = 200
|
|
278
|
-
mock_resp.json =
|
|
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 =
|
|
319
|
+
mock_resp = Mock()
|
|
304
320
|
mock_resp.status_code = 200
|
|
305
|
-
mock_resp.json =
|
|
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
|
|
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 =
|
|
51
|
+
mock_resp = Mock()
|
|
52
52
|
mock_resp.status_code = 200
|
|
53
|
-
mock_resp.json =
|
|
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 =
|
|
80
|
+
mock_resp = Mock()
|
|
79
81
|
mock_resp.status_code = 200
|
|
80
|
-
mock_resp.json =
|
|
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 =
|
|
120
|
+
mock_resp = Mock()
|
|
117
121
|
mock_resp.status_code = 200
|
|
118
|
-
mock_resp.json =
|
|
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 =
|
|
157
|
+
mock_resp = Mock()
|
|
152
158
|
mock_resp.status_code = 200
|
|
153
|
-
mock_resp.json =
|
|
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 = (
|