Skip to content

Commit db3da6f

Browse files
fsilvaortizclaude
andcommitted
test: add tests for create-new-feature.sh JSON output and fetch silencing
Adds 4 tests covering: - Valid JSON output from --json flag - FEATURE_NUM is always a zero-padded numeric string - No git fetch stdout leaks into JSON output - Regression guard: git fetch redirects both stdout and stderr Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b6c6493 commit db3da6f

File tree

1 file changed

+112
-0
lines changed

1 file changed

+112
-0
lines changed

tests/test_create_new_feature.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""
2+
Tests for scripts/bash/create-new-feature.sh.
3+
4+
Tests cover:
5+
- JSON output validity when git fetch produces stdout noise (#1592)
6+
- Correct stdout/stderr suppression in check_existing_branches()
7+
"""
8+
9+
import json
10+
import subprocess
11+
import tempfile
12+
import shutil
13+
from pathlib import Path
14+
15+
import pytest
16+
17+
18+
SCRIPT_PATH = Path(__file__).resolve().parent.parent / "scripts" / "bash" / "create-new-feature.sh"
19+
20+
21+
@pytest.fixture
22+
def git_repo(tmp_path):
23+
"""Create a temporary git repo with a fake remote that produces stdout on fetch."""
24+
repo = tmp_path / "repo"
25+
repo.mkdir()
26+
27+
# Initialize git repo
28+
subprocess.run(["git", "init", str(repo)], capture_output=True, check=True)
29+
subprocess.run(["git", "-C", str(repo), "config", "user.email", "test@test.com"], capture_output=True, check=True)
30+
subprocess.run(["git", "-C", str(repo), "config", "user.name", "Test"], capture_output=True, check=True)
31+
32+
# Create an initial commit so HEAD exists
33+
(repo / "README.md").write_text("# Test")
34+
subprocess.run(["git", "-C", str(repo), "add", "."], capture_output=True, check=True)
35+
subprocess.run(["git", "-C", str(repo), "commit", "-m", "init"], capture_output=True, check=True)
36+
37+
# Create .specify dir to simulate an initialized project
38+
(repo / ".specify").mkdir()
39+
(repo / ".specify" / "templates").mkdir()
40+
41+
yield repo
42+
shutil.rmtree(tmp_path)
43+
44+
45+
class TestCreateNewFeatureJsonOutput:
46+
"""Test that --json output is always valid JSON (#1592)."""
47+
48+
def test_json_output_is_valid(self, git_repo):
49+
"""Script should produce valid JSON with BRANCH_NAME, SPEC_FILE, FEATURE_NUM."""
50+
result = subprocess.run(
51+
["bash", str(SCRIPT_PATH), "--json", "Test feature description"],
52+
capture_output=True,
53+
text=True,
54+
cwd=str(git_repo),
55+
)
56+
57+
assert result.returncode == 0, f"Script failed: {result.stderr}"
58+
59+
output = result.stdout.strip()
60+
parsed = json.loads(output)
61+
62+
assert "BRANCH_NAME" in parsed
63+
assert "SPEC_FILE" in parsed
64+
assert "FEATURE_NUM" in parsed
65+
66+
def test_feature_num_is_numeric(self, git_repo):
67+
"""FEATURE_NUM should be a zero-padded numeric string."""
68+
result = subprocess.run(
69+
["bash", str(SCRIPT_PATH), "--json", "Another feature"],
70+
capture_output=True,
71+
text=True,
72+
cwd=str(git_repo),
73+
)
74+
75+
assert result.returncode == 0, f"Script failed: {result.stderr}"
76+
77+
parsed = json.loads(result.stdout.strip())
78+
feature_num = parsed["FEATURE_NUM"]
79+
80+
assert feature_num.isdigit(), f"FEATURE_NUM is not numeric: {feature_num}"
81+
assert len(feature_num) == 3, f"FEATURE_NUM is not zero-padded: {feature_num}"
82+
83+
84+
class TestGitFetchSilencing:
85+
"""Verify that git fetch stdout does not contaminate branch number detection."""
86+
87+
def test_script_does_not_leak_git_fetch_stdout(self, git_repo):
88+
"""JSON output should contain only the JSON line, no git fetch noise."""
89+
result = subprocess.run(
90+
["bash", str(SCRIPT_PATH), "--json", "Verify no fetch noise"],
91+
capture_output=True,
92+
text=True,
93+
cwd=str(git_repo),
94+
)
95+
96+
assert result.returncode == 0, f"Script failed: {result.stderr}"
97+
98+
stdout_lines = result.stdout.strip().splitlines()
99+
# In JSON mode, stdout should contain exactly one line: the JSON object
100+
json_lines = [line for line in stdout_lines if line.startswith("{")]
101+
assert len(json_lines) == 1, f"Expected 1 JSON line, got {len(json_lines)}: {stdout_lines}"
102+
103+
def test_git_fetch_redirect_pattern_in_script(self):
104+
"""The git fetch call should redirect both stdout and stderr to /dev/null."""
105+
script_content = SCRIPT_PATH.read_text()
106+
107+
assert "git fetch --all --prune >/dev/null 2>&1" in script_content, (
108+
"git fetch should redirect both stdout and stderr: >/dev/null 2>&1"
109+
)
110+
assert "git fetch --all --prune 2>/dev/null" not in script_content, (
111+
"git fetch should NOT redirect only stderr (old pattern)"
112+
)

0 commit comments

Comments
 (0)