youtrack-cli 0.3.7__tar.gz → 0.3.9__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.3.7 → youtrack_cli-0.3.9}/CLAUDE.md +11 -1
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/PKG-INFO +1 -1
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/justfile +1 -1
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/pyproject.toml +1 -1
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/test_admin.py +57 -38
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/test_articles.py +45 -33
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/test_boards.py +29 -19
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/test_issues.py +109 -54
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/test_logging.py +2 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/test_projects.py +40 -22
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/test_reports.py +24 -11
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/test_time.py +21 -13
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/test_users.py +79 -72
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/uv.lock +1 -1
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/__init__.py +2 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/admin.py +252 -250
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/articles.py +137 -136
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/boards.py +48 -50
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/client.py +3 -2
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/issues.py +276 -274
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/main.py +2 -1
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/projects.py +113 -112
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/reports.py +112 -120
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/time.py +33 -31
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/users.py +101 -164
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/.claude/settings.local.json +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/.github/dependabot.yml +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/.github/workflows/ci.yml +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/.github/workflows/release.yml +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/.gitignore +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/.pre-commit-config.yaml +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/.readthedocs.yaml +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/PUBLISHING.md +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/README.md +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/Makefile +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/api/index.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/changelog.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/command-aliases.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/commands/admin.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/commands/articles.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/commands/auth.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/commands/boards.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/commands/config.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/commands/index.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/commands/issues.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/commands/projects.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/commands/reports.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/commands/time.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/commands/users.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/conf.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/configuration.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/development.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/index.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/installation.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/learning-path.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/logging.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/performance.md +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/progress-indicators.md +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/quickstart.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/requirements.txt +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/security.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/troubleshooting.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/workflows.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/docs/youtrack-concepts.rst +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/package-lock.json +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/package.json +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/__init__.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/conftest.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/test_auth.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/test_config.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/test_main.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tests/test_security.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/tox.ini +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/auth.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/cache.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/cli_utils/__init__.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/cli_utils/aliases.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/commands/__init__.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/commands/articles.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/commands/boards.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/commands/common.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/commands/issues.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/commands/projects.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/commands/time_tracking.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/commands/users.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/common.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/config.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/exceptions.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/logging.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/performance.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/progress.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/py.typed +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/security.py +0 -0
- {youtrack_cli-0.3.7 → youtrack_cli-0.3.9}/youtrack_cli/utils.py +0 -0
|
@@ -16,7 +16,7 @@ We use `uv` for managing dependencies.
|
|
|
16
16
|
|
|
17
17
|
Each new feature must have a corresponding github issue. When working on a new issue a new feature branch must be created with the name of the branch matching the name of the issue with the issue number in it.
|
|
18
18
|
|
|
19
|
-
For every change that is implemented, the
|
|
19
|
+
For every change that is implemented, the corresponding documentation in the docs/ flder MUST be updated to reflect that change. Updates to README.md should be made to include a very short summary but not comprehensive details.
|
|
20
20
|
|
|
21
21
|
## Test
|
|
22
22
|
|
|
@@ -30,6 +30,16 @@ Documentation is available in the docs/ folder. Any new functionality should hav
|
|
|
30
30
|
|
|
31
31
|
Deployment will always be done to a feature branch. When a feature is significant enough, we'll bump the version of the tool and tag it with that version. We will have a github action that deploys this to Test PyPI and PyPI using a `release.yml` GitHub Action.
|
|
32
32
|
|
|
33
|
+
## GitHub Issue resolution steps
|
|
34
|
+
|
|
35
|
+
1. Make sure a new branch has been created
|
|
36
|
+
2. Think through the change that needs to be implemented
|
|
37
|
+
3. Write the plan to scratch/issue-id.md where id is the issue number from GitHub. For example issue 42 would be written to scratch/issue-42.md
|
|
38
|
+
4. Implement the changes from the plan written in scratch/issue-id.md
|
|
39
|
+
5. Create a PR. never bypass the pre-commit checks
|
|
40
|
+
6. Once the PR has been squashed and merged, switch back to main. You'll need to check the PR status every 60 seconds
|
|
41
|
+
7. Pull the changes from main to local development
|
|
42
|
+
|
|
33
43
|
## Current Configuration
|
|
34
44
|
|
|
35
45
|
- Claude Code permissions are configured in `.claude/settings.local.json`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: youtrack-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
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/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Tests for the admin module."""
|
|
2
2
|
|
|
3
|
-
from unittest.mock import Mock, patch
|
|
3
|
+
from unittest.mock import AsyncMock, Mock, patch
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
6
|
import pytest
|
|
@@ -46,12 +46,13 @@ class TestAdminManager:
|
|
|
46
46
|
},
|
|
47
47
|
]
|
|
48
48
|
|
|
49
|
-
with patch("
|
|
49
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
50
|
+
mock_client_manager = Mock()
|
|
50
51
|
mock_response = Mock()
|
|
51
52
|
mock_response.json.return_value = mock_settings
|
|
52
|
-
mock_response.raise_for_status.return_value = None
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_response)
|
|
55
|
+
mock_get_client.return_value = mock_client_manager
|
|
55
56
|
|
|
56
57
|
result = await admin_manager.get_global_settings()
|
|
57
58
|
|
|
@@ -71,12 +72,14 @@ class TestAdminManager:
|
|
|
71
72
|
@pytest.mark.asyncio
|
|
72
73
|
async def test_get_global_settings_insufficient_permissions(self, admin_manager, auth_manager):
|
|
73
74
|
"""Test global settings retrieval with insufficient permissions."""
|
|
74
|
-
with patch("
|
|
75
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
76
|
+
mock_client_manager = Mock()
|
|
75
77
|
mock_response = Mock()
|
|
76
78
|
mock_response.status_code = 403
|
|
77
79
|
mock_request = Mock()
|
|
78
80
|
http_error = httpx.HTTPStatusError("Forbidden", request=mock_request, response=mock_response)
|
|
79
|
-
|
|
81
|
+
mock_client_manager.make_request = AsyncMock(side_effect=http_error)
|
|
82
|
+
mock_get_client.return_value = mock_client_manager
|
|
80
83
|
|
|
81
84
|
result = await admin_manager.get_global_settings()
|
|
82
85
|
|
|
@@ -86,11 +89,12 @@ class TestAdminManager:
|
|
|
86
89
|
@pytest.mark.asyncio
|
|
87
90
|
async def test_set_global_setting_success(self, admin_manager, auth_manager):
|
|
88
91
|
"""Test successful global setting update."""
|
|
89
|
-
with patch("
|
|
92
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
93
|
+
mock_client_manager = Mock()
|
|
90
94
|
mock_response = Mock()
|
|
91
|
-
mock_response.raise_for_status.return_value = None
|
|
92
95
|
|
|
93
|
-
|
|
96
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_response)
|
|
97
|
+
mock_get_client.return_value = mock_client_manager
|
|
94
98
|
|
|
95
99
|
result = await admin_manager.set_global_setting("server.name", "New Name")
|
|
96
100
|
|
|
@@ -100,12 +104,14 @@ class TestAdminManager:
|
|
|
100
104
|
@pytest.mark.asyncio
|
|
101
105
|
async def test_set_global_setting_invalid_data(self, admin_manager, auth_manager):
|
|
102
106
|
"""Test global setting update with invalid data."""
|
|
103
|
-
with patch("
|
|
107
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
108
|
+
mock_client_manager = Mock()
|
|
104
109
|
mock_response = Mock()
|
|
105
110
|
mock_response.status_code = 400
|
|
106
111
|
mock_request = Mock()
|
|
107
112
|
http_error = httpx.HTTPStatusError("Bad Request", request=mock_request, response=mock_response)
|
|
108
|
-
|
|
113
|
+
mock_client_manager.make_request = AsyncMock(side_effect=http_error)
|
|
114
|
+
mock_get_client.return_value = mock_client_manager
|
|
109
115
|
|
|
110
116
|
result = await admin_manager.set_global_setting("invalid.key", "value")
|
|
111
117
|
|
|
@@ -123,12 +129,13 @@ class TestAdminManager:
|
|
|
123
129
|
"isActive": True,
|
|
124
130
|
}
|
|
125
131
|
|
|
126
|
-
with patch("
|
|
132
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
133
|
+
mock_client_manager = Mock()
|
|
127
134
|
mock_response = Mock()
|
|
128
135
|
mock_response.json.return_value = mock_license
|
|
129
|
-
mock_response.raise_for_status.return_value = None
|
|
130
136
|
|
|
131
|
-
|
|
137
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_response)
|
|
138
|
+
mock_get_client.return_value = mock_client_manager
|
|
132
139
|
|
|
133
140
|
result = await admin_manager.get_license_info()
|
|
134
141
|
|
|
@@ -140,12 +147,13 @@ class TestAdminManager:
|
|
|
140
147
|
"""Test successful license usage retrieval."""
|
|
141
148
|
mock_usage = {"totalUsers": 75, "activeUsers": 50, "remainingUsers": 25}
|
|
142
149
|
|
|
143
|
-
with patch("
|
|
150
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
151
|
+
mock_client_manager = Mock()
|
|
144
152
|
mock_response = Mock()
|
|
145
153
|
mock_response.json.return_value = mock_usage
|
|
146
|
-
mock_response.raise_for_status.return_value = None
|
|
147
154
|
|
|
148
|
-
|
|
155
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_response)
|
|
156
|
+
mock_get_client.return_value = mock_client_manager
|
|
149
157
|
|
|
150
158
|
result = await admin_manager.get_license_usage()
|
|
151
159
|
|
|
@@ -163,12 +171,13 @@ class TestAdminManager:
|
|
|
163
171
|
],
|
|
164
172
|
}
|
|
165
173
|
|
|
166
|
-
with patch("
|
|
174
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
175
|
+
mock_client_manager = Mock()
|
|
167
176
|
mock_response = Mock()
|
|
168
177
|
mock_response.json.return_value = mock_health
|
|
169
|
-
mock_response.raise_for_status.return_value = None
|
|
170
178
|
|
|
171
|
-
|
|
179
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_response)
|
|
180
|
+
mock_get_client.return_value = mock_client_manager
|
|
172
181
|
|
|
173
182
|
result = await admin_manager.get_system_health()
|
|
174
183
|
|
|
@@ -178,12 +187,14 @@ class TestAdminManager:
|
|
|
178
187
|
@pytest.mark.asyncio
|
|
179
188
|
async def test_get_system_health_404_error(self, admin_manager, auth_manager):
|
|
180
189
|
"""Test system health check with 404 error on all endpoints."""
|
|
181
|
-
with patch("
|
|
190
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
191
|
+
mock_client_manager = Mock()
|
|
182
192
|
mock_response = Mock()
|
|
183
193
|
mock_response.status_code = 404
|
|
184
194
|
mock_request = Mock()
|
|
185
195
|
http_error = httpx.HTTPStatusError("Not Found", request=mock_request, response=mock_response)
|
|
186
|
-
|
|
196
|
+
mock_client_manager.make_request = AsyncMock(side_effect=http_error)
|
|
197
|
+
mock_get_client.return_value = mock_client_manager
|
|
187
198
|
|
|
188
199
|
result = await admin_manager.get_system_health()
|
|
189
200
|
|
|
@@ -194,12 +205,14 @@ class TestAdminManager:
|
|
|
194
205
|
@pytest.mark.asyncio
|
|
195
206
|
async def test_get_system_health_403_error(self, admin_manager, auth_manager):
|
|
196
207
|
"""Test system health check with 403 permission error."""
|
|
197
|
-
with patch("
|
|
208
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
209
|
+
mock_client_manager = Mock()
|
|
198
210
|
mock_response = Mock()
|
|
199
211
|
mock_response.status_code = 403
|
|
200
212
|
mock_request = Mock()
|
|
201
213
|
http_error = httpx.HTTPStatusError("Forbidden", request=mock_request, response=mock_response)
|
|
202
|
-
|
|
214
|
+
mock_client_manager.make_request = AsyncMock(side_effect=http_error)
|
|
215
|
+
mock_get_client.return_value = mock_client_manager
|
|
203
216
|
|
|
204
217
|
result = await admin_manager.get_system_health()
|
|
205
218
|
|
|
@@ -210,11 +223,12 @@ class TestAdminManager:
|
|
|
210
223
|
@pytest.mark.asyncio
|
|
211
224
|
async def test_clear_caches_success(self, admin_manager, auth_manager):
|
|
212
225
|
"""Test successful cache clearing."""
|
|
213
|
-
with patch("
|
|
226
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
227
|
+
mock_client_manager = Mock()
|
|
214
228
|
mock_response = Mock()
|
|
215
|
-
mock_response.raise_for_status.return_value = None
|
|
216
229
|
|
|
217
|
-
|
|
230
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_response)
|
|
231
|
+
mock_get_client.return_value = mock_client_manager
|
|
218
232
|
|
|
219
233
|
result = await admin_manager.clear_caches()
|
|
220
234
|
|
|
@@ -239,12 +253,13 @@ class TestAdminManager:
|
|
|
239
253
|
},
|
|
240
254
|
]
|
|
241
255
|
|
|
242
|
-
with patch("
|
|
256
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
257
|
+
mock_client_manager = Mock()
|
|
243
258
|
mock_response = Mock()
|
|
244
259
|
mock_response.json.return_value = {"usergroups": mock_groups}
|
|
245
|
-
mock_response.raise_for_status.return_value = None
|
|
246
260
|
|
|
247
|
-
|
|
261
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_response)
|
|
262
|
+
mock_get_client.return_value = mock_client_manager
|
|
248
263
|
|
|
249
264
|
result = await admin_manager.list_user_groups()
|
|
250
265
|
|
|
@@ -260,12 +275,13 @@ class TestAdminManager:
|
|
|
260
275
|
"description": "A new group",
|
|
261
276
|
}
|
|
262
277
|
|
|
263
|
-
with patch("
|
|
278
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
279
|
+
mock_client_manager = Mock()
|
|
264
280
|
mock_response = Mock()
|
|
265
281
|
mock_response.json.return_value = mock_created_group
|
|
266
|
-
mock_response.raise_for_status.return_value = None
|
|
267
282
|
|
|
268
|
-
|
|
283
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_response)
|
|
284
|
+
mock_get_client.return_value = mock_client_manager
|
|
269
285
|
|
|
270
286
|
result = await admin_manager.create_user_group("New Group", "A new group")
|
|
271
287
|
|
|
@@ -276,12 +292,14 @@ class TestAdminManager:
|
|
|
276
292
|
@pytest.mark.asyncio
|
|
277
293
|
async def test_create_user_group_already_exists(self, admin_manager, auth_manager):
|
|
278
294
|
"""Test user group creation when group already exists."""
|
|
279
|
-
with patch("
|
|
295
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
296
|
+
mock_client_manager = Mock()
|
|
280
297
|
mock_response = Mock()
|
|
281
298
|
mock_response.status_code = 400
|
|
282
299
|
mock_request = Mock()
|
|
283
300
|
http_error = httpx.HTTPStatusError("Bad Request", request=mock_request, response=mock_response)
|
|
284
|
-
|
|
301
|
+
mock_client_manager.make_request = AsyncMock(side_effect=http_error)
|
|
302
|
+
mock_get_client.return_value = mock_client_manager
|
|
285
303
|
|
|
286
304
|
result = await admin_manager.create_user_group("Existing Group")
|
|
287
305
|
|
|
@@ -308,12 +326,13 @@ class TestAdminManager:
|
|
|
308
326
|
},
|
|
309
327
|
]
|
|
310
328
|
|
|
311
|
-
with patch("
|
|
329
|
+
with patch("youtrack_cli.admin.get_client_manager") as mock_get_client:
|
|
330
|
+
mock_client_manager = Mock()
|
|
312
331
|
mock_response = Mock()
|
|
313
332
|
mock_response.json.return_value = mock_fields
|
|
314
|
-
mock_response.raise_for_status.return_value = None
|
|
315
333
|
|
|
316
|
-
|
|
334
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_response)
|
|
335
|
+
mock_get_client.return_value = mock_client_manager
|
|
317
336
|
|
|
318
337
|
result = await admin_manager.list_custom_fields()
|
|
319
338
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Tests for article management functionality."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from unittest.mock import MagicMock, Mock, patch
|
|
4
|
+
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
7
|
from click.testing import CliRunner
|
|
@@ -46,14 +46,15 @@ class TestArticleManager:
|
|
|
46
46
|
"content": "Test content",
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
with patch("
|
|
49
|
+
with patch("youtrack_cli.articles.get_client_manager") as mock_get_client:
|
|
50
|
+
mock_client_manager = Mock()
|
|
50
51
|
mock_resp = Mock()
|
|
51
52
|
mock_resp.status_code = 200
|
|
52
53
|
mock_resp.json.return_value = mock_response
|
|
53
54
|
mock_resp.text = '{"id": "123", "summary": "Test Article"}'
|
|
54
55
|
mock_resp.headers = {"content-type": "application/json"}
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
57
|
+
mock_get_client.return_value = mock_client_manager
|
|
57
58
|
|
|
58
59
|
result = await article_manager.create_article(
|
|
59
60
|
title="Test Article",
|
|
@@ -67,11 +68,13 @@ class TestArticleManager:
|
|
|
67
68
|
@pytest.mark.asyncio
|
|
68
69
|
async def test_create_article_failure(self, article_manager):
|
|
69
70
|
"""Test article creation failure."""
|
|
70
|
-
with patch("
|
|
71
|
+
with patch("youtrack_cli.articles.get_client_manager") as mock_get_client:
|
|
72
|
+
mock_client_manager = Mock()
|
|
71
73
|
mock_resp = Mock()
|
|
72
74
|
mock_resp.status_code = 400
|
|
73
75
|
mock_resp.text = "Bad Request"
|
|
74
|
-
|
|
76
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
77
|
+
mock_get_client.return_value = mock_client_manager
|
|
75
78
|
|
|
76
79
|
result = await article_manager.create_article(
|
|
77
80
|
title="Test Article",
|
|
@@ -97,14 +100,15 @@ class TestArticleManager:
|
|
|
97
100
|
},
|
|
98
101
|
]
|
|
99
102
|
|
|
100
|
-
with patch("
|
|
103
|
+
with patch("youtrack_cli.articles.get_client_manager") as mock_get_client:
|
|
104
|
+
mock_client_manager = Mock()
|
|
101
105
|
mock_resp = Mock()
|
|
102
106
|
mock_resp.status_code = 200
|
|
103
107
|
mock_resp.json.return_value = mock_response
|
|
104
108
|
mock_resp.text = '[{"id": "123", "summary": "Article 1"}]'
|
|
105
109
|
mock_resp.headers = {"content-type": "application/json"}
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
111
|
+
mock_get_client.return_value = mock_client_manager
|
|
108
112
|
|
|
109
113
|
result = await article_manager.list_articles()
|
|
110
114
|
|
|
@@ -121,14 +125,15 @@ class TestArticleManager:
|
|
|
121
125
|
"content": "Test content",
|
|
122
126
|
}
|
|
123
127
|
|
|
124
|
-
with patch("
|
|
128
|
+
with patch("youtrack_cli.articles.get_client_manager") as mock_get_client:
|
|
129
|
+
mock_client_manager = Mock()
|
|
125
130
|
mock_resp = Mock()
|
|
126
131
|
mock_resp.status_code = 200
|
|
127
132
|
mock_resp.json.return_value = mock_response
|
|
128
133
|
mock_resp.text = '{"mock": "response"}'
|
|
129
134
|
mock_resp.headers = {"content-type": "application/json"}
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
136
|
+
mock_get_client.return_value = mock_client_manager
|
|
132
137
|
|
|
133
138
|
result = await article_manager.get_article("123")
|
|
134
139
|
|
|
@@ -144,14 +149,15 @@ class TestArticleManager:
|
|
|
144
149
|
"content": "Updated content",
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
with patch("
|
|
152
|
+
with patch("youtrack_cli.articles.get_client_manager") as mock_get_client:
|
|
153
|
+
mock_client_manager = Mock()
|
|
148
154
|
mock_resp = Mock()
|
|
149
155
|
mock_resp.status_code = 200
|
|
150
156
|
mock_resp.json.return_value = mock_response
|
|
151
157
|
mock_resp.text = '{"mock": "response"}'
|
|
152
158
|
mock_resp.headers = {"content-type": "application/json"}
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
160
|
+
mock_get_client.return_value = mock_client_manager
|
|
155
161
|
|
|
156
162
|
result = await article_manager.update_article(
|
|
157
163
|
article_id="123",
|
|
@@ -174,11 +180,12 @@ class TestArticleManager:
|
|
|
174
180
|
@pytest.mark.asyncio
|
|
175
181
|
async def test_delete_article_success(self, article_manager):
|
|
176
182
|
"""Test successful article deletion."""
|
|
177
|
-
with patch("
|
|
183
|
+
with patch("youtrack_cli.articles.get_client_manager") as mock_get_client:
|
|
184
|
+
mock_client_manager = Mock()
|
|
178
185
|
mock_resp = Mock()
|
|
179
186
|
mock_resp.status_code = 200
|
|
180
|
-
|
|
181
|
-
|
|
187
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
188
|
+
mock_get_client.return_value = mock_client_manager
|
|
182
189
|
|
|
183
190
|
result = await article_manager.delete_article("123")
|
|
184
191
|
|
|
@@ -194,14 +201,15 @@ class TestArticleManager:
|
|
|
194
201
|
"visibility": {"type": "public"},
|
|
195
202
|
}
|
|
196
203
|
|
|
197
|
-
with patch("
|
|
204
|
+
with patch("youtrack_cli.articles.get_client_manager") as mock_get_client:
|
|
205
|
+
mock_client_manager = Mock()
|
|
198
206
|
mock_resp = Mock()
|
|
199
207
|
mock_resp.status_code = 200
|
|
200
208
|
mock_resp.json.return_value = mock_response
|
|
201
209
|
mock_resp.text = '{"mock": "response"}'
|
|
202
210
|
mock_resp.headers = {"content-type": "application/json"}
|
|
203
|
-
|
|
204
|
-
|
|
211
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
212
|
+
mock_get_client.return_value = mock_client_manager
|
|
205
213
|
|
|
206
214
|
result = await article_manager.publish_article("123")
|
|
207
215
|
|
|
@@ -220,14 +228,15 @@ class TestArticleManager:
|
|
|
220
228
|
}
|
|
221
229
|
]
|
|
222
230
|
|
|
223
|
-
with patch("
|
|
231
|
+
with patch("youtrack_cli.articles.get_client_manager") as mock_get_client:
|
|
232
|
+
mock_client_manager = Mock()
|
|
224
233
|
mock_resp = Mock()
|
|
225
234
|
mock_resp.status_code = 200
|
|
226
235
|
mock_resp.json.return_value = mock_response
|
|
227
236
|
mock_resp.text = '{"mock": "response"}'
|
|
228
237
|
mock_resp.headers = {"content-type": "application/json"}
|
|
229
|
-
|
|
230
|
-
|
|
238
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
239
|
+
mock_get_client.return_value = mock_client_manager
|
|
231
240
|
|
|
232
241
|
result = await article_manager.search_articles("search query")
|
|
233
242
|
|
|
@@ -246,14 +255,15 @@ class TestArticleManager:
|
|
|
246
255
|
}
|
|
247
256
|
]
|
|
248
257
|
|
|
249
|
-
with patch("
|
|
258
|
+
with patch("youtrack_cli.articles.get_client_manager") as mock_get_client:
|
|
259
|
+
mock_client_manager = Mock()
|
|
250
260
|
mock_resp = Mock()
|
|
251
261
|
mock_resp.status_code = 200
|
|
252
262
|
mock_resp.json.return_value = mock_response
|
|
253
263
|
mock_resp.text = '{"mock": "response"}'
|
|
254
264
|
mock_resp.headers = {"content-type": "application/json"}
|
|
255
|
-
|
|
256
|
-
|
|
265
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
266
|
+
mock_get_client.return_value = mock_client_manager
|
|
257
267
|
|
|
258
268
|
result = await article_manager.get_article_comments("123")
|
|
259
269
|
|
|
@@ -269,14 +279,15 @@ class TestArticleManager:
|
|
|
269
279
|
"author": {"fullName": "Test User"},
|
|
270
280
|
}
|
|
271
281
|
|
|
272
|
-
with patch("
|
|
282
|
+
with patch("youtrack_cli.articles.get_client_manager") as mock_get_client:
|
|
283
|
+
mock_client_manager = Mock()
|
|
273
284
|
mock_resp = Mock()
|
|
274
285
|
mock_resp.status_code = 200
|
|
275
286
|
mock_resp.json.return_value = mock_response
|
|
276
287
|
mock_resp.text = '{"mock": "response"}'
|
|
277
288
|
mock_resp.headers = {"content-type": "application/json"}
|
|
278
|
-
|
|
279
|
-
|
|
289
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
290
|
+
mock_get_client.return_value = mock_client_manager
|
|
280
291
|
|
|
281
292
|
result = await article_manager.add_comment("123", "Test comment")
|
|
282
293
|
|
|
@@ -296,14 +307,15 @@ class TestArticleManager:
|
|
|
296
307
|
}
|
|
297
308
|
]
|
|
298
309
|
|
|
299
|
-
with patch("
|
|
310
|
+
with patch("youtrack_cli.articles.get_client_manager") as mock_get_client:
|
|
311
|
+
mock_client_manager = Mock()
|
|
300
312
|
mock_resp = Mock()
|
|
301
313
|
mock_resp.status_code = 200
|
|
302
314
|
mock_resp.json.return_value = mock_response
|
|
303
315
|
mock_resp.text = '{"mock": "response"}'
|
|
304
316
|
mock_resp.headers = {"content-type": "application/json"}
|
|
305
|
-
|
|
306
|
-
|
|
317
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
318
|
+
mock_get_client.return_value = mock_client_manager
|
|
307
319
|
|
|
308
320
|
result = await article_manager.get_article_attachments("123")
|
|
309
321
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Tests for board management functionality."""
|
|
2
2
|
|
|
3
|
-
from unittest.mock import MagicMock, Mock, patch
|
|
3
|
+
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
@@ -47,15 +47,16 @@ class TestBoardManager:
|
|
|
47
47
|
}
|
|
48
48
|
]
|
|
49
49
|
|
|
50
|
-
with patch("
|
|
50
|
+
with patch("youtrack_cli.boards.get_client_manager") as mock_get_client_manager:
|
|
51
51
|
mock_resp = Mock()
|
|
52
52
|
mock_resp.status_code = 200
|
|
53
53
|
mock_resp.json.return_value = mock_response
|
|
54
54
|
mock_resp.text = '{"mock": "response"}'
|
|
55
55
|
mock_resp.headers = {"content-type": "application/json"}
|
|
56
|
-
mock_resp.raise_for_status.return_value = None
|
|
57
56
|
|
|
58
|
-
|
|
57
|
+
mock_client_manager = Mock()
|
|
58
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
59
|
+
mock_get_client_manager.return_value = mock_client_manager
|
|
59
60
|
|
|
60
61
|
result = await board_manager.list_boards()
|
|
61
62
|
|
|
@@ -74,15 +75,16 @@ class TestBoardManager:
|
|
|
74
75
|
}
|
|
75
76
|
]
|
|
76
77
|
|
|
77
|
-
with patch("
|
|
78
|
+
with patch("youtrack_cli.boards.get_client_manager") as mock_get_client_manager:
|
|
78
79
|
mock_resp = Mock()
|
|
79
80
|
mock_resp.status_code = 200
|
|
80
81
|
mock_resp.json.return_value = mock_response
|
|
81
82
|
mock_resp.text = '{"mock": "response"}'
|
|
82
83
|
mock_resp.headers = {"content-type": "application/json"}
|
|
83
|
-
mock_resp.raise_for_status.return_value = None
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
mock_client_manager = Mock()
|
|
86
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
87
|
+
mock_get_client_manager.return_value = mock_client_manager
|
|
86
88
|
|
|
87
89
|
result = await board_manager.list_boards(project_id="TEST")
|
|
88
90
|
|
|
@@ -112,15 +114,16 @@ class TestBoardManager:
|
|
|
112
114
|
"sprints": [{"name": "Sprint 1"}],
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
with patch("
|
|
117
|
+
with patch("youtrack_cli.boards.get_client_manager") as mock_get_client_manager:
|
|
116
118
|
mock_resp = Mock()
|
|
117
119
|
mock_resp.status_code = 200
|
|
118
120
|
mock_resp.json.return_value = mock_response
|
|
119
121
|
mock_resp.text = '{"mock": "response"}'
|
|
120
122
|
mock_resp.headers = {"content-type": "application/json"}
|
|
121
|
-
mock_resp.raise_for_status.return_value = None
|
|
122
123
|
|
|
123
|
-
|
|
124
|
+
mock_client_manager = Mock()
|
|
125
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
126
|
+
mock_get_client_manager.return_value = mock_client_manager
|
|
124
127
|
|
|
125
128
|
result = await board_manager.view_board("123")
|
|
126
129
|
|
|
@@ -147,15 +150,16 @@ class TestBoardManager:
|
|
|
147
150
|
"owner": {"name": "Test User"},
|
|
148
151
|
}
|
|
149
152
|
|
|
150
|
-
with patch("
|
|
153
|
+
with patch("youtrack_cli.boards.get_client_manager") as mock_get_client_manager:
|
|
151
154
|
mock_resp = Mock()
|
|
152
155
|
mock_resp.status_code = 200
|
|
153
156
|
mock_resp.json.return_value = mock_response
|
|
154
157
|
mock_resp.text = '{"mock": "response"}'
|
|
155
158
|
mock_resp.headers = {"content-type": "application/json"}
|
|
156
|
-
mock_resp.raise_for_status.return_value = None
|
|
157
159
|
|
|
158
|
-
|
|
160
|
+
mock_client_manager = Mock()
|
|
161
|
+
mock_client_manager.make_request = AsyncMock(return_value=mock_resp)
|
|
162
|
+
mock_get_client_manager.return_value = mock_client_manager
|
|
159
163
|
|
|
160
164
|
result = await board_manager.update_board("123", name="Updated Board Name")
|
|
161
165
|
|
|
@@ -184,8 +188,10 @@ class TestBoardManager:
|
|
|
184
188
|
@pytest.mark.asyncio
|
|
185
189
|
async def test_list_boards_general_error(self, board_manager):
|
|
186
190
|
"""Test board listing with general error."""
|
|
187
|
-
with patch("
|
|
188
|
-
|
|
191
|
+
with patch("youtrack_cli.boards.get_client_manager") as mock_get_client_manager:
|
|
192
|
+
mock_client_manager = Mock()
|
|
193
|
+
mock_client_manager.make_request = AsyncMock(side_effect=Exception("Connection error"))
|
|
194
|
+
mock_get_client_manager.return_value = mock_client_manager
|
|
189
195
|
|
|
190
196
|
result = await board_manager.list_boards()
|
|
191
197
|
|
|
@@ -195,8 +201,10 @@ class TestBoardManager:
|
|
|
195
201
|
@pytest.mark.asyncio
|
|
196
202
|
async def test_view_board_general_error(self, board_manager):
|
|
197
203
|
"""Test board viewing with general error."""
|
|
198
|
-
with patch("
|
|
199
|
-
|
|
204
|
+
with patch("youtrack_cli.boards.get_client_manager") as mock_get_client_manager:
|
|
205
|
+
mock_client_manager = Mock()
|
|
206
|
+
mock_client_manager.make_request = AsyncMock(side_effect=Exception("Connection error"))
|
|
207
|
+
mock_get_client_manager.return_value = mock_client_manager
|
|
200
208
|
|
|
201
209
|
result = await board_manager.view_board("123")
|
|
202
210
|
|
|
@@ -206,8 +214,10 @@ class TestBoardManager:
|
|
|
206
214
|
@pytest.mark.asyncio
|
|
207
215
|
async def test_update_board_general_error(self, board_manager):
|
|
208
216
|
"""Test board updating with general error."""
|
|
209
|
-
with patch("
|
|
210
|
-
|
|
217
|
+
with patch("youtrack_cli.boards.get_client_manager") as mock_get_client_manager:
|
|
218
|
+
mock_client_manager = Mock()
|
|
219
|
+
mock_client_manager.make_request = AsyncMock(side_effect=Exception("Connection error"))
|
|
220
|
+
mock_get_client_manager.return_value = mock_client_manager
|
|
211
221
|
|
|
212
222
|
result = await board_manager.update_board("123", name="New Name")
|
|
213
223
|
|