sprint: fixes batch — saveNote history, DeepSeek retry, channel rotation, test fixes, docker env, blocking transcript, sync errors
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
"""Tests for summarizer.py — mocks transcript API + DeepSeek API."""
|
||||
import pytest
|
||||
import os
|
||||
import httpx
|
||||
@@ -5,6 +6,7 @@ import aiosqlite
|
||||
import summarizer
|
||||
from unittest.mock import patch, MagicMock, AsyncMock
|
||||
|
||||
|
||||
class MockTranscript:
|
||||
def fetch(self):
|
||||
return [
|
||||
@@ -25,6 +27,17 @@ class MockTranscriptList:
|
||||
def __iter__(self):
|
||||
return iter([self.transcript])
|
||||
|
||||
|
||||
def _mock_deepseek_response(content: str) -> MagicMock:
|
||||
"""Build a DeepSeek/OpenAI-compatible API response."""
|
||||
r = MagicMock(spec=httpx.Response)
|
||||
r.status_code = 200
|
||||
r.json.return_value = {
|
||||
"choices": [{"message": {"content": content}}]
|
||||
}
|
||||
return r
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_download_transcript_success():
|
||||
mock_trans = MockTranscript()
|
||||
@@ -34,9 +47,9 @@ async def test_download_transcript_success():
|
||||
text = summarizer.download_transcript("test_vid")
|
||||
assert text == "Welcome to this video tutorial. Today we are learning about Svelte 5 and runes."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_summarize_video_success(test_db):
|
||||
# Insert a pending video in db first
|
||||
await test_db.execute("""
|
||||
INSERT INTO videos (video_id, title, url, status)
|
||||
VALUES ('summarize_vid', 'Video to Summarize', 'https://www.youtube.com/watch?v=summarize_vid', 'pending')
|
||||
@@ -45,19 +58,8 @@ async def test_summarize_video_success(test_db):
|
||||
|
||||
mock_trans = MockTranscript()
|
||||
mock_list = MockTranscriptList(mock_trans)
|
||||
mock_response = _mock_deepseek_response("**Mock Core Hook**\n- Takeaway 1\n- Takeaway 2\nVerdict line.")
|
||||
|
||||
# Mock Claude HTTP response
|
||||
mock_response = MagicMock(spec=httpx.Response)
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"content": [
|
||||
{
|
||||
"text": "**Mock Core Hook**\n- Takeaway 1\n- Takeaway 2\nVerdict line."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Patch both transcript api and httpx AsyncClient post call
|
||||
with patch("youtube_transcript_api.YouTubeTranscriptApi.list", return_value=mock_list), \
|
||||
patch("httpx.AsyncClient.post", new_callable=AsyncMock, return_value=mock_response) as mock_post:
|
||||
|
||||
@@ -69,12 +71,12 @@ async def test_summarize_video_success(test_db):
|
||||
assert summarizer.BASE_URL in url
|
||||
|
||||
headers = mock_post.call_args[1]["headers"]
|
||||
assert "x-api-key" in headers
|
||||
assert "Authorization" in headers
|
||||
assert headers["User-Agent"] is not None
|
||||
|
||||
payload = mock_post.call_args[1]["json"]
|
||||
assert payload["model"] == "claude-haiku-4-5-20251001"
|
||||
assert len(payload["messages"]) == 1
|
||||
assert payload["model"] == "deepseek-chat"
|
||||
assert len(payload["messages"]) == 2 # system + user
|
||||
|
||||
assert summary == "**Mock Core Hook**\n- Takeaway 1\n- Takeaway 2\nVerdict line."
|
||||
|
||||
@@ -85,9 +87,40 @@ async def test_summarize_video_success(test_db):
|
||||
assert video["transcript"] == "Welcome to this video tutorial. Today we are learning about Svelte 5 and runes."
|
||||
assert video["summary"] == "**Mock Core Hook**\n- Takeaway 1\n- Takeaway 2\nVerdict line."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_summarize_video_deepseek_error_leaves_null(test_db):
|
||||
"""When DeepSeek fails, summary should stay NULL for retry."""
|
||||
await test_db.execute("""
|
||||
INSERT INTO videos (video_id, title, url, status)
|
||||
VALUES ('err_vid', 'Error Video', 'https://youtube.com/watch?v=err_vid', 'pending')
|
||||
""")
|
||||
await test_db.commit()
|
||||
|
||||
mock_trans = MockTranscript()
|
||||
mock_list = MockTranscriptList(mock_trans)
|
||||
mock_response = MagicMock(spec=httpx.Response)
|
||||
mock_response.status_code = 429
|
||||
# raise_for_status on 429 should raise HTTPStatusError
|
||||
mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
|
||||
"429 Rate Limited", request=MagicMock(), response=mock_response
|
||||
)
|
||||
|
||||
with patch("youtube_transcript_api.YouTubeTranscriptApi.list", return_value=mock_list), \
|
||||
patch("httpx.AsyncClient.post", new_callable=AsyncMock, return_value=mock_response):
|
||||
|
||||
result = await summarizer.summarize_video("err_vid")
|
||||
|
||||
assert result is None # Returns None instead of an error string
|
||||
|
||||
# Verify DB — summary should remain NULL
|
||||
cursor = await test_db.execute("SELECT summary FROM videos WHERE video_id = 'err_vid'")
|
||||
row = await cursor.fetchone()
|
||||
assert row["summary"] is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_summarize_all_pending(test_db):
|
||||
# Insert multiple videos, one pending and one already summarized
|
||||
await test_db.execute("""
|
||||
INSERT INTO videos (video_id, title, url, summary, status)
|
||||
VALUES
|
||||
@@ -98,28 +131,21 @@ async def test_summarize_all_pending(test_db):
|
||||
|
||||
mock_trans = MockTranscript()
|
||||
mock_list = MockTranscriptList(mock_trans)
|
||||
|
||||
mock_response = MagicMock(spec=httpx.Response)
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"content": [{"text": "Fresh summary"}]
|
||||
}
|
||||
mock_response = _mock_deepseek_response("Fresh summary")
|
||||
|
||||
with patch("youtube_transcript_api.YouTubeTranscriptApi.list", return_value=mock_list), \
|
||||
patch("httpx.AsyncClient.post", new_callable=AsyncMock, return_value=mock_response) as mock_post:
|
||||
|
||||
await summarizer.summarize_all_pending()
|
||||
|
||||
# Should only call API once for 'pending_vid'
|
||||
# Should only call API once for 'pending_vid' (done_vid already has summary)
|
||||
mock_post.assert_called_once()
|
||||
payload = mock_post.call_args[1]["json"]
|
||||
assert "Welcome to this video tutorial." in payload["messages"][0]["content"]
|
||||
|
||||
# Verify db states
|
||||
cursor = await test_db.execute("SELECT video_id, summary FROM videos ORDER BY video_id")
|
||||
videos = await cursor.fetchall()
|
||||
assert videos[0]["video_id"] == "done_vid"
|
||||
assert videos[0]["summary"] == "Existing summary" # Untouched
|
||||
assert videos[0]["summary"] == "Existing summary" # Untouched
|
||||
|
||||
assert videos[1]["video_id"] == "pending_vid"
|
||||
assert videos[1]["summary"] == "Fresh summary" # Updated
|
||||
assert videos[1]["summary"] == "Fresh summary" # Updated
|
||||
|
||||
Reference in New Issue
Block a user