Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/agentready/assessors/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,7 @@ def _detect_ci_configs(self, repository: Repository) -> list:
repository.path / ".circleci" / "config.yml", # CircleCI
repository.path / ".travis.yml", # Travis CI
repository.path / "Jenkinsfile", # Jenkins
repository.path / ".tekton", # Pipelines-as-Code
]

configs = []
Expand Down Expand Up @@ -815,6 +816,25 @@ def _has_pr_trigger(self, config: Path) -> bool:
return bool(re.search(r"\bpull_request", content))
except (OSError, UnicodeDecodeError):
return False
elif ".tekton" in str(config):
# Check each pipeline definition for annotation: pipelinesascode.tekton.dev/on-event,
# note, values could be just "pull_request" or be an array of events "[pull_request,push]"
# Also, the matcher could be configured as CEL expression, so check for
# pipelinesascode.tekton.dev/on-cel-expression: ... event == "pull_request" ...
try:
content = config.read_text()
# Check for on-event annotation with pull_request value
on_event_pattern = (
r"pipelinesascode\.tekton\.dev/on-event[:\s]+.*\bpull_request\b"
)
# Check for CEL expression with event == "pull_request" (use DOTALL to match across newlines)
cel_pattern = r'pipelinesascode\.tekton\.dev/on-cel-expression.*event\s*==\s*["\']pull_request["\']'
return bool(
re.search(on_event_pattern, content)
or re.search(cel_pattern, content, re.DOTALL)
)
except (OSError, UnicodeDecodeError):
return False
return True

def _assess_quality_gates(self, ci_configs: list) -> tuple:
Expand Down
141 changes: 141 additions & 0 deletions tests/unit/test_assessors_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,147 @@ def test_config_quality_adds_score(self, tmp_path):
# 50 (CI) + 30 (all gates) + config quality bonus
assert finding.score > 80

# --- Tekton Pipelines as Code tests ---

def test_tekton_on_event_simple_pull_request(self, tmp_path):
"""Test Tekton pipeline with simple pull_request on-event annotation."""
tekton_dir = tmp_path / ".tekton"
tekton_dir.mkdir()
(tekton_dir / "pull-request.yaml").write_text(
"apiVersion: tekton.dev/v1beta1\n"
"kind: PipelineRun\n"
"metadata:\n"
" name: pr-pipeline\n"
" annotations:\n"
' pipelinesascode.tekton.dev/on-event: "pull_request"\n'
"spec:\n"
" pipelineRef:\n"
" name: test-pipeline\n"
" tasks:\n"
" - name: test\n"
" taskRef:\n"
" name: pytest\n"
)

assessor = CIQualityGatesAssessor()
# Use internal method to verify PR trigger detection
assert assessor._has_pr_trigger(tekton_dir / "pull-request.yaml")

def test_tekton_on_event_array_single(self, tmp_path):
"""Test Tekton pipeline with on-event as array containing only pull_request."""
tekton_dir = tmp_path / ".tekton"
tekton_dir.mkdir()
(tekton_dir / "pull-request.yaml").write_text(
"apiVersion: tekton.dev/v1beta1\n"
"kind: PipelineRun\n"
"metadata:\n"
" name: pr-pipeline\n"
" annotations:\n"
' pipelinesascode.tekton.dev/on-event: "[pull_request]"\n'
"spec:\n"
" pipelineRef:\n"
" name: test-pipeline\n"
)

assessor = CIQualityGatesAssessor()
assert assessor._has_pr_trigger(tekton_dir / "pull-request.yaml")

def test_tekton_on_event_array_multiple(self, tmp_path):
"""Test Tekton pipeline with on-event as array containing pull_request and push."""
tekton_dir = tmp_path / ".tekton"
tekton_dir.mkdir()
(tekton_dir / "pull-request.yaml").write_text(
"apiVersion: tekton.dev/v1beta1\n"
"kind: PipelineRun\n"
"metadata:\n"
" name: pr-pipeline\n"
" annotations:\n"
' pipelinesascode.tekton.dev/on-event: "[push,pull_request]"\n'
"spec:\n"
" pipelineRef:\n"
" name: test-pipeline\n"
)

assessor = CIQualityGatesAssessor()
assert assessor._has_pr_trigger(tekton_dir / "pull-request.yaml")

def test_tekton_cel_expression_simple(self, tmp_path):
"""Test Tekton pipeline with CEL expression checking for pull_request event."""
tekton_dir = tmp_path / ".tekton"
tekton_dir.mkdir()
(tekton_dir / "pull-request.yaml").write_text(
"apiVersion: tekton.dev/v1beta1\n"
"kind: PipelineRun\n"
"metadata:\n"
" name: pr-pipeline\n"
" annotations:\n"
' pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request"\n'
"spec:\n"
" pipelineRef:\n"
" name: test-pipeline\n"
)

assessor = CIQualityGatesAssessor()
assert assessor._has_pr_trigger(tekton_dir / "pull-request.yaml")

def test_tekton_cel_expression_complex(self, tmp_path):
"""Test Tekton pipeline with complex CEL expression containing pull_request check."""
tekton_dir = tmp_path / ".tekton"
tekton_dir.mkdir()
(tekton_dir / "pull-request.yaml").write_text(
"apiVersion: tekton.dev/v1beta1\n"
"kind: PipelineRun\n"
"metadata:\n"
" name: pr-pipeline\n"
" annotations:\n"
" pipelinesascode.tekton.dev/on-cel-expression: |\n"
' target_branch == "main" && (event == "push" || event == "pull_request") && \n'
' ( "my-component/***".pathChanged() || ".tekton/my-component-sample-pull-request.yaml".pathChanged() )\n'
"spec:\n"
" pipelineRef:\n"
" name: test-pipeline\n"
)

assessor = CIQualityGatesAssessor()
assert assessor._has_pr_trigger(tekton_dir / "pull-request.yaml")

def test_tekton_no_pr_trigger_push_only(self, tmp_path):
"""Test Tekton pipeline without pull_request trigger (push only)."""
tekton_dir = tmp_path / ".tekton"
tekton_dir.mkdir()
(tekton_dir / "push-only.yaml").write_text(
"apiVersion: tekton.dev/v1beta1\n"
"kind: PipelineRun\n"
"metadata:\n"
" name: push-pipeline\n"
" annotations:\n"
' pipelinesascode.tekton.dev/on-event: "push"\n'
"spec:\n"
" pipelineRef:\n"
" name: test-pipeline\n"
)

assessor = CIQualityGatesAssessor()
assert not assessor._has_pr_trigger(tekton_dir / "push-only.yaml")

def test_tekton_cel_expression_no_pull_request(self, tmp_path):
"""Test Tekton pipeline with CEL expression that doesn't check for pull_request."""
tekton_dir = tmp_path / ".tekton"
tekton_dir.mkdir()
(tekton_dir / "push-only.yaml").write_text(
"apiVersion: tekton.dev/v1beta1\n"
"kind: PipelineRun\n"
"metadata:\n"
" name: push-pipeline\n"
" annotations:\n"
' pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch == "main"\n'
"spec:\n"
" pipelineRef:\n"
" name: test-pipeline\n"
)

assessor = CIQualityGatesAssessor()
assert not assessor._has_pr_trigger(tekton_dir / "push-only.yaml")

class TestDeterministicEnforcementAssessor:
"""Test DeterministicEnforcementAssessor."""
Expand Down