"""CLI command tests."""
from pathlib import Path
from typer.testing import CliRunner
from wassden.clis.core import app
class TestCLICommands:
"""Test CLI command functionality."""
def setup_method(self):
"""Set up test environment."""
self.runner = CliRunner()
def test_app_help(self):
"""Test CLI help command."""
result = self.runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "wassden - MCP-based Spec-Driven Development toolkit" in result.output
assert "prompt-requirements" in result.output
assert "validate-requirements" in result.output
def test_app_version(self):
"""Test CLI version command."""
result = self.runner.invoke(app, ["--version"])
assert result.exit_code == 0
assert "0.1.0" in result.output
def test_prompt_requirements_missing_input(self):
"""Test prompt_requirements command without required input."""
result = self.runner.invoke(app, ["prompt-requirements"])
assert result.exit_code != 0
assert "Missing option" in result.output or "required" in result.output.lower()
def test_prompt_requirements_command(self):
"""Test prompt_requirements command."""
result = self.runner.invoke(app, ["prompt-requirements", "--userInput", "Test project for CLI testing"])
assert result.exit_code == 0
assert "requirements.md" in result.output
assert "EARS format" in result.output
assert "Test project for CLI testing" in result.output
def test_prompt_requirements_with_force_flag(self):
"""Test prompt_requirements with force flag."""
result = self.runner.invoke(
app,
[
"prompt-requirements",
"--userInput",
"Test project",
"--force",
],
)
assert result.exit_code == 0
assert "Test project" in result.output
def test_validate_requirements_file_not_found(self):
"""Test validate_requirements with non-existent file."""
result = self.runner.invoke(app, ["validate-requirements", "/nonexistent/requirements.md"])
assert result.exit_code == 0 # Command succeeds but shows error message
assert "エラー" in result.output
assert "見つかりません" in result.output
def test_validate_requirements_default_path(self):
"""Test validate_requirements with default path."""
result = self.runner.invoke(app, ["validate-requirements"])
assert result.exit_code == 0
# Should show validation success since specs/requirements.md exists
assert "検証に成功しました" in result.output or "エラー" in result.output
def test_prompt_design_command(self):
"""Test prompt_design command."""
# Create temporary requirements file
with self.runner.isolated_filesystem():
Path("specs").mkdir(exist_ok=True)
Path("specs/requirements.md").write_text("""
## 機能要件
- **REQ-01**: Test requirement
""")
result = self.runner.invoke(app, ["prompt-design"])
assert result.exit_code == 0
assert "design.md" in result.output
assert "アーキテクチャ" in result.output
def test_validate_design_command(self):
"""Test validate_design command."""
with self.runner.isolated_filesystem():
Path("specs").mkdir(exist_ok=True)
Path("specs/requirements.md").write_text("""
## 機能要件
- **REQ-01**: Test requirement
""")
Path("specs/design.md").write_text("""
## アーキテクチャ概要
Test architecture [REQ-01]
## コンポーネント設計
Test components
## データ設計
Test data
## API設計
Test API
## 非機能設計
Test NFR
## テスト戦略
Test strategy
""")
result = self.runner.invoke(app, ["validate-design"])
assert result.exit_code == 0
assert "検証" in result.output
def test_prompt_tasks_command(self):
"""Test prompt_tasks command."""
with self.runner.isolated_filesystem():
Path("specs").mkdir(exist_ok=True)
Path("specs/requirements.md").write_text("## 機能要件\n- **REQ-01**: Test")
Path("specs/design.md").write_text("## コンポーネント設計\n- **component**: Test")
result = self.runner.invoke(app, ["prompt-tasks"])
assert result.exit_code == 0
assert "tasks.md" in result.output
assert "WBS" in result.output
def test_validate_tasks_command(self):
"""Test validate_tasks command."""
with self.runner.isolated_filesystem():
Path("specs").mkdir(exist_ok=True)
Path("specs/tasks.md").write_text("""
## 概要
Test overview
## タスク一覧
- **TASK-01-01**: Test task
## 依存関係
Dependencies
## マイルストーン
Milestones
""")
result = self.runner.invoke(app, ["validate-tasks"])
assert result.exit_code == 0
assert "検証" in result.output
def test_prompt_code_command(self):
"""Test prompt_code command."""
with self.runner.isolated_filesystem():
Path("specs").mkdir(exist_ok=True)
Path("specs/requirements.md").write_text("# Requirements\n- **REQ-01**: Test")
Path("specs/design.md").write_text("# Design\n- Component: Test")
Path("specs/tasks.md").write_text("# Tasks\n- **TASK-01-01**: Test")
result = self.runner.invoke(app, ["prompt-code"])
assert result.exit_code == 0
assert "実装" in result.output
assert "TASK-01-01" in result.output
def test_generate_review_prompt_command(self):
"""Test generate_review_prompt command."""
with self.runner.isolated_filesystem():
Path("specs").mkdir(exist_ok=True)
Path("specs/requirements.md").write_text("## 機能要件\n- **REQ-01**: システムは、テストすること")
Path("specs/design.md").write_text("## コンポーネント設計\n- **component**: Test [REQ-01]")
Path("specs/tasks.md").write_text("## タスク一覧\n- **TASK-01-01**: テスト実装 [REQ-01]")
result = self.runner.invoke(app, ["generate-review-prompt", "TASK-01-01"])
assert result.exit_code == 0
assert "実装レビュープロンプト" in result.output
assert "TASK-01-01" in result.output
assert "品質ガードレール" in result.output
assert "静的品質チェック" in result.output
def test_generate_review_prompt_help(self):
"""Test generate_review_prompt help command."""
result = self.runner.invoke(app, ["generate-review-prompt", "--help"])
assert result.exit_code == 0
assert "Task ID to review for spec compliance" in result.output
assert "TASK-XX-XX" in result.output
def test_generate_review_prompt_missing_task_id(self):
"""Test generate_review_prompt command without task ID."""
result = self.runner.invoke(app, ["generate-review-prompt"])
assert result.exit_code != 0 # Missing required argument
assert "Missing argument" in result.output or "Usage:" in result.output
def test_generate_review_prompt_custom_paths(self):
"""Test generate_review_prompt command with custom paths."""
with self.runner.isolated_filesystem():
Path("custom").mkdir(exist_ok=True)
Path("custom/req.md").write_text("## 機能要件\n- **REQ-01**: Test")
Path("custom/design.md").write_text("## 設計\n- Component: Test [REQ-01]")
Path("custom/tasks.md").write_text("## タスク\n- **TASK-01-01**: Test [REQ-01]")
result = self.runner.invoke(
app,
[
"generate-review-prompt",
"TASK-01-01",
"--requirementsPath",
"custom/req.md",
"--designPath",
"custom/design.md",
"custom/tasks.md",
],
)
assert result.exit_code == 0
assert "実装レビュープロンプト" in result.output
assert "TASK-01-01" in result.output
def test_analyze_changes_command(self):
"""Test analyze_changes command."""
result = self.runner.invoke(
app,
[
"analyze-changes",
"--changedFile",
"specs/requirements.md",
"--changeDescription",
"Added REQ-03 for new feature",
],
)
assert result.exit_code == 0
assert "変更影響分析" in result.output
assert "REQ-03" in result.output
def test_get_traceability_command(self):
"""Test get_traceability command."""
with self.runner.isolated_filesystem():
Path("specs").mkdir(exist_ok=True)
Path("specs/requirements.md").write_text("## 機能要件\n- **REQ-01**: Test")
Path("specs/design.md").write_text("## アーキテクチャ\n[REQ-01]")
Path("specs/tasks.md").write_text("## タスク一覧\n- **TASK-01-01**: Test")
result = self.runner.invoke(app, ["get-traceability"])
assert result.exit_code == 0
assert "トレーサビリティレポート" in result.output
def test_all_commands_have_help(self):
"""Test that all commands have help text."""
commands = [
"prompt-requirements",
"prompt-requirements",
"validate-requirements",
"prompt-design",
"validate-design",
"prompt-tasks",
"validate-tasks",
"prompt-code",
"generate-review-prompt",
"analyze-changes",
"get-traceability",
"start-mcp-server",
]
for cmd in commands:
result = self.runner.invoke(app, [cmd, "--help"])
assert result.exit_code == 0
assert "Usage:" in result.output
# Typer uses rich formatting with box characters
assert "Options:" in result.output or "╭─" in result.output
def test_command_error_handling(self):
"""Test command error handling."""
# Test with invalid file path that will cause an error
result = self.runner.invoke(app, ["validate-requirements", "/invalid/path/that/definitely/does/not/exist.md"])
# Command should handle the error gracefully
assert result.exit_code == 0
assert "エラー" in result.output
class TestCLIEdgeCases:
"""Test CLI edge cases and error conditions."""
def setup_method(self):
"""Set up test environment."""
self.runner = CliRunner()
def test_empty_user_input(self):
"""Test prompt_requirements with empty input."""
result = self.runner.invoke(app, ["prompt-requirements", "--userInput", ""])
assert result.exit_code == 0
# Should still provide guidance even with empty input
def test_very_long_user_input(self):
"""Test prompt_requirements with very long input."""
long_input = "A" * 10000 # Very long input
result = self.runner.invoke(app, ["prompt-requirements", "--userInput", long_input])
assert result.exit_code == 0
def test_japanese_input(self):
"""Test CLI with Japanese input."""
result = self.runner.invoke(
app,
[
"prompt-requirements",
"--userInput",
"日本語のプロジェクト説明です。Python、FastAPI、Reactを使用します。",
],
)
assert result.exit_code == 0
assert "プロジェクト情報を確認" in result.output
def test_special_characters_input(self):
"""Test CLI with special characters."""
result = self.runner.invoke(
app, ["prompt-requirements", "--userInput", "Project with symbols: !@#$%^&*()_+-={}[]|\\:;\"'<>?,./"]
)
assert result.exit_code == 0
def test_multiple_flags_same_command(self):
"""Test command with multiple flags."""
result = self.runner.invoke(
app,
[
"prompt-requirements",
"--userInput",
"Test project",
"--force",
"--language",
"en",
],
)
assert result.exit_code == 0
assert "Test project" in result.output
class TestCLIIntegration:
"""Test CLI integration scenarios."""
def setup_method(self):
"""Set up test environment."""
self.runner = CliRunner()
def test_full_workflow_simulation(self):
"""Test a full SDD workflow through CLI."""
with self.runner.isolated_filesystem():
# Step 1: Check completeness
result1 = self.runner.invoke(
app, ["prompt-requirements", "--userInput", "Python FastAPI web application for task management"]
)
assert result1.exit_code == 0
# Step 2: Generate requirements (simulated by creating file)
Path("specs").mkdir(exist_ok=True)
Path("specs/requirements.md").write_text("""
## 0. サマリー
タスク管理用Webアプリケーション
## 1. 用語集
- **API**: Application Programming Interface
## 2. スコープ
### インスコープ
- タスク管理機能
## 3. 制約
- Python FastAPI使用
## 4. 非機能要件(NFR)
- **NFR-01**: レスポンス時間1秒以内
## 5. KPI
- **KPI-01**: ユーザー満足度90%以上
## 6. 機能要件(EARS)
- **REQ-01**: システムは、タスクを作成すること
- **REQ-02**: システムは、タスクを一覧表示すること
""")
# Step 3: Validate requirements
result3 = self.runner.invoke(app, ["validate-requirements"])
assert result3.exit_code == 0
assert "検証に成功" in result3.output or "検証" in result3.output
# Step 4: Generate design prompt
result4 = self.runner.invoke(app, ["prompt-design"])
assert result4.exit_code == 0
assert "design.md" in result4.output
# Create design file for next steps
Path("specs/design.md").write_text("""
## アーキテクチャ概要
FastAPI RESTful API [REQ-01, REQ-02]
## コンポーネント設計
- **task-service**: タスク管理 [REQ-01, REQ-02]
## データ設計
Task model definition
## API設計
REST endpoints
## 非機能設計
Performance optimization
## テスト戦略
Unit and integration tests
""")
# Step 5: Validate design
result5 = self.runner.invoke(app, ["validate-design"])
assert result5.exit_code == 0
# Step 6: Generate tasks prompt
result6 = self.runner.invoke(app, ["prompt-tasks"])
assert result6.exit_code == 0
assert "tasks.md" in result6.output
# Create tasks file
Path("specs/tasks.md").write_text("""
## 概要
開発タスクの分解
## タスク一覧
- **TASK-01-01**: 環境構築
- **TASK-01-02**: データモデル作成
- **TASK-02-01**: API実装
## 依存関係
TASK-01-01 → TASK-01-02 → TASK-02-01
## マイルストーン
- M1: 基盤完成
""")
# Step 7: Validate tasks
result7 = self.runner.invoke(app, ["validate-tasks"])
assert result7.exit_code == 0
# Step 8: Get traceability report
result8 = self.runner.invoke(app, ["get-traceability"])
assert result8.exit_code == 0
assert "トレーサビリティレポート" in result8.output
# Step 9: Generate implementation prompt
result9 = self.runner.invoke(app, ["prompt-code"])
assert result9.exit_code == 0
assert "実装" in result9.output