ytptube/app/tests/test_datastore.py

989 lines
35 KiB
Python

import json
import sqlite3
from collections import OrderedDict
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
from app.library.operations import Operation
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
def test_get_item_returns_none_when_no_kwargs(self) -> None:
"""Test that get_item returns None when no kwargs provided."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
result = store.get_item()
assert result is None
def test_get_item_finds_by_single_attribute(self) -> None:
"""Test that get_item correctly finds item by a single attribute."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
# Create items with different attributes
item1 = make_item(id="vid1", url="http://example.com/1", title="Video 1", folder="folder1")
item1._id = "id1" # Override auto-generated UUID
item2 = make_item(id="vid2", url="http://example.com/2", title="Video 2", folder="folder2")
item2._id = "id2" # Override auto-generated UUID
d1 = StubDownload(info=item1)
d2 = StubDownload(info=item2)
store.put(d1)
store.put(d2)
# Test finding by title
result = store.get_item(title="Video 1")
assert result is not None
assert result.info._id == "id1"
assert result.info.title == "Video 1"
# Test finding by folder
result = store.get_item(folder="folder2")
assert result is not None
assert result.info._id == "id2"
assert result.info.folder == "folder2"
# Test finding by url
result = store.get_item(url="http://example.com/1")
assert result is not None
assert result.info._id == "id1"
def test_get_item_finds_by_multiple_attributes(self) -> None:
"""Test that get_item finds item when ANY of the provided attributes match."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", url="http://example.com/1", title="Video 1", folder="folder1")
item1._id = "id1"
item2 = make_item(id="vid2", url="http://example.com/2", title="Video 2", folder="folder2")
item2._id = "id2"
d1 = StubDownload(info=item1)
d2 = StubDownload(info=item2)
store.put(d1)
store.put(d2)
# Test finding by multiple attributes where one matches
result = store.get_item(title="Video 1", folder="wrong_folder")
assert result is not None
assert result.info._id == "id1"
# Test finding where second attribute matches
result = store.get_item(title="Wrong Title", folder="folder2")
assert result is not None
assert result.info._id == "id2"
def test_get_item_returns_none_when_no_match(self) -> None:
"""Test that get_item returns None when no attributes match."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item = make_item(id="vid1", url="http://example.com/1", title="Video 1", folder="folder1")
item._id = "id1"
d = StubDownload(info=item)
store.put(d)
# Test with non-matching attribute
result = store.get_item(title="Nonexistent Video")
assert result is None
# Test with non-existent attribute key
result = store.get_item(nonexistent_field="value")
assert result is None
def test_get_item_skips_items_with_no_info(self) -> None:
"""Test that get_item skips items that have no info attribute."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
# Create a valid item
item = make_item(id="vid1", url="http://example.com/1", title="Video 1")
item._id = "id1"
d = StubDownload(info=item)
store.put(d)
# Manually add an item with None info
class BrokenDownload:
def __init__(self):
self.info = None
store._dict["broken"] = BrokenDownload()
# Should still find the valid item
result = store.get_item(title="Video 1")
assert result is not None
assert result.info._id == "id1"
def test_get_item_returns_first_match(self) -> None:
"""Test that get_item returns the first matching item when multiple match."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
# Create multiple items with same title
item1 = make_item(id="vid1", url="http://example.com/1", title="Same Title", folder="folder1")
item1._id = "id1"
item2 = make_item(id="vid2", url="http://example.com/2", title="Same Title", folder="folder2")
item2._id = "id2"
d1 = StubDownload(info=item1)
d2 = StubDownload(info=item2)
store.put(d1)
store.put(d2)
# Should return first match (note: OrderedDict maintains insertion order)
result = store.get_item(title="Same Title")
assert result is not None
assert result.info._id == "id1"
def test_init(self) -> None:
"""Test DataStore initialization."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
assert store._type == StoreType.QUEUE
assert store._connection is conn
assert isinstance(store._dict, OrderedDict)
assert len(store._dict) == 0
def test_load(self) -> None:
"""Test loading items from database into memory."""
conn = make_conn()
# Insert items directly into database
item1_data = asdict(make_item(id="vid1", url="http://example.com/1", title="Video 1"))
item1_data.pop("_id", None)
item2_data = asdict(make_item(id="vid2", url="http://example.com/2", title="Video 2"))
item2_data.pop("_id", None)
created = datetime(2024, 1, 1, 12, 0, 0, tzinfo=UTC)
conn.execute(
"INSERT INTO history (id, type, url, data, created_at) VALUES (?, ?, ?, ?, ?)",
(
"id1",
str(StoreType.QUEUE),
"http://example.com/1",
json.dumps(item1_data),
created.strftime("%Y-%m-%d %H:%M:%S"),
),
)
conn.execute(
"INSERT INTO history (id, type, url, data, created_at) VALUES (?, ?, ?, ?, ?)",
(
"id2",
str(StoreType.QUEUE),
"http://example.com/2",
json.dumps(item2_data),
created.strftime("%Y-%m-%d %H:%M:%S"),
),
)
# Create store and load
store = DataStore(StoreType.QUEUE, conn)
assert len(store._dict) == 0
store.load()
assert len(store._dict) == 2
assert "id1" in store._dict
assert "id2" in store._dict
assert store._dict["id1"].info.url == "http://example.com/1"
assert store._dict["id2"].info.url == "http://example.com/2"
def test_load_with_different_store_types(self) -> None:
"""Test that load only loads items matching the store type."""
conn = make_conn()
# Insert items with different types
item1_data = asdict(make_item(id="vid1", url="http://example.com/1"))
item1_data.pop("_id", None)
item2_data = asdict(make_item(id="vid2", url="http://example.com/2"))
item2_data.pop("_id", None)
created = datetime(2024, 1, 1, 12, 0, 0, tzinfo=UTC)
conn.execute(
"INSERT INTO history (id, type, url, data, created_at) VALUES (?, ?, ?, ?, ?)",
(
"id1",
str(StoreType.QUEUE),
"http://example.com/1",
json.dumps(item1_data),
created.strftime("%Y-%m-%d %H:%M:%S"),
),
)
conn.execute(
"INSERT INTO history (id, type, url, data, created_at) VALUES (?, ?, ?, ?, ?)",
(
"id2",
str(StoreType.HISTORY),
"http://example.com/2",
json.dumps(item2_data),
created.strftime("%Y-%m-%d %H:%M:%S"),
),
)
# Load QUEUE store - should only get queue items
queue_store = DataStore(StoreType.QUEUE, conn)
queue_store.load()
assert len(queue_store._dict) == 1
assert "id1" in queue_store._dict
# Load HISTORY store - should only get history items
history_store = DataStore(StoreType.HISTORY, conn)
history_store.load()
assert len(history_store._dict) == 1
assert "id2" in history_store._dict
def test_get_by_id(self) -> None:
"""Test getting item by ID."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", url="http://example.com/1", title="Video 1")
item1._id = "id1"
item2 = make_item(id="vid2", url="http://example.com/2", title="Video 2")
item2._id = "id2"
d1 = StubDownload(info=item1)
d2 = StubDownload(info=item2)
store.put(d1)
store.put(d2)
# Test getting existing items
result = store.get_by_id("id1")
assert result is not None
assert result.info._id == "id1"
assert result.info.title == "Video 1"
result = store.get_by_id("id2")
assert result is not None
assert result.info._id == "id2"
# Test getting non-existent item
result = store.get_by_id("nonexistent")
assert result is None
def test_items(self) -> None:
"""Test getting all items as list of tuples."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
# Empty store
result = store.items()
assert len(list(result)) == 0
# Add items
item1 = make_item(id="vid1", url="http://example.com/1", title="Video 1")
item1._id = "id1"
item2 = make_item(id="vid2", url="http://example.com/2", title="Video 2")
item2._id = "id2"
d1 = StubDownload(info=item1)
d2 = StubDownload(info=item2)
store.put(d1)
store.put(d2)
# Test getting all items
result = list(store.items())
assert len(result) == 2
# Verify structure (list of tuples)
ids = [item[0] for item in result]
assert "id1" in ids
assert "id2" in ids
# Verify order is maintained (OrderedDict)
assert result[0][0] == "id1"
assert result[1][0] == "id2"
def test_get_total_count_with_empty_store(self) -> None:
"""Test get_total_count with empty datastore."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
count = store.get_total_count()
assert count == 0
def test_get_total_count_with_items(self) -> None:
"""Test get_total_count with items in database."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
# Add items directly to database
created = datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S")
for i in range(5):
item_data = asdict(make_item(id=f"vid{i}"))
item_data.pop("_id", None)
conn.execute(
"INSERT INTO history (id, type, url, data, created_at) VALUES (?, ?, ?, ?, ?)",
(f"id{i}", str(StoreType.QUEUE), f"http://example.com/{i}", json.dumps(item_data), created),
)
count = store.get_total_count()
assert count == 5
def test_get_total_count_respects_store_type(self) -> None:
"""Test that get_total_count only counts items of the correct type."""
conn = make_conn()
created = datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S")
# Add 3 QUEUE items
for i in range(3):
item_data = asdict(make_item(id=f"vid{i}"))
item_data.pop("_id", None)
conn.execute(
"INSERT INTO history (id, type, url, data, created_at) VALUES (?, ?, ?, ?, ?)",
(f"q{i}", str(StoreType.QUEUE), f"http://example.com/{i}", json.dumps(item_data), created),
)
# Add 2 HISTORY items
for i in range(2):
item_data = asdict(make_item(id=f"vid{i}"))
item_data.pop("_id", None)
conn.execute(
"INSERT INTO history (id, type, url, data, created_at) VALUES (?, ?, ?, ?, ?)",
(f"h{i}", str(StoreType.HISTORY), f"http://example.com/{i}", json.dumps(item_data), created),
)
queue_store = DataStore(StoreType.QUEUE, conn)
assert queue_store.get_total_count() == 3
history_store = DataStore(StoreType.HISTORY, conn)
assert history_store.get_total_count() == 2
def test_put_with_error_status_emits_event(self) -> None:
"""Test that put() emits an event when item has error status."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item = make_item(id="vid1")
item.status = "error"
item.error = "Test error message"
d = StubDownload(info=item)
# We can't easily test event emission without mocking EventBus
# Just verify it doesn't crash
result = store.put(d)
assert result is not None
def test_put_with_no_notify_skips_event(self) -> None:
"""Test that put() with no_notify=True doesn't emit events."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item = make_item(id="vid1")
item.status = "error"
item.error = "Test error message"
d = StubDownload(info=item)
# Should not emit event when no_notify=True
result = store.put(d, no_notify=True)
assert result is not None
assert result.info._id == item._id
def test_delete_nonexistent_item(self) -> None:
"""Test that deleting non-existent item doesn't raise error."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
# Should not raise error
store.delete("nonexistent_id")
# Verify nothing was deleted from database
row = conn.execute("SELECT * FROM history WHERE id=?", ("nonexistent_id",)).fetchone()
assert row is None
def test_has_downloads_with_empty_dict(self) -> None:
"""Test has_downloads returns False when dict is empty."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
assert store.has_downloads() is False
def test_has_downloads_with_no_eligible_downloads(self) -> None:
"""Test has_downloads returns False when no downloads are eligible."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
# Add item that's already started
item1 = make_item(id="vid1")
store.put(StubDownload(info=item1, started=True))
# Add item with auto_start=False
item2 = make_item(id="vid2")
item2.auto_start = False
store.put(StubDownload(info=item2, started=False))
assert store.has_downloads() is False
def test_get_next_download_returns_none_when_empty(self) -> None:
"""Test get_next_download returns None when no eligible downloads."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
result = store.get_next_download()
assert result is None
def test_get_next_download_skips_cancelled(self) -> None:
"""Test get_next_download skips cancelled downloads."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
# Add cancelled download
item1 = make_item(id="vid1")
item1._id = "id1"
store.put(StubDownload(info=item1, started=False, cancelled=True))
# Add eligible download
item2 = make_item(id="vid2")
item2._id = "id2"
store.put(StubDownload(info=item2, started=False, cancelled=False))
result = store.get_next_download()
assert result is not None
assert result.info._id == "id2"
def test_update_store_item_removes_datetime_field(self) -> None:
"""Test that _update_store_item removes datetime field before storage."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item = make_item(id="vid1")
item.datetime = "Thu, 01 Jan 2024 12:00:00 GMT" # Add datetime field
d = StubDownload(info=item)
store.put(d)
# Verify datetime field is not in stored JSON
row = conn.execute("SELECT data FROM history WHERE id=?", (item._id,)).fetchone()
assert row is not None
data = json.loads(row["data"])
assert "datetime" not in data
def test_update_store_item_removes_live_in_when_finished(self) -> None:
"""Test that _update_store_item removes live_in field when status is finished."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item = make_item(id="vid1")
item.status = "finished"
item.live_in = "PT5M" # Add live_in field
d = StubDownload(info=item)
store.put(d)
# Verify live_in field is not in stored JSON when status is finished
row = conn.execute("SELECT data FROM history WHERE id=?", (item._id,)).fetchone()
assert row is not None
data = json.loads(row["data"])
assert "live_in" not in data
def test_update_store_item_keeps_live_in_when_not_finished(self) -> None:
"""Test that _update_store_item keeps live_in field when status is not finished."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item = make_item(id="vid1")
item.status = "downloading"
item.live_in = "PT5M" # Add live_in field
d = StubDownload(info=item)
store.put(d)
# Verify live_in field IS in stored JSON when status is not finished
row = conn.execute("SELECT data FROM history WHERE id=?", (item._id,)).fetchone()
assert row is not None
data = json.loads(row["data"])
assert "live_in" in data
assert data["live_in"] == "PT5M"
class TestDataStoreOperations:
"""Test get_item with different comparison operations."""
def test_operation_equal(self) -> None:
"""Test EQUAL operation (default behavior)."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Exact Match", folder="folder1")
item1._id = "id1"
store.put(StubDownload(info=item1))
# Test with explicit EQUAL operation
result = store.get_item(title=(Operation.EQUAL, "Exact Match"))
assert result is not None
assert result.info._id == "id1"
# Test default behavior (no operation specified)
result = store.get_item(title="Exact Match")
assert result is not None
assert result.info._id == "id1"
# Test no match
result = store.get_item(title=(Operation.EQUAL, "No Match"))
assert result is None
def test_operation_not_equal(self) -> None:
"""Test NOT_EQUAL operation."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Video 1", folder="folder1")
item1._id = "id1"
item2 = make_item(id="vid2", title="Video 2", folder="folder2")
item2._id = "id2"
store.put(StubDownload(info=item1))
store.put(StubDownload(info=item2))
# Find item where title is not "Video 1"
result = store.get_item(title=(Operation.NOT_EQUAL, "Video 1"))
assert result is not None
assert result.info._id == "id2"
def test_operation_contain(self) -> None:
"""Test CONTAIN operation (substring match)."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Python Tutorial Video", folder="folder1")
item1._id = "id1"
item2 = make_item(id="vid2", title="JavaScript Course", folder="folder2")
item2._id = "id2"
store.put(StubDownload(info=item1))
store.put(StubDownload(info=item2))
# Find item with "Python" in title
result = store.get_item(title=(Operation.CONTAIN, "Python"))
assert result is not None
assert result.info._id == "id1"
# Find item with "Tutorial" in title
result = store.get_item(title=(Operation.CONTAIN, "Tutorial"))
assert result is not None
assert result.info._id == "id1"
# No match
result = store.get_item(title=(Operation.CONTAIN, "Rust"))
assert result is None
def test_operation_not_contain(self) -> None:
"""Test NOT_CONTAIN operation."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Python Tutorial", folder="folder1")
item1._id = "id1"
item2 = make_item(id="vid2", title="JavaScript Course", folder="folder2")
item2._id = "id2"
store.put(StubDownload(info=item1))
store.put(StubDownload(info=item2))
# Find item that doesn't contain "Python"
result = store.get_item(title=(Operation.NOT_CONTAIN, "Python"))
assert result is not None
assert result.info._id == "id2"
def test_operation_starts_with(self) -> None:
"""Test STARTS_WITH operation."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Tutorial: Python Basics", folder="folder1")
item1._id = "id1"
item2 = make_item(id="vid2", title="Course: JavaScript", folder="folder2")
item2._id = "id2"
store.put(StubDownload(info=item1))
store.put(StubDownload(info=item2))
# Find item starting with "Tutorial"
result = store.get_item(title=(Operation.STARTS_WITH, "Tutorial"))
assert result is not None
assert result.info._id == "id1"
# Find item starting with "Course"
result = store.get_item(title=(Operation.STARTS_WITH, "Course"))
assert result is not None
assert result.info._id == "id2"
# No match
result = store.get_item(title=(Operation.STARTS_WITH, "Video"))
assert result is None
def test_operation_ends_with(self) -> None:
"""Test ENDS_WITH operation."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Learn Python", folder="folder1")
item1._id = "id1"
item2 = make_item(id="vid2", title="Learn JavaScript", folder="folder2")
item2._id = "id2"
store.put(StubDownload(info=item1))
store.put(StubDownload(info=item2))
# Find item ending with "Python"
result = store.get_item(title=(Operation.ENDS_WITH, "Python"))
assert result is not None
assert result.info._id == "id1"
# Find item ending with "JavaScript"
result = store.get_item(title=(Operation.ENDS_WITH, "JavaScript"))
assert result is not None
assert result.info._id == "id2"
# No match
result = store.get_item(title=(Operation.ENDS_WITH, "Course"))
assert result is None
def test_operation_greater_than(self) -> None:
"""Test GREATER_THAN operation with numeric values."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Video 1")
item1._id = "id1"
item1.filesize = 1000
item2 = make_item(id="vid2", title="Video 2")
item2._id = "id2"
item2.filesize = 2000
store.put(StubDownload(info=item1))
store.put(StubDownload(info=item2))
# Find item with filesize > 1500
result = store.get_item(filesize=(Operation.GREATER_THAN, 1500))
assert result is not None
assert result.info._id == "id2"
# Find item with filesize > 500 (should return first match)
result = store.get_item(filesize=(Operation.GREATER_THAN, 500))
assert result is not None
assert result.info._id == "id1"
def test_operation_less_than(self) -> None:
"""Test LESS_THAN operation."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Video 1")
item1._id = "id1"
item1.filesize = 1000
item2 = make_item(id="vid2", title="Video 2")
item2._id = "id2"
item2.filesize = 2000
store.put(StubDownload(info=item1))
store.put(StubDownload(info=item2))
# Find item with filesize < 1500
result = store.get_item(filesize=(Operation.LESS_THAN, 1500))
assert result is not None
assert result.info._id == "id1"
def test_operation_greater_equal(self) -> None:
"""Test GREATER_EQUAL operation."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Video 1")
item1._id = "id1"
item1.filesize = 1000
store.put(StubDownload(info=item1))
# Test >= with exact match
result = store.get_item(filesize=(Operation.GREATER_EQUAL, 1000))
assert result is not None
assert result.info._id == "id1"
# Test >= with less than
result = store.get_item(filesize=(Operation.GREATER_EQUAL, 500))
assert result is not None
assert result.info._id == "id1"
# Test >= with greater than
result = store.get_item(filesize=(Operation.GREATER_EQUAL, 1500))
assert result is None
def test_operation_less_equal(self) -> None:
"""Test LESS_EQUAL operation."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Video 1")
item1._id = "id1"
item1.filesize = 1000
store.put(StubDownload(info=item1))
# Test <= with exact match
result = store.get_item(filesize=(Operation.LESS_EQUAL, 1000))
assert result is not None
# Test <= with greater than
result = store.get_item(filesize=(Operation.LESS_EQUAL, 1500))
assert result is not None
# Test <= with less than
result = store.get_item(filesize=(Operation.LESS_EQUAL, 500))
assert result is None
def test_mixed_operations(self) -> None:
"""Test using multiple operations in a single query."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Python Tutorial", folder="tutorials")
item1._id = "id1"
item2 = make_item(id="vid2", title="Python Advanced", folder="courses")
item2._id = "id2"
item3 = make_item(id="vid3", title="JavaScript Basics", folder="tutorials")
item3._id = "id3"
store.put(StubDownload(info=item1))
store.put(StubDownload(info=item2))
store.put(StubDownload(info=item3))
# Mix of operation and default (any match returns true)
result = store.get_item(title=(Operation.CONTAIN, "Python"), folder="tutorials")
assert result is not None
assert result.info._id == "id1"
# Mix where first condition matches
result = store.get_item(title=(Operation.CONTAIN, "JavaScript"), folder="nonexistent")
assert result is not None
assert result.info._id == "id3"
def test_operation_with_none_values(self) -> None:
"""Test operations handle None values gracefully."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Video 1")
item1._id = "id1"
item1.description = None
store.put(StubDownload(info=item1))
# CONTAIN with None field should return False
result = store.get_item(description=(Operation.CONTAIN, "test"))
assert result is None
# NOT_CONTAIN with None field should return True
result = store.get_item(description=(Operation.NOT_CONTAIN, "test"))
assert result is not None
# GREATER_THAN with None should return False
result = store.get_item(description=(Operation.GREATER_THAN, 100))
assert result is None
def test_operation_with_invalid_comparisons(self) -> None:
"""Test that invalid comparisons are handled gracefully."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Video 1")
item1._id = "id1"
store.put(StubDownload(info=item1))
# Try to compare string with number using > (should return False/None)
result = store.get_item(title=(Operation.GREATER_THAN, 100))
assert result is None
def test_operation_backward_compatibility(self) -> None:
"""Test that string operation names work for backward compatibility."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Python Tutorial")
item1._id = "id1"
store.put(StubDownload(info=item1))
# Using string operation value
result = store.get_item(title=("in", "Python"))
assert result is not None
assert result.info._id == "id1"
# Using string for EQUAL
result = store.get_item(title=("==", "Python Tutorial"))
assert result is not None
def test_operation_with_nonexistent_field(self) -> None:
"""Test operations with fields that don't exist."""
conn = make_conn()
store = DataStore(StoreType.QUEUE, conn)
item1 = make_item(id="vid1", title="Video 1")
item1._id = "id1"
store.put(StubDownload(info=item1))
# Try to match on non-existent field
result = store.get_item(nonexistent_field=(Operation.EQUAL, "value"))
assert result is None
result = store.get_item(nonexistent_field=(Operation.CONTAIN, "value"))
assert result is None