We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/tbrennem-source/sf-permits-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""Tests for feedback triage tier classification system."""
from datetime import datetime, timezone, timedelta
import pytest
from scripts.feedback_triage import (
classify_severity,
classify_tier,
detect_duplicates,
is_already_fixed,
is_test_submission,
_message_similarity,
_within_days,
ADMIN_EMAILS,
)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_item(
feedback_id=1,
feedback_type="bug",
message="Something is broken",
page_url="https://sfpermits.ai/analyze",
email="user@test.com",
has_screenshot=False,
status="new",
created_at=None,
admin_note=None,
resolved_at=None,
):
return {
"feedback_id": feedback_id,
"feedback_type": feedback_type,
"message": message,
"page_url": page_url,
"email": email,
"has_screenshot": has_screenshot,
"status": status,
"created_at": created_at or datetime.now(timezone.utc).isoformat(),
"admin_note": admin_note,
"resolved_at": resolved_at,
}
# ---------------------------------------------------------------------------
# is_test_submission
# ---------------------------------------------------------------------------
class TestIsTestSubmission:
def test_test_keyword_short_message(self):
item = _make_item(message="test")
assert is_test_submission(item) is True
def test_test_keyword_in_longer_message(self):
"""Test keyword in a 50+ char message should NOT be flagged."""
item = _make_item(
message="I'm testing the new search feature and it keeps returning wrong results for my address"
)
assert is_test_submission(item) is False
def test_asdf_junk(self):
item = _make_item(message="asdf")
assert is_test_submission(item) is True
def test_hello_world(self):
item = _make_item(message="hello world")
assert is_test_submission(item) is True
def test_punctuation_only(self):
item = _make_item(message="... !!! ???")
assert is_test_submission(item) is True
def test_short_admin_message(self):
"""Short message from admin user is flagged as test."""
ADMIN_EMAILS.add("admin@sfpermits.ai")
try:
item = _make_item(message="ok", email="admin@sfpermits.ai")
assert is_test_submission(item) is True
finally:
ADMIN_EMAILS.discard("admin@sfpermits.ai")
def test_short_non_admin_not_flagged(self):
"""Short message from non-admin with real content is not flagged."""
item = _make_item(message="Page blank", email="user@test.com")
assert is_test_submission(item) is False
def test_real_bug_report(self):
item = _make_item(message="The search results page shows an error when I enter 123 Main St")
assert is_test_submission(item) is False
def test_real_suggestion(self):
item = _make_item(
feedback_type="suggestion",
message="Would be nice to have dark mode on the report page",
)
assert is_test_submission(item) is False
# ---------------------------------------------------------------------------
# _message_similarity
# ---------------------------------------------------------------------------
class TestMessageSimilarity:
def test_identical_messages(self):
assert _message_similarity("hello world", "hello world") == 1.0
def test_completely_different(self):
assert _message_similarity("hello world", "foo bar baz") == 0.0
def test_partial_overlap(self):
sim = _message_similarity("search is broken", "search feature is working")
assert 0.2 < sim < 0.8
def test_high_similarity(self):
sim = _message_similarity(
"the search page shows an error",
"the search page shows an error message",
)
assert sim > 0.7
def test_empty_strings(self):
assert _message_similarity("", "") == 0.0
assert _message_similarity("hello", "") == 0.0
# ---------------------------------------------------------------------------
# _within_days
# ---------------------------------------------------------------------------
class TestWithinDays:
def test_same_day(self):
now = datetime.now(timezone.utc)
assert _within_days(now.isoformat(), now.isoformat(), 7) is True
def test_within_range(self):
now = datetime.now(timezone.utc)
three_days_ago = (now - timedelta(days=3)).isoformat()
assert _within_days(now.isoformat(), three_days_ago, 7) is True
def test_outside_range(self):
now = datetime.now(timezone.utc)
ten_days_ago = (now - timedelta(days=10)).isoformat()
assert _within_days(now.isoformat(), ten_days_ago, 7) is False
def test_none_values(self):
assert _within_days(None, None, 7) is False
assert _within_days("2025-01-01T00:00:00+00:00", None, 7) is False
def test_datetime_objects(self):
now = datetime.now(timezone.utc)
yesterday = now - timedelta(days=1)
assert _within_days(now, yesterday, 7) is True
# ---------------------------------------------------------------------------
# detect_duplicates
# ---------------------------------------------------------------------------
class TestDetectDuplicates:
def test_exact_duplicate(self):
now = datetime.now(timezone.utc)
items = [
_make_item(feedback_id=1, message="Button is broken", created_at=(now - timedelta(hours=2)).isoformat()),
_make_item(feedback_id=2, message="Button is broken", created_at=now.isoformat()),
]
dupes = detect_duplicates(items)
assert 2 in dupes
assert dupes[2] == 1
def test_similar_same_user_page(self):
"""High-similarity messages from same user/page within 7 days = duplicate."""
now = datetime.now(timezone.utc)
items = [
_make_item(
feedback_id=1,
message="The search results are completely wrong for my address",
email="a@b.com",
page_url="/analyze",
created_at=(now - timedelta(days=1)).isoformat(),
),
_make_item(
feedback_id=2,
message="The search results are completely wrong for my address query",
email="a@b.com",
page_url="/analyze",
created_at=now.isoformat(),
),
]
dupes = detect_duplicates(items)
assert 2 in dupes
def test_different_users_not_duplicate(self):
"""Same message from different users is still an exact-match duplicate."""
now = datetime.now(timezone.utc)
items = [
_make_item(feedback_id=1, message="Page is blank", email="a@b.com",
created_at=(now - timedelta(hours=1)).isoformat()),
_make_item(feedback_id=2, message="Page is blank", email="c@d.com",
created_at=now.isoformat()),
]
dupes = detect_duplicates(items)
# Exact match still counts
assert 2 in dupes
def test_no_duplicates(self):
items = [
_make_item(feedback_id=1, message="Search is broken"),
_make_item(feedback_id=2, message="Add dark mode please"),
]
dupes = detect_duplicates(items)
assert len(dupes) == 0
def test_chain_not_double_counted(self):
"""If A is original and B,C are dupes of A, both should reference A."""
now = datetime.now(timezone.utc)
items = [
_make_item(feedback_id=1, message="test bug", created_at=(now - timedelta(hours=3)).isoformat()),
_make_item(feedback_id=2, message="test bug", created_at=(now - timedelta(hours=2)).isoformat()),
_make_item(feedback_id=3, message="test bug", created_at=now.isoformat()),
]
dupes = detect_duplicates(items)
assert dupes.get(2) == 1
assert dupes.get(3) == 1
# ---------------------------------------------------------------------------
# is_already_fixed
# ---------------------------------------------------------------------------
class TestIsAlreadyFixed:
def test_similar_resolved_issue(self):
item = _make_item(
message="Search page shows error for my address",
page_url="/analyze",
feedback_type="bug",
)
resolved = [_make_item(
feedback_id=99,
message="Search page shows error for an address",
page_url="/analyze",
feedback_type="bug",
status="resolved",
admin_note="Fixed and deployed in v2.1",
)]
assert is_already_fixed(item, resolved) is True
def test_different_page_not_fixed(self):
item = _make_item(message="Error on report page", page_url="/report/123")
resolved = [_make_item(
feedback_id=99,
message="Error on report page",
page_url="/analyze",
status="resolved",
admin_note="Fixed",
)]
assert is_already_fixed(item, resolved) is False
def test_no_fix_note_not_flagged(self):
"""Resolved without a fix-indicating note should not match."""
item = _make_item(message="Button broken", page_url="/analyze")
resolved = [_make_item(
feedback_id=99,
message="Button broken",
page_url="/analyze",
status="resolved",
admin_note="Won't fix — by design",
)]
assert is_already_fixed(item, resolved) is False
def test_no_page_url_not_flagged(self):
item = _make_item(message="Something broke", page_url=None)
resolved = [_make_item(
feedback_id=99,
message="Something broke",
page_url=None,
admin_note="Fixed",
)]
assert is_already_fixed(item, resolved) is False
# ---------------------------------------------------------------------------
# classify_tier
# ---------------------------------------------------------------------------
class TestClassifyTier:
def test_duplicate_is_tier1(self):
item = _make_item(feedback_id=5)
tier, reason = classify_tier(item, {5: 1}, [])
assert tier == 1
assert "Duplicate" in reason
def test_test_submission_is_tier1(self):
item = _make_item(message="test")
tier, reason = classify_tier(item, {}, [])
assert tier == 1
assert "Test" in reason or "test" in reason.lower()
def test_already_fixed_is_tier1(self):
item = _make_item(
feedback_id=10,
message="Search page shows error for an address",
page_url="/analyze",
)
resolved = [_make_item(
feedback_id=5,
message="Search page shows error for an address query",
page_url="/analyze",
admin_note="Fixed and deployed",
)]
tier, reason = classify_tier(item, {}, resolved)
assert tier == 1
assert "already fixed" in reason.lower()
def test_bug_with_clear_context_is_tier2(self):
"""Bug with page_url + screenshot + actionable signals = Tier 2."""
item = _make_item(
feedback_type="bug",
message="When I click the search button, the page shows an error message",
page_url="/analyze",
has_screenshot=True,
)
tier, reason = classify_tier(item, {}, [])
assert tier == 2
def test_detailed_bug_with_page_is_tier2(self):
"""Long bug message with page URL = Tier 2."""
item = _make_item(
feedback_type="bug",
message="I searched for 123 Main Street and the results page loaded but then showed "
"a completely blank white screen. I tried refreshing and it happened again. "
"This is on Chrome on macOS.",
page_url="/analyze",
)
tier, reason = classify_tier(item, {}, [])
assert tier == 2
def test_scoped_suggestion_is_tier2(self):
"""Suggestion with >50 chars and page URL = Tier 2."""
item = _make_item(
feedback_type="suggestion",
message="It would be helpful to show the permit timeline as a visual chart on the report page",
page_url="/report/123",
)
tier, reason = classify_tier(item, {}, [])
assert tier == 2
def test_vague_bug_is_tier3(self):
"""Short bug with no context = Tier 3."""
item = _make_item(
feedback_type="bug",
message="doesn't work",
page_url=None,
has_screenshot=False,
)
tier, reason = classify_tier(item, {}, [])
assert tier == 3
def test_question_is_tier3(self):
"""Questions always go to Tier 3."""
item = _make_item(
feedback_type="question",
message="How do I look up a permit by number?",
)
tier, reason = classify_tier(item, {}, [])
assert tier == 3
assert "question" in reason.lower()
def test_short_suggestion_no_page_is_tier3(self):
"""Short suggestion with no page URL = Tier 3."""
item = _make_item(
feedback_type="suggestion",
message="Add dark mode",
page_url=None,
)
tier, reason = classify_tier(item, {}, [])
assert tier == 3
# ---------------------------------------------------------------------------
# Email triage module
# ---------------------------------------------------------------------------
class TestEmailTriage:
def test_is_today_with_today(self):
from web.email_triage import _is_today
now = datetime.now(timezone.utc)
assert _is_today(now.isoformat()) is True
def test_is_today_with_yesterday(self):
from web.email_triage import _is_today
yesterday = (datetime.now(timezone.utc) - timedelta(days=1))
assert _is_today(yesterday.isoformat()) is False
def test_is_today_with_none(self):
from web.email_triage import _is_today
assert _is_today(None) is False
def test_is_today_with_invalid(self):
from web.email_triage import _is_today
assert _is_today("not-a-date") is False
def test_render_triage_email(self):
"""Triage email renders without errors."""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from web.app import app
from web.email_triage import render_triage_email
triage_data = {
"tier1": [
{"feedback_id": 1, "feedback_type": "bug", "message": "test",
"admin_note": "[Auto-triage] Test submission", "tier_reason": "Test submission"},
],
"tier2": [
{"feedback_id": 2, "feedback_type": "bug",
"message": "When I click search the page goes blank",
"page_url": "/analyze", "email": "user@test.com",
"has_screenshot": True, "tier_reason": "Bug with clear reproduction context"},
],
"tier3": [
{"feedback_id": 3, "feedback_type": "question",
"message": "How does permit tracking work?",
"page_url": None, "email": None,
"has_screenshot": False, "tier_reason": "Question requiring human answer"},
],
"counts": {"new": 2, "reviewed": 1, "resolved": 5, "total": 8},
"auto_resolved": 1,
"total_triaged": 3,
}
with app.app_context():
html = render_triage_email(triage_data)
assert "Triage Report" in html
assert "Auto-Resolved" in html
assert "Actionable" in html
assert "Needs Your Input" in html
assert "#1" in html
assert "#2" in html
assert "#3" in html
assert "View Feedback Queue" in html
def test_render_triage_email_empty_tiers(self):
"""Triage email renders cleanly with empty tiers."""
from web.app import app
from web.email_triage import render_triage_email
triage_data = {
"tier1": [],
"tier2": [],
"tier3": [],
"counts": {"new": 0, "resolved": 0, "total": 0},
"auto_resolved": 0,
"total_triaged": 0,
}
with app.app_context():
html = render_triage_email(triage_data)
assert "Triage Report" in html
assert "View Feedback Queue" in html
# Empty tiers should not show section headers
assert "Auto-Resolved" not in html