Add automated unit and mock integration test suite

- Configure conftest.py with pytest tmp_path DB isolation fixtures
- Add test_db.py for schema connection validations
- Add test_fetcher.py with mocked yt-dlp subprocess parser checks
- Add test_summarizer.py with mocked transcripts and Claude API requests
- Add test_main.py for FastAPI router endpoints and state updates

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Gan, Jimmy
2026-06-03 11:15:27 +08:00
parent dbd3dc0237
commit 1e67eeb25b
7 changed files with 419 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
import pytest
from fastapi.testclient import TestClient
from main import app, get_db
import aiosqlite
from unittest.mock import patch
@pytest.fixture
def client(test_db):
# Override get_db dependency to use the isolated test database
async def override_get_db():
yield test_db
app.dependency_overrides[get_db] = override_get_db
yield TestClient(app)
app.dependency_overrides.clear()
def test_health(client):
response = client.get("/api/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
@pytest.mark.asyncio
async def test_get_videos(client, test_db):
# Insert mock videos with different statuses
await test_db.execute("""
INSERT INTO videos (video_id, title, url, status)
VALUES
('v1', 'Pending Video', 'https://youtube.com/v1', 'pending'),
('v2', 'Read Video', 'https://youtube.com/v2', 'read'),
('v3', 'Skipped Video', 'https://youtube.com/v3', 'skipped')
""")
await test_db.commit()
# Test pending filter
resp = client.get("/api/videos?status=pending")
assert resp.status_code == 200
data = resp.json()
assert len(data) == 1
assert data[0]["video_id"] == "v1"
# Test read filter
resp = client.get("/api/videos?status=read")
assert resp.status_code == 200
data = resp.json()
assert len(data) == 1
assert data[0]["video_id"] == "v2"
def test_update_status_success(client, test_db):
# Insert pending video
test_db.execute_insert = lambda *args: None # Helper since we execute raw
async def insert():
await test_db.execute("""
INSERT INTO videos (video_id, title, url, status)
VALUES ('status_vid', 'Title', 'https://youtube.com/watch?v=status_vid', 'pending')
""")
await test_db.commit()
import asyncio
asyncio.run(insert())
# Send status update
resp = client.post("/api/videos/status_vid/status", json={"status": "read"})
assert resp.status_code == 200
assert resp.json() == {"message": "Video status_vid status updated to read."}
# Verify db was updated
async def verify():
cursor = await test_db.execute("SELECT status FROM videos WHERE video_id = 'status_vid'")
row = await cursor.fetchone()
return row["status"]
status = asyncio.run(verify())
assert status == "read"
def test_update_status_invalid_value(client):
resp = client.post("/api/videos/status_vid/status", json={"status": "archived"})
assert resp.status_code == 400
assert "Invalid status value" in resp.json()["detail"]
def test_update_status_not_found(client):
resp = client.post("/api/videos/missing_vid/status", json={"status": "read"})
assert resp.status_code == 404
assert "Video not found" in resp.json()["detail"]
def test_trigger_fetch(client):
with patch("main.bg_fetch_and_summarize") as mock_task:
resp = client.post("/api/videos/fetch")
assert resp.status_code == 200
assert "sync and summarization running in background" in resp.json()["message"]
@pytest.mark.asyncio
async def test_trigger_summarize(client, test_db):
# Insert video
await test_db.execute("""
INSERT INTO videos (video_id, title, url, status)
VALUES ('sum_vid', 'Title', 'https://youtube.com/watch?v=sum_vid', 'pending')
""")
await test_db.commit()
with patch("main.summarize_video") as mock_sum:
resp = client.post("/api/videos/sum_vid/summarize")
assert resp.status_code == 200
assert "summarization for video sum_vid queued" in resp.json()["message"].lower()
def test_trigger_summarize_not_found(client):
resp = client.post("/api/videos/missing_sum/summarize")
assert resp.status_code == 404
assert "Video not found" in resp.json()["detail"]