my-env / tests /test_heuristics.py
TheOnlyKaks's picture
some fixes
f9cf02d
Raw
History Blame Contribute Delete
9.51 kB
"""
Tests for the shared heuristics module (server/heuristics.py).
These tests verify that component, severity, priority, and info-request logic
produce the expected outputs for representative ticket inputs.
"""
from __future__ import annotations
import pytest
from datetime import datetime
from models import TicketModel
from server.heuristics import (
infer_component,
infer_severity,
infer_priority,
needs_more_info,
suggested_info_type,
severity_to_priority,
COMPONENT_TEAM_MAP,
)
AVAILABLE_COMPONENTS = [
"api-gateway", "auth-service", "user-service", "payment-service",
"web-app", "ios-app", "android-app", "database", "cache", "cdn",
]
AVAILABLE_TEAMS = [
"backend-api", "frontend-web", "mobile-ios",
"mobile-android", "infrastructure", "data-platform",
]
def _ticket(**kwargs) -> TicketModel:
"""Helper to construct a minimal TicketModel with sane defaults."""
defaults = dict(
ticket_id="BUG-TEST",
title="Test ticket",
description="A test description",
reporter_type="user",
service="api",
component_candidates=["api-gateway"],
created_at=datetime(2024, 1, 1),
customer_tier="pro",
repro_steps_present=True,
logs_present=True,
attachments_count=0,
suspected_duplicate_ids=[],
)
defaults.update(kwargs)
return TicketModel(**defaults)
# ---------------------------------------------------------------------------
# severity_to_priority
# ---------------------------------------------------------------------------
class TestSeverityToPriority:
def test_sev0_maps_to_p0(self):
assert severity_to_priority("sev0") == "p0"
def test_sev3_maps_to_p3(self):
assert severity_to_priority("sev3") == "p3"
def test_unknown_defaults_to_p2(self):
assert severity_to_priority("unknown") == "p2"
# ---------------------------------------------------------------------------
# infer_component
# ---------------------------------------------------------------------------
class TestInferComponent:
def test_returns_first_candidate_when_no_available(self):
t = _ticket(component_candidates=["ios-app", "auth-service"])
result = infer_component(t, [])
assert result == "api-gateway" # fallback default
def test_keyword_match_wins(self):
t = _ticket(
title="iOS login broken",
description="tapping login does nothing on iPhone",
service="mobile-app",
component_candidates=["ios-app", "auth-service"],
)
result = infer_component(t, AVAILABLE_COMPONENTS)
assert result == "ios-app"
def test_payment_keyword_picks_payment_service(self):
t = _ticket(
title="Payment timeout",
description="checkout is failing, billing error",
service="payments",
component_candidates=["payment-service", "api-gateway"],
)
result = infer_component(t, AVAILABLE_COMPONENTS)
assert result == "payment-service"
def test_cdn_keyword(self):
t = _ticket(
title="Images broken",
description="CDN asset fails to load",
service="web-app",
component_candidates=["cdn", "web-app"],
)
result = infer_component(t, AVAILABLE_COMPONENTS)
assert result == "cdn"
def test_filters_out_unavailable_candidates(self):
t = _ticket(component_candidates=["nonexistent-service", "database"])
result = infer_component(t, AVAILABLE_COMPONENTS)
assert result == "database"
# ---------------------------------------------------------------------------
# infer_severity
# ---------------------------------------------------------------------------
class TestInferSeverity:
def test_security_keyword_is_sev0(self):
t = _ticket(description="unauthorized access detected, security breach")
assert infer_severity(t, "auth-service") == "sev0"
def test_double_charge_is_sev0(self):
t = _ticket(description="customer was double charged")
assert infer_severity(t, "payment-service") == "sev0"
def test_timeout_is_sev1(self):
t = _ticket(description="service is timing out after 30s")
assert infer_severity(t, "api-gateway") == "sev1"
def test_outage_is_sev1(self):
t = _ticket(description="full outage detected by monitoring")
assert infer_severity(t, "api-gateway") == "sev1"
def test_crash_is_sev2(self):
t = _ticket(description="app crashes on launch every time")
assert infer_severity(t, "android-app") == "sev2"
def test_slow_db_free_tier_is_sev3(self):
t = _ticket(
description="dashboard is slow",
customer_tier="free",
)
assert infer_severity(t, "database") == "sev3"
def test_slow_db_enterprise_is_sev2(self):
t = _ticket(
description="dashboard is slow",
customer_tier="enterprise",
)
assert infer_severity(t, "database") == "sev2"
def test_incorrect_tax_enterprise_is_sev1(self):
t = _ticket(description="incorrect tax calculation on invoice", customer_tier="enterprise")
assert infer_severity(t, "payment-service") == "sev1"
def test_incorrect_tax_free_is_sev2(self):
t = _ticket(description="incorrect tax calculation on invoice", customer_tier="free")
assert infer_severity(t, "payment-service") == "sev2"
def test_low_signal_free_is_sev3(self):
t = _ticket(description="something is wrong, signal quality is low", customer_tier="free")
assert infer_severity(t, "web-app") == "sev3"
def test_no_keywords_default_sev3(self):
t = _ticket(title="Minor UI glitch", description="button looks slightly off")
assert infer_severity(t, "web-app") == "sev3"
# ---------------------------------------------------------------------------
# infer_priority
# ---------------------------------------------------------------------------
class TestInferPriority:
def test_sev2_enterprise_upgrades_to_p1(self):
t = _ticket(customer_tier="enterprise")
assert infer_priority(t, "sev2") == "p1"
def test_sev2_monitoring_upgrades_to_p1(self):
t = _ticket(reporter_type="monitoring", customer_tier="free")
assert infer_priority(t, "sev2") == "p1"
def test_sev2_payment_keyword_upgrades_to_p1(self):
t = _ticket(description="payment gateway slow", customer_tier="free")
assert infer_priority(t, "sev2") == "p1"
def test_sev3_free_stays_p3(self):
t = _ticket(customer_tier="free")
assert infer_priority(t, "sev3") == "p3"
# ---------------------------------------------------------------------------
# needs_more_info
# ---------------------------------------------------------------------------
class TestNeedsMoreInfo:
def test_no_repro_no_logs_needs_info(self):
t = _ticket(repro_steps_present=False, logs_present=False)
assert needs_more_info(t) is True
def test_duplicate_overrides_info_need(self):
t = _ticket(
repro_steps_present=False,
logs_present=False,
suspected_duplicate_ids=["BUG-999"],
)
assert needs_more_info(t) is False
def test_user_without_logs_needs_info(self):
t = _ticket(reporter_type="user", repro_steps_present=True, logs_present=False)
assert needs_more_info(t) is True
def test_monitoring_without_repro_ok(self):
"""Monitoring reporters don't need manual repro steps."""
t = _ticket(reporter_type="monitoring", repro_steps_present=False, logs_present=True)
assert needs_more_info(t) is False
def test_low_signal_flag_needs_info(self):
t = _ticket(
description="signal quality is low on this ticket",
repro_steps_present=True,
logs_present=True,
)
assert needs_more_info(t) is True
def test_full_info_not_needed(self):
t = _ticket(repro_steps_present=True, logs_present=True, reporter_type="user")
assert needs_more_info(t) is False
# ---------------------------------------------------------------------------
# suggested_info_type
# ---------------------------------------------------------------------------
class TestSuggestedInfoType:
def test_neither_present_returns_both(self):
t = _ticket(repro_steps_present=False, logs_present=False)
assert suggested_info_type(t) == "both"
def test_only_logs_missing_returns_logs(self):
t = _ticket(repro_steps_present=True, logs_present=False)
assert suggested_info_type(t) == "logs"
def test_only_repro_missing_returns_repro_steps(self):
t = _ticket(repro_steps_present=False, logs_present=True)
assert suggested_info_type(t) == "repro_steps"
# ---------------------------------------------------------------------------
# COMPONENT_TEAM_MAP sanity check
# ---------------------------------------------------------------------------
class TestComponentTeamMap:
def test_all_components_have_teams(self):
for component in AVAILABLE_COMPONENTS:
assert component in COMPONENT_TEAM_MAP, f"{component} missing from COMPONENT_TEAM_MAP"
def test_teams_are_valid(self):
for team in COMPONENT_TEAM_MAP.values():
assert team in AVAILABLE_TEAMS, f"Team '{team}' not in AVAILABLE_TEAMS"