mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-08 22:22:17 +02:00
test(capabilities): cover youtube verb schemas, executors, and registry
This commit is contained in:
parent
f967cebe92
commit
5d0bd8125b
5 changed files with 249 additions and 0 deletions
|
|
@ -0,0 +1,55 @@
|
|||
"""``youtube.comments`` executor: verb input → actor input mapping → typed items."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.capabilities.youtube.comments.executor import build_comments_executor
|
||||
from app.capabilities.youtube.comments.schemas import CommentsInput, CommentsOutput
|
||||
from app.proprietary.scrapers.youtube import YouTubeCommentsInput
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
class _FakeScraper:
|
||||
def __init__(self, items: list[dict]):
|
||||
self._items = items
|
||||
self.calls: list[YouTubeCommentsInput] = []
|
||||
|
||||
async def __call__(self, actor_input: YouTubeCommentsInput) -> list[dict]:
|
||||
self.calls.append(actor_input)
|
||||
return self._items
|
||||
|
||||
|
||||
async def test_maps_urls_and_wraps_comment_items():
|
||||
scraper = _FakeScraper([{"cid": "c1", "comment": "nice", "author": "@a"}])
|
||||
execute = build_comments_executor(scrape_fn=scraper)
|
||||
|
||||
out = await execute(CommentsInput(urls=["https://www.youtube.com/watch?v=abc"]))
|
||||
|
||||
assert isinstance(out, CommentsOutput)
|
||||
assert len(out.items) == 1
|
||||
assert out.items[0].cid == "c1"
|
||||
assert out.items[0].comment == "nice"
|
||||
|
||||
(actor_input,) = scraper.calls
|
||||
assert [u.url for u in actor_input.startUrls] == [
|
||||
"https://www.youtube.com/watch?v=abc"
|
||||
]
|
||||
|
||||
|
||||
async def test_forwards_max_comments_and_sort():
|
||||
scraper = _FakeScraper([])
|
||||
execute = build_comments_executor(scrape_fn=scraper)
|
||||
|
||||
await execute(
|
||||
CommentsInput(
|
||||
urls=["https://youtu.be/abc"],
|
||||
max_comments=50,
|
||||
sort_by="TOP_COMMENTS",
|
||||
)
|
||||
)
|
||||
|
||||
(actor_input,) = scraper.calls
|
||||
assert actor_input.maxComments == 50
|
||||
assert actor_input.sortCommentsBy == "TOP_COMMENTS"
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
"""``youtube.comments`` input guards: URLs required and batch/count bounded."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from app.capabilities.youtube.comments.schemas import (
|
||||
MAX_COMMENT_VIDEOS,
|
||||
CommentsInput,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
def test_rejects_empty_url_batch():
|
||||
with pytest.raises(ValidationError):
|
||||
CommentsInput(urls=[])
|
||||
|
||||
|
||||
def test_rejects_batch_over_the_cap():
|
||||
too_many = [f"https://youtu.be/{i}" for i in range(MAX_COMMENT_VIDEOS + 1)]
|
||||
with pytest.raises(ValidationError):
|
||||
CommentsInput(urls=too_many)
|
||||
|
||||
|
||||
def test_defaults_max_comments_and_newest_first():
|
||||
payload = CommentsInput(urls=["https://youtu.be/abc"])
|
||||
assert payload.max_comments == 20
|
||||
assert payload.sort_by == "NEWEST_FIRST"
|
||||
|
||||
|
||||
def test_rejects_zero_max_comments():
|
||||
with pytest.raises(ValidationError):
|
||||
CommentsInput(urls=["https://youtu.be/abc"], max_comments=0)
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
"""``youtube.scrape`` executor: verb input → actor input mapping → typed items.
|
||||
|
||||
Boundary mocked: the proprietary scraper (injected fake). NOT mocked: the verb's
|
||||
own payload→YouTubeScrapeInput mapping and the dict→VideoItem wrapping.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.capabilities.youtube.scrape.executor import build_scrape_executor
|
||||
from app.capabilities.youtube.scrape.schemas import ScrapeInput, ScrapeOutput
|
||||
from app.proprietary.scrapers.youtube import YouTubeScrapeInput
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
class _FakeScraper:
|
||||
"""Records the actor input it was called with and returns canned items."""
|
||||
|
||||
def __init__(self, items: list[dict]):
|
||||
self._items = items
|
||||
self.calls: list[YouTubeScrapeInput] = []
|
||||
|
||||
async def __call__(self, actor_input: YouTubeScrapeInput) -> list[dict]:
|
||||
self.calls.append(actor_input)
|
||||
return self._items
|
||||
|
||||
|
||||
async def test_maps_urls_to_start_urls_and_wraps_items():
|
||||
scraper = _FakeScraper([{"id": "abc", "title": "Hello"}])
|
||||
execute = build_scrape_executor(scrape_fn=scraper)
|
||||
|
||||
out = await execute(ScrapeInput(urls=["https://www.youtube.com/watch?v=abc"]))
|
||||
|
||||
assert isinstance(out, ScrapeOutput)
|
||||
assert len(out.items) == 1
|
||||
assert out.items[0].id == "abc"
|
||||
assert out.items[0].title == "Hello"
|
||||
|
||||
(actor_input,) = scraper.calls
|
||||
assert [u.url for u in actor_input.startUrls] == [
|
||||
"https://www.youtube.com/watch?v=abc"
|
||||
]
|
||||
assert actor_input.searchQueries == []
|
||||
|
||||
|
||||
async def test_forwards_search_queries():
|
||||
scraper = _FakeScraper([])
|
||||
execute = build_scrape_executor(scrape_fn=scraper)
|
||||
|
||||
await execute(ScrapeInput(search_queries=["python", "rust"]))
|
||||
|
||||
(actor_input,) = scraper.calls
|
||||
assert actor_input.searchQueries == ["python", "rust"]
|
||||
assert actor_input.startUrls == []
|
||||
|
||||
|
||||
async def test_max_results_caps_every_content_type():
|
||||
scraper = _FakeScraper([])
|
||||
execute = build_scrape_executor(scrape_fn=scraper)
|
||||
|
||||
await execute(ScrapeInput(search_queries=["x"], max_results=7))
|
||||
|
||||
(actor_input,) = scraper.calls
|
||||
# Videos, shorts, and streams must all inherit the caller's cap, otherwise a
|
||||
# channel scrape would silently return only plain videos (actor default 0).
|
||||
assert actor_input.maxResults == 7
|
||||
assert actor_input.maxResultsShorts == 7
|
||||
assert actor_input.maxResultStreams == 7
|
||||
|
||||
|
||||
async def test_forwards_subtitle_options():
|
||||
scraper = _FakeScraper([])
|
||||
execute = build_scrape_executor(scrape_fn=scraper)
|
||||
|
||||
await execute(
|
||||
ScrapeInput(
|
||||
urls=["https://youtu.be/abc"],
|
||||
download_subtitles=True,
|
||||
subtitles_language="fr",
|
||||
)
|
||||
)
|
||||
|
||||
(actor_input,) = scraper.calls
|
||||
assert actor_input.downloadSubtitles is True
|
||||
assert actor_input.subtitlesLanguage == "fr"
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
"""``youtube.scrape`` input guards: a source is required and the batch is bounded."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from app.capabilities.youtube.scrape.schemas import (
|
||||
MAX_YOUTUBE_SOURCES,
|
||||
ScrapeInput,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
def test_rejects_input_with_neither_urls_nor_queries():
|
||||
with pytest.raises(ValidationError):
|
||||
ScrapeInput()
|
||||
|
||||
|
||||
def test_accepts_urls_only():
|
||||
payload = ScrapeInput(urls=["https://www.youtube.com/watch?v=abc"])
|
||||
assert payload.search_queries == []
|
||||
|
||||
|
||||
def test_accepts_search_queries_only():
|
||||
payload = ScrapeInput(search_queries=["python tutorial"])
|
||||
assert payload.urls == []
|
||||
|
||||
|
||||
def test_max_results_defaults_and_is_bounded():
|
||||
assert ScrapeInput(search_queries=["x"]).max_results == 10
|
||||
with pytest.raises(ValidationError):
|
||||
ScrapeInput(search_queries=["x"], max_results=0)
|
||||
|
||||
|
||||
def test_rejects_more_sources_than_the_cap():
|
||||
too_many = [f"https://youtu.be/{i}" for i in range(MAX_YOUTUBE_SOURCES + 1)]
|
||||
with pytest.raises(ValidationError):
|
||||
ScrapeInput(urls=too_many)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
"""The youtube namespace registers each verb as one Capability the doors/agent read."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.capabilities import (
|
||||
youtube, # noqa: F401 — importing the namespace registers its verbs
|
||||
)
|
||||
from app.capabilities.core.store import get_capability
|
||||
from app.capabilities.youtube.comments.schemas import CommentsInput, CommentsOutput
|
||||
from app.capabilities.youtube.scrape.schemas import ScrapeInput, ScrapeOutput
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
def test_youtube_scrape_is_registered_and_free():
|
||||
cap = get_capability("youtube.scrape")
|
||||
|
||||
assert cap.name == "youtube.scrape"
|
||||
assert cap.input_schema is ScrapeInput
|
||||
assert cap.output_schema is ScrapeOutput
|
||||
assert cap.billing_unit is None
|
||||
|
||||
|
||||
def test_youtube_comments_is_registered_and_free():
|
||||
cap = get_capability("youtube.comments")
|
||||
|
||||
assert cap.name == "youtube.comments"
|
||||
assert cap.input_schema is CommentsInput
|
||||
assert cap.output_schema is CommentsOutput
|
||||
assert cap.billing_unit is None
|
||||
Loading…
Add table
Add a link
Reference in a new issue