mirror of
https://github.com/ivuorinen/ghaw-auditor.git
synced 2026-01-26 03:14:09 +00:00
377 lines
10 KiB
Python
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
|