Files
q3alique acc1b4f3e7 Initial release of codeflow
Static taint-analysis and visualization tool for source-code security review.
Supports deep analysis for Python, JavaScript/TypeScript, Java, Go, and C#,
with structural support for all other languages via the generic extractor.

Outputs: interactive HTML report, LLM-ready Markdown review document,
and optional Burp Suite JSON export. Self-bootstrapping launcher (run.py)
requires no virtual environment.
2026-06-08 00:55:14 +02:00

143 lines
4.1 KiB
Python

"""Task 003 tests: CLI entry point."""
import pytest
from click.testing import CliRunner
from codeflow.cli import cli
from pathlib import Path
from tests.fixtures import FLASK_FIXTURE_DIR
def test_valid_invocation():
runner = CliRunner()
result = runner.invoke(cli, [
"analyze",
str(FLASK_FIXTURE_DIR),
"--output", str(FLASK_FIXTURE_DIR / "test_report.html"),
])
assert result.exit_code in (0, 2)
def test_nonexistent_output_dir():
runner = CliRunner()
result = runner.invoke(cli, [
"analyze",
str(FLASK_FIXTURE_DIR),
"--output", "/nonexistent_dir/report.html",
])
assert result.exit_code == 1
assert "output directory does not exist" in result.output.lower()
def test_no_html_extension():
runner = CliRunner()
result = runner.invoke(cli, [
"analyze",
str(FLASK_FIXTURE_DIR),
"--output", str(FLASK_FIXTURE_DIR / "report"),
])
assert result.exit_code == 1
assert "output must be a .html file" in result.output.lower()
def test_cluster_threshold_zero(mocker):
mock_run = mocker.patch("codeflow.orchestrator.run_analysis")
mock_run.return_value = {
"html_path": "", "burp_path": None,
"file_count": 0, "node_count": 0, "flow_count": 0,
"elapsed_seconds": 0.0, "errors": [],
}
runner = CliRunner()
result = runner.invoke(cli, [
"analyze",
str(FLASK_FIXTURE_DIR),
"--output", str(FLASK_FIXTURE_DIR / "r.html"),
"--cluster-threshold", "0",
])
assert result.exit_code == 0
_, kwargs = mock_run.call_args
assert kwargs["cluster_threshold"] == 0
def test_no_burp(mocker):
mock_run = mocker.patch("codeflow.orchestrator.run_analysis")
mock_run.return_value = {
"html_path": "", "burp_path": None,
"file_count": 0, "node_count": 0, "flow_count": 0,
"elapsed_seconds": 0.0, "errors": [],
}
runner = CliRunner()
result = runner.invoke(cli, [
"analyze",
str(FLASK_FIXTURE_DIR),
"--output", str(FLASK_FIXTURE_DIR / "r.html"),
"--no-burp",
])
assert result.exit_code == 0
_, kwargs = mock_run.call_args
assert kwargs["generate_burp"] is False
def test_verbose_flag(mocker):
mock_run = mocker.patch("codeflow.orchestrator.run_analysis")
mock_run.return_value = {
"html_path": "", "burp_path": None,
"file_count": 0, "node_count": 0, "flow_count": 0,
"elapsed_seconds": 0.0, "errors": [],
}
runner = CliRunner()
result = runner.invoke(cli, [
"analyze",
str(FLASK_FIXTURE_DIR),
"--output", str(FLASK_FIXTURE_DIR / "r.html"),
"-v",
])
assert result.exit_code == 0
_, kwargs = mock_run.call_args
assert kwargs["verbose"] is True
def test_help_shown():
runner = CliRunner()
result = runner.invoke(cli, ["analyze", "--help"])
assert result.exit_code == 0
assert "REPO_PATH" in result.output
def test_parse_error_warning(mocker):
mock_run = mocker.patch("codeflow.orchestrator.run_analysis")
mock_run.return_value = {
"html_path": "/tmp/r.html", "burp_path": None,
"file_count": 3, "node_count": 10, "flow_count": 2,
"elapsed_seconds": 0.5, "errors": ["bad.py: parse error"],
}
runner = CliRunner()
result = runner.invoke(cli, [
"analyze",
str(FLASK_FIXTURE_DIR),
"--output", str(FLASK_FIXTURE_DIR / "r.html"),
])
assert result.exit_code == 0
assert "WARNING" in result.output
def test_max_file_size_passed(mocker):
mock_run = mocker.patch("codeflow.orchestrator.run_analysis")
mock_run.return_value = {
"html_path": "", "burp_path": None,
"file_count": 0, "node_count": 0, "flow_count": 0,
"elapsed_seconds": 0.0, "errors": [],
}
runner = CliRunner()
result = runner.invoke(cli, [
"analyze",
str(FLASK_FIXTURE_DIR),
"--output", str(FLASK_FIXTURE_DIR / "r.html"),
"--max-file-size", "10",
])
assert result.exit_code == 0
def test_missing_repo_path():
runner = CliRunner()
result = runner.invoke(cli, ["analyze"])
assert result.exit_code != 0