@pennyfarthing/core 7.8.1 → 7.8.4
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/README.md +1 -1
- package/package.json +2 -1
- package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/init.js +7 -6
- package/packages/core/dist/cli/commands/init.js.map +1 -1
- package/packages/core/dist/cli/utils/symlinks.d.ts +7 -0
- package/packages/core/dist/cli/utils/symlinks.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/symlinks.js +25 -0
- package/packages/core/dist/cli/utils/symlinks.js.map +1 -1
- package/pennyfarthing-dist/scripts/core/phase-check-start.sh +1 -1
- package/pennyfarthing-dist/scripts/core/prime.sh +23 -0
- package/pennyfarthing-dist/scripts/core/run.sh +5 -5
- package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +2 -2
- package/pennyfarthing-dist/scripts/git/release.sh +2 -2
- package/pennyfarthing-dist/scripts/health/drift-detection.sh +1 -1
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +2 -2
- package/pennyfarthing-dist/scripts/hooks/pre-push.sh +2 -2
- package/pennyfarthing-dist/scripts/hooks/session-stop.sh +1 -1
- package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +1 -1
- package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +1 -1
- package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +1 -1
- package/pennyfarthing-dist/scripts/lib/common.sh +1 -1
- package/pennyfarthing-dist/scripts/lib/find-root.sh +4 -4
- package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +1 -1
- package/pennyfarthing-dist/scripts/misc/add_short_names.py +2 -2
- package/pennyfarthing-dist/scripts/misc/backlog.sh +2 -2
- package/pennyfarthing-dist/scripts/misc/deploy.sh +2 -2
- package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +4 -4
- package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +2 -2
- package/pennyfarthing-dist/scripts/misc/run-ci.sh +1 -1
- package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +2 -2
- package/pennyfarthing-dist/scripts/sprint/archive-story.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/available-stories.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/check-story.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/get-epic-field.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/get-story-field.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/import_epic_to_future.py +2 -2
- package/pennyfarthing-dist/scripts/sprint/list-future.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/new-sprint.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/promote-epic.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/sprint-common.sh +3 -3
- package/pennyfarthing-dist/scripts/sprint/sprint-info.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/sprint-metrics.sh +2 -2
- package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +2 -2
- package/pennyfarthing-dist/scripts/workflow/check.py +2 -2
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +2 -2
- package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +1 -1
- 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,245 @@
|
|
|
1
|
+
"""Tests for CLI entry point modules.
|
|
2
|
+
|
|
3
|
+
Story 63-9: Reorganize pennyfarthing_scripts into fan-out CLI pattern.
|
|
4
|
+
|
|
5
|
+
These tests verify the CLI modules work as entry points and
|
|
6
|
+
properly delegate to library modules.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from typing import Any
|
|
12
|
+
from unittest.mock import MagicMock, patch
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestJiraCLIModule:
|
|
18
|
+
"""Tests for jira CLI module."""
|
|
19
|
+
|
|
20
|
+
def test_jira_cli_help(self) -> None:
|
|
21
|
+
"""jira CLI should show help with --help."""
|
|
22
|
+
result = subprocess.run(
|
|
23
|
+
[sys.executable, "-m", "pennyfarthing_scripts.jira", "--help"],
|
|
24
|
+
capture_output=True,
|
|
25
|
+
text=True,
|
|
26
|
+
timeout=30,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
assert result.returncode == 0
|
|
30
|
+
# Should show usage info
|
|
31
|
+
assert "usage" in result.stdout.lower() or "Usage" in result.stdout
|
|
32
|
+
|
|
33
|
+
def test_jira_cli_view_subcommand(self) -> None:
|
|
34
|
+
"""jira CLI should have view subcommand."""
|
|
35
|
+
result = subprocess.run(
|
|
36
|
+
[sys.executable, "-m", "pennyfarthing_scripts.jira", "view", "--help"],
|
|
37
|
+
capture_output=True,
|
|
38
|
+
text=True,
|
|
39
|
+
timeout=30,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Should exit 0 with help or fail gracefully without --help
|
|
43
|
+
assert result.returncode in (0, 1, 2)
|
|
44
|
+
|
|
45
|
+
def test_jira_cli_sync_subcommand(self) -> None:
|
|
46
|
+
"""jira CLI should have sync subcommand."""
|
|
47
|
+
result = subprocess.run(
|
|
48
|
+
[sys.executable, "-m", "pennyfarthing_scripts.jira", "sync", "--help"],
|
|
49
|
+
capture_output=True,
|
|
50
|
+
text=True,
|
|
51
|
+
timeout=30,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
assert result.returncode in (0, 1, 2)
|
|
55
|
+
|
|
56
|
+
def test_jira_cli_claim_subcommand(self) -> None:
|
|
57
|
+
"""jira CLI should have claim subcommand."""
|
|
58
|
+
result = subprocess.run(
|
|
59
|
+
[sys.executable, "-m", "pennyfarthing_scripts.jira", "claim", "--help"],
|
|
60
|
+
capture_output=True,
|
|
61
|
+
text=True,
|
|
62
|
+
timeout=30,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
assert result.returncode in (0, 1, 2)
|
|
66
|
+
|
|
67
|
+
def test_jira_cli_create_subcommand(self) -> None:
|
|
68
|
+
"""jira CLI should have create subcommand."""
|
|
69
|
+
result = subprocess.run(
|
|
70
|
+
[sys.executable, "-m", "pennyfarthing_scripts.jira", "create", "--help"],
|
|
71
|
+
capture_output=True,
|
|
72
|
+
text=True,
|
|
73
|
+
timeout=30,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
assert result.returncode in (0, 1, 2)
|
|
77
|
+
|
|
78
|
+
def test_jira_cli_bidirectional_subcommand(self) -> None:
|
|
79
|
+
"""jira CLI should have bidirectional subcommand."""
|
|
80
|
+
result = subprocess.run(
|
|
81
|
+
[sys.executable, "-m", "pennyfarthing_scripts.jira", "bidirectional", "--help"],
|
|
82
|
+
capture_output=True,
|
|
83
|
+
text=True,
|
|
84
|
+
timeout=30,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
assert result.returncode in (0, 1, 2)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TestSprintCLIModule:
|
|
91
|
+
"""Tests for sprint CLI module."""
|
|
92
|
+
|
|
93
|
+
def test_sprint_cli_help(self) -> None:
|
|
94
|
+
"""sprint CLI should show help with --help."""
|
|
95
|
+
result = subprocess.run(
|
|
96
|
+
[sys.executable, "-m", "pennyfarthing_scripts.sprint", "--help"],
|
|
97
|
+
capture_output=True,
|
|
98
|
+
text=True,
|
|
99
|
+
timeout=30,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
assert result.returncode == 0
|
|
103
|
+
assert "usage" in result.stdout.lower() or "Usage" in result.stdout
|
|
104
|
+
|
|
105
|
+
def test_sprint_cli_status_subcommand(self) -> None:
|
|
106
|
+
"""sprint CLI should have status subcommand."""
|
|
107
|
+
result = subprocess.run(
|
|
108
|
+
[sys.executable, "-m", "pennyfarthing_scripts.sprint", "status", "--help"],
|
|
109
|
+
capture_output=True,
|
|
110
|
+
text=True,
|
|
111
|
+
timeout=30,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
assert result.returncode in (0, 1, 2)
|
|
115
|
+
|
|
116
|
+
def test_sprint_cli_backlog_subcommand(self) -> None:
|
|
117
|
+
"""sprint CLI should have backlog subcommand."""
|
|
118
|
+
result = subprocess.run(
|
|
119
|
+
[sys.executable, "-m", "pennyfarthing_scripts.sprint", "backlog", "--help"],
|
|
120
|
+
capture_output=True,
|
|
121
|
+
text=True,
|
|
122
|
+
timeout=30,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
assert result.returncode in (0, 1, 2)
|
|
126
|
+
|
|
127
|
+
def test_sprint_cli_work_subcommand(self) -> None:
|
|
128
|
+
"""sprint CLI should have work subcommand."""
|
|
129
|
+
result = subprocess.run(
|
|
130
|
+
[sys.executable, "-m", "pennyfarthing_scripts.sprint", "work", "--help"],
|
|
131
|
+
capture_output=True,
|
|
132
|
+
text=True,
|
|
133
|
+
timeout=30,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
assert result.returncode in (0, 1, 2)
|
|
137
|
+
|
|
138
|
+
def test_sprint_cli_archive_subcommand(self) -> None:
|
|
139
|
+
"""sprint CLI should have archive subcommand."""
|
|
140
|
+
result = subprocess.run(
|
|
141
|
+
[sys.executable, "-m", "pennyfarthing_scripts.sprint", "archive", "--help"],
|
|
142
|
+
capture_output=True,
|
|
143
|
+
text=True,
|
|
144
|
+
timeout=30,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
assert result.returncode in (0, 1, 2)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TestStoryCLIModule:
|
|
151
|
+
"""Tests for story CLI module."""
|
|
152
|
+
|
|
153
|
+
def test_story_cli_help(self) -> None:
|
|
154
|
+
"""story CLI should show help with --help."""
|
|
155
|
+
result = subprocess.run(
|
|
156
|
+
[sys.executable, "-m", "pennyfarthing_scripts.story", "--help"],
|
|
157
|
+
capture_output=True,
|
|
158
|
+
text=True,
|
|
159
|
+
timeout=30,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
assert result.returncode == 0
|
|
163
|
+
assert "usage" in result.stdout.lower() or "Usage" in result.stdout
|
|
164
|
+
|
|
165
|
+
def test_story_cli_size_subcommand(self) -> None:
|
|
166
|
+
"""story CLI should have size subcommand."""
|
|
167
|
+
result = subprocess.run(
|
|
168
|
+
[sys.executable, "-m", "pennyfarthing_scripts.story", "size", "--help"],
|
|
169
|
+
capture_output=True,
|
|
170
|
+
text=True,
|
|
171
|
+
timeout=30,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
assert result.returncode in (0, 1, 2)
|
|
175
|
+
|
|
176
|
+
def test_story_cli_template_subcommand(self) -> None:
|
|
177
|
+
"""story CLI should have template subcommand."""
|
|
178
|
+
result = subprocess.run(
|
|
179
|
+
[sys.executable, "-m", "pennyfarthing_scripts.story", "template", "--help"],
|
|
180
|
+
capture_output=True,
|
|
181
|
+
text=True,
|
|
182
|
+
timeout=30,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
assert result.returncode in (0, 1, 2)
|
|
186
|
+
|
|
187
|
+
def test_story_cli_create_subcommand(self) -> None:
|
|
188
|
+
"""story CLI should have create subcommand."""
|
|
189
|
+
result = subprocess.run(
|
|
190
|
+
[sys.executable, "-m", "pennyfarthing_scripts.story", "create", "--help"],
|
|
191
|
+
capture_output=True,
|
|
192
|
+
text=True,
|
|
193
|
+
timeout=30,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
assert result.returncode in (0, 1, 2)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class TestOldCLICompatibility:
|
|
200
|
+
"""Tests for backwards compatibility of old CLI modules."""
|
|
201
|
+
|
|
202
|
+
def test_jira_sync_module_runnable(self) -> None:
|
|
203
|
+
"""jira_sync.py should still be runnable as module."""
|
|
204
|
+
result = subprocess.run(
|
|
205
|
+
[sys.executable, "-m", "pennyfarthing_scripts.jira_sync", "--help"],
|
|
206
|
+
capture_output=True,
|
|
207
|
+
text=True,
|
|
208
|
+
timeout=30,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Should exit with help or error (not crash)
|
|
212
|
+
assert result.returncode in (0, 1, 2)
|
|
213
|
+
|
|
214
|
+
def test_jira_bidirectional_sync_module_runnable(self) -> None:
|
|
215
|
+
"""jira_bidirectional_sync.py should still be runnable."""
|
|
216
|
+
result = subprocess.run(
|
|
217
|
+
[sys.executable, "-m", "pennyfarthing_scripts.jira_bidirectional_sync", "--help"],
|
|
218
|
+
capture_output=True,
|
|
219
|
+
text=True,
|
|
220
|
+
timeout=30,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
assert result.returncode in (0, 1, 2)
|
|
224
|
+
|
|
225
|
+
def test_jira_epic_creation_module_runnable(self) -> None:
|
|
226
|
+
"""jira_epic_creation.py should still be runnable."""
|
|
227
|
+
result = subprocess.run(
|
|
228
|
+
[sys.executable, "-m", "pennyfarthing_scripts.jira_epic_creation", "--help"],
|
|
229
|
+
capture_output=True,
|
|
230
|
+
text=True,
|
|
231
|
+
timeout=30,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
assert result.returncode in (0, 1, 2)
|
|
235
|
+
|
|
236
|
+
def test_jira_sync_story_module_runnable(self) -> None:
|
|
237
|
+
"""jira_sync_story.py should still be runnable."""
|
|
238
|
+
result = subprocess.run(
|
|
239
|
+
[sys.executable, "-m", "pennyfarthing_scripts.jira_sync_story", "--help"],
|
|
240
|
+
capture_output=True,
|
|
241
|
+
text=True,
|
|
242
|
+
timeout=30,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
assert result.returncode in (0, 1, 2)
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Tests for common/ shared utilities package.
|
|
2
|
+
|
|
3
|
+
Story 63-9: Reorganize pennyfarthing_scripts into fan-out CLI pattern.
|
|
4
|
+
|
|
5
|
+
These tests verify the common/ package provides shared utilities
|
|
6
|
+
that work correctly when imported from the new location.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import io
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Generator
|
|
14
|
+
from unittest.mock import patch
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestOutputModule:
|
|
20
|
+
"""Tests for common/output.py module."""
|
|
21
|
+
|
|
22
|
+
def test_success_prints_green_prefix(self) -> None:
|
|
23
|
+
"""success() should print with [OK] prefix."""
|
|
24
|
+
from pennyfarthing_scripts.common import output
|
|
25
|
+
|
|
26
|
+
buffer = io.StringIO()
|
|
27
|
+
# Force color support for testing
|
|
28
|
+
with patch.object(output, "_supports_color", return_value=True):
|
|
29
|
+
output.success("Test message", file=buffer)
|
|
30
|
+
|
|
31
|
+
result = buffer.getvalue()
|
|
32
|
+
assert "[OK]" in result or "\x1b[32m" in result # Green or prefix
|
|
33
|
+
assert "Test message" in result
|
|
34
|
+
|
|
35
|
+
def test_info_prints_blue_prefix(self) -> None:
|
|
36
|
+
"""info() should print with [INFO] prefix."""
|
|
37
|
+
from pennyfarthing_scripts.common import output
|
|
38
|
+
|
|
39
|
+
buffer = io.StringIO()
|
|
40
|
+
output.info("Info message", file=buffer)
|
|
41
|
+
|
|
42
|
+
result = buffer.getvalue()
|
|
43
|
+
assert "INFO" in result or "Info message" in result
|
|
44
|
+
|
|
45
|
+
def test_warn_prints_yellow_prefix(self) -> None:
|
|
46
|
+
"""warn() should print with [WARN] prefix."""
|
|
47
|
+
from pennyfarthing_scripts.common import output
|
|
48
|
+
|
|
49
|
+
buffer = io.StringIO()
|
|
50
|
+
output.warn("Warning message", file=buffer)
|
|
51
|
+
|
|
52
|
+
result = buffer.getvalue()
|
|
53
|
+
assert "WARN" in result or "Warning message" in result
|
|
54
|
+
|
|
55
|
+
def test_error_prints_red_prefix(self) -> None:
|
|
56
|
+
"""error() should print with [ERROR] prefix."""
|
|
57
|
+
from pennyfarthing_scripts.common import output
|
|
58
|
+
|
|
59
|
+
buffer = io.StringIO()
|
|
60
|
+
output.error("Error message", file=buffer)
|
|
61
|
+
|
|
62
|
+
result = buffer.getvalue()
|
|
63
|
+
assert "ERROR" in result or "Error message" in result
|
|
64
|
+
|
|
65
|
+
def test_no_color_env_disables_colors(self) -> None:
|
|
66
|
+
"""NO_COLOR environment variable should disable colors."""
|
|
67
|
+
from pennyfarthing_scripts.common import output
|
|
68
|
+
|
|
69
|
+
with patch.dict(os.environ, {"NO_COLOR": "1"}):
|
|
70
|
+
buffer = io.StringIO()
|
|
71
|
+
output.success("Test", file=buffer)
|
|
72
|
+
result = buffer.getvalue()
|
|
73
|
+
# Should not contain ANSI escape codes
|
|
74
|
+
assert "\x1b[" not in result
|
|
75
|
+
|
|
76
|
+
def test_force_color_env_enables_colors(self) -> None:
|
|
77
|
+
"""FORCE_COLOR environment variable should enable colors."""
|
|
78
|
+
from pennyfarthing_scripts.common import output
|
|
79
|
+
|
|
80
|
+
with patch.dict(os.environ, {"FORCE_COLOR": "1"}, clear=False):
|
|
81
|
+
# Remove NO_COLOR if present
|
|
82
|
+
with patch.dict(os.environ, {"NO_COLOR": ""}, clear=False):
|
|
83
|
+
assert output._supports_color(sys.stderr) is True
|
|
84
|
+
|
|
85
|
+
def test_header_prints_decorated_line(self) -> None:
|
|
86
|
+
"""header() should print decorated header."""
|
|
87
|
+
from pennyfarthing_scripts.common import output
|
|
88
|
+
|
|
89
|
+
buffer = io.StringIO()
|
|
90
|
+
output.header("Test Header", char="=", width=40, file=buffer)
|
|
91
|
+
|
|
92
|
+
result = buffer.getvalue()
|
|
93
|
+
assert "Test Header" in result
|
|
94
|
+
assert "=" * 40 in result
|
|
95
|
+
|
|
96
|
+
def test_divider_prints_line(self) -> None:
|
|
97
|
+
"""divider() should print a line."""
|
|
98
|
+
from pennyfarthing_scripts.common import output
|
|
99
|
+
|
|
100
|
+
buffer = io.StringIO()
|
|
101
|
+
output.divider(char="-", width=20, file=buffer)
|
|
102
|
+
|
|
103
|
+
result = buffer.getvalue()
|
|
104
|
+
assert "-" * 20 in result
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class TestConfigModule:
|
|
108
|
+
"""Tests for common/config.py module."""
|
|
109
|
+
|
|
110
|
+
def test_get_project_root_finds_pennyfarthing_dir(self) -> None:
|
|
111
|
+
"""get_project_root() should find .pennyfarthing directory."""
|
|
112
|
+
from pennyfarthing_scripts.common import config
|
|
113
|
+
|
|
114
|
+
# This test assumes we're running from within the pennyfarthing project
|
|
115
|
+
root = config.get_project_root()
|
|
116
|
+
assert root is not None
|
|
117
|
+
assert (root / ".pennyfarthing").is_dir()
|
|
118
|
+
|
|
119
|
+
def test_get_project_root_raises_if_not_found(self) -> None:
|
|
120
|
+
"""get_project_root() should raise if no .pennyfarthing found."""
|
|
121
|
+
from pennyfarthing_scripts.common import config
|
|
122
|
+
|
|
123
|
+
# Start from root filesystem where there's no .pennyfarthing
|
|
124
|
+
with pytest.raises(FileNotFoundError):
|
|
125
|
+
config.get_project_root(start_dir=Path("/"))
|
|
126
|
+
|
|
127
|
+
def test_load_yaml_config_returns_dict(self) -> None:
|
|
128
|
+
"""load_yaml_config() should return parsed YAML as dict."""
|
|
129
|
+
from pennyfarthing_scripts.common import config
|
|
130
|
+
|
|
131
|
+
# Test with existing sprint file
|
|
132
|
+
root = config.get_project_root()
|
|
133
|
+
sprint_path = root / "sprint" / "current-sprint.yaml"
|
|
134
|
+
|
|
135
|
+
if sprint_path.exists():
|
|
136
|
+
result = config.load_yaml_config(sprint_path)
|
|
137
|
+
assert isinstance(result, dict)
|
|
138
|
+
|
|
139
|
+
def test_load_yaml_config_returns_none_if_missing(self) -> None:
|
|
140
|
+
"""load_yaml_config() should return None for missing files."""
|
|
141
|
+
from pennyfarthing_scripts.common import config
|
|
142
|
+
|
|
143
|
+
result = config.load_yaml_config(Path("/nonexistent/file.yaml"))
|
|
144
|
+
assert result is None
|
|
145
|
+
|
|
146
|
+
def test_find_project_root_alias(self) -> None:
|
|
147
|
+
"""find_project_root should be an alias for get_project_root."""
|
|
148
|
+
from pennyfarthing_scripts.common import config
|
|
149
|
+
|
|
150
|
+
assert config.find_project_root == config.get_project_root
|
|
151
|
+
|
|
152
|
+
def test_load_pennyfarthing_config_returns_dict(self) -> None:
|
|
153
|
+
"""load_pennyfarthing_config() should return config or empty dict."""
|
|
154
|
+
from pennyfarthing_scripts.common import config
|
|
155
|
+
|
|
156
|
+
result = config.load_pennyfarthing_config()
|
|
157
|
+
assert isinstance(result, dict)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class TestColorsClass:
|
|
161
|
+
"""Tests for Colors class with ANSI codes."""
|
|
162
|
+
|
|
163
|
+
def test_colors_has_expected_constants(self) -> None:
|
|
164
|
+
"""Colors class should have standard ANSI color codes."""
|
|
165
|
+
from pennyfarthing_scripts.common.output import Colors
|
|
166
|
+
|
|
167
|
+
assert hasattr(Colors, "RED")
|
|
168
|
+
assert hasattr(Colors, "GREEN")
|
|
169
|
+
assert hasattr(Colors, "YELLOW")
|
|
170
|
+
assert hasattr(Colors, "BLUE")
|
|
171
|
+
assert hasattr(Colors, "RESET")
|
|
172
|
+
assert hasattr(Colors, "BOLD")
|
|
173
|
+
assert hasattr(Colors, "DIM")
|
|
174
|
+
|
|
175
|
+
def test_colors_are_ansi_escape_codes(self) -> None:
|
|
176
|
+
"""Color constants should be ANSI escape sequences."""
|
|
177
|
+
from pennyfarthing_scripts.common.output import Colors
|
|
178
|
+
|
|
179
|
+
assert Colors.RED.startswith("\x1b[")
|
|
180
|
+
assert Colors.RESET == "\x1b[0m"
|