refactor: voice-service entfernt (verschoben nach breakpilot-core)

This commit is contained in:
Benjamin Boenisch
2026-02-15 13:26:07 +01:00
parent d075973a08
commit 5ff2c8bad4
59 changed files with 5 additions and 12874 deletions

View File

@@ -1,407 +0,0 @@
"""
Tests for BQAS Notifier Module
Tests for the local notification system that replaces GitHub Actions notifications.
"""
import json
import os
import sys
import tempfile
from datetime import datetime
from pathlib import Path
from unittest.mock import patch, MagicMock
import subprocess
import pytest
# Import notifier directly to avoid __init__.py dependency issues
import importlib.util
spec = importlib.util.spec_from_file_location(
"notifier",
Path(__file__).parent.parent.parent / "bqas" / "notifier.py"
)
notifier_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(notifier_module)
BQASNotifier = notifier_module.BQASNotifier
Notification = notifier_module.Notification
NotificationConfig = notifier_module.NotificationConfig
class TestNotificationConfig:
"""Tests for NotificationConfig dataclass."""
def test_default_config(self):
"""Test default configuration values."""
config = NotificationConfig()
assert config.enabled is True
assert config.desktop_enabled is True
assert config.slack_enabled is False
assert config.email_enabled is False
assert config.log_file == "/var/log/bqas/notifications.log"
def test_config_from_env(self):
"""Test configuration from environment variables."""
with patch.dict(os.environ, {
"BQAS_NOTIFY_ENABLED": "true",
"BQAS_NOTIFY_DESKTOP": "false",
"BQAS_NOTIFY_SLACK": "true",
"BQAS_SLACK_WEBHOOK": "https://hooks.slack.com/test",
"BQAS_SLACK_CHANNEL": "#test-channel",
}):
config = NotificationConfig.from_env()
assert config.enabled is True
assert config.desktop_enabled is False
assert config.slack_enabled is True
assert config.slack_webhook_url == "https://hooks.slack.com/test"
assert config.slack_channel == "#test-channel"
def test_config_disabled(self):
"""Test disabled notification configuration."""
with patch.dict(os.environ, {"BQAS_NOTIFY_ENABLED": "false"}):
config = NotificationConfig.from_env()
assert config.enabled is False
class TestNotification:
"""Tests for Notification dataclass."""
def test_notification_creation(self):
"""Test creating a notification."""
notification = Notification(
status="success",
message="All tests passed",
details="Golden: 97/97, RAG: 26/26",
)
assert notification.status == "success"
assert notification.message == "All tests passed"
assert notification.details == "Golden: 97/97, RAG: 26/26"
assert notification.source == "bqas"
assert notification.timestamp # Should be auto-generated
def test_notification_timestamp_auto(self):
"""Test that timestamp is auto-generated."""
notification = Notification(status="failure", message="Test")
# Timestamp should be in ISO format
datetime.fromisoformat(notification.timestamp)
def test_notification_statuses(self):
"""Test different notification statuses."""
for status in ["success", "failure", "warning"]:
notification = Notification(status=status, message="Test")
assert notification.status == status
class TestBQASNotifier:
"""Tests for BQASNotifier class."""
def test_notifier_creation(self):
"""Test creating a notifier instance."""
notifier = BQASNotifier()
assert notifier.config is not None
def test_notifier_with_config(self):
"""Test creating notifier with custom config."""
config = NotificationConfig(
desktop_enabled=False,
slack_enabled=True,
slack_webhook_url="https://test.webhook",
)
notifier = BQASNotifier(config=config)
assert notifier.config.desktop_enabled is False
assert notifier.config.slack_enabled is True
def test_notify_disabled(self):
"""Test that notify returns False when disabled."""
config = NotificationConfig(enabled=False)
notifier = BQASNotifier(config=config)
notification = Notification(status="success", message="Test")
result = notifier.notify(notification)
assert result is False
def test_log_notification(self):
"""Test logging notifications to file."""
with tempfile.NamedTemporaryFile(mode='w', suffix='.log', delete=False) as f:
log_path = f.name
try:
config = NotificationConfig(
enabled=True,
desktop_enabled=False,
log_file=log_path,
)
notifier = BQASNotifier(config=config)
notification = Notification(
status="success",
message="Test message",
details="Test details",
)
notifier._log_notification(notification)
# Check log file contents
with open(log_path) as f:
log_content = f.read()
log_entry = json.loads(log_content.strip())
assert log_entry["status"] == "success"
assert log_entry["message"] == "Test message"
assert log_entry["details"] == "Test details"
assert "logged_at" in log_entry
finally:
os.unlink(log_path)
@patch("subprocess.run")
def test_send_desktop_success(self, mock_run):
"""Test sending desktop notification."""
mock_run.return_value = MagicMock(returncode=0)
config = NotificationConfig(desktop_enabled=True)
notifier = BQASNotifier(config=config)
notification = Notification(status="success", message="Test")
result = notifier._send_desktop(notification)
assert result is True
mock_run.assert_called_once()
# Check osascript was called
call_args = mock_run.call_args
assert call_args[0][0][0] == "osascript"
@patch("subprocess.run")
def test_send_desktop_failure_sound(self, mock_run):
"""Test that failure notifications use different sound."""
mock_run.return_value = MagicMock(returncode=0)
config = NotificationConfig(
desktop_enabled=True,
desktop_sound_failure="Basso",
)
notifier = BQASNotifier(config=config)
notification = Notification(status="failure", message="Test failed")
notifier._send_desktop(notification)
# Check that Basso sound was used
call_args = mock_run.call_args[0][0]
assert "Basso" in call_args[2]
@patch("urllib.request.urlopen")
def test_send_slack(self, mock_urlopen):
"""Test sending Slack notification."""
mock_response = MagicMock()
mock_response.status = 200
mock_urlopen.return_value.__enter__.return_value = mock_response
config = NotificationConfig(
slack_enabled=True,
slack_webhook_url="https://hooks.slack.com/test",
slack_channel="#test",
)
notifier = BQASNotifier(config=config)
notification = Notification(
status="failure",
message="Tests failed",
details="INT-005, INT-012",
)
result = notifier._send_slack(notification)
assert result is True
mock_urlopen.assert_called_once()
def test_get_title(self):
"""Test title generation based on status."""
assert BQASNotifier._get_title("success") == "BQAS Erfolgreich"
assert BQASNotifier._get_title("failure") == "BQAS Fehlgeschlagen"
assert BQASNotifier._get_title("warning") == "BQAS Warnung"
assert BQASNotifier._get_title("unknown") == "BQAS"
def test_get_emoji(self):
"""Test emoji generation for Slack."""
assert BQASNotifier._get_emoji("success") == ":white_check_mark:"
assert BQASNotifier._get_emoji("failure") == ":x:"
assert BQASNotifier._get_emoji("warning") == ":warning:"
def test_get_color(self):
"""Test color generation for Slack attachments."""
assert BQASNotifier._get_color("success") == "good"
assert BQASNotifier._get_color("failure") == "danger"
assert BQASNotifier._get_color("warning") == "warning"
class TestNotifierIntegration:
"""Integration tests for the notifier system."""
def test_full_notification_flow(self):
"""Test complete notification flow with logging only."""
with tempfile.NamedTemporaryFile(mode='w', suffix='.log', delete=False) as f:
log_path = f.name
try:
config = NotificationConfig(
enabled=True,
desktop_enabled=False, # Disable for CI
slack_enabled=False,
email_enabled=False,
log_file=log_path,
)
notifier = BQASNotifier(config=config)
# Success notification
success_notif = Notification(
status="success",
message="All BQAS tests passed",
details="Golden: 97/97, RAG: 26/26, Synthetic: 50/50",
)
result = notifier.notify(success_notif)
assert result is True
# Failure notification
failure_notif = Notification(
status="failure",
message="3 tests failed",
details="INT-005, INT-012, RAG-003",
)
result = notifier.notify(failure_notif)
assert result is True
# Check both notifications were logged
with open(log_path) as f:
lines = f.readlines()
assert len(lines) == 2
first = json.loads(lines[0])
assert first["status"] == "success"
second = json.loads(lines[1])
assert second["status"] == "failure"
finally:
os.unlink(log_path)
def test_notification_with_special_characters(self):
"""Test notifications with special characters in message."""
with tempfile.NamedTemporaryFile(mode='w', suffix='.log', delete=False) as f:
log_path = f.name
try:
config = NotificationConfig(
enabled=True,
desktop_enabled=False,
log_file=log_path,
)
notifier = BQASNotifier(config=config)
notification = Notification(
status="warning",
message='Test mit "Anführungszeichen" und Umlauten: äöü',
details="Spezielle Zeichen: <>&'",
)
result = notifier.notify(notification)
assert result is True
# Verify logged correctly
with open(log_path) as f:
log_entry = json.loads(f.read().strip())
assert "Anführungszeichen" in log_entry["message"]
assert "äöü" in log_entry["message"]
finally:
os.unlink(log_path)
class TestSchedulerScripts:
"""Tests for scheduler shell scripts."""
def test_run_bqas_script_exists(self):
"""Test that run_bqas.sh exists and is executable."""
script_path = Path(__file__).parent.parent.parent / "scripts" / "run_bqas.sh"
assert script_path.exists(), f"Script not found: {script_path}"
# Check executable
assert os.access(script_path, os.X_OK), "Script is not executable"
def test_run_bqas_script_syntax(self):
"""Test run_bqas.sh has valid bash syntax."""
script_path = Path(__file__).parent.parent.parent / "scripts" / "run_bqas.sh"
result = subprocess.run(
["bash", "-n", str(script_path)],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Syntax error: {result.stderr}"
def test_install_script_exists(self):
"""Test that install_bqas_scheduler.sh exists."""
script_path = Path(__file__).parent.parent.parent / "scripts" / "install_bqas_scheduler.sh"
assert script_path.exists(), f"Script not found: {script_path}"
assert os.access(script_path, os.X_OK), "Script is not executable"
def test_install_script_syntax(self):
"""Test install_bqas_scheduler.sh has valid bash syntax."""
script_path = Path(__file__).parent.parent.parent / "scripts" / "install_bqas_scheduler.sh"
result = subprocess.run(
["bash", "-n", str(script_path)],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Syntax error: {result.stderr}"
def test_plist_file_exists(self):
"""Test that launchd plist template exists."""
plist_path = Path(__file__).parent.parent.parent / "scripts" / "com.breakpilot.bqas.plist"
assert plist_path.exists(), f"Plist not found: {plist_path}"
@pytest.mark.skipif(sys.platform != "darwin", reason="plutil only available on macOS")
def test_plist_valid_xml(self):
"""Test that plist is valid XML."""
plist_path = Path(__file__).parent.parent.parent / "scripts" / "com.breakpilot.bqas.plist"
result = subprocess.run(
["plutil", "-lint", str(plist_path)],
capture_output=True,
text=True,
)
assert result.returncode == 0, f"Invalid plist: {result.stderr}"
def test_git_hook_exists(self):
"""Test that git hook template exists."""
hook_path = Path(__file__).parent.parent.parent / "scripts" / "post-commit.hook"
assert hook_path.exists(), f"Hook not found: {hook_path}"
def test_run_bqas_help(self):
"""Test run_bqas.sh --help flag."""
script_path = Path(__file__).parent.parent.parent / "scripts" / "run_bqas.sh"
result = subprocess.run(
[str(script_path), "--help"],
capture_output=True,
text=True,
)
assert result.returncode == 0
assert "Usage" in result.stdout
assert "--quick" in result.stdout
assert "--golden" in result.stdout
def test_install_script_status(self):
"""Test install_bqas_scheduler.sh status command."""
script_path = Path(__file__).parent.parent.parent / "scripts" / "install_bqas_scheduler.sh"
result = subprocess.run(
[str(script_path), "status"],
capture_output=True,
text=True,
)
# Status should always work (even if not installed)
assert result.returncode == 0
assert "BQAS Scheduler Status" in result.stdout