ytptube/app/tests/test_datastore.py
2025-09-13 18:35:41 +03:00

157 lines
5.2 KiB
Python

import json
import sqlite3
from dataclasses import asdict
from datetime import UTC, datetime
from email.utils import formatdate
import pytest
from app.library.DataStore import DataStore, StoreType
from app.library.ItemDTO import ItemDTO
class StubDownload:
def __init__(self, info: ItemDTO, started: bool = False, cancelled: bool = False):
self.info = info
self._started = started
self._cancelled = cancelled
def started(self) -> bool:
return self._started
def is_cancelled(self) -> bool:
return self._cancelled
def make_conn() -> sqlite3.Connection:
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
conn.execute(
"CREATE TABLE history (id TEXT PRIMARY KEY, type TEXT, url TEXT, data TEXT, created_at TEXT)"
)
return conn
def make_item(id: str, url: str = "http://u", title: str = "t", folder: str = "f") -> ItemDTO:
return ItemDTO(id=id, title=title, url=url, folder=folder)
class TestStoreType:
def test_all_and_from_value_and_str(self) -> None:
assert set(StoreType.all()) == {"done", "queue"}
assert StoreType.from_value("queue") is StoreType.QUEUE
assert StoreType.from_value("done") is StoreType.HISTORY
assert str(StoreType.QUEUE) == "queue"
with pytest.raises(ValueError, match="Invalid StoreType value"):
StoreType.from_value("invalid")
class TestDataStore:
def test_saved_items_parses_rows(self) -> None:
conn = make_conn()
# Prepare a stored item JSON (without _id, it will be set from the row)
dto = make_item(id="ignore", url="http://x", title="Title", folder="F")
data = asdict(dto)
data.pop("_id", None)
created = datetime(2024, 1, 2, 3, 4, 5, tzinfo=UTC)
conn.execute(
"INSERT INTO history (id, type, url, data, created_at) VALUES (?, ?, ?, ?, ?)",
("abc", "queue", "http://x", json.dumps(data), created.strftime("%Y-%m-%d %H:%M:%S")),
)
store = DataStore(StoreType.QUEUE, conn)
items = store.saved_items()
assert len(items) == 1
key, item = items[0]
assert key == "abc"
assert item._id == "abc"
assert item.url == "http://x"
assert isinstance(item.datetime, str)
assert item.datetime == formatdate(created.timestamp())
def test_put_and_delete_persist(self) -> None:
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item = make_item(id="vid1")
d = StubDownload(info=item)
ret = store.put(d)
assert ret is store._dict[item._id]
# Verify row written; JSON should not contain datetime field
row = conn.execute("SELECT * FROM history WHERE id=?", (item._id,)).fetchone()
assert row is not None
assert row["type"] == "queue"
assert row["url"] == item.url
assert '"datetime"' not in row["data"]
# Delete and ensure removal
store.delete(item._id)
row2 = conn.execute("SELECT * FROM history WHERE id=?", (item._id,)).fetchone()
assert row2 is None
def test_exists_and_get(self) -> None:
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item = make_item(id="a1", url="http://u1")
d = StubDownload(info=item)
store.put(d)
assert store.exists(key=item._id) is True
assert store.exists(url=item.url) is True
with pytest.raises(KeyError):
store.exists()
got = store.get(key=item._id)
assert got.info._id == item._id
got2 = store.get(url=item.url)
assert got2.info.url == item.url
with pytest.raises(KeyError):
store.get()
with pytest.raises(KeyError):
store.get(key="missing")
def test_next_and_empty(self) -> None:
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
assert store.empty() is True
d1 = StubDownload(info=make_item(id="x1"))
d2 = StubDownload(info=make_item(id="x2"))
store.put(d1)
store.put(d2)
assert store.empty() is False
first_key, first_val = store.next()
assert first_key == d1.info._id
assert first_val.info._id == d1.info._id
def test_has_downloads_and_get_next_download(self) -> None:
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
# One non-auto-start, one started, one cancelled, and one eligible
i1 = make_item(id="n1")
i1.auto_start = False
i2 = make_item(id="s1")
i3 = make_item(id="c1")
i4 = make_item(id="ok1")
store.put(StubDownload(info=i1, started=False))
store.put(StubDownload(info=i2, started=True))
store.put(StubDownload(info=i3, started=False, cancelled=True))
store.put(StubDownload(info=i4, started=False))
assert store.has_downloads() is True
nxt = store.get_next_download()
assert isinstance(nxt, StubDownload)
assert nxt.info._id == i4._id
@pytest.mark.asyncio
async def test_test_method_executes_query(self) -> None:
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
# Should not raise
ok = await store.test()
assert ok is True