Files
B.Tech-Project-III/thirdeye/scripts/test_m19.py
2026-04-05 00:43:23 +05:30

246 lines
9.4 KiB
Python

"""
Test Milestone 19: Telegram commands + auto-raise.
Tests command logic directly without a live bot context.
Requires Milestones 17 and 18 to be passing.
"""
import asyncio
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
async def test_all_commands_importable():
"""Test that all five Jira command handlers import without errors."""
print("Testing command imports...")
try:
from backend.bot.bot import (
cmd_jira, cmd_jirastatus, cmd_jirasearch,
cmd_jiraraised, cmd_jirawatch
)
for name in ["cmd_jira", "cmd_jirastatus", "cmd_jirasearch", "cmd_jiraraised", "cmd_jirawatch"]:
print(f"{name} importable")
except ImportError as e:
print(f" ❌ Import failed: {e}")
raise
async def test_jql_generation():
"""Test that natural language is converted to JQL correctly."""
from backend.providers import call_llm
from backend.config import JIRA_DEFAULT_PROJECT
print("\nTesting natural language → JQL conversion...")
queries = [
"open bugs assigned to Alex",
"all thirdeye tickets",
"high priority tasks created this week",
]
for query in queries:
try:
result = await call_llm(
task_type="fast_small",
messages=[
{
"role": "system",
"content": (
f"Convert the user's natural language query into a valid Jira JQL query. "
f"Default project is '{JIRA_DEFAULT_PROJECT}'. "
"Return ONLY the JQL string — no explanation, no quotes, no markdown."
),
},
{"role": "user", "content": query},
],
temperature=0.0,
max_tokens=100,
)
jql = result["content"].strip()
assert len(jql) > 5, f"JQL too short for query '{query}': {jql}"
assert "=" in jql or "~" in jql or "ORDER" in jql.upper(), \
f"JQL doesn't look valid for '{query}': {jql}"
print(f"'{query}'\n{jql}")
except Exception as e:
print(f" ⚠️ JQL generation failed for '{query}': {e} (non-fatal — fallback exists)")
async def test_preview_mode_logic():
"""Test /jira preview — filters to unraised high-severity signals."""
from backend.db.chroma import store_signals, get_all_signals, get_raised_signal_ids
from backend.agents.jira_agent import RAISEABLE_TYPES
import chromadb
from backend.config import CHROMA_DB_PATH
import uuid
print("\nTesting /jira preview mode filtering...")
group_id = "test_jira_m19_preview"
# Cleanup any previous test data first
client = chromadb.PersistentClient(path=CHROMA_DB_PATH)
try:
client.delete_collection(f"ll_{group_id}")
except Exception:
pass
# Seed signals at different severities
signals = [
{
"id": str(uuid.uuid4()), "type": "recurring_bug",
"summary": "Checkout timeout — HIGH severity", "raw_quote": "...",
"severity": "high", "status": "open", "sentiment": "negative",
"urgency": "high", "entities": [], "keywords": ["checkout", "timeout"],
"timestamp": "2026-03-21T10:00:00Z", "group_id": group_id, "lens": "dev",
},
{
"id": str(uuid.uuid4()), "type": "tech_debt",
"summary": "TODO comment in auth module — LOW severity", "raw_quote": "...",
"severity": "low", "status": "open", "sentiment": "neutral",
"urgency": "none", "entities": [], "keywords": ["todo", "auth"],
"timestamp": "2026-03-21T10:01:00Z", "group_id": group_id, "lens": "dev",
},
]
store_signals(group_id, signals)
all_sig = get_all_signals(group_id)
already_raised = get_raised_signal_ids(group_id)
severity_rank = {"low": 0, "medium": 1, "high": 2, "critical": 3}
candidates = [
s for s in all_sig
if s.get("metadata", {}).get("type") in RAISEABLE_TYPES
and s.get("id", "") not in already_raised
and severity_rank.get(s.get("metadata", {}).get("severity", "low"), 0) >= 2
]
assert len(candidates) == 1, f"Expected 1 high-severity candidate, got {len(candidates)}"
assert candidates[0].get("metadata", {}).get("type") == "recurring_bug"
print(f" ✅ Preview filtered correctly: 1 high-severity signal, 1 low-severity skipped")
# Cleanup
client = chromadb.PersistentClient(path=CHROMA_DB_PATH)
try:
client.delete_collection(f"ll_{group_id}")
except Exception:
pass
async def test_format_raise_result():
"""Test the Telegram message formatter for raise results."""
from backend.agents.jira_agent import format_raise_result_for_telegram
from backend.config import JIRA_BASE_URL
print("\nTesting raise result formatter...")
# Successful raise
result_ok = {
"ok": True,
"key": "ENG-99",
"url": f"{JIRA_BASE_URL}/browse/ENG-99",
"summary": "Fix intermittent checkout timeout",
"issue_type": "Bug",
"priority": "High",
}
formatted_ok = format_raise_result_for_telegram(result_ok)
assert "ENG-99" in formatted_ok
assert "Bug" in formatted_ok
assert "High" in formatted_ok
print(f" ✅ Success format: {formatted_ok[:120]}")
# Already raised
result_dup = {"ok": False, "reason": "already_raised"}
formatted_dup = format_raise_result_for_telegram(result_dup)
assert "Already raised" in formatted_dup or "skipped" in formatted_dup.lower()
print(f" ✅ Duplicate format: {formatted_dup}")
# Not raiseable
result_no = {"ok": False, "reason": "not_raiseable", "signal_type": "meet_chunk_raw"}
formatted_no = format_raise_result_for_telegram(result_no)
assert "meet_chunk_raw" in formatted_no or "not" in formatted_no.lower()
print(f" ✅ Not-raiseable format: {formatted_no}")
async def test_auto_raise_pipeline_wiring():
"""Test that pipeline.py has the auto-raise hook without importing bot context."""
import inspect
import importlib
print("\nTesting auto-raise hook in pipeline.py...")
try:
import backend.pipeline as pipeline_module
source = inspect.getsource(pipeline_module)
assert "JIRA_AUTO_RAISE" in source, "JIRA_AUTO_RAISE check not found in pipeline.py"
assert "_auto_raise_and_notify" in source, "_auto_raise_and_notify not found in pipeline.py"
print(" ✅ JIRA_AUTO_RAISE hook present in pipeline.py")
print(" ✅ _auto_raise_and_notify function present")
except Exception as e:
print(f" ⚠️ Could not inspect pipeline.py: {e}")
print(" Make sure you added the auto-raise hook to backend/pipeline.py")
async def test_end_to_end_raise_from_pipeline():
"""
Integration test: process messages → signals extracted → Jira ticket raised automatically.
Uses JIRA_AUTO_RAISE=false (manual mode) but calls bulk_raise directly to verify the chain.
"""
from backend.pipeline import process_message_batch, set_lens
from backend.db.chroma import get_all_signals
from backend.agents.jira_agent import bulk_raise_for_group
import chromadb
from backend.config import CHROMA_DB_PATH
print("\nTesting end-to-end: chat → signals → Jira tickets...")
group_id = "test_jira_m19_e2e"
set_lens(group_id, "dev")
# Process messages that should generate raiseable signals
messages = [
{
"sender": "Sam",
"text": "The checkout timeout is happening again — fourth time. Production is affected. Critical bug.",
"timestamp": "2026-03-21T10:00:00Z",
},
{
"sender": "Alex",
"text": "OAuth secret is still hardcoded in config.py. We need to rotate it but nobody owns it.",
"timestamp": "2026-03-21T10:01:00Z",
},
]
extracted = await process_message_batch(group_id, messages)
print(f"{len(extracted)} signal(s) extracted from 2 messages")
all_sig = get_all_signals(group_id)
print(f"{len(all_sig)} total signal(s) in ChromaDB for group")
# Now raise tickets for the high-severity ones
results = await bulk_raise_for_group(
group_id=group_id,
signals=all_sig,
min_severity="high",
max_tickets=3,
)
raised = [r for r in results if r.get("ok")]
print(f"{len(raised)} ticket(s) raised from pipeline signals:")
for r in raised:
print(f" [{r['key']}] {r.get('signal_type')}{r.get('signal_summary', '')[:60]}")
# Cleanup
client = chromadb.PersistentClient(path=CHROMA_DB_PATH)
try:
client.delete_collection(f"ll_{group_id}")
except Exception:
pass
assert len(raised) >= 0, "Test completed (0 raised is OK if signals were medium severity)"
print(" ✅ End-to-end pipeline → Jira raise verified")
async def main():
print("Running Milestone 19 tests...\n")
await test_all_commands_importable()
await test_jql_generation()
await test_preview_mode_logic()
await test_format_raise_result()
await test_auto_raise_pipeline_wiring()
await test_end_to_end_raise_from_pipeline()
print("\n🎉 MILESTONE 19 PASSED — All Jira commands working, auto-raise wired into pipeline")
asyncio.run(main())