mirror of
https://github.com/ivuorinen/ghaw-auditor.git
synced 2026-01-26 11:24:00 +00:00
756 lines
20 KiB
Python
756 lines
20 KiB
Python
"""Tests for renderer."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from ghaw_auditor.models import ActionManifest, JobMeta, WorkflowMeta
|
|
from ghaw_auditor.renderer import Renderer
|
|
|
|
|
|
def test_renderer_initialization(tmp_path: Path) -> None:
|
|
"""Test renderer initialization."""
|
|
renderer = Renderer(tmp_path)
|
|
assert renderer.output_dir == tmp_path
|
|
assert renderer.output_dir.exists()
|
|
|
|
|
|
def test_render_json(tmp_path: Path) -> None:
|
|
"""Test JSON rendering."""
|
|
renderer = Renderer(tmp_path)
|
|
|
|
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",
|
|
)
|
|
}
|
|
|
|
violations = [
|
|
{
|
|
"workflow": "test.yml",
|
|
"rule": "test_rule",
|
|
"severity": "error",
|
|
"message": "Test violation",
|
|
}
|
|
]
|
|
|
|
renderer.render_json(workflows, actions, violations)
|
|
|
|
# Check files exist
|
|
assert (tmp_path / "workflows.json").exists()
|
|
assert (tmp_path / "actions.json").exists()
|
|
assert (tmp_path / "violations.json").exists()
|
|
|
|
# Verify JSON content
|
|
with open(tmp_path / "workflows.json") as f:
|
|
data = json.load(f)
|
|
assert "test.yml" in data
|
|
assert data["test.yml"]["name"] == "Test"
|
|
|
|
with open(tmp_path / "actions.json") as f:
|
|
data = json.load(f)
|
|
assert "actions/checkout@v4" in data
|
|
|
|
with open(tmp_path / "violations.json") as f:
|
|
data = json.load(f)
|
|
assert len(data) == 1
|
|
assert data[0]["rule"] == "test_rule"
|
|
|
|
|
|
def test_render_markdown(tmp_path: Path) -> None:
|
|
"""Test Markdown rendering."""
|
|
renderer = Renderer(tmp_path)
|
|
|
|
workflows = {
|
|
"test.yml": WorkflowMeta(
|
|
name="Test Workflow",
|
|
path="test.yml",
|
|
triggers=["push", "pull_request"],
|
|
jobs={"test": JobMeta(name="test", runs_on="ubuntu-latest")},
|
|
)
|
|
}
|
|
|
|
actions = {
|
|
"actions/checkout@v4": ActionManifest(
|
|
name="Checkout",
|
|
description="Checkout repository",
|
|
)
|
|
}
|
|
|
|
violations = [
|
|
{
|
|
"workflow": "test.yml",
|
|
"rule": "require_pinned_actions",
|
|
"severity": "error",
|
|
"message": "Action not pinned to SHA",
|
|
}
|
|
]
|
|
|
|
analysis = {
|
|
"total_jobs": 1,
|
|
"reusable_workflows": 0,
|
|
"triggers": {"push": 1, "pull_request": 1},
|
|
"runners": {"ubuntu-latest": 1},
|
|
"secrets": {"total_unique_secrets": 0, "secrets": []},
|
|
}
|
|
|
|
renderer.render_markdown(workflows, actions, violations, analysis)
|
|
|
|
report_file = tmp_path / "report.md"
|
|
assert report_file.exists()
|
|
|
|
content = report_file.read_text()
|
|
assert "# GitHub Actions & Workflows Audit Report" in content
|
|
assert "Test Workflow" in content
|
|
assert "Checkout" in content
|
|
assert "require_pinned_actions" in content
|
|
assert "push" in content
|
|
assert "pull_request" in content
|
|
|
|
|
|
def test_render_empty_data(tmp_path: Path) -> None:
|
|
"""Test rendering with empty data."""
|
|
renderer = Renderer(tmp_path)
|
|
|
|
renderer.render_json({}, {}, [])
|
|
|
|
assert (tmp_path / "workflows.json").exists()
|
|
assert (tmp_path / "actions.json").exists()
|
|
assert (tmp_path / "violations.json").exists()
|
|
|
|
with open(tmp_path / "workflows.json") as f:
|
|
assert json.load(f) == {}
|
|
|
|
with open(tmp_path / "violations.json") as f:
|
|
assert json.load(f) == []
|
|
|
|
|
|
def test_render_markdown_with_actions_used(tmp_path: Path) -> None:
|
|
"""Test Markdown rendering with job actions_used."""
|
|
from ghaw_auditor.models import ActionRef, ActionType
|
|
|
|
renderer = Renderer(tmp_path)
|
|
|
|
# Create a job with actions_used
|
|
action_ref = ActionRef(
|
|
type=ActionType.GITHUB,
|
|
owner="actions",
|
|
repo="checkout",
|
|
ref="v4",
|
|
source_file="test.yml",
|
|
)
|
|
|
|
job = JobMeta(
|
|
name="test",
|
|
runs_on="ubuntu-latest",
|
|
actions_used=[action_ref],
|
|
)
|
|
|
|
workflows = {
|
|
"test.yml": WorkflowMeta(
|
|
name="Test Workflow",
|
|
path="test.yml",
|
|
triggers=["push"],
|
|
jobs={"test": job},
|
|
)
|
|
}
|
|
|
|
renderer.render_markdown(workflows, {}, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
assert report_file.exists()
|
|
|
|
content = report_file.read_text()
|
|
# Should render the actions used with link
|
|
assert "Actions used:" in content
|
|
assert "[actions/checkout](#actions-checkout)" in content
|
|
|
|
|
|
def test_render_markdown_with_secrets(tmp_path: Path) -> None:
|
|
"""Test Markdown rendering with secrets."""
|
|
renderer = Renderer(tmp_path)
|
|
|
|
workflows = {
|
|
"test.yml": WorkflowMeta(
|
|
name="Test Workflow",
|
|
path="test.yml",
|
|
triggers=["push"],
|
|
jobs={},
|
|
)
|
|
}
|
|
|
|
analysis = {
|
|
"total_jobs": 0,
|
|
"reusable_workflows": 0,
|
|
"secrets": {
|
|
"total_unique_secrets": 2,
|
|
"secrets": ["API_KEY", "DATABASE_URL"],
|
|
},
|
|
}
|
|
|
|
renderer.render_markdown(workflows, {}, [], analysis)
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Should render secrets
|
|
assert "API_KEY" in content
|
|
assert "DATABASE_URL" in content
|
|
|
|
|
|
def test_render_markdown_with_action_inputs(tmp_path: Path) -> None:
|
|
"""Test Markdown rendering with action inputs."""
|
|
from ghaw_auditor.models import ActionInput
|
|
|
|
renderer = Renderer(tmp_path)
|
|
|
|
action = ActionManifest(
|
|
name="Test Action",
|
|
description="A test action",
|
|
inputs={
|
|
"token": ActionInput(
|
|
name="token",
|
|
description="GitHub token",
|
|
required=True,
|
|
),
|
|
"debug": ActionInput(
|
|
name="debug",
|
|
description="Enable debug mode",
|
|
required=False,
|
|
),
|
|
},
|
|
)
|
|
|
|
renderer.render_markdown({}, {"test/action@v1": action}, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Should render inputs with required/optional status
|
|
assert "token" in content
|
|
assert "required" in content
|
|
assert "debug" in content
|
|
assert "optional" in content
|
|
assert "GitHub token" in content
|
|
assert "Enable debug mode" in content
|
|
|
|
|
|
def test_render_markdown_with_action_anchors(tmp_path: Path) -> None:
|
|
"""Test that action anchors are created for linking."""
|
|
from ghaw_auditor.models import ActionRef, ActionType
|
|
|
|
renderer = Renderer(tmp_path)
|
|
|
|
action_ref = ActionRef(
|
|
type=ActionType.GITHUB,
|
|
owner="actions",
|
|
repo="checkout",
|
|
ref="v4",
|
|
resolved_sha="abc123",
|
|
source_file="test.yml",
|
|
)
|
|
|
|
workflow = WorkflowMeta(
|
|
name="Test",
|
|
path="test.yml",
|
|
triggers=["push"],
|
|
jobs={},
|
|
actions_used=[action_ref],
|
|
)
|
|
|
|
action = ActionManifest(
|
|
name="Checkout",
|
|
description="Checkout code",
|
|
)
|
|
|
|
renderer.render_markdown({"test.yml": workflow}, {"actions/checkout@abc123": action}, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Should have anchor tag
|
|
assert '<a id="actions-checkout"></a>' in content
|
|
|
|
|
|
def test_render_markdown_with_repo_urls(tmp_path: Path) -> None:
|
|
"""Test that GitHub action repository URLs are included."""
|
|
from ghaw_auditor.models import ActionRef, ActionType
|
|
|
|
renderer = Renderer(tmp_path)
|
|
|
|
action_ref = ActionRef(
|
|
type=ActionType.GITHUB,
|
|
owner="actions",
|
|
repo="setup-node",
|
|
ref="v4",
|
|
resolved_sha="def456",
|
|
source_file="test.yml",
|
|
)
|
|
|
|
workflow = WorkflowMeta(
|
|
name="Test",
|
|
path="test.yml",
|
|
triggers=["push"],
|
|
jobs={},
|
|
actions_used=[action_ref],
|
|
)
|
|
|
|
action = ActionManifest(
|
|
name="Setup Node",
|
|
description="Setup Node.js",
|
|
)
|
|
|
|
renderer.render_markdown({"test.yml": workflow}, {"actions/setup-node@def456": action}, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Should have repository link
|
|
assert "https://github.com/actions/setup-node" in content
|
|
assert "[actions/setup-node](https://github.com/actions/setup-node)" in content
|
|
|
|
|
|
def test_render_markdown_with_details_tags(tmp_path: Path) -> None:
|
|
"""Test that inputs are wrapped in details tags."""
|
|
from ghaw_auditor.models import ActionInput, ActionRef, ActionType
|
|
|
|
renderer = Renderer(tmp_path)
|
|
|
|
action_ref = ActionRef(
|
|
type=ActionType.GITHUB,
|
|
owner="actions",
|
|
repo="checkout",
|
|
ref="v4",
|
|
source_file="test.yml",
|
|
)
|
|
|
|
workflow = WorkflowMeta(
|
|
name="Test",
|
|
path="test.yml",
|
|
triggers=["push"],
|
|
jobs={},
|
|
actions_used=[action_ref],
|
|
)
|
|
|
|
action = ActionManifest(
|
|
name="Checkout",
|
|
description="Checkout code",
|
|
inputs={
|
|
"token": ActionInput(
|
|
name="token",
|
|
description="GitHub token",
|
|
required=False,
|
|
),
|
|
},
|
|
)
|
|
|
|
renderer.render_markdown({"test.yml": workflow}, {"actions/checkout@v4": action}, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Should have details tags
|
|
assert "<details>" in content
|
|
assert "<summary><b>Inputs</b></summary>" in content
|
|
assert "</details>" in content
|
|
|
|
|
|
def test_render_markdown_with_job_action_links(tmp_path: Path) -> None:
|
|
"""Test that job actions are linked to inventory."""
|
|
from ghaw_auditor.models import ActionRef, ActionType
|
|
|
|
renderer = Renderer(tmp_path)
|
|
|
|
action_ref = ActionRef(
|
|
type=ActionType.GITHUB,
|
|
owner="actions",
|
|
repo="checkout",
|
|
ref="v4",
|
|
source_file="test.yml",
|
|
)
|
|
|
|
job = JobMeta(
|
|
name="test",
|
|
runs_on="ubuntu-latest",
|
|
actions_used=[action_ref],
|
|
)
|
|
|
|
workflow = WorkflowMeta(
|
|
name="CI",
|
|
path="ci.yml",
|
|
triggers=["push"],
|
|
jobs={"test": job},
|
|
actions_used=[action_ref],
|
|
)
|
|
|
|
action = ActionManifest(
|
|
name="Checkout",
|
|
description="Checkout code",
|
|
)
|
|
|
|
renderer.render_markdown({"ci.yml": workflow}, {"actions/checkout@v4": action}, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Should have action link in jobs section
|
|
assert "Actions used:" in content
|
|
assert "[actions/checkout](#actions-checkout) (GitHub)" in content
|
|
|
|
|
|
def test_create_action_anchor() -> None:
|
|
"""Test anchor creation from action keys."""
|
|
# GitHub action
|
|
assert Renderer._create_action_anchor("actions/checkout@abc123") == "actions-checkout"
|
|
|
|
# Local action
|
|
assert Renderer._create_action_anchor("local:./sync-labels") == "local-sync-labels"
|
|
|
|
# Docker action
|
|
assert Renderer._create_action_anchor("docker://alpine:3.8") == "docker-alpine-3-8"
|
|
|
|
# Long SHA
|
|
assert (
|
|
Renderer._create_action_anchor("actions/setup-node@1234567890abcdef1234567890abcdef12345678")
|
|
== "actions-setup-node"
|
|
)
|
|
|
|
|
|
def test_get_action_repo_url() -> None:
|
|
"""Test repository URL generation."""
|
|
from ghaw_auditor.models import ActionRef, ActionType
|
|
|
|
# GitHub action
|
|
github_action = ActionRef(
|
|
type=ActionType.GITHUB,
|
|
owner="actions",
|
|
repo="checkout",
|
|
ref="v4",
|
|
source_file="test.yml",
|
|
)
|
|
assert Renderer._get_action_repo_url(github_action) == "https://github.com/actions/checkout"
|
|
|
|
# Local action (no URL)
|
|
local_action = ActionRef(
|
|
type=ActionType.LOCAL,
|
|
path="./my-action",
|
|
source_file="test.yml",
|
|
)
|
|
assert Renderer._get_action_repo_url(local_action) is None
|
|
|
|
# Docker action (no URL)
|
|
docker_action = ActionRef(
|
|
type=ActionType.DOCKER,
|
|
path="docker://alpine:3.8",
|
|
source_file="test.yml",
|
|
)
|
|
assert Renderer._get_action_repo_url(docker_action) is None
|
|
|
|
|
|
def test_render_markdown_with_docker_action(tmp_path: Path) -> None:
|
|
"""Test Markdown rendering with Docker action in jobs."""
|
|
from ghaw_auditor.models import ActionRef, ActionType
|
|
|
|
renderer = Renderer(tmp_path)
|
|
|
|
docker_action = ActionRef(
|
|
type=ActionType.DOCKER,
|
|
path="docker://alpine:3.8",
|
|
source_file="test.yml",
|
|
)
|
|
|
|
job = JobMeta(
|
|
name="test",
|
|
runs_on="ubuntu-latest",
|
|
actions_used=[docker_action],
|
|
)
|
|
|
|
workflow = WorkflowMeta(
|
|
name="Test",
|
|
path="test.yml",
|
|
triggers=["push"],
|
|
jobs={"test": job},
|
|
)
|
|
|
|
renderer.render_markdown({"test.yml": workflow}, {}, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Should show Docker action with correct type label
|
|
assert "Actions used:" in content
|
|
assert "(Docker)" in content
|
|
assert "docker://alpine:3.8" in content
|
|
|
|
|
|
def test_render_markdown_with_reusable_workflow(tmp_path: Path) -> None:
|
|
"""Test Markdown rendering with reusable workflow in jobs."""
|
|
from ghaw_auditor.models import ActionRef, ActionType
|
|
|
|
renderer = Renderer(tmp_path)
|
|
|
|
reusable_wf = ActionRef(
|
|
type=ActionType.REUSABLE_WORKFLOW,
|
|
owner="org",
|
|
repo="workflows",
|
|
path=".github/workflows/reusable.yml",
|
|
ref="main",
|
|
source_file="test.yml",
|
|
)
|
|
|
|
job = JobMeta(
|
|
name="test",
|
|
runs_on="ubuntu-latest",
|
|
actions_used=[reusable_wf],
|
|
)
|
|
|
|
workflow = WorkflowMeta(
|
|
name="Test",
|
|
path="test.yml",
|
|
triggers=["push"],
|
|
jobs={"test": job},
|
|
)
|
|
|
|
renderer.render_markdown({"test.yml": workflow}, {}, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Should show reusable workflow with correct type label
|
|
assert "Actions used:" in content
|
|
assert "(Reusable Workflow)" in content
|
|
assert ".github/workflows/reusable.yml" in content
|
|
|
|
|
|
def test_render_markdown_with_docker_action_in_inventory(tmp_path: Path) -> None:
|
|
"""Test Markdown rendering with Docker action in inventory."""
|
|
from ghaw_auditor.models import ActionRef, ActionType
|
|
|
|
renderer = Renderer(tmp_path)
|
|
|
|
docker_action_ref = ActionRef(
|
|
type=ActionType.DOCKER,
|
|
path="docker://node:18-alpine",
|
|
source_file="test.yml",
|
|
)
|
|
|
|
workflow = WorkflowMeta(
|
|
name="Test",
|
|
path="test.yml",
|
|
triggers=["push"],
|
|
jobs={},
|
|
actions_used=[docker_action_ref],
|
|
)
|
|
|
|
action_manifest = ActionManifest(
|
|
name="Node Alpine",
|
|
description="Node.js on Alpine Linux",
|
|
)
|
|
|
|
renderer.render_markdown({"test.yml": workflow}, {"docker:docker://node:18-alpine": action_manifest}, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Docker actions shouldn't have repository links or Local Action type
|
|
assert "**Repository:**" not in content or "node:18-alpine" not in content
|
|
assert "Node Alpine" in content
|
|
|
|
|
|
def test_render_markdown_with_local_action_without_path(tmp_path: Path) -> None:
|
|
"""Test Markdown rendering with LOCAL action that has no path."""
|
|
from ghaw_auditor.models import ActionRef, ActionType
|
|
|
|
renderer = Renderer(tmp_path)
|
|
|
|
local_action = ActionRef(
|
|
type=ActionType.LOCAL,
|
|
path=None,
|
|
source_file="test.yml",
|
|
)
|
|
|
|
job = JobMeta(
|
|
name="test",
|
|
runs_on="ubuntu-latest",
|
|
actions_used=[local_action],
|
|
)
|
|
|
|
workflow = WorkflowMeta(
|
|
name="Test",
|
|
path="test.yml",
|
|
triggers=["push"],
|
|
jobs={"test": job},
|
|
)
|
|
|
|
renderer.render_markdown({"test.yml": workflow}, {}, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Should show "local" as display name when path is None
|
|
assert "Actions used:" in content
|
|
assert "[local](#local-none) (Local)" in content
|
|
|
|
|
|
def test_render_markdown_with_local_action_in_inventory(tmp_path: Path) -> None:
|
|
"""Test Markdown rendering with LOCAL action in inventory showing Type label."""
|
|
from ghaw_auditor.models import ActionRef, ActionType
|
|
|
|
renderer = Renderer(tmp_path)
|
|
|
|
local_action_ref = ActionRef(
|
|
type=ActionType.LOCAL,
|
|
path="./my-custom-action",
|
|
source_file="test.yml",
|
|
)
|
|
|
|
workflow = WorkflowMeta(
|
|
name="Test",
|
|
path="test.yml",
|
|
triggers=["push"],
|
|
jobs={},
|
|
actions_used=[local_action_ref],
|
|
)
|
|
|
|
action_manifest = ActionManifest(
|
|
name="My Custom Action",
|
|
description="A custom local action",
|
|
)
|
|
|
|
renderer.render_markdown({"test.yml": workflow}, {"local:./my-custom-action": action_manifest}, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Local actions should have "Type: Local Action" label
|
|
assert "**Type:** Local Action" in content
|
|
assert "My Custom Action" in content
|
|
|
|
|
|
def test_render_markdown_with_job_permissions(tmp_path: Path) -> None:
|
|
"""Test Markdown rendering with job permissions."""
|
|
from ghaw_auditor.models import PermissionLevel, Permissions
|
|
|
|
renderer = Renderer(tmp_path)
|
|
|
|
job = JobMeta(
|
|
name="test",
|
|
runs_on="ubuntu-latest",
|
|
permissions=Permissions(
|
|
contents=PermissionLevel.READ,
|
|
issues=PermissionLevel.WRITE,
|
|
security_events=PermissionLevel.WRITE,
|
|
),
|
|
)
|
|
|
|
workflow = WorkflowMeta(
|
|
name="Test",
|
|
path="test.yml",
|
|
triggers=["push"],
|
|
jobs={"test": job},
|
|
)
|
|
|
|
renderer.render_markdown({"test.yml": workflow}, {}, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Should show permissions
|
|
assert "Permissions:" in content
|
|
assert "`contents`: read" in content
|
|
assert "`issues`: write" in content
|
|
assert "`security-events`: write" in content
|
|
|
|
|
|
def test_render_markdown_without_job_permissions(tmp_path: Path) -> None:
|
|
"""Test Markdown rendering with job that has no permissions set."""
|
|
renderer = Renderer(tmp_path)
|
|
|
|
job = JobMeta(
|
|
name="test",
|
|
runs_on="ubuntu-latest",
|
|
permissions=None,
|
|
)
|
|
|
|
workflow = WorkflowMeta(
|
|
name="Test",
|
|
path="test.yml",
|
|
triggers=["push"],
|
|
jobs={"test": job},
|
|
)
|
|
|
|
renderer.render_markdown({"test.yml": workflow}, {}, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Should not show permissions section
|
|
assert "Permissions:" not in content
|
|
|
|
|
|
def test_render_markdown_with_workflows_using_action(tmp_path: Path) -> None:
|
|
"""Test that actions show which workflows use them."""
|
|
from ghaw_auditor.models import ActionRef, ActionType
|
|
|
|
renderer = Renderer(tmp_path)
|
|
|
|
# Create an action reference
|
|
action_ref = ActionRef(
|
|
type=ActionType.GITHUB,
|
|
owner="actions",
|
|
repo="checkout",
|
|
ref="v4",
|
|
source_file=".github/workflows/ci.yml",
|
|
)
|
|
|
|
# Create two workflows that use the same action
|
|
workflow1 = WorkflowMeta(
|
|
name="CI Workflow",
|
|
path=".github/workflows/ci.yml",
|
|
triggers=["push"],
|
|
actions_used=[action_ref],
|
|
)
|
|
|
|
workflow2 = WorkflowMeta(
|
|
name="Deploy Workflow",
|
|
path=".github/workflows/deploy.yml",
|
|
triggers=["push"],
|
|
actions_used=[action_ref],
|
|
)
|
|
|
|
# Create the action manifest
|
|
action = ActionManifest(
|
|
name="Checkout",
|
|
description="Checkout repository",
|
|
)
|
|
|
|
workflows = {
|
|
".github/workflows/ci.yml": workflow1,
|
|
".github/workflows/deploy.yml": workflow2,
|
|
}
|
|
actions = {"actions/checkout@v4": action}
|
|
|
|
renderer.render_markdown(workflows, actions, [], {})
|
|
|
|
report_file = tmp_path / "report.md"
|
|
content = report_file.read_text()
|
|
|
|
# Should show "Used in Workflows" section
|
|
assert "Used in Workflows" in content
|
|
assert "CI Workflow" in content
|
|
assert "Deploy Workflow" in content
|
|
assert ".github/workflows/ci.yml" in content
|
|
assert ".github/workflows/deploy.yml" in content
|
|
# Should have links to workflow sections
|
|
assert "[CI Workflow](#ci-workflow)" in content
|
|
assert "[Deploy Workflow](#deploy-workflow)" in content
|