ytptube/app/tests/test_system_routes.py
Copilot b9c5b0b7ad
Decouple queue from config endpoint; load via paginated API with correct ordering (#2)
* Initial plan

* Refactor: remove queue from /api/system/configuration, use existing paginated /api/history endpoint for queue data

Co-authored-by: jbatesy <51190172+jbatesy@users.noreply.github.com>

* Remove accidentally committed package-lock.json and add to gitignore

Co-authored-by: jbatesy <51190172+jbatesy@users.noreply.github.com>

* Simplify loadMoreQueue to use loadNextPage for reduced duplication

Co-authored-by: jbatesy <51190172+jbatesy@users.noreply.github.com>

* Order in-progress downloads oldest-to-newest (order of processing)

Co-authored-by: jbatesy <51190172+jbatesy@users.noreply.github.com>

* Fix queue ordering: remove client-side timestamp sort, rely on backend created_at ASC

Co-authored-by: jbatesy <51190172+jbatesy@users.noreply.github.com>

* Revert "Fix queue ordering: remove client-side timestamp sort, rely on backend created_at ASC"

This reverts commit 9b53112e0050a45e4db95c85064577d22ed47a50. Changes not needed

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jbatesy <51190172+jbatesy@users.noreply.github.com>
Co-authored-by: Jesse Bate <noreply@idump.me>
2026-03-09 15:05:22 +10:30

159 lines
7.1 KiB
Python

import json
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from app.library.config import Config
from app.library.encoder import Encoder
from app.library.UpdateChecker import UpdateChecker
from app.routes.api.system import check_updates, system_config
class TestSystemConfigEndpoint:
"""Tests for the system configuration endpoint."""
def setup_method(self):
"""Reset singletons before each test."""
Config._reset_singleton()
@pytest.mark.asyncio
async def test_system_config_does_not_return_queue(self):
"""Test that the configuration endpoint does not include queue data."""
config = Config.get_instance()
encoder = Encoder()
mock_queue = MagicMock()
mock_queue.is_paused.return_value = False
mock_done = AsyncMock()
mock_done.get_total_count = AsyncMock(return_value=0)
mock_queue.done = mock_done
with (
patch("app.routes.api.system.Presets") as mock_presets_cls,
patch("app.routes.api.system.DLFields") as mock_dl_fields_cls,
patch("app.routes.api.system.list_folders", return_value=[]),
):
mock_presets_cls.get_instance.return_value.get_all.return_value = []
mock_dl_fields_cls.get_instance.return_value.get_all_serialized = AsyncMock(return_value=[])
response = await system_config(mock_queue, config, encoder)
assert 200 == response.status
body = json.loads(response.body.decode("utf-8"))
assert "queue" not in body, "Configuration response should not include queue data"
assert "app" in body, "Configuration response should include app data"
assert "paused" in body, "Configuration response should include paused status"
assert "history_count" in body, "Configuration response should include history_count"
assert "presets" in body, "Configuration response should include presets"
assert "dl_fields" in body, "Configuration response should include dl_fields"
assert "folders" in body, "Configuration response should include folders"
class TestCheckUpdatesEndpoint:
"""Tests for the check updates endpoint."""
def setup_method(self):
"""Reset singletons before each test."""
Config._reset_singleton()
UpdateChecker._reset_singleton()
@pytest.mark.asyncio
async def test_check_updates_disabled(self):
"""Test check updates returns error when disabled in config."""
config = Config.get_instance()
config.check_for_updates = False
encoder = Encoder()
update_checker = UpdateChecker.get_instance()
response = await check_updates(config, encoder, update_checker)
assert 200 == response.status, "Should work even if disabled as it's manual check."
body = response.body
assert b"disabled" in body.lower(), "Response should mention update checking is disabled"
@pytest.mark.asyncio
async def test_check_updates_up_to_date(self):
"""Test check updates returns up_to_date status."""
config = Config.get_instance()
config.check_for_updates = True
config.app_version = "v1.0.0"
encoder = Encoder()
update_checker = UpdateChecker.get_instance()
with patch.object(update_checker, "check_for_updates", new_callable=AsyncMock) as mock_check:
mock_check.return_value = (("up_to_date", None), ("up_to_date", None))
response = await check_updates(config, encoder, update_checker)
assert 200 == response.status, "Should return 200"
body = response.body.decode("utf-8")
assert "up_to_date" in body, "Response should include up_to_date status"
assert mock_check.called, "Should have called check_for_updates"
@pytest.mark.asyncio
async def test_check_updates_returns_current_version(self):
"""Test check updates includes current version in response."""
config = Config.get_instance()
config.check_for_updates = True
config.app_version = "v1.2.3"
encoder = Encoder()
update_checker = UpdateChecker.get_instance()
with patch.object(update_checker, "check_for_updates", new_callable=AsyncMock) as mock_check:
mock_check.return_value = (("up_to_date", None), ("up_to_date", None))
response = await check_updates(config, encoder, update_checker)
assert 200 == response.status, "Should return 200"
body = response.body.decode("utf-8")
assert "1.2.3" in body, "Response should include current version"
@pytest.mark.asyncio
async def test_check_updates_update_available(self):
"""Test check updates returns update_available status with new version."""
config = Config.get_instance()
config.check_for_updates = True
config.app_version = "v1.0.0"
encoder = Encoder()
update_checker = UpdateChecker.get_instance()
with patch.object(update_checker, "check_for_updates", new_callable=AsyncMock) as mock_check:
mock_check.return_value = (("update_available", "v1.0.5"), ("up_to_date", None))
response = await check_updates(config, encoder, update_checker)
assert 200 == response.status, "Should return 200"
body = response.body.decode("utf-8")
assert "v1.0.5" in body, "Response should include new version"
assert "update_available" in body, "Response should include update_available status"
@pytest.mark.asyncio
async def test_check_updates_null_when_no_new_version(self):
"""Test check updates returns null for new_version when none available."""
config = Config.get_instance()
config.check_for_updates = True
config.app_version = "v1.0.0"
encoder = Encoder()
update_checker = UpdateChecker.get_instance()
with patch.object(update_checker, "check_for_updates", new_callable=AsyncMock) as mock_check:
mock_check.return_value = (("up_to_date", None), ("up_to_date", None))
response = await check_updates(config, encoder, update_checker)
assert 200 == response.status, "Should return 200"
body = response.body.decode("utf-8")
assert "null" in body.lower(), "Response should include null for new_version when not available"
@pytest.mark.asyncio
async def test_check_updates_error_status(self):
"""Test check updates handles error status correctly."""
config = Config.get_instance()
config.check_for_updates = True
config.app_version = "v1.0.0"
encoder = Encoder()
update_checker = UpdateChecker.get_instance()
with patch.object(update_checker, "check_for_updates", new_callable=AsyncMock) as mock_check:
mock_check.return_value = (("error", None), ("error", None))
response = await check_updates(config, encoder, update_checker)
assert 200 == response.status, "Should return 200"
body = response.body.decode("utf-8")
assert "error" in body, "Response should include error status"