|
|
|
|
@@ -0,0 +1,467 @@
|
|
|
|
|
"""Tests for the Quality Gate modules.
|
|
|
|
|
|
|
|
|
|
Tests for:
|
|
|
|
|
- ci_automation_gate.py: linting, function length, auto-fix, counters
|
|
|
|
|
- task_gate.py: pre/post task gate logic, lane checking, filter tags
|
|
|
|
|
|
|
|
|
|
Refs: #629
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
import tempfile
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
|
|
|
|
|
|
# Add scripts/ to path
|
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
|
|
|
|
|
|
|
|
|
|
from ci_automation_gate import QualityGate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ===========================================================================
|
|
|
|
|
# CI AUTOMATION GATE TESTS
|
|
|
|
|
# ===========================================================================
|
|
|
|
|
|
|
|
|
|
# -- helpers ---------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def _write_file(dirpath, relpath, content):
|
|
|
|
|
"""Write a file in a temp directory and return its Path."""
|
|
|
|
|
p = Path(dirpath) / relpath
|
|
|
|
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
p.write_text(content)
|
|
|
|
|
return p
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _run_gate_on_file(dirpath, relpath, content, fix=False):
|
|
|
|
|
"""Write a file, run QualityGate on it, return the gate instance."""
|
|
|
|
|
p = _write_file(dirpath, relpath, content)
|
|
|
|
|
gate = QualityGate(fix=fix)
|
|
|
|
|
gate.check_file(p)
|
|
|
|
|
return gate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- trailing whitespace ---------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def test_trailing_whitespace_warns():
|
|
|
|
|
"""Lines with trailing whitespace should produce a warning."""
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
gate = _run_gate_on_file(tmp, "test.py", "x = 1 \ny = 2\n")
|
|
|
|
|
assert gate.warnings >= 1, "Expected warning for trailing whitespace"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_trailing_whitespace_fixes():
|
|
|
|
|
"""With fix=True, trailing whitespace should be removed."""
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
p = _write_file(tmp, "test.py", "x = 1 \ny = 2\n")
|
|
|
|
|
gate = QualityGate(fix=True)
|
|
|
|
|
gate.check_file(p)
|
|
|
|
|
fixed = p.read_text()
|
|
|
|
|
assert "x = 1 \n" not in fixed, "Trailing whitespace should be removed"
|
|
|
|
|
assert fixed == "x = 1\ny = 2\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_clean_file_no_warnings():
|
|
|
|
|
"""A clean file should produce no warnings."""
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
gate = _run_gate_on_file(tmp, "test.py", "x = 1\ny = 2\n")
|
|
|
|
|
assert gate.warnings == 0
|
|
|
|
|
assert gate.failures == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- missing final newline -------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def test_missing_final_newline_warns():
|
|
|
|
|
"""File without trailing newline should warn."""
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
gate = _run_gate_on_file(tmp, "test.py", "x = 1")
|
|
|
|
|
assert gate.warnings >= 1, "Expected warning for missing final newline"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_missing_final_newline_fixed():
|
|
|
|
|
"""With fix=True, missing final newline should be added."""
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
p = _write_file(tmp, "test.py", "x = 1")
|
|
|
|
|
gate = QualityGate(fix=True)
|
|
|
|
|
gate.check_file(p)
|
|
|
|
|
fixed = p.read_text()
|
|
|
|
|
assert fixed.endswith("\n"), "Fixed file should end with newline"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- function length (JS/TS) -----------------------------------------------
|
|
|
|
|
|
|
|
|
|
def test_short_function_passes():
|
|
|
|
|
"""A short JS function should not warn or fail."""
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
code = "function hello() {\n return 1;\n}\n"
|
|
|
|
|
gate = _run_gate_on_file(tmp, "test.js", code)
|
|
|
|
|
assert gate.failures == 0
|
|
|
|
|
assert gate.warnings == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_medium_function_warns():
|
|
|
|
|
"""JS function over 20 lines should warn."""
|
|
|
|
|
body = "\n".join(f" console.log({i});" for i in range(22))
|
|
|
|
|
code = f"function big() {{\n{body}\n}}\n"
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
gate = _run_gate_on_file(tmp, "test.js", code)
|
|
|
|
|
assert gate.warnings >= 1, "Expected warning for function over 20 lines"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_long_function_fails():
|
|
|
|
|
"""JS function over 50 lines should fail."""
|
|
|
|
|
body = "\n".join(f" console.log({i});" for i in range(52))
|
|
|
|
|
code = f"function huge() {{\n{body}\n}}\n"
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
gate = _run_gate_on_file(tmp, "test.js", code)
|
|
|
|
|
assert gate.failures >= 1, "Expected failure for function over 50 lines"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_python_function_length_not_checked():
|
|
|
|
|
"""Python functions should not be checked by the JS regex."""
|
|
|
|
|
body = "\n".join(f" print({i})" for i in range(60))
|
|
|
|
|
code = f"def huge():\n{body}\n"
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
gate = _run_gate_on_file(tmp, "test.py", code)
|
|
|
|
|
assert gate.failures == 0, "Python functions should not trigger JS length check"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- file type filtering ---------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def test_non_code_file_skipped():
|
|
|
|
|
"""Non-code files (.md, .json, .txt) should be skipped."""
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
gate = _run_gate_on_file(tmp, "README.md", "# Title \ntrailing ws\n")
|
|
|
|
|
assert gate.warnings == 0, "Markdown files should be skipped"
|
|
|
|
|
assert gate.failures == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_typescript_checked():
|
|
|
|
|
"""TypeScript files should be checked."""
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
gate = _run_gate_on_file(tmp, "test.ts", "x = 1 \n")
|
|
|
|
|
assert gate.warnings >= 1, "TypeScript files should be checked"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- directory traversal ---------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def test_run_scans_directory():
|
|
|
|
|
"""Gate.run() should scan all files in a directory tree."""
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
_write_file(tmp, "clean.py", "x = 1\n")
|
|
|
|
|
_write_file(tmp, "dirty.js", "x = 1 \n")
|
|
|
|
|
_write_file(tmp, "sub/nested.ts", "y = 2 \n")
|
|
|
|
|
gate = QualityGate()
|
|
|
|
|
gate.run(tmp)
|
|
|
|
|
assert gate.warnings >= 2, "Should find trailing whitespace in both dirty files"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_skips_node_modules():
|
|
|
|
|
"""Gate.run() should skip node_modules directories."""
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
_write_file(tmp, "clean.py", "x = 1\n")
|
|
|
|
|
_write_file(tmp, "node_modules/pkg/index.js", "x = 1 \n")
|
|
|
|
|
gate = QualityGate()
|
|
|
|
|
gate.run(tmp)
|
|
|
|
|
assert gate.warnings == 0, "node_modules should be skipped"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_skips_git_dir():
|
|
|
|
|
"""Gate.run() should skip .git directories."""
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
_write_file(tmp, "clean.py", "x = 1\n")
|
|
|
|
|
_write_file(tmp, ".git/hooks/pre-commit", "x = 1 \n")
|
|
|
|
|
gate = QualityGate()
|
|
|
|
|
gate.run(tmp)
|
|
|
|
|
assert gate.warnings == 0, ".git should be skipped"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- exit code -------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def test_failures_cause_exit_code_1():
|
|
|
|
|
"""Gate with failures should exit with code 1."""
|
|
|
|
|
import subprocess
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
body = "\n".join(f" console.log({i});" for i in range(52))
|
|
|
|
|
_write_file(tmp, "huge.js", f"function f() {{\n{body}\n}}\n")
|
|
|
|
|
r = subprocess.run(
|
|
|
|
|
[sys.executable, str(Path(__file__).resolve().parent.parent / "scripts" / "ci_automation_gate.py"), tmp],
|
|
|
|
|
capture_output=True, text=True
|
|
|
|
|
)
|
|
|
|
|
assert r.returncode == 1, f"Expected exit 1, got {r.returncode}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_clean_directory_exits_0():
|
|
|
|
|
"""Gate on clean directory should exit 0."""
|
|
|
|
|
import subprocess
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
_write_file(tmp, "clean.py", "x = 1\ny = 2\n")
|
|
|
|
|
r = subprocess.run(
|
|
|
|
|
[sys.executable, str(Path(__file__).resolve().parent.parent / "scripts" / "ci_automation_gate.py"), tmp],
|
|
|
|
|
capture_output=True, text=True
|
|
|
|
|
)
|
|
|
|
|
assert r.returncode == 0, f"Expected exit 0, got {r.returncode}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ===========================================================================
|
|
|
|
|
# TASK GATE TESTS
|
|
|
|
|
# ===========================================================================
|
|
|
|
|
|
|
|
|
|
# Import task_gate functions directly — test the pure logic
|
|
|
|
|
from task_gate import check_agent_lane, FILTER_TAGS, AGENT_USERNAMES
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- filter tags -----------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def test_epic_tag_filtered():
|
|
|
|
|
"""Issues with [EPIC] tag should be filtered."""
|
|
|
|
|
title = "[EPIC] Build the thing"
|
|
|
|
|
for tag in FILTER_TAGS:
|
|
|
|
|
tag_clean = tag.upper().replace("[", "").replace("]", "")
|
|
|
|
|
if tag_clean in title.upper():
|
|
|
|
|
return # Found
|
|
|
|
|
assert False, "EPIC tag should be detected by FILTER_TAGS"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_permanent_tag_filtered():
|
|
|
|
|
"""Issues with [DO NOT CLOSE] tag should be filtered."""
|
|
|
|
|
title = "[DO NOT CLOSE] Keep this open forever"
|
|
|
|
|
title_upper = title.upper()
|
|
|
|
|
matched = any(
|
|
|
|
|
tag.upper().replace("[", "").replace("]", "") in title_upper
|
|
|
|
|
for tag in FILTER_TAGS
|
|
|
|
|
)
|
|
|
|
|
assert matched, "[DO NOT CLOSE] should be filtered"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_normal_title_not_filtered():
|
|
|
|
|
"""Normal issue titles should not be filtered."""
|
|
|
|
|
title = "Fix the login bug in auth.py"
|
|
|
|
|
title_upper = title.upper()
|
|
|
|
|
matched = any(
|
|
|
|
|
tag.upper().replace("[", "").replace("]", "") in title_upper
|
|
|
|
|
for tag in FILTER_TAGS
|
|
|
|
|
)
|
|
|
|
|
assert not matched, "Normal title should not be filtered"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_morning_report_filtered():
|
|
|
|
|
"""[MORNING REPORT] issues should be filtered."""
|
|
|
|
|
title = "[MORNING REPORT] Fleet status 2026-04-13"
|
|
|
|
|
title_upper = title.upper()
|
|
|
|
|
matched = any(
|
|
|
|
|
tag.upper().replace("[", "").replace("]", "") in title_upper
|
|
|
|
|
for tag in FILTER_TAGS
|
|
|
|
|
)
|
|
|
|
|
assert matched, "[MORNING REPORT] should be filtered"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- agent lane checker ----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def test_lane_check_no_config():
|
|
|
|
|
"""With no lane config, lane check should pass."""
|
|
|
|
|
ok, msg = check_agent_lane("groq", "Fix bug", [], {})
|
|
|
|
|
assert ok
|
|
|
|
|
assert "No lane config" in msg
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_lane_check_agent_not_in_config():
|
|
|
|
|
"""Agent not in lane config should pass."""
|
|
|
|
|
lanes = {"ezra": ["docs"]}
|
|
|
|
|
ok, msg = check_agent_lane("groq", "Fix bug", [], lanes)
|
|
|
|
|
assert ok
|
|
|
|
|
assert "No lanes defined" in msg
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_lane_check_agent_in_config():
|
|
|
|
|
"""Agent in lane config should return their lanes."""
|
|
|
|
|
lanes = {"groq": ["code", "infra"]}
|
|
|
|
|
ok, msg = check_agent_lane("groq", "Fix bug", [], lanes)
|
|
|
|
|
assert ok
|
|
|
|
|
assert "groq" in msg
|
|
|
|
|
assert "code" in msg
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- agent usernames -------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
def test_known_agents_in_usernames():
|
|
|
|
|
"""Core agent usernames should be registered."""
|
|
|
|
|
assert "groq" in AGENT_USERNAMES
|
|
|
|
|
assert "ezra" in AGENT_USERNAMES
|
|
|
|
|
assert "bezalel" in AGENT_USERNAMES
|
|
|
|
|
assert "timmy" in AGENT_USERNAMES
|
|
|
|
|
assert "codex-agent" in AGENT_USERNAMES
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- pre-task gate (mocked API) -------------------------------------------
|
|
|
|
|
|
|
|
|
|
def test_pre_task_gate_issue_not_found():
|
|
|
|
|
"""Pre-task gate should fail if issue doesn't exist."""
|
|
|
|
|
from task_gate import pre_task_gate
|
|
|
|
|
with patch("task_gate.gitea_get", return_value=None):
|
|
|
|
|
passed, msgs = pre_task_gate("timmy-config", 99999, "groq")
|
|
|
|
|
assert not passed
|
|
|
|
|
assert any("not found" in m for m in msgs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pre_task_gate_filter_tag_blocks():
|
|
|
|
|
"""Pre-task gate should block filtered issues."""
|
|
|
|
|
from task_gate import pre_task_gate
|
|
|
|
|
mock_issue = {
|
|
|
|
|
"title": "[EPIC] Big thing",
|
|
|
|
|
"assignees": [],
|
|
|
|
|
"labels": [],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def mock_gitea_get(path):
|
|
|
|
|
if "issues/100" in path:
|
|
|
|
|
return mock_issue
|
|
|
|
|
if "branches" in path:
|
|
|
|
|
return []
|
|
|
|
|
if "pulls" in path:
|
|
|
|
|
return []
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
with patch("task_gate.gitea_get", side_effect=mock_gitea_get):
|
|
|
|
|
passed, msgs = pre_task_gate("timmy-config", 100, "groq")
|
|
|
|
|
assert not passed
|
|
|
|
|
assert any("filter" in m.lower() for m in msgs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pre_task_gate_assigned_agent_blocks():
|
|
|
|
|
"""Pre-task gate should block issues assigned to other agents."""
|
|
|
|
|
from task_gate import pre_task_gate
|
|
|
|
|
mock_issue = {
|
|
|
|
|
"title": "Fix bug",
|
|
|
|
|
"assignees": [{"login": "ezra"}],
|
|
|
|
|
"labels": [],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def mock_gitea_get(path):
|
|
|
|
|
if "issues/100" in path:
|
|
|
|
|
return mock_issue
|
|
|
|
|
if "branches" in path:
|
|
|
|
|
return []
|
|
|
|
|
if "pulls" in path:
|
|
|
|
|
return []
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
with patch("task_gate.gitea_get", side_effect=mock_gitea_get):
|
|
|
|
|
passed, msgs = pre_task_gate("timmy-config", 100, "groq")
|
|
|
|
|
assert not passed
|
|
|
|
|
assert any("Already assigned" in m for m in msgs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pre_task_gate_existing_pr_blocks():
|
|
|
|
|
"""Pre-task gate should block issues with existing PRs."""
|
|
|
|
|
from task_gate import pre_task_gate
|
|
|
|
|
mock_issue = {
|
|
|
|
|
"title": "Fix bug",
|
|
|
|
|
"assignees": [],
|
|
|
|
|
"labels": [],
|
|
|
|
|
}
|
|
|
|
|
mock_prs = [{"number": 50, "title": "Fix for #100", "body": "Closes #100"}]
|
|
|
|
|
|
|
|
|
|
def mock_gitea_get(path):
|
|
|
|
|
if "issues/100" in path:
|
|
|
|
|
return mock_issue
|
|
|
|
|
if "branches" in path:
|
|
|
|
|
return []
|
|
|
|
|
if "pulls" in path:
|
|
|
|
|
return mock_prs
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
with patch("task_gate.gitea_get", side_effect=mock_gitea_get):
|
|
|
|
|
passed, msgs = pre_task_gate("timmy-config", 100, "groq")
|
|
|
|
|
assert not passed
|
|
|
|
|
assert any("Open PR" in m for m in msgs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pre_task_gate_clean_passes():
|
|
|
|
|
"""Pre-task gate should pass for clean issues."""
|
|
|
|
|
from task_gate import pre_task_gate
|
|
|
|
|
|
|
|
|
|
def mock_gitea_get(path):
|
|
|
|
|
if "issues/100" in path:
|
|
|
|
|
return {"title": "Fix bug", "assignees": [], "labels": []}
|
|
|
|
|
if "branches" in path:
|
|
|
|
|
return []
|
|
|
|
|
if "pulls" in path:
|
|
|
|
|
return []
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
with patch("task_gate.gitea_get", side_effect=mock_gitea_get):
|
|
|
|
|
passed, msgs = pre_task_gate("timmy-config", 100, "groq")
|
|
|
|
|
assert passed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -- post-task gate (mocked API) ------------------------------------------
|
|
|
|
|
|
|
|
|
|
def test_post_task_gate_missing_branch():
|
|
|
|
|
"""Post-task gate should fail if branch doesn't exist."""
|
|
|
|
|
from task_gate import post_task_gate
|
|
|
|
|
with patch("task_gate.gitea_get", return_value=None):
|
|
|
|
|
passed, msgs = post_task_gate("timmy-config", 100, "groq", "groq/fix-100")
|
|
|
|
|
assert not passed
|
|
|
|
|
assert any("does not exist" in m for m in msgs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_post_task_gate_no_agent_prefix_warns():
|
|
|
|
|
"""Post-task gate should warn if branch doesn't start with agent name."""
|
|
|
|
|
from task_gate import post_task_gate
|
|
|
|
|
|
|
|
|
|
def mock_gitea_get(path):
|
|
|
|
|
if "branches/fix-100" in path:
|
|
|
|
|
return {"name": "fix-100"}
|
|
|
|
|
if "compare" in path:
|
|
|
|
|
return {"commits": [{"id": "abc"}], "diff_files": ["file.py"]}
|
|
|
|
|
if "pulls" in path:
|
|
|
|
|
return []
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
with patch("task_gate.gitea_get", side_effect=mock_gitea_get):
|
|
|
|
|
passed, msgs = post_task_gate("timmy-config", 100, "groq", "fix-100")
|
|
|
|
|
assert passed # Warning, not failure
|
|
|
|
|
assert any("doesn't start with agent" in m or "convention" in m for m in msgs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_post_task_gate_no_commits_fails():
|
|
|
|
|
"""Post-task gate should fail if branch has no commits ahead of main."""
|
|
|
|
|
from task_gate import post_task_gate
|
|
|
|
|
|
|
|
|
|
def mock_gitea_get(path):
|
|
|
|
|
if "branches/" in path:
|
|
|
|
|
return {"name": "groq/fix-100"}
|
|
|
|
|
if "compare" in path:
|
|
|
|
|
return {"commits": [], "diff_files": []}
|
|
|
|
|
if "pulls" in path:
|
|
|
|
|
return []
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
with patch("task_gate.gitea_get", side_effect=mock_gitea_get):
|
|
|
|
|
passed, msgs = post_task_gate("timmy-config", 100, "groq", "groq/fix-100")
|
|
|
|
|
assert not passed
|
|
|
|
|
assert any("no commits" in m.lower() for m in msgs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ===========================================================================
|
|
|
|
|
# INTEGRATION: gate on real script files
|
|
|
|
|
# ===========================================================================
|
|
|
|
|
|
|
|
|
|
def test_ci_gate_on_actual_task_gate():
|
|
|
|
|
"""Run QualityGate on task_gate.py itself — should pass."""
|
|
|
|
|
gate_path = Path(__file__).resolve().parent.parent / "scripts" / "task_gate.py"
|
|
|
|
|
if gate_path.exists():
|
|
|
|
|
gate = QualityGate()
|
|
|
|
|
gate.check_file(gate_path)
|
|
|
|
|
assert gate.failures == 0, f"task_gate.py should pass quality gate, got {gate.failures} failures"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_ci_gate_on_actual_ci_automation_gate():
|
|
|
|
|
"""Run QualityGate on ci_automation_gate.py itself — should pass."""
|
|
|
|
|
gate_path = Path(__file__).resolve().parent.parent / "scripts" / "ci_automation_gate.py"
|
|
|
|
|
if gate_path.exists():
|
|
|
|
|
gate = QualityGate()
|
|
|
|
|
gate.check_file(gate_path)
|
|
|
|
|
assert gate.failures == 0, f"ci_automation_gate.py should pass quality gate, got {gate.failures} failures"
|