Add test coverage for CLI commands, Flask routes, and shared DB methods
74 new tests across 3 files: - test_cli.py: CLI help, version, config, report generation, wg/viz/pipeline subcommands - test_web.py: All public pages, admin pages (dev/prod), API JSON endpoints, CSV export, 404s - test_db_shared.py: rated_count, gap_count, false_positive_names, category_counts, source_counts, draft_author_count_map, search_gaps, search_authors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
157
tests/test_cli.py
Normal file
157
tests/test_cli.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
"""Tests for CLI commands using Click's CliRunner."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
from ietf_analyzer.cli import main
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def runner():
|
||||||
|
return CliRunner()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_config(tmp_path):
|
||||||
|
"""Patch Config.load to return a config pointing at a temp DB."""
|
||||||
|
from ietf_analyzer.config import Config
|
||||||
|
|
||||||
|
cfg = Config(
|
||||||
|
data_dir=str(tmp_path),
|
||||||
|
db_path=str(tmp_path / "test.db"),
|
||||||
|
)
|
||||||
|
with patch("ietf_analyzer.cli.Config.load", return_value=cfg):
|
||||||
|
yield cfg
|
||||||
|
|
||||||
|
|
||||||
|
# ── Help & version ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_help(runner, mock_config):
|
||||||
|
result = runner.invoke(main, ["--help"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "IETF Draft Analyzer" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_version(runner, mock_config):
|
||||||
|
result = runner.invoke(main, ["--version"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "0.3.0" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_command(runner, mock_config):
|
||||||
|
result = runner.invoke(main, ["nonexistent-command"])
|
||||||
|
assert result.exit_code != 0
|
||||||
|
|
||||||
|
|
||||||
|
# ── config command ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_show(runner, mock_config):
|
||||||
|
result = runner.invoke(main, ["config", "--show"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "data_dir" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_show_default(runner, mock_config):
|
||||||
|
"""config without --show or --set also prints config."""
|
||||||
|
result = runner.invoke(main, ["config"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "data_dir" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
# ── report subcommands ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_help(runner, mock_config):
|
||||||
|
result = runner.invoke(main, ["report", "--help"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "overview" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_overview(runner, mock_config, seeded_db):
|
||||||
|
"""report overview should run and produce a file."""
|
||||||
|
mock_config.db_path = seeded_db.config.db_path
|
||||||
|
mock_config.data_dir = seeded_db.config.data_dir
|
||||||
|
result = runner.invoke(main, ["report", "overview"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Report saved" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_landscape(runner, mock_config, seeded_db):
|
||||||
|
mock_config.db_path = seeded_db.config.db_path
|
||||||
|
mock_config.data_dir = seeded_db.config.data_dir
|
||||||
|
result = runner.invoke(main, ["report", "landscape"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Report saved" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_timeline(runner, mock_config, seeded_db):
|
||||||
|
mock_config.db_path = seeded_db.config.db_path
|
||||||
|
mock_config.data_dir = seeded_db.config.data_dir
|
||||||
|
result = runner.invoke(main, ["report", "timeline"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Report saved" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_ideas(runner, mock_config, seeded_db):
|
||||||
|
mock_config.db_path = seeded_db.config.db_path
|
||||||
|
mock_config.data_dir = seeded_db.config.data_dir
|
||||||
|
result = runner.invoke(main, ["report", "ideas"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Report saved" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_authors(runner, mock_config, seeded_db):
|
||||||
|
mock_config.db_path = seeded_db.config.db_path
|
||||||
|
mock_config.data_dir = seeded_db.config.data_dir
|
||||||
|
result = runner.invoke(main, ["report", "authors"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "Report saved" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
# ── export command ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_export_help(runner, mock_config):
|
||||||
|
result = runner.invoke(main, ["export", "--help"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "json" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
# ── wg commands ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_wg_help(runner, mock_config):
|
||||||
|
result = runner.invoke(main, ["wg", "--help"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "list" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_wg_list(runner, mock_config, seeded_db):
|
||||||
|
mock_config.db_path = seeded_db.config.db_path
|
||||||
|
mock_config.data_dir = seeded_db.config.data_dir
|
||||||
|
result = runner.invoke(main, ["wg", "list"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
|
||||||
|
# ── viz commands ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_viz_help(runner, mock_config):
|
||||||
|
result = runner.invoke(main, ["viz", "--help"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "landscape" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
# ── pipeline commands ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_pipeline_help(runner, mock_config):
|
||||||
|
result = runner.invoke(main, ["pipeline", "--help"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "generate" in result.output
|
||||||
175
tests/test_db_shared.py
Normal file
175
tests/test_db_shared.py
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
"""Tests for shared Database query methods added in the DB refactor."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
# ── rated_count ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_rated_count_returns_int(seeded_db):
|
||||||
|
result = seeded_db.rated_count()
|
||||||
|
assert isinstance(result, int)
|
||||||
|
assert result == 5 # seeded_db has 5 ratings
|
||||||
|
|
||||||
|
|
||||||
|
def test_rated_count_empty(tmp_db):
|
||||||
|
result = tmp_db.rated_count()
|
||||||
|
assert result == 0
|
||||||
|
|
||||||
|
|
||||||
|
# ── gap_count ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_gap_count_empty(tmp_db):
|
||||||
|
result = tmp_db.gap_count()
|
||||||
|
assert isinstance(result, int)
|
||||||
|
assert result == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_gap_count_after_insert(seeded_db):
|
||||||
|
seeded_db.conn.execute(
|
||||||
|
"INSERT INTO gaps (topic, description, category, severity) VALUES (?, ?, ?, ?)",
|
||||||
|
("Test Gap", "A test gap", "A2A protocols", "high"),
|
||||||
|
)
|
||||||
|
seeded_db.conn.commit()
|
||||||
|
assert seeded_db.gap_count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
# ── false_positive_names ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_false_positive_names_empty(seeded_db):
|
||||||
|
result = seeded_db.false_positive_names()
|
||||||
|
assert isinstance(result, set)
|
||||||
|
assert len(result) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_false_positive_names_after_flag(seeded_db):
|
||||||
|
seeded_db.conn.execute(
|
||||||
|
"UPDATE ratings SET false_positive = 1 WHERE draft_name = 'draft-alpha-agent-comm'"
|
||||||
|
)
|
||||||
|
seeded_db.conn.commit()
|
||||||
|
result = seeded_db.false_positive_names()
|
||||||
|
assert "draft-alpha-agent-comm" in result
|
||||||
|
assert len(result) == 1
|
||||||
|
|
||||||
|
|
||||||
|
# ── category_counts ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_category_counts_returns_dict(seeded_db):
|
||||||
|
result = seeded_db.category_counts()
|
||||||
|
assert isinstance(result, dict)
|
||||||
|
assert len(result) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_category_counts_values_are_ints(seeded_db):
|
||||||
|
result = seeded_db.category_counts()
|
||||||
|
for k, v in result.items():
|
||||||
|
assert isinstance(k, str)
|
||||||
|
assert isinstance(v, int)
|
||||||
|
assert v > 0
|
||||||
|
|
||||||
|
|
||||||
|
# ── source_counts ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_source_counts_returns_list(seeded_db):
|
||||||
|
result = seeded_db.source_counts()
|
||||||
|
assert isinstance(result, list)
|
||||||
|
|
||||||
|
|
||||||
|
def test_source_counts_tuples(seeded_db):
|
||||||
|
result = seeded_db.source_counts()
|
||||||
|
for item in result:
|
||||||
|
assert isinstance(item, tuple)
|
||||||
|
assert len(item) == 2
|
||||||
|
|
||||||
|
|
||||||
|
# ── draft_author_count_map ───────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_draft_author_count_map_returns_dict(seeded_db):
|
||||||
|
result = seeded_db.draft_author_count_map()
|
||||||
|
assert isinstance(result, dict)
|
||||||
|
# seeded_db has 4 drafts with authors
|
||||||
|
assert len(result) >= 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_draft_author_count_map_values(seeded_db):
|
||||||
|
result = seeded_db.draft_author_count_map()
|
||||||
|
# draft-alpha-agent-comm has 2 authors
|
||||||
|
assert result.get("draft-alpha-agent-comm") == 2
|
||||||
|
# draft-beta-ml-traffic has 1 author
|
||||||
|
assert result.get("draft-beta-ml-traffic") == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_draft_author_count_map_empty(tmp_db):
|
||||||
|
result = tmp_db.draft_author_count_map()
|
||||||
|
assert result == {}
|
||||||
|
|
||||||
|
|
||||||
|
# ── search_gaps ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_gaps_empty_db(tmp_db):
|
||||||
|
result = tmp_db.search_gaps("anything")
|
||||||
|
assert isinstance(result, list)
|
||||||
|
assert len(result) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_gaps_finds_match(seeded_db):
|
||||||
|
seeded_db.conn.execute(
|
||||||
|
"INSERT INTO gaps (topic, description, category, severity) VALUES (?, ?, ?, ?)",
|
||||||
|
("Agent Discovery Gap", "No standard for agent discovery", "Agent discovery/reg", "high"),
|
||||||
|
)
|
||||||
|
seeded_db.conn.commit()
|
||||||
|
result = seeded_db.search_gaps("discovery")
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0]["topic"] == "Agent Discovery Gap"
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_gaps_no_match(seeded_db):
|
||||||
|
seeded_db.conn.execute(
|
||||||
|
"INSERT INTO gaps (topic, description, category, severity) VALUES (?, ?, ?, ?)",
|
||||||
|
("Agent Discovery Gap", "No standard", "Agent discovery/reg", "high"),
|
||||||
|
)
|
||||||
|
seeded_db.conn.commit()
|
||||||
|
result = seeded_db.search_gaps("zzz-nonexistent-zzz")
|
||||||
|
assert len(result) == 0
|
||||||
|
|
||||||
|
|
||||||
|
# ── search_authors ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_authors_by_name(seeded_db):
|
||||||
|
result = seeded_db.search_authors("Alice")
|
||||||
|
assert len(result) >= 1
|
||||||
|
assert any(a["name"] == "Alice Researcher" for a in result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_authors_by_affiliation(seeded_db):
|
||||||
|
result = seeded_db.search_authors("ExampleCorp")
|
||||||
|
assert len(result) >= 2 # Alice and Carol
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_authors_no_match(seeded_db):
|
||||||
|
result = seeded_db.search_authors("zzz-nonexistent-zzz")
|
||||||
|
assert len(result) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_authors_empty_db(tmp_db):
|
||||||
|
result = tmp_db.search_authors("anyone")
|
||||||
|
assert isinstance(result, list)
|
||||||
|
assert len(result) == 0
|
||||||
|
|
||||||
|
|
||||||
|
# ── search_authors dict shape ────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_authors_result_shape(seeded_db):
|
||||||
|
result = seeded_db.search_authors("Alice")
|
||||||
|
for item in result:
|
||||||
|
assert "person_id" in item
|
||||||
|
assert "name" in item
|
||||||
|
assert "affiliation" in item
|
||||||
269
tests/test_web.py
Normal file
269
tests/test_web.py
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
"""Tests for Flask web routes using the test client.
|
||||||
|
|
||||||
|
Uses the real data/drafts.db so templates can render with actual data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# Ensure src is on path
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||||
|
|
||||||
|
from webui.app import create_app
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client():
|
||||||
|
"""Create a Flask test client in dev mode (admin enabled)."""
|
||||||
|
# Reset auth module state so create_app can re-initialize
|
||||||
|
import webui.auth as auth_mod
|
||||||
|
auth_mod._initialized = False
|
||||||
|
auth_mod._dev_mode = False
|
||||||
|
|
||||||
|
app = create_app(dev=True)
|
||||||
|
app.config["TESTING"] = True
|
||||||
|
with app.test_client() as c:
|
||||||
|
yield c
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def prod_client():
|
||||||
|
"""Create a Flask test client in production mode (admin disabled)."""
|
||||||
|
import webui.auth as auth_mod
|
||||||
|
auth_mod._initialized = False
|
||||||
|
auth_mod._dev_mode = False
|
||||||
|
|
||||||
|
app = create_app(dev=False)
|
||||||
|
app.config["TESTING"] = True
|
||||||
|
with app.test_client() as c:
|
||||||
|
yield c
|
||||||
|
|
||||||
|
|
||||||
|
# ── Public pages ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_overview_page(client):
|
||||||
|
resp = client.get("/")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_drafts_page(client):
|
||||||
|
resp = client.get("/drafts")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_authors_page(client):
|
||||||
|
resp = client.get("/authors")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_timeline_page(client):
|
||||||
|
resp = client.get("/timeline")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_page_empty(client):
|
||||||
|
resp = client.get("/search")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_page_with_query(client):
|
||||||
|
resp = client.get("/search?q=agent")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_about_page(client):
|
||||||
|
resp = client.get("/about")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_ratings_page(client):
|
||||||
|
resp = client.get("/ratings")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_citations_page(client):
|
||||||
|
resp = client.get("/citations")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_ideas_page(client):
|
||||||
|
resp = client.get("/ideas")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_idea_clusters_page(client):
|
||||||
|
resp = client.get("/idea-clusters")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_architecture_page(client):
|
||||||
|
resp = client.get("/architecture")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_impressum_page(client):
|
||||||
|
resp = client.get("/impressum")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_datenschutz_page(client):
|
||||||
|
resp = client.get("/datenschutz")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
# ── 404 handling ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_404_for_nonexistent_page(client):
|
||||||
|
resp = client.get("/this-page-does-not-exist")
|
||||||
|
assert resp.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_404_for_nonexistent_draft(client):
|
||||||
|
resp = client.get("/drafts/draft-nonexistent-foobar-xyz")
|
||||||
|
assert resp.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
# ── Admin pages (dev mode) ───────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_gaps_page_dev(client):
|
||||||
|
resp = client.get("/gaps")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_monitor_page_dev(client):
|
||||||
|
resp = client.get("/monitor")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_proposals_page_dev(client):
|
||||||
|
resp = client.get("/proposals")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
# ── Admin pages (production = 404) ───────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_gaps_page_prod_hidden(prod_client):
|
||||||
|
resp = prod_client.get("/gaps")
|
||||||
|
assert resp.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_monitor_page_prod_hidden(prod_client):
|
||||||
|
resp = prod_client.get("/monitor")
|
||||||
|
assert resp.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_proposals_page_prod_hidden(prod_client):
|
||||||
|
resp = prod_client.get("/proposals")
|
||||||
|
assert resp.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
# ── API endpoints ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_stats(client):
|
||||||
|
resp = client.get("/api/stats")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.get_json()
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
assert "total_drafts" in data or "drafts" in data or len(data) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_drafts(client):
|
||||||
|
resp = client.get("/api/drafts")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.get_json()
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_ratings(client):
|
||||||
|
resp = client.get("/api/ratings")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.get_json()
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_timeline(client):
|
||||||
|
resp = client.get("/api/timeline")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.get_json()
|
||||||
|
assert isinstance(data, (dict, list))
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_categories(client):
|
||||||
|
resp = client.get("/api/categories")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.get_json()
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_ideas(client):
|
||||||
|
resp = client.get("/api/ideas")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.get_json()
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_search_empty(client):
|
||||||
|
resp = client.get("/api/search")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.get_json()
|
||||||
|
assert "drafts" in data
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_search_with_query(client):
|
||||||
|
resp = client.get("/api/search?q=agent")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.get_json()
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_draft_detail_not_found(client):
|
||||||
|
resp = client.get("/api/drafts/nonexistent-draft-xyz")
|
||||||
|
assert resp.status_code == 404
|
||||||
|
data = resp.get_json()
|
||||||
|
assert "error" in data
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_architecture(client):
|
||||||
|
resp = client.get("/api/architecture")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.get_json()
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_idea_clusters(client):
|
||||||
|
resp = client.get("/api/idea-clusters")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_citations(client):
|
||||||
|
resp = client.get("/api/citations")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_author_network(client):
|
||||||
|
resp = client.get("/api/authors/network")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
# ── CSV export format ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_drafts_csv(client):
|
||||||
|
resp = client.get("/api/drafts?format=csv")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert "text/csv" in resp.content_type
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_categories_csv(client):
|
||||||
|
resp = client.get("/api/categories?format=csv")
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert "text/csv" in resp.content_type
|
||||||
Reference in New Issue
Block a user