@pennyfarthing/core 7.8.1 → 7.8.2
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.
- package/package.json +2 -1
- package/pennyfarthing-dist/scripts/core/prime.sh +8 -0
- package/pennyfarthing_scripts/__init__.py +17 -0
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bellmode_hook.py +154 -0
- package/pennyfarthing_scripts/brownfield/__init__.py +35 -0
- package/pennyfarthing_scripts/brownfield/__main__.py +7 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/cli.py +131 -0
- package/pennyfarthing_scripts/brownfield/discover.py +753 -0
- package/pennyfarthing_scripts/common/__init__.py +49 -0
- package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/config.py +65 -0
- package/pennyfarthing_scripts/common/output.py +180 -0
- package/pennyfarthing_scripts/config.py +21 -0
- package/pennyfarthing_scripts/git/__init__.py +29 -0
- package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/create_branches.py +439 -0
- package/pennyfarthing_scripts/git/status_all.py +310 -0
- package/pennyfarthing_scripts/hooks.py +455 -0
- package/pennyfarthing_scripts/jira/__init__.py +93 -0
- package/pennyfarthing_scripts/jira/__main__.py +10 -0
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/bidirectional.py +561 -0
- package/pennyfarthing_scripts/jira/claim.py +211 -0
- package/pennyfarthing_scripts/jira/cli.py +150 -0
- package/pennyfarthing_scripts/jira/client.py +613 -0
- package/pennyfarthing_scripts/jira/epic.py +176 -0
- package/pennyfarthing_scripts/jira/story.py +219 -0
- package/pennyfarthing_scripts/jira/sync.py +350 -0
- package/pennyfarthing_scripts/jira_bidirectional_sync.py +37 -0
- package/pennyfarthing_scripts/jira_epic_creation.py +30 -0
- package/pennyfarthing_scripts/jira_sync.py +36 -0
- package/pennyfarthing_scripts/jira_sync_story.py +30 -0
- package/pennyfarthing_scripts/output.py +37 -0
- package/pennyfarthing_scripts/preflight/__init__.py +17 -0
- package/pennyfarthing_scripts/preflight/__main__.py +10 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/cli.py +141 -0
- package/pennyfarthing_scripts/preflight/finish.py +382 -0
- package/pennyfarthing_scripts/pretooluse_hook.py +142 -0
- package/pennyfarthing_scripts/prime/__init__.py +38 -0
- package/pennyfarthing_scripts/prime/__main__.py +8 -0
- package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/cli.py +220 -0
- package/pennyfarthing_scripts/prime/loader.py +239 -0
- package/pennyfarthing_scripts/sprint/__init__.py +66 -0
- package/pennyfarthing_scripts/sprint/__main__.py +10 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/archive.py +108 -0
- package/pennyfarthing_scripts/sprint/cli.py +124 -0
- package/pennyfarthing_scripts/sprint/loader.py +193 -0
- package/pennyfarthing_scripts/sprint/status.py +122 -0
- package/pennyfarthing_scripts/sprint/validator.py +405 -0
- package/pennyfarthing_scripts/sprint/work.py +192 -0
- package/pennyfarthing_scripts/story/__init__.py +67 -0
- package/pennyfarthing_scripts/story/__main__.py +10 -0
- package/pennyfarthing_scripts/story/cli.py +105 -0
- package/pennyfarthing_scripts/story/create.py +167 -0
- package/pennyfarthing_scripts/story/size.py +113 -0
- package/pennyfarthing_scripts/story/template.py +151 -0
- package/pennyfarthing_scripts/swebench.py +216 -0
- package/pennyfarthing_scripts/tests/__init__.py +1 -0
- package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/conftest.py +106 -0
- package/pennyfarthing_scripts/tests/test_brownfield.py +842 -0
- package/pennyfarthing_scripts/tests/test_cli_modules.py +245 -0
- package/pennyfarthing_scripts/tests/test_common.py +180 -0
- package/pennyfarthing_scripts/tests/test_git_utils.py +866 -0
- package/pennyfarthing_scripts/tests/test_jira_package.py +334 -0
- package/pennyfarthing_scripts/tests/test_package_structure.py +372 -0
- package/pennyfarthing_scripts/tests/test_prime.py +397 -0
- package/pennyfarthing_scripts/tests/test_sprint_package.py +236 -0
- package/pennyfarthing_scripts/tests/test_sprint_validator.py +675 -0
- package/pennyfarthing_scripts/tests/test_story_package.py +156 -0
- package/pennyfarthing_scripts/welcome_hook.py +157 -0
- package/pennyfarthing_scripts/workflow.py +183 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""Tests for jira/ library package.
|
|
2
|
+
|
|
3
|
+
Story 63-9: Reorganize pennyfarthing_scripts into fan-out CLI pattern.
|
|
4
|
+
|
|
5
|
+
These tests verify the jira/ package modules work correctly
|
|
6
|
+
after reorganization from flat modules.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
from unittest.mock import MagicMock, patch
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestJiraClient:
|
|
16
|
+
"""Tests for jira/client.py JiraClient class."""
|
|
17
|
+
|
|
18
|
+
def test_jira_client_initialization(self) -> None:
|
|
19
|
+
"""JiraClient should initialize with default or custom values."""
|
|
20
|
+
from pennyfarthing_scripts.jira.client import JiraClient
|
|
21
|
+
|
|
22
|
+
# Default initialization
|
|
23
|
+
client = JiraClient()
|
|
24
|
+
assert client.base_url is not None
|
|
25
|
+
assert client.user is not None
|
|
26
|
+
|
|
27
|
+
# Custom initialization
|
|
28
|
+
client = JiraClient(
|
|
29
|
+
base_url="https://custom.atlassian.net",
|
|
30
|
+
user="test@example.com",
|
|
31
|
+
token="test-token",
|
|
32
|
+
)
|
|
33
|
+
assert client.base_url == "https://custom.atlassian.net"
|
|
34
|
+
assert client.user == "test@example.com"
|
|
35
|
+
assert client.token == "test-token"
|
|
36
|
+
|
|
37
|
+
def test_get_auth_header_returns_basic_auth(self) -> None:
|
|
38
|
+
"""_get_auth_header should return Basic auth header."""
|
|
39
|
+
from pennyfarthing_scripts.jira.client import JiraClient
|
|
40
|
+
|
|
41
|
+
client = JiraClient(
|
|
42
|
+
user="user@example.com",
|
|
43
|
+
token="my-token",
|
|
44
|
+
)
|
|
45
|
+
headers = client._get_auth_header()
|
|
46
|
+
|
|
47
|
+
assert "Authorization" in headers
|
|
48
|
+
assert headers["Authorization"].startswith("Basic ")
|
|
49
|
+
|
|
50
|
+
def test_get_auth_header_empty_without_token(self) -> None:
|
|
51
|
+
"""_get_auth_header should return empty dict without token."""
|
|
52
|
+
from pennyfarthing_scripts.jira.client import JiraClient
|
|
53
|
+
|
|
54
|
+
client = JiraClient(token="")
|
|
55
|
+
headers = client._get_auth_header()
|
|
56
|
+
|
|
57
|
+
assert headers == {}
|
|
58
|
+
|
|
59
|
+
def test_get_headers_includes_accept(self) -> None:
|
|
60
|
+
"""_get_headers should include Accept header."""
|
|
61
|
+
from pennyfarthing_scripts.jira.client import JiraClient
|
|
62
|
+
|
|
63
|
+
client = JiraClient(token="test")
|
|
64
|
+
headers = client._get_headers()
|
|
65
|
+
|
|
66
|
+
assert "Accept" in headers
|
|
67
|
+
assert headers["Accept"] == "application/json"
|
|
68
|
+
|
|
69
|
+
def test_get_headers_includes_content_type_when_requested(self) -> None:
|
|
70
|
+
"""_get_headers should include Content-Type when content_type=True."""
|
|
71
|
+
from pennyfarthing_scripts.jira.client import JiraClient
|
|
72
|
+
|
|
73
|
+
client = JiraClient(token="test")
|
|
74
|
+
headers = client._get_headers(content_type=True)
|
|
75
|
+
|
|
76
|
+
assert "Content-Type" in headers
|
|
77
|
+
assert headers["Content-Type"] == "application/json"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestStatusMappings:
|
|
81
|
+
"""Tests for status mapping functions in jira/client.py."""
|
|
82
|
+
|
|
83
|
+
def test_map_status_to_jira_known_status(self) -> None:
|
|
84
|
+
"""map_status_to_jira should map known statuses correctly."""
|
|
85
|
+
from pennyfarthing_scripts.jira.client import map_status_to_jira
|
|
86
|
+
|
|
87
|
+
assert map_status_to_jira("backlog") == "To Do"
|
|
88
|
+
assert map_status_to_jira("in-progress") == "In Progress"
|
|
89
|
+
assert map_status_to_jira("in_progress") == "In Progress"
|
|
90
|
+
assert map_status_to_jira("done") == "Done"
|
|
91
|
+
assert map_status_to_jira("review") == "In Review"
|
|
92
|
+
|
|
93
|
+
def test_map_status_to_jira_unknown_defaults_to_todo(self) -> None:
|
|
94
|
+
"""map_status_to_jira should default to 'To Do' for unknown."""
|
|
95
|
+
from pennyfarthing_scripts.jira.client import map_status_to_jira
|
|
96
|
+
|
|
97
|
+
assert map_status_to_jira("unknown_status") == "To Do"
|
|
98
|
+
assert map_status_to_jira(None) == "To Do"
|
|
99
|
+
|
|
100
|
+
def test_map_jira_to_status_known_status(self) -> None:
|
|
101
|
+
"""map_jira_to_status should map known Jira statuses correctly."""
|
|
102
|
+
from pennyfarthing_scripts.jira.client import map_jira_to_status
|
|
103
|
+
|
|
104
|
+
assert map_jira_to_status("To Do") == "backlog"
|
|
105
|
+
assert map_jira_to_status("In Progress") == "in_progress"
|
|
106
|
+
assert map_jira_to_status("Done") == "done"
|
|
107
|
+
assert map_jira_to_status("In Review") == "review"
|
|
108
|
+
|
|
109
|
+
def test_map_jira_to_status_unknown_defaults_to_backlog(self) -> None:
|
|
110
|
+
"""map_jira_to_status should default to 'backlog' for unknown."""
|
|
111
|
+
from pennyfarthing_scripts.jira.client import map_jira_to_status
|
|
112
|
+
|
|
113
|
+
assert map_jira_to_status("Unknown Status") == "backlog"
|
|
114
|
+
assert map_jira_to_status(None) == "backlog"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class TestExtractJiraKey:
|
|
118
|
+
"""Tests for extract_jira_key function."""
|
|
119
|
+
|
|
120
|
+
def test_extract_jira_key_from_key(self) -> None:
|
|
121
|
+
"""extract_jira_key should return key as-is if already a key."""
|
|
122
|
+
from pennyfarthing_scripts.jira.client import extract_jira_key
|
|
123
|
+
|
|
124
|
+
assert extract_jira_key("MSSCI-12345") == "MSSCI-12345"
|
|
125
|
+
|
|
126
|
+
def test_extract_jira_key_from_url(self) -> None:
|
|
127
|
+
"""extract_jira_key should extract key from URL."""
|
|
128
|
+
from pennyfarthing_scripts.jira.client import extract_jira_key
|
|
129
|
+
|
|
130
|
+
url = "https://1898andco.atlassian.net/browse/MSSCI-12345"
|
|
131
|
+
assert extract_jira_key(url) == "MSSCI-12345"
|
|
132
|
+
|
|
133
|
+
def test_extract_jira_key_returns_none_for_none(self) -> None:
|
|
134
|
+
"""extract_jira_key should return None for None input."""
|
|
135
|
+
from pennyfarthing_scripts.jira.client import extract_jira_key
|
|
136
|
+
|
|
137
|
+
assert extract_jira_key(None) is None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class TestGetJiraField:
|
|
141
|
+
"""Tests for get_jira_field helper function."""
|
|
142
|
+
|
|
143
|
+
def test_get_jira_field_simple_path(self) -> None:
|
|
144
|
+
"""get_jira_field should extract simple field paths."""
|
|
145
|
+
from pennyfarthing_scripts.jira.client import get_jira_field
|
|
146
|
+
|
|
147
|
+
issue = {"key": "MSSCI-123", "id": "10001"}
|
|
148
|
+
assert get_jira_field(issue, "key") == "MSSCI-123"
|
|
149
|
+
|
|
150
|
+
def test_get_jira_field_nested_path(self) -> None:
|
|
151
|
+
"""get_jira_field should extract nested field paths."""
|
|
152
|
+
from pennyfarthing_scripts.jira.client import get_jira_field
|
|
153
|
+
|
|
154
|
+
issue = {
|
|
155
|
+
"fields": {
|
|
156
|
+
"status": {"name": "In Progress"},
|
|
157
|
+
"customfield_10031": 5,
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
assert get_jira_field(issue, "fields.status.name") == "In Progress"
|
|
161
|
+
assert get_jira_field(issue, "fields.customfield_10031") == 5
|
|
162
|
+
|
|
163
|
+
def test_get_jira_field_returns_default_if_missing(self) -> None:
|
|
164
|
+
"""get_jira_field should return default for missing paths."""
|
|
165
|
+
from pennyfarthing_scripts.jira.client import get_jira_field
|
|
166
|
+
|
|
167
|
+
issue = {"key": "MSSCI-123"}
|
|
168
|
+
assert get_jira_field(issue, "fields.missing", "default") == "default"
|
|
169
|
+
assert get_jira_field(issue, "nonexistent") is None
|
|
170
|
+
|
|
171
|
+
def test_get_jira_field_handles_none_input(self) -> None:
|
|
172
|
+
"""get_jira_field should handle None issue input."""
|
|
173
|
+
from pennyfarthing_scripts.jira.client import get_jira_field
|
|
174
|
+
|
|
175
|
+
assert get_jira_field(None, "key", "default") == "default"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class TestJiraSyncModule:
|
|
179
|
+
"""Tests for jira/sync.py module."""
|
|
180
|
+
|
|
181
|
+
def test_sync_result_dataclass(self) -> None:
|
|
182
|
+
"""SyncResult should be a valid dataclass."""
|
|
183
|
+
from pennyfarthing_scripts.jira.sync import SyncResult
|
|
184
|
+
|
|
185
|
+
result = SyncResult(
|
|
186
|
+
story_id="63-1",
|
|
187
|
+
success=True,
|
|
188
|
+
skipped=False,
|
|
189
|
+
error=None,
|
|
190
|
+
actions=["transitioned"],
|
|
191
|
+
dry_run=False,
|
|
192
|
+
)
|
|
193
|
+
assert result.story_id == "63-1"
|
|
194
|
+
assert result.success is True
|
|
195
|
+
assert "transitioned" in result.actions
|
|
196
|
+
|
|
197
|
+
def test_format_story_line(self) -> None:
|
|
198
|
+
"""format_story_line should format story for display."""
|
|
199
|
+
from pennyfarthing_scripts.jira.sync import format_story_line
|
|
200
|
+
|
|
201
|
+
story = {"id": "63-1", "title": "Test Story", "status": "in_progress"}
|
|
202
|
+
result = format_story_line(story)
|
|
203
|
+
|
|
204
|
+
assert "63-1" in result
|
|
205
|
+
assert "Test Story" in result
|
|
206
|
+
assert "in_progress" in result
|
|
207
|
+
|
|
208
|
+
def test_format_summary(self) -> None:
|
|
209
|
+
"""format_summary should format sync summary."""
|
|
210
|
+
from pennyfarthing_scripts.jira.sync import format_summary
|
|
211
|
+
|
|
212
|
+
result = format_summary(synced=5, skipped=2, errors=1)
|
|
213
|
+
|
|
214
|
+
assert "5" in result
|
|
215
|
+
assert "2" in result
|
|
216
|
+
assert "1" in result
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class TestJiraBidirectionalModule:
|
|
220
|
+
"""Tests for jira/bidirectional.py module."""
|
|
221
|
+
|
|
222
|
+
def test_sync_change_dataclass(self) -> None:
|
|
223
|
+
"""SyncChange should be a valid dataclass."""
|
|
224
|
+
from pennyfarthing_scripts.jira.bidirectional import SyncChange
|
|
225
|
+
|
|
226
|
+
change = SyncChange(
|
|
227
|
+
key="MSSCI-12345",
|
|
228
|
+
field="status",
|
|
229
|
+
action="update-jira",
|
|
230
|
+
yaml_value="in_progress",
|
|
231
|
+
jira_value="To Do",
|
|
232
|
+
target_value="In Progress",
|
|
233
|
+
)
|
|
234
|
+
assert change.key == "MSSCI-12345"
|
|
235
|
+
assert change.action == "update-jira"
|
|
236
|
+
|
|
237
|
+
def test_sync_plan_dataclass(self) -> None:
|
|
238
|
+
"""SyncPlan should be a valid dataclass."""
|
|
239
|
+
from pennyfarthing_scripts.jira.bidirectional import SyncPlan
|
|
240
|
+
|
|
241
|
+
plan = SyncPlan()
|
|
242
|
+
assert plan.changes == []
|
|
243
|
+
assert plan.yaml_only == []
|
|
244
|
+
assert plan.jira_only == []
|
|
245
|
+
assert plan.both == []
|
|
246
|
+
|
|
247
|
+
def test_generate_sync_plan_empty_inputs(self) -> None:
|
|
248
|
+
"""generate_sync_plan should handle empty inputs."""
|
|
249
|
+
from pennyfarthing_scripts.jira.bidirectional import generate_sync_plan
|
|
250
|
+
|
|
251
|
+
plan = generate_sync_plan([], [], sync_status=True)
|
|
252
|
+
|
|
253
|
+
assert plan.changes == []
|
|
254
|
+
assert plan.yaml_only == []
|
|
255
|
+
assert plan.jira_only == []
|
|
256
|
+
|
|
257
|
+
def test_generate_sync_plan_identifies_yaml_only(self) -> None:
|
|
258
|
+
"""generate_sync_plan should identify stories only in YAML."""
|
|
259
|
+
from pennyfarthing_scripts.jira.bidirectional import generate_sync_plan
|
|
260
|
+
|
|
261
|
+
yaml_stories = [{"id": "63-1", "jira": "MSSCI-12345", "status": "in_progress"}]
|
|
262
|
+
jira_stories: list[dict[str, Any]] = []
|
|
263
|
+
|
|
264
|
+
plan = generate_sync_plan(yaml_stories, jira_stories, sync_status=True)
|
|
265
|
+
|
|
266
|
+
assert "MSSCI-12345" in plan.yaml_only
|
|
267
|
+
|
|
268
|
+
def test_generate_sync_plan_identifies_jira_only(self) -> None:
|
|
269
|
+
"""generate_sync_plan should identify stories only in Jira."""
|
|
270
|
+
from pennyfarthing_scripts.jira.bidirectional import generate_sync_plan
|
|
271
|
+
|
|
272
|
+
yaml_stories: list[dict[str, Any]] = []
|
|
273
|
+
jira_stories = [{"key": "MSSCI-12345", "fields": {"status": {"name": "To Do"}}}]
|
|
274
|
+
|
|
275
|
+
plan = generate_sync_plan(yaml_stories, jira_stories, sync_status=True)
|
|
276
|
+
|
|
277
|
+
assert "MSSCI-12345" in plan.jira_only
|
|
278
|
+
|
|
279
|
+
def test_format_sync_plan(self) -> None:
|
|
280
|
+
"""format_sync_plan should return formatted string."""
|
|
281
|
+
from pennyfarthing_scripts.jira.bidirectional import (
|
|
282
|
+
SyncPlan,
|
|
283
|
+
format_sync_plan,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
plan = SyncPlan(
|
|
287
|
+
yaml_only=["MSSCI-111"],
|
|
288
|
+
jira_only=["MSSCI-222"],
|
|
289
|
+
both=["MSSCI-333"],
|
|
290
|
+
)
|
|
291
|
+
result = format_sync_plan(plan)
|
|
292
|
+
|
|
293
|
+
assert "MSSCI-111" in result
|
|
294
|
+
assert "MSSCI-222" in result
|
|
295
|
+
assert "Sync Plan" in result
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class TestJiraEpicModule:
|
|
299
|
+
"""Tests for jira/epic.py module."""
|
|
300
|
+
|
|
301
|
+
def test_build_epic_payload(self) -> None:
|
|
302
|
+
"""build_epic_payload should create valid Jira API payload."""
|
|
303
|
+
from pennyfarthing_scripts.jira.epic import build_epic_payload
|
|
304
|
+
|
|
305
|
+
epic_data = {"title": "Test Epic", "description": "Epic description"}
|
|
306
|
+
payload = build_epic_payload(epic_data)
|
|
307
|
+
|
|
308
|
+
assert "fields" in payload
|
|
309
|
+
assert payload["fields"]["summary"] == "Test Epic"
|
|
310
|
+
assert payload["fields"]["issuetype"]["name"] == "Epic"
|
|
311
|
+
assert "description" in payload["fields"]
|
|
312
|
+
|
|
313
|
+
def test_create_epic_dry_run(self) -> None:
|
|
314
|
+
"""create_epic with dry_run should not call API."""
|
|
315
|
+
from pennyfarthing_scripts.jira.epic import create_epic
|
|
316
|
+
|
|
317
|
+
result = create_epic("Test Epic", "Description", dry_run=True)
|
|
318
|
+
|
|
319
|
+
assert result["success"] is True
|
|
320
|
+
assert result["dry_run"] is True
|
|
321
|
+
assert "payload" in result
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class TestJiraStoryModule:
|
|
325
|
+
"""Tests for jira/story.py module."""
|
|
326
|
+
|
|
327
|
+
def test_sync_story_missing_story(self) -> None:
|
|
328
|
+
"""sync_story should return error for missing story."""
|
|
329
|
+
from pennyfarthing_scripts.jira.story import sync_story
|
|
330
|
+
|
|
331
|
+
result = sync_story("nonexistent-99", do_transition=False, dry_run=True)
|
|
332
|
+
|
|
333
|
+
assert result["success"] is False
|
|
334
|
+
assert "not found" in result.get("error", "").lower()
|