Files
awoooi/apps/api/tests/test_telegram_group_chat_provider_router.py
2026-07-19 00:41:44 +08:00

221 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from unittest.mock import AsyncMock
import pytest
from src.services import ai_rate_limiter as limiter_module
from src.services import chat_manager as chat_module
from src.services import telegram_gateway as gateway_module
from src.services.chat_manager import ChatManager
from src.services.telegram_gateway import (
TelegramGateway,
_sre_group_message_requires_ai_response,
)
class _ReceiptBackedCostLimiter:
async def get_usage_stats(self, provider: str) -> dict:
return {
"provider": provider,
"readback_status": "verified",
"total_cost_usd": {
"current": 0.011802 if provider == "claude" else 0.000297,
},
"accounting": {
"complete": True,
"status": "receipt_backed",
"degraded": False,
},
}
@pytest.mark.parametrize(
"message",
[
r"new-item -itemtype directory -force -path \wooo\agent99\tmp",
r"set-content -encoding ascii -path \wooo\agent99\tmp\fingerprint.txt",
r"add-content -encoding ascii -path \wooo\agent99\tmp\events.log",
"kubectl get pods -n awoooi-prod",
"2026-07-19T00:26:31+08:00 ERROR provider timeout",
'{"event":"agent99_runtime_receipt","status":"pending"}',
"```powershell\nRestart-Service alertmanager\n```",
],
)
def test_unaddressed_machine_payload_does_not_request_ai(message: str) -> None:
assert _sre_group_message_requires_ai_response(message) is False
@pytest.mark.parametrize(
"message,reply_to_message",
[
("kubectl get pods 為什麼失敗?", None),
("@OpenClawAwoooI_Bot kubectl get pods 失敗", None),
("set-content 這行是什麼意思", None),
("目前有哪些異常服務", None),
(
"Restart-Service alertmanager",
{"from": {"is_bot": True, "username": "OpenClawAwoooI_Bot"}},
),
],
)
def test_explicit_dialogue_about_machine_payload_still_requests_ai(
message: str,
reply_to_message: dict | None,
) -> None:
assert (
_sre_group_message_requires_ai_response(message, reply_to_message)
is True
)
@pytest.mark.asyncio
async def test_group_machine_payload_stops_before_context_or_provider(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(gateway_module.settings, "HERMES_NL_ENABLED", False)
def _chat_manager_forbidden():
raise AssertionError("machine payload must not build context or call AI")
monkeypatch.setattr(chat_module, "get_chat_manager", _chat_manager_forbidden)
gateway = object.__new__(TelegramGateway)
await gateway._handle_group_message(
r"new-item -itemtype directory -force -path \wooo\agent99\tmp",
user_id=99,
username="operator",
chat_id=-100123,
message_id=455,
)
@pytest.mark.asyncio
async def test_nemotron_group_mention_uses_structured_provider_status_router(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Telegram mention must not invent a Gemini lock or call a provider."""
readback = {
"schema_version": "sre_chat_provider_runtime_readback_v1",
"policy_order": [
"ollama_gcp_a",
"ollama_gcp_b",
"ollama_local",
"claude",
"gemini",
],
"ollama_health": {
"ollama_gcp_a": "offline",
"ollama_gcp_b": "healthy",
"ollama_local": "not_checked",
},
"paid": {
"claude": {
"configured": True,
"authenticated": True,
"authentication_verified": True,
"verification_status": "authenticated_verified_by_generation",
"execution_mode": "canary",
"canary_percent": 5,
"route_lease_state": "inactive_disabled_after_canary",
"runtime_state": (
"configured_authenticated_canary_waiting_for_run_owned_lease"
),
},
"gemini": {
"configured": True,
"authenticated": False,
"authentication_verified": False,
"verification_status": "authentication_rejected",
"execution_mode": "canary",
"canary_percent": 5,
"route_lease_state": "inactive_disabled_after_canary",
"runtime_state": "configured_canary_authentication_rejected",
},
},
"provider_route_switch_performed": False,
"infrastructure_remediation_write_allowed": False,
"cross_domain_execution_allowed": False,
}
manager = ChatManager()
monkeypatch.setattr(
manager,
"get_system_context",
AsyncMock(return_value="bounded system context"),
)
monkeypatch.setattr(
manager,
"get_provider_runtime_readback",
AsyncMock(return_value=readback),
)
monkeypatch.setattr(chat_module, "get_chat_manager", lambda: manager)
def _provider_call_forbidden():
raise AssertionError("provider executor must not run for a status readback")
monkeypatch.setattr(chat_module, "get_ai_executor", _provider_call_forbidden)
monkeypatch.setattr(gateway_module.settings, "HERMES_NL_ENABLED", False)
gateway = object.__new__(TelegramGateway)
send_as_nemotron = AsyncMock(return_value={"ok": True})
monkeypatch.setattr(gateway, "send_as_nemotron", send_as_nemotron)
await gateway._handle_group_message(
"@NemoTronAwoooI_Bot Gemini API 現在是硬鎖定狀態嗎?",
user_id=99,
username="operator",
chat_id=-100123,
message_id=456,
)
send_as_nemotron.assert_awaited_once()
outbound = send_as_nemotron.await_args.kwargs["text"]
assert "硬鎖" not in outbound
assert "locked" not in outbound.casefold()
assert "無法正常回應" not in outbound
assert "Claudeconfigured=trueauthenticated=true" in outbound
assert "Geminiconfigured=trueauthenticated=false" in outbound
assert "verification=authentication_rejected" in outbound
assert "canary=canary 5%" in outbound
assert "route_lease=inactive_disabled_after_canary" in outbound
assert "provider=runtime_readback" in outbound
assert "model=deterministic_policy" in outbound
assert "fallback=provider_status_request_no_generation" in outbound
assert "cost_receipt=none" in outbound
assert send_as_nemotron.await_args.kwargs == {
"text": outbound,
"reply_to_message_id": 456,
"inbound_chat_id": -100123,
}
@pytest.mark.asyncio
async def test_group_cost_uses_receipt_backed_accounting_not_legacy_keys(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(
limiter_module,
"get_ai_rate_limiter",
lambda: _ReceiptBackedCostLimiter(),
)
gateway = object.__new__(TelegramGateway)
send_as_openclaw = AsyncMock(return_value={"ok": True})
monkeypatch.setattr(gateway, "send_as_openclaw", send_as_openclaw)
await gateway._handle_group_command(
"/cost",
-100123,
789,
full_text="/cost",
)
send_as_openclaw.assert_awaited_once()
outbound = send_as_openclaw.await_args.kwargs["text"]
assert "Claude: <b>$0.011802</b> | receipt=verified" in outbound
assert "Gemini: <b>$0.000297</b> | receipt=verified" in outbound
assert "合計: <b>$0.012099</b>" in outbound
assert "receipt-backed" in outbound
assert "Flash-Lite" not in outbound
assert "Haiku" not in outbound