Files
ietf-draft-analyzer/tests/test_web_data.py
Christian Nennemann 757b781c67 Platform upgrade: semantic search, citations, readiness, tests, Docker
Major features added by 5 parallel agent teams:
- Semantic "Ask" (NL queries via FTS5 + embeddings + Claude synthesis)
- Global search across drafts, ideas, authors, gaps
- REST API expansion (14 endpoints, up from 3) with CSV/JSON export
- Citation graph visualization (D3.js, 440 nodes, 2422 edges)
- Standards readiness scoring (0-100 composite from 6 factors)
- Side-by-side draft comparison view with shared/unique analysis
- Annotation system (notes + tags per draft, DB-persisted)
- Docker deployment (Dockerfile + docker-compose with Ollama)
- Scheduled updates (cron script with log rotation)
- Pipeline health dashboard (stage progress bars, cost tracking)
- Test suite foundation (54 pytest tests covering DB, models, web data)

Fixes: compare_drafts() stubbed→working, get_authors_for_draft() bug,
source-aware analysis prompts, config env var overrides + validation,
resilient batch error handling with --retry-failed, observatory --dry-run

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:52:56 +01:00

159 lines
5.0 KiB
Python

"""Tests for src/webui/data.py data access functions."""
from __future__ import annotations
import sys
from functools import wraps
from pathlib import Path
import pytest
# Ensure webui is importable
_project_root = Path(__file__).resolve().parent.parent
if str(_project_root / "src") not in sys.path:
sys.path.insert(0, str(_project_root / "src"))
from webui.data import (
get_overview_stats,
get_category_counts,
get_drafts_page,
get_draft_detail,
get_ideas_by_type,
get_all_gaps,
get_timeline_data,
get_top_authors,
)
def _skip_on_missing_module(fn):
"""Decorator that skips tests when webui.data references unavailable modules."""
@wraps(fn)
def wrapper(*args, **kwargs):
try:
return fn(*args, **kwargs)
except (ModuleNotFoundError, AttributeError) as e:
pytest.skip(f"webui.data depends on module not in this worktree: {e}")
return wrapper
def test_get_overview_stats(seeded_db):
"""Overview stats should return correct counts from seeded data."""
stats = get_overview_stats(seeded_db)
assert stats["total_drafts"] == 5
assert stats["rated_count"] == 5
assert stats["author_count"] == 3
# 2 + 1 + 1 = 4 ideas in seeded data
assert stats["idea_count"] == 4
assert stats["gap_count"] == 0
assert "input_tokens" in stats
assert "output_tokens" in stats
def test_get_category_counts(seeded_db):
"""Category counts should reflect the seeded ratings."""
counts = get_category_counts(seeded_db)
assert isinstance(counts, dict)
assert "A2A protocols" in counts
assert counts["A2A protocols"] == 1
assert "ML traffic mgmt" in counts
@_skip_on_missing_module
def test_get_drafts_page_basic(seeded_db):
"""Drafts page should return paginated results."""
result = get_drafts_page(seeded_db, page=1, per_page=3)
assert len(result["drafts"]) == 3
assert result["total"] == 5
assert result["page"] == 1
assert result["per_page"] == 3
assert result["pages"] == 2
@_skip_on_missing_module
def test_get_drafts_page_with_category_filter(seeded_db):
"""Filtering by category should narrow results."""
result = get_drafts_page(seeded_db, category="A2A protocols")
assert result["total"] == 1
assert result["drafts"][0]["categories"] == ["A2A protocols"]
@_skip_on_missing_module
def test_get_drafts_page_with_search_filter(seeded_db):
"""Text search should filter by name/title/summary."""
result = get_drafts_page(seeded_db, search="alpha")
assert result["total"] == 1
assert "alpha" in result["drafts"][0]["name"]
@_skip_on_missing_module
def test_get_drafts_page_empty_search(seeded_db):
"""Search for non-matching term should return 0 results."""
result = get_drafts_page(seeded_db, search="zzzznonexistent")
assert result["total"] == 0
assert result["drafts"] == []
@_skip_on_missing_module
def test_get_draft_detail(seeded_db):
"""Draft detail should include draft, rating, authors, ideas, refs."""
detail = get_draft_detail(seeded_db, "draft-alpha-agent-comm")
assert detail is not None
assert detail["name"] == "draft-alpha-agent-comm"
assert detail["title"] == "Alpha Agent Communication"
assert "rating" in detail
assert detail["rating"]["novelty"] == 4
assert len(detail["authors"]) == 2
assert len(detail["ideas"]) == 2
assert len(detail["refs"]) == 3
@_skip_on_missing_module
def test_get_draft_detail_not_found(seeded_db):
"""Draft detail for non-existent draft should return None."""
assert get_draft_detail(seeded_db, "draft-nonexistent") is None
def test_get_ideas_by_type(seeded_db):
"""Ideas by type should group and count correctly."""
result = get_ideas_by_type(seeded_db)
assert result["total"] == 4
assert "by_type" in result
assert isinstance(result["by_type"], dict)
# We have protocol, mechanism, extension types
assert "protocol" in result["by_type"] or "mechanism" in result["by_type"]
def test_get_all_gaps_empty(seeded_db):
"""With no gaps inserted, should return empty list."""
gaps = get_all_gaps(seeded_db)
assert gaps == []
def test_get_all_gaps_with_data(seeded_db):
"""After inserting gaps, should return them."""
seeded_db.insert_gaps([
{"topic": "Gap A", "description": "Desc A", "severity": "high", "evidence": "Ev A"},
])
gaps = get_all_gaps(seeded_db)
assert len(gaps) == 1
assert gaps[0]["topic"] == "Gap A"
def test_get_timeline_data(seeded_db):
"""Timeline data should group drafts by month."""
data = get_timeline_data(seeded_db)
assert "months" in data
assert "series" in data
assert "categories" in data
# Seeded drafts span Jan-May 2025
assert len(data["months"]) >= 1
def test_get_top_authors(seeded_db):
"""Top authors should return ranked list with draft counts."""
authors = get_top_authors(seeded_db, limit=10)
assert len(authors) >= 1
assert "name" in authors[0]
assert "draft_count" in authors[0]
assert authors[0]["draft_count"] >= 2