344 lines
13 KiB
Python
344 lines
13 KiB
Python
import json
|
|
from datetime import UTC, datetime
|
|
|
|
import pytest
|
|
import pytest_asyncio
|
|
|
|
from app.library.DataStore import DataStore, StoreType
|
|
from app.library.ItemDTO import ItemDTO
|
|
from app.library.sqlite_store import SqliteStore
|
|
|
|
|
|
async def make_db(data: int = 100) -> SqliteStore:
|
|
"""Create a temporary database with test data."""
|
|
SqliteStore._reset_singleton()
|
|
ins = SqliteStore.get_instance(db_path=":memory:")
|
|
await ins._ensure_conn()
|
|
|
|
base_time = datetime.now(UTC)
|
|
for i in range(data):
|
|
created_at = base_time.replace(hour=(i // 4) % 24, minute=(i * 15) % 60, second=i % 60).strftime(
|
|
"%Y-%m-%d %H:%M:%S"
|
|
)
|
|
|
|
item_data = {
|
|
"url": f"https://example.com/video{i}",
|
|
"title": f"Test Video {i}",
|
|
"id": f"video{i}",
|
|
"folder": "/downloads",
|
|
"status": "finished",
|
|
}
|
|
|
|
await ins._conn.execute(
|
|
'INSERT INTO "history" ("id", "type", "url", "data", "created_at") VALUES (?, ?, ?, ?, ?)',
|
|
(
|
|
f"test-id-{i}",
|
|
str(StoreType.HISTORY),
|
|
item_data["url"],
|
|
json.dumps(item_data),
|
|
created_at,
|
|
),
|
|
)
|
|
if data > 0:
|
|
await ins._conn.commit()
|
|
|
|
return ins
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
class TestDataStorePagination:
|
|
"""Test pagination functionality of DataStore."""
|
|
|
|
@pytest_asyncio.fixture
|
|
async def make_db(self, data: int = 100):
|
|
"""Fixture to provide a temporary database with test data."""
|
|
return await make_db(data=data)
|
|
|
|
async def test_get_total_count(self):
|
|
"""Test getting total count of items."""
|
|
db = await make_db(data=100)
|
|
try:
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
count = await datastore.get_total_count()
|
|
assert count == 100
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_basic(self):
|
|
"""Test basic pagination functionality."""
|
|
db = await make_db(data=100)
|
|
try:
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
items, total, page, total_pages = await datastore.get_items_paginated(page=1, per_page=10)
|
|
assert len(items) == 10
|
|
assert total == 100
|
|
assert page == 1
|
|
assert total_pages == 10
|
|
for _, item in items:
|
|
assert isinstance(item, ItemDTO)
|
|
assert item._id.startswith("test-id-")
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_last_page(self):
|
|
"""Test pagination on last page."""
|
|
db = await make_db(data=100)
|
|
try:
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
items, total, page, total_pages = await datastore.get_items_paginated(page=10, per_page=10)
|
|
assert len(items) == 10
|
|
assert total == 100
|
|
assert page == 10
|
|
assert total_pages == 10
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_partial_page(self):
|
|
"""Test pagination with partial last page."""
|
|
db = await make_db(data=100)
|
|
try:
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
items, total, page, total_pages = await datastore.get_items_paginated(page=4, per_page=30)
|
|
assert len(items) == 10 # 100 items / 30 per page = 3 full pages + 10 items
|
|
assert total == 100
|
|
assert page == 4
|
|
assert total_pages == 4
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_out_of_range(self):
|
|
"""Test pagination with page number out of range."""
|
|
db = await make_db(data=100)
|
|
try:
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
items, total, page, total_pages = await datastore.get_items_paginated(page=999, per_page=10)
|
|
assert len(items) == 10
|
|
assert total == 100
|
|
assert page == 10 # Adjusted to last page
|
|
assert total_pages == 10
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_order_desc(self):
|
|
"""Test pagination with descending order (newest first)."""
|
|
db = await make_db(data=100)
|
|
try:
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
items, _, _, _ = await datastore.get_items_paginated(page=1, per_page=5, order="DESC")
|
|
assert len(items) == 5
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_order_asc(self):
|
|
"""Test pagination with ascending order (oldest first)."""
|
|
db = await make_db(data=100)
|
|
try:
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
items, _, _, _ = await datastore.get_items_paginated(page=1, per_page=5, order="ASC")
|
|
assert len(items) == 5
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_invalid_page(self):
|
|
"""Test pagination with invalid page number."""
|
|
db = await make_db(data=100)
|
|
try:
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
with pytest.raises(ValueError, match="page must be >= 1"):
|
|
await datastore.get_items_paginated(page=0, per_page=10)
|
|
|
|
with pytest.raises(ValueError, match="page must be >= 1"):
|
|
await datastore.get_items_paginated(page=-1, per_page=10)
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_invalid_per_page(self):
|
|
"""Test pagination with invalid per_page value."""
|
|
db = await make_db(data=100)
|
|
try:
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
|
|
with pytest.raises(ValueError, match="per_page must be >= 1"):
|
|
await datastore.get_items_paginated(page=1, per_page=0)
|
|
|
|
with pytest.raises(ValueError, match="per_page must be >= 1"):
|
|
await datastore.get_items_paginated(page=1, per_page=-10)
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_invalid_order(self):
|
|
"""Test pagination with invalid order parameter."""
|
|
db = await make_db(data=100)
|
|
try:
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
with pytest.raises(ValueError, match="order must be 'ASC' or 'DESC'"):
|
|
await datastore.get_items_paginated(page=1, per_page=10, order="INVALID")
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_empty_store(self):
|
|
"""Test pagination with empty datastore."""
|
|
db = await make_db(data=0)
|
|
try:
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
items, total, page, total_pages = await datastore.get_items_paginated(page=1, per_page=10)
|
|
|
|
assert len(items) == 0
|
|
assert total == 0
|
|
assert page == 1
|
|
assert total_pages == 1
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_single_item(self):
|
|
"""Test pagination with single item."""
|
|
db = await make_db(data=1)
|
|
try:
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
items, total, page, total_pages = await datastore.get_items_paginated(page=1, per_page=10)
|
|
|
|
assert len(items) == 1
|
|
assert total == 1
|
|
assert page == 1
|
|
assert total_pages == 1
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_status_filter_include(self):
|
|
"""Test pagination with status filter (inclusion)."""
|
|
# Add some items with different status values
|
|
item_data_pending = {
|
|
"url": "https://example.com/pending",
|
|
"title": "Pending Video",
|
|
"id": "pending-video",
|
|
"folder": "/downloads",
|
|
"status": "pending",
|
|
}
|
|
item_data_downloading = {
|
|
"url": "https://example.com/downloading",
|
|
"title": "Downloading Video",
|
|
"id": "downloading-video",
|
|
"folder": "/downloads",
|
|
"status": "downloading",
|
|
}
|
|
|
|
db = await make_db(data=100)
|
|
try:
|
|
await db._conn.execute(
|
|
'INSERT INTO "history" ("id", "type", "url", "data", "created_at") VALUES (?, ?, ?, ?, ?)',
|
|
(
|
|
"pending-id",
|
|
str(StoreType.HISTORY),
|
|
item_data_pending["url"],
|
|
json.dumps(item_data_pending),
|
|
datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S"),
|
|
),
|
|
)
|
|
await db._conn.execute(
|
|
'INSERT INTO "history" ("id", "type", "url", "data", "created_at") VALUES (?, ?, ?, ?, ?)',
|
|
(
|
|
"downloading-id",
|
|
str(StoreType.HISTORY),
|
|
item_data_downloading["url"],
|
|
json.dumps(item_data_downloading),
|
|
datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S"),
|
|
),
|
|
)
|
|
await db._conn.commit()
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
|
|
# Filter for finished items only
|
|
items, total, _, __ = await datastore.get_items_paginated(page=1, per_page=50, status_filter="finished")
|
|
|
|
assert len(items) == 50 # First page of finished items
|
|
assert total == 100 # Only 100 finished items in fixture
|
|
for _item_id, item in items:
|
|
assert item.status == "finished"
|
|
|
|
# Filter for pending items only
|
|
items, total, _page, _total_pages = await datastore.get_items_paginated(
|
|
page=1, per_page=50, status_filter="pending"
|
|
)
|
|
|
|
assert len(items) == 1
|
|
assert total == 1
|
|
assert items[0][1].status == "pending"
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_status_filter_exclude(self):
|
|
"""Test pagination with status filter (exclusion)."""
|
|
# Add some items with different status values
|
|
item_data_pending = {
|
|
"url": "https://example.com/pending2",
|
|
"title": "Pending Video 2",
|
|
"id": "pending-video-2",
|
|
"folder": "/downloads",
|
|
"status": "pending",
|
|
}
|
|
item_data_error = {
|
|
"url": "https://example.com/error",
|
|
"title": "Error Video",
|
|
"id": "error-video",
|
|
"folder": "/downloads",
|
|
"status": "error",
|
|
}
|
|
db = await make_db(data=0)
|
|
try:
|
|
await db._conn.execute(
|
|
'INSERT INTO "history" ("id", "type", "url", "data", "created_at") VALUES (?, ?, ?, ?, ?)',
|
|
(
|
|
"pending-id-2",
|
|
str(StoreType.HISTORY),
|
|
item_data_pending["url"],
|
|
json.dumps(item_data_pending),
|
|
datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S"),
|
|
),
|
|
)
|
|
await db._conn.execute(
|
|
'INSERT INTO "history" ("id", "type", "url", "data", "created_at") VALUES (?, ?, ?, ?, ?)',
|
|
(
|
|
"error-id",
|
|
str(StoreType.HISTORY),
|
|
item_data_error["url"],
|
|
json.dumps(item_data_error),
|
|
datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S"),
|
|
),
|
|
)
|
|
await db._conn.commit()
|
|
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
|
|
# Exclude finished items
|
|
items, total, _page, _total_pages = await datastore.get_items_paginated(
|
|
page=1, per_page=50, status_filter="!finished"
|
|
)
|
|
|
|
assert total == 2 # Only 2 non-finished items
|
|
assert len(items) == 2
|
|
for _item_id, item in items:
|
|
assert item.status != "finished"
|
|
|
|
# Verify we have pending and error
|
|
statuses = {item.status for _, item in items}
|
|
assert statuses == {"pending", "error"}
|
|
finally:
|
|
await db.close()
|
|
|
|
async def test_pagination_status_filter_none_matching(self):
|
|
"""Test pagination with status filter that matches no items."""
|
|
db = await make_db(data=0)
|
|
try:
|
|
datastore = DataStore(type=StoreType.HISTORY, connection=db)
|
|
|
|
# Filter for a status that doesn't exist
|
|
items, total, page, total_pages = await datastore.get_items_paginated(
|
|
page=1, per_page=50, status_filter="nonexistent"
|
|
)
|
|
|
|
assert len(items) == 0
|
|
assert total == 0
|
|
assert page == 1
|
|
assert total_pages == 1
|
|
finally:
|
|
await db.close()
|