Files
agent-desktop/scripts/check_channel_contracts.py

107 lines
3.1 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Check that all Channel subclasses have Contract test coverage.
Usage:
python scripts/check_channel_contracts.py
Notes:
- Static scan, no dependencies required (including pytest)
- Compares Channel classes in src/ with test files in tests/contract/
CI Integration (future):
- Run on PR to ensure new Channels have contract tests
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
def get_all_channel_classes() -> set[str]:
"""Scan all Channel subclasses from source code (non-runtime)."""
src_dir = Path(__file__).parent.parent / "src"
channels_dir = src_dir / "qwenpaw" / "app" / "channels"
classes = set()
for channel_file in channels_dir.rglob("channel.py"):
content = channel_file.read_text()
# Match class XXXChannel(BaseChannel)
matches = re.findall(
r"class\s+(\w+Channel)\s*\(\s*BaseChannel\s*\)",
content,
)
classes.update(matches)
return classes
def get_tested_channels_from_content() -> set[str]:
"""Read actual tested channel class names from test files."""
contract_dir = (
Path(__file__).parent.parent / "tests" / "contract" / "channels"
)
tested = set()
if not contract_dir.exists():
return tested
for test_file in contract_dir.glob("test_*_contract.py"):
content = test_file.read_text()
# Find from XXXXX import YYYYChannel
# Or find in create_instance return XXXXChannel(...)
# Match common channel import patterns
import_matches = re.findall(
r"from\s+[\w.]+\s+import\s+(\w+Channel)",
content,
)
# Also directly instantiated in create_instance
instance_matches = re.findall(
r"return\s+(\w+Channel)\s*\(",
content,
)
tested.update(import_matches)
tested.update(instance_matches)
return tested
def main() -> int:
all_channels = get_all_channel_classes()
tested = get_tested_channels_from_content()
untested = all_channels - tested
print("\n📊 Channel Contract Coverage")
print(f" Total channels: {len(all_channels)}")
print(f" With tests: {len(tested)}")
print(f" Missing: {len(untested)}")
if tested:
print(f"\n✅ Tested: {', '.join(sorted(tested))}")
if untested:
print("\n❌ Missing contract tests:")
for name in sorted(untested):
# Convert to snake_case for filename suggestion
snake = (
re.sub(r"(?<!^)(?=[A-Z])", "_", name)
.lower()
.replace("_channel", "")
)
print(f" - {name}")
print(f" 👉 tests/contract/channels/test_{snake}_contract.py")
print(
"\n💡 Based on existing patterns, copy "
"tests/contract/channels/test_console_contract.py",
)
return 1
print("\n🎉 All channels have contract tests!")
return 0
if __name__ == "__main__":
sys.exit(main())