Files
danbooru-mcp/scripts/test_danbooru_api.py
Aodhan Collins 08c6e14616 Initial commit
2026-03-02 23:29:58 +00:00

222 lines
7.2 KiB
Python

#!/usr/bin/env python3
"""
Test script for the Danbooru API.
Verifies:
1. Authentication with the API key works
2. Tag listing endpoint returns expected fields
3. Pagination works (multiple pages)
4. Tag search / filtering by category works
Usage:
python scripts/test_danbooru_api.py
Reads DANBOORU_API_KEY from .env or environment.
"""
import json
import os
import sys
import time
from pathlib import Path
try:
import requests
except ImportError:
print("ERROR: 'requests' is not installed. Run: pip install requests")
sys.exit(1)
# ---------------------------------------------------------------------------
# Load .env
# ---------------------------------------------------------------------------
def load_env() -> dict[str, str]:
env: dict[str, str] = {}
env_path = Path(__file__).parent.parent / ".env"
if env_path.exists():
for line in env_path.read_text().splitlines():
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, _, v = line.partition("=")
env[k.strip()] = v.strip()
return env
# ---------------------------------------------------------------------------
# API helpers
# ---------------------------------------------------------------------------
BASE_URL = "https://danbooru.donmai.us"
# Danbooru tag categories
CATEGORY_NAMES = {
0: "general",
1: "artist",
3: "copyright",
4: "character",
5: "meta",
}
def make_session(api_key: str | None = None, username: str | None = None) -> requests.Session:
"""Create a requests Session.
Danbooru public endpoints (tag listing, searching) do not require
authentication. Auth is only needed for account-specific actions.
When provided, credentials must be (login, api_key) — NOT (user, api_key).
"""
session = requests.Session()
if api_key and username:
session.auth = (username, api_key)
session.headers.update({"User-Agent": "danbooru-mcp-test/0.1"})
return session
def get_tags_page(
session: requests.Session,
page: int = 1,
limit: int = 20,
search_name: str | None = None,
search_category: int | None = None,
order: str = "count", # "count" | "name" | "date"
) -> list[dict]:
"""Fetch one page of tags from the Danbooru API."""
params: dict = {
"page": page,
"limit": limit,
"search[order]": order,
}
if search_name:
params["search[name_matches]"] = search_name
if search_category is not None:
params["search[category]"] = search_category
resp = session.get(f"{BASE_URL}/tags.json", params=params, timeout=15)
resp.raise_for_status()
return resp.json()
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
def test_basic_fetch(session: requests.Session) -> None:
print("\n[1] Basic fetch — top 5 tags by post count")
tags = get_tags_page(session, page=1, limit=5, order="count")
assert isinstance(tags, list), f"Expected list, got {type(tags)}"
assert len(tags) > 0, "No tags returned"
for tag in tags:
cat = CATEGORY_NAMES.get(tag.get("category", -1), "unknown")
print(f" [{cat:12s}] {tag['name']:40s} posts={tag['post_count']:>8,}")
print(" PASS ✓")
def test_fields_present(session: requests.Session) -> None:
print("\n[2] Field presence check")
tags = get_tags_page(session, page=1, limit=1, order="count")
tag = tags[0]
required = {"id", "name", "post_count", "category", "is_deprecated", "words"}
missing = required - set(tag.keys())
assert not missing, f"Missing fields: {missing}"
print(f" Fields present: {sorted(tag.keys())}")
print(f" Sample tag: name={tag['name']!r} category={CATEGORY_NAMES.get(tag['category'])} deprecated={tag['is_deprecated']}")
print(" PASS ✓")
def test_pagination(session: requests.Session) -> None:
print("\n[3] Pagination — page 1 vs page 2 should differ")
p1 = get_tags_page(session, page=1, limit=5, order="count")
time.sleep(0.5)
p2 = get_tags_page(session, page=2, limit=5, order="count")
names_p1 = {t["name"] for t in p1}
names_p2 = {t["name"] for t in p2}
overlap = names_p1 & names_p2
assert not overlap, f"Pages 1 and 2 share tags: {overlap}"
print(f" Page 1: {sorted(names_p1)}")
print(f" Page 2: {sorted(names_p2)}")
print(" PASS ✓")
def test_category_filter(session: requests.Session) -> None:
print("\n[4] Category filter — fetch only 'character' tags (category=4)")
tags = get_tags_page(session, page=1, limit=5, search_category=4, order="count")
for tag in tags:
assert tag["category"] == 4, f"Expected category 4, got {tag['category']} for {tag['name']}"
print(f" {tag['name']:40s} posts={tag['post_count']:>8,}")
print(" PASS ✓")
def test_name_search(session: requests.Session) -> None:
print("\n[5] Name search — tags matching 'blue_hair*'")
tags = get_tags_page(session, page=1, limit=5, search_name="blue_hair*", order="count")
assert len(tags) > 0, "No results for blue_hair*"
for tag in tags:
cat = CATEGORY_NAMES.get(tag.get("category", -1), "unknown")
print(f" [{cat:12s}] {tag['name']:40s} posts={tag['post_count']:>8,}")
print(" PASS ✓")
def test_well_known_tags(session: requests.Session) -> None:
print("\n[6] Well-known tags — '1girl', 'blue_hair', 'sword' should exist")
for tag_name in ("1girl", "blue_hair", "sword"):
tags = get_tags_page(session, page=1, limit=1, search_name=tag_name, order="count")
found = [t for t in tags if t["name"] == tag_name]
assert found, f"Tag '{tag_name}' not found in API response"
t = found[0]
cat = CATEGORY_NAMES.get(t.get("category", -1), "unknown")
print(f" {tag_name:20s} category={cat:12s} posts={t['post_count']:>8,}")
print(" PASS ✓")
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main() -> None:
env = load_env()
api_key = env.get("DANBOORU_API_KEY") or os.environ.get("DANBOORU_API_KEY")
username = env.get("DANBOORU_USERNAME") or os.environ.get("DANBOORU_USERNAME")
if api_key:
print(f"API key loaded: {api_key[:8]}")
else:
print("No API key found — using unauthenticated access (public endpoints only)")
# Danbooru public tag endpoints don't require auth.
# Pass username + api_key only when both are available.
session = make_session(
api_key=api_key if (api_key and username) else None,
username=username,
)
tests = [
test_basic_fetch,
test_fields_present,
test_pagination,
test_category_filter,
test_name_search,
test_well_known_tags,
]
passed = 0
failed = 0
for test_fn in tests:
try:
test_fn(session)
passed += 1
except Exception as exc:
print(f" FAIL ✗ {exc}")
failed += 1
time.sleep(0.3) # be polite to the API
print(f"\n{'='*50}")
print(f"Results: {passed} passed, {failed} failed")
if failed:
sys.exit(1)
if __name__ == "__main__":
main()