Files
ghaw-auditor/tests/test_differ.py
2025-10-19 09:52:13 +03:00

377 lines
10 KiB
Python

"""Tests for differ module."""
from pathlib import Path
import pytest
from ghaw_auditor.differ import Differ
from ghaw_auditor.models import (
ActionManifest,
JobMeta,
PermissionLevel,
Permissions,
WorkflowMeta,
)
def test_save_and_load_baseline(tmp_path: Path) -> None:
"""Test saving and loading baseline."""
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
# Create sample data
workflows = {
"test.yml": WorkflowMeta(
name="Test",
path="test.yml",
triggers=["push"],
jobs={"test": JobMeta(name="test", runs_on="ubuntu-latest")},
)
}
actions = {
"actions/checkout@v4": ActionManifest(
name="Checkout",
description="Checkout code",
)
}
# Save baseline
differ.save_baseline(workflows, actions, "abc123")
assert (baseline_path / "workflows.json").exists()
assert (baseline_path / "actions.json").exists()
assert (baseline_path / "meta.json").exists()
# Load baseline
baseline = differ.load_baseline()
assert baseline.meta.commit_sha == "abc123"
assert len(baseline.workflows) == 1
assert len(baseline.actions) == 1
def test_diff_workflows(tmp_path: Path) -> None:
"""Test workflow diff."""
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
old_workflow = WorkflowMeta(
name="Test",
path="test.yml",
triggers=["push"],
jobs={},
)
new_workflow = WorkflowMeta(
name="Test",
path="test.yml",
triggers=["push", "pull_request"],
jobs={},
)
diffs = differ.diff_workflows({"test.yml": old_workflow}, {"test.yml": new_workflow})
assert len(diffs) == 1
assert diffs[0].status == "modified"
assert len(diffs[0].changes) > 0
def test_diff_added_workflow(tmp_path: Path) -> None:
"""Test added workflow detection."""
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
new_workflow = WorkflowMeta(
name="New",
path="new.yml",
triggers=["push"],
jobs={},
)
diffs = differ.diff_workflows({}, {"new.yml": new_workflow})
assert len(diffs) == 1
assert diffs[0].status == "added"
assert diffs[0].path == "new.yml"
def test_diff_removed_workflow(tmp_path: Path) -> None:
"""Test removed workflow detection."""
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
old_workflow = WorkflowMeta(
name="Old",
path="old.yml",
triggers=["push"],
jobs={},
)
diffs = differ.diff_workflows({"old.yml": old_workflow}, {})
assert len(diffs) == 1
assert diffs[0].status == "removed"
assert diffs[0].path == "old.yml"
def test_load_baseline_not_found(tmp_path: Path) -> None:
"""Test loading baseline when it doesn't exist."""
baseline_path = tmp_path / "nonexistent"
differ = Differ(baseline_path)
with pytest.raises(FileNotFoundError, match="Baseline not found"):
differ.load_baseline()
def test_load_baseline_without_meta(tmp_path: Path) -> None:
"""Test loading baseline when meta.json doesn't exist."""
baseline_path = tmp_path / "baseline"
baseline_path.mkdir()
# Create only workflows.json and actions.json
(baseline_path / "workflows.json").write_text("{}")
(baseline_path / "actions.json").write_text("{}")
differ = Differ(baseline_path)
baseline = differ.load_baseline()
# Should still load with default meta
assert baseline.meta is not None
assert baseline.workflows == {}
assert baseline.actions == {}
def test_diff_workflows_permissions_change(tmp_path: Path) -> None:
"""Test workflow diff with permissions changes."""
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
old_workflow = WorkflowMeta(
name="Test",
path="test.yml",
triggers=["push"],
permissions=Permissions(contents=PermissionLevel.READ),
jobs={},
)
new_workflow = WorkflowMeta(
name="Test",
path="test.yml",
triggers=["push"],
permissions=Permissions(contents=PermissionLevel.WRITE),
jobs={},
)
diffs = differ.diff_workflows({"test.yml": old_workflow}, {"test.yml": new_workflow})
assert len(diffs) == 1
assert diffs[0].status == "modified"
assert any(c.field == "permissions" for c in diffs[0].changes)
def test_diff_workflows_concurrency_change(tmp_path: Path) -> None:
"""Test workflow diff with concurrency changes."""
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
old_workflow = WorkflowMeta(
name="Test",
path="test.yml",
triggers=["push"],
concurrency="group1",
jobs={},
)
new_workflow = WorkflowMeta(
name="Test",
path="test.yml",
triggers=["push"],
concurrency="group2",
jobs={},
)
diffs = differ.diff_workflows({"test.yml": old_workflow}, {"test.yml": new_workflow})
assert len(diffs) == 1
assert diffs[0].status == "modified"
assert any(c.field == "concurrency" for c in diffs[0].changes)
def test_diff_workflows_jobs_change(tmp_path: Path) -> None:
"""Test workflow diff with job changes."""
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
old_workflow = WorkflowMeta(
name="Test",
path="test.yml",
triggers=["push"],
jobs={"build": JobMeta(name="build", runs_on="ubuntu-latest")},
)
new_workflow = WorkflowMeta(
name="Test",
path="test.yml",
triggers=["push"],
jobs={
"build": JobMeta(name="build", runs_on="ubuntu-latest"),
"test": JobMeta(name="test", runs_on="ubuntu-latest"),
},
)
diffs = differ.diff_workflows({"test.yml": old_workflow}, {"test.yml": new_workflow})
assert len(diffs) == 1
assert diffs[0].status == "modified"
assert any(c.field == "jobs" for c in diffs[0].changes)
def test_diff_workflows_secrets_change(tmp_path: Path) -> None:
"""Test workflow diff with secrets changes."""
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
old_workflow = WorkflowMeta(
name="Test",
path="test.yml",
triggers=["push"],
jobs={},
secrets_used={"API_KEY"},
)
new_workflow = WorkflowMeta(
name="Test",
path="test.yml",
triggers=["push"],
jobs={},
secrets_used={"API_KEY", "DATABASE_URL"},
)
diffs = differ.diff_workflows({"test.yml": old_workflow}, {"test.yml": new_workflow})
assert len(diffs) == 1
assert diffs[0].status == "modified"
assert any(c.field == "secrets_used" for c in diffs[0].changes)
def test_diff_workflows_unchanged(tmp_path: Path) -> None:
"""Test workflow diff when unchanged."""
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
workflow = WorkflowMeta(
name="Test",
path="test.yml",
triggers=["push"],
jobs={},
)
diffs = differ.diff_workflows({"test.yml": workflow}, {"test.yml": workflow})
assert len(diffs) == 1
assert diffs[0].status == "unchanged"
assert len(diffs[0].changes) == 0
def test_diff_actions_added(tmp_path: Path) -> None:
"""Test action diff with added action."""
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
new_action = ActionManifest(name="New Action", description="Test")
diffs = differ.diff_actions({}, {"actions/new@v1": new_action})
assert len(diffs) == 1
assert diffs[0].status == "added"
assert diffs[0].key == "actions/new@v1"
def test_diff_actions_removed(tmp_path: Path) -> None:
"""Test action diff with removed action."""
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
old_action = ActionManifest(name="Old Action", description="Test")
diffs = differ.diff_actions({"actions/old@v1": old_action}, {})
assert len(diffs) == 1
assert diffs[0].status == "removed"
assert diffs[0].key == "actions/old@v1"
def test_diff_actions_unchanged(tmp_path: Path) -> None:
"""Test action diff when unchanged."""
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
action = ActionManifest(name="Test Action", description="Test")
diffs = differ.diff_actions({"actions/test@v1": action}, {"actions/test@v1": action})
assert len(diffs) == 1
assert diffs[0].status == "unchanged"
assert len(diffs[0].changes) == 0
def test_render_diff_markdown(tmp_path: Path) -> None:
"""Test rendering diff as Markdown."""
from ghaw_auditor.models import ActionDiff, DiffEntry, WorkflowDiff
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
workflow_diffs = [
WorkflowDiff(path="added.yml", status="added", changes=[]),
WorkflowDiff(path="removed.yml", status="removed", changes=[]),
WorkflowDiff(
path="modified.yml",
status="modified",
changes=[
DiffEntry(
field="triggers",
old_value=["push"],
new_value=["push", "pull_request"],
change_type="modified",
)
],
),
]
action_diffs = [
ActionDiff(key="actions/new@v1", status="added", changes=[]),
ActionDiff(key="actions/old@v1", status="removed", changes=[]),
]
output_path = tmp_path / "diff.md"
differ.render_diff_markdown(workflow_diffs, action_diffs, output_path)
assert output_path.exists()
content = output_path.read_text()
# Check content
assert "# Audit Diff Report" in content
assert "## Workflow Changes" in content
assert "## Action Changes" in content
assert "added.yml" in content
assert "removed.yml" in content
assert "modified.yml" in content
assert "actions/new@v1" in content
assert "actions/old@v1" in content
assert "triggers" in content
def test_render_diff_markdown_empty(tmp_path: Path) -> None:
"""Test rendering empty diff."""
baseline_path = tmp_path / "baseline"
differ = Differ(baseline_path)
output_path = tmp_path / "diff.md"
differ.render_diff_markdown([], [], output_path)
assert output_path.exists()
content = output_path.read_text()
assert "# Audit Diff Report" in content
assert "**Added:** 0" in content
assert "**Removed:** 0" in content