Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fe22dbfcc9 |
165
tests/test_quality_gates.py
Normal file
165
tests/test_quality_gates.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
"""Tests for CI Automation Gate and Task Gate.
|
||||||
|
|
||||||
|
Tests the quality gate infrastructure:
|
||||||
|
- ci_automation_gate.py: function length, linting, trailing whitespace
|
||||||
|
- task_gate.py: pre/post task validation
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
|
||||||
|
|
||||||
|
from ci_automation_gate import QualityGate
|
||||||
|
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# QualityGate — ci_automation_gate.py
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
class TestQualityGateLinting:
|
||||||
|
"""Test trailing whitespace and final newline checks."""
|
||||||
|
|
||||||
|
def test_clean_file_passes(self, tmp_path):
|
||||||
|
f = tmp_path / "clean.py"
|
||||||
|
f.write_text("def foo():\n return 1\n")
|
||||||
|
gate = QualityGate(fix=False)
|
||||||
|
gate.check_file(f)
|
||||||
|
assert gate.failures == 0
|
||||||
|
assert gate.warnings == 0
|
||||||
|
|
||||||
|
def test_trailing_whitespace_warns(self, tmp_path):
|
||||||
|
f = tmp_path / "messy.py"
|
||||||
|
f.write_text("def foo(): \n return 1\n")
|
||||||
|
gate = QualityGate(fix=False)
|
||||||
|
gate.check_file(f)
|
||||||
|
assert gate.warnings >= 1
|
||||||
|
assert gate.failures == 0
|
||||||
|
|
||||||
|
def test_missing_final_newline_warns(self, tmp_path):
|
||||||
|
f = tmp_path / "no_newline.py"
|
||||||
|
f.write_text("def foo():\n return 1")
|
||||||
|
gate = QualityGate(fix=False)
|
||||||
|
gate.check_file(f)
|
||||||
|
assert gate.warnings >= 1
|
||||||
|
|
||||||
|
def test_fix_mode_cleans_whitespace(self, tmp_path):
|
||||||
|
f = tmp_path / "messy.py"
|
||||||
|
f.write_text("def foo(): \n return 1\n")
|
||||||
|
gate = QualityGate(fix=True)
|
||||||
|
gate.check_file(f)
|
||||||
|
fixed = f.read_text()
|
||||||
|
assert " \n" not in fixed # trailing spaces removed
|
||||||
|
assert fixed.endswith("\n")
|
||||||
|
|
||||||
|
def test_fix_mode_adds_final_newline(self, tmp_path):
|
||||||
|
f = tmp_path / "no_newline.py"
|
||||||
|
f.write_text("def foo():\n return 1")
|
||||||
|
gate = QualityGate(fix=True)
|
||||||
|
gate.check_file(f)
|
||||||
|
fixed = f.read_text()
|
||||||
|
assert fixed.endswith("\n")
|
||||||
|
|
||||||
|
|
||||||
|
class TestQualityGateFunctionLength:
|
||||||
|
"""Test function length detection for JS/TS files."""
|
||||||
|
|
||||||
|
def test_short_function_passes(self, tmp_path):
|
||||||
|
f = tmp_path / "short.js"
|
||||||
|
f.write_text("function foo() {\n return 1;\n}\n")
|
||||||
|
gate = QualityGate(fix=False)
|
||||||
|
gate.check_file(f)
|
||||||
|
assert gate.failures == 0
|
||||||
|
|
||||||
|
def test_long_function_warns(self, tmp_path):
|
||||||
|
body = "\n".join(f" console.log({i});" for i in range(25))
|
||||||
|
f = tmp_path / "long.js"
|
||||||
|
f.write_text(f"function foo() {{\n{body}\n}}\n")
|
||||||
|
gate = QualityGate(fix=False)
|
||||||
|
gate.check_file(f)
|
||||||
|
assert gate.warnings >= 1
|
||||||
|
|
||||||
|
def test_very_long_function_fails(self, tmp_path):
|
||||||
|
body = "\n".join(f" console.log({i});" for i in range(55))
|
||||||
|
f = tmp_path / "huge.js"
|
||||||
|
f.write_text(f"function foo() {{\n{body}\n}}\n")
|
||||||
|
gate = QualityGate(fix=False)
|
||||||
|
gate.check_file(f)
|
||||||
|
assert gate.failures >= 1
|
||||||
|
|
||||||
|
def test_python_files_skip_length_check(self, tmp_path):
|
||||||
|
"""Python files should not trigger JS function length regex."""
|
||||||
|
body = "\n".join(f" x = {i}" for i in range(60))
|
||||||
|
f = tmp_path / "long.py"
|
||||||
|
f.write_text(f"def foo():\n{body}\n return x\n")
|
||||||
|
gate = QualityGate(fix=False)
|
||||||
|
gate.check_file(f)
|
||||||
|
assert gate.failures == 0 # JS regex won't match Python
|
||||||
|
|
||||||
|
def test_non_code_files_skipped(self, tmp_path):
|
||||||
|
f = tmp_path / "readme.md"
|
||||||
|
f.write_text("# Hello \n\nSome text")
|
||||||
|
gate = QualityGate(fix=False)
|
||||||
|
gate.check_file(f)
|
||||||
|
# .md files should be skipped entirely
|
||||||
|
assert gate.failures == 0
|
||||||
|
assert gate.warnings == 0
|
||||||
|
|
||||||
|
|
||||||
|
class TestQualityGateRun:
|
||||||
|
"""Test the full directory scan."""
|
||||||
|
|
||||||
|
def test_run_exits_1_on_failure(self, tmp_path):
|
||||||
|
body = "\n".join(f" console.log({i});" for i in range(55))
|
||||||
|
f = tmp_path / "huge.js"
|
||||||
|
f.write_text(f"function foo() {{\n{body}\n}}\n")
|
||||||
|
gate = QualityGate(fix=False)
|
||||||
|
with pytest.raises(SystemExit) as exc:
|
||||||
|
gate.run(str(tmp_path))
|
||||||
|
assert exc.value.code == 1
|
||||||
|
|
||||||
|
def test_run_exits_0_on_clean(self, tmp_path):
|
||||||
|
f = tmp_path / "clean.py"
|
||||||
|
f.write_text("x = 1\n")
|
||||||
|
gate = QualityGate(fix=False)
|
||||||
|
gate.run(str(tmp_path)) # should not raise
|
||||||
|
assert gate.failures == 0
|
||||||
|
|
||||||
|
def test_run_skips_node_modules(self, tmp_path):
|
||||||
|
nm = tmp_path / "node_modules"
|
||||||
|
nm.mkdir()
|
||||||
|
bad = nm / "huge.js"
|
||||||
|
body = "\n".join(f" console.log({i});" for i in range(55))
|
||||||
|
bad.write_text(f"function foo() {{\n{body}\n}}\n")
|
||||||
|
gate = QualityGate(fix=False)
|
||||||
|
gate.run(str(tmp_path))
|
||||||
|
assert gate.failures == 0 # node_modules skipped
|
||||||
|
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Task Gate — task_gate.py (integration-level tests)
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
class TestTaskGateImports:
|
||||||
|
"""Verify task_gate module is importable."""
|
||||||
|
|
||||||
|
def test_import_task_gate(self):
|
||||||
|
from task_gate import FILTER_TAGS, AGENT_USERNAMES
|
||||||
|
assert isinstance(FILTER_TAGS, list)
|
||||||
|
assert len(FILTER_TAGS) > 0
|
||||||
|
assert isinstance(AGENT_USERNAMES, set)
|
||||||
|
assert "timmy" in AGENT_USERNAMES
|
||||||
|
|
||||||
|
def test_filter_tags_contain_epic(self):
|
||||||
|
from task_gate import FILTER_TAGS
|
||||||
|
assert any("EPIC" in tag for tag in FILTER_TAGS)
|
||||||
|
|
||||||
|
def test_filter_tags_contain_permanent(self):
|
||||||
|
from task_gate import FILTER_TAGS
|
||||||
|
assert any("PERMANENT" in tag for tag in FILTER_TAGS)
|
||||||
Reference in New Issue
Block a user