import asyncio import io import os import uuid import zipfile from pathlib import Path import pytest from app import common, store, pipeline, download @pytest.fixture def isolated(tmp_path, monkeypatch): monkeypatch.setattr(store, 'DATA', tmp_path) monkeypatch.setattr(store, 'DB_PATH', tmp_path / 'queue.sqlite3') monkeypatch.setattr(pipeline, 'RAW', tmp_path / 'raw') monkeypatch.setattr(pipeline, 'READY', tmp_path / 'ready') monkeypatch.setattr(pipeline, 'WORK', tmp_path / 'work') monkeypatch.setattr(pipeline, 'LOGS', tmp_path / 'logs') store.init_db() return tmp_path def test_uris(): assert common.validate_url('https://archive.org/download/demo/game.iso')[0] == 'url' assert common.validate_url('https://vimm.net/vault/12345')[0] == 'vimm' assert common.validate_url('magnet:?xt=urn:btih:c98fbaa2a36ee48e3d9423be02cdbefd9281bc7d')[0] == 'magnet' for url in ('file:///etc/passwd', 'http://127.0.0.1/admin', 'https://user:password@example.org', 'https://localhost/file', 'ftp://example.org/file'): with pytest.raises(ValueError): common.validate_url(url) def test_archive_id_and_escape(): assert common.parse_archive_id('https://archive.org/details/some-item') == 'some-item' assert common.archive_download_url('some-item', 'Game (1).7z').endswith('Game%20%281%29.7z') for name in ('../target.iso', '/etc/password', 'foo/../../abc', 'a\\b', ''): with pytest.raises(ValueError): common.archive_download_url('item', name) for name in ('../../secret', '/absolute/file', 'C:\\Windows\\System32\\x', 'folder/../x'): with pytest.raises(pipeline.ProcessingError): pipeline.safe_archive_path(name) def test_database_queue(isolated): for t in ('Test1', 'Test2'): store.create({'id': t, 'title': t, 'source_type': 'url', 'source': 'https://example.org/test.zip', 'format': 'xex'}) assert len(store.list_jobs()) == 2 assert store.next_job()['id'] == 'Test1' store.update('Test1', state='done', progress=100) assert store.next_job()['id'] == 'Test2' def test_process_xex_zip(isolated): jid = str(uuid.uuid4()) store.create({'id': jid, 'title': 'Demo Game', 'source_type': 'import', 'source': 'game.zip', 'format': 'xex'}) raw = isolated / 'raw' / jid raw.mkdir(parents=True) with zipfile.ZipFile(raw / 'game.zip', 'w') as archive: archive.writestr('folder/default.xex', b'This is a fake XEX for queue testing') archive.writestr('folder/data/readme.txt', b'hello') asyncio.run(pipeline.process_job(store.get(jid))) job = store.get(jid) assert job['state'] == 'done' assert (Path(job['output']) / 'Gra' / 'default.xex').exists() assert (Path(job['output']) / 'Gra' / 'data' / 'readme.txt').read_bytes() == b'hello' def test_zip_traversal_rejected(isolated): jid = 'traversal' store.create({'id': jid, 'title': 'Bad', 'source_type': 'import', 'source': 'bad.zip', 'format': 'xex'}) raw = isolated / 'raw' / jid raw.mkdir(parents=True) with zipfile.ZipFile(raw / 'bad.zip', 'w') as archive: archive.writestr('../../pwned', b'nope') with pytest.raises(pipeline.ProcessingError): asyncio.run(pipeline.process_job(store.get(jid))) assert not (isolated / 'pwned').exists() def test_raw_copy(isolated): jid = 'simple' store.create({'id': jid, 'title': 'Keep File', 'source_type': 'import', 'source': 'anything.bin', 'format': 'raw'}) raw = isolated / 'raw' / jid raw.mkdir(parents=True) (raw / 'anything.bin').write_bytes(b'abc') asyncio.run(pipeline.process_job(store.get(jid))) assert (Path(store.get(jid)['output']) / 'files' / 'anything.bin').read_bytes() == b'abc' def test_multipart_urls(): job={'source_type':'archive_many', 'source':'["https://host/A.part1.rar", "https://host/A.part2.rar"]'} assert len(download.source_urls(job)) == 2 assert download.target_filename('https://example.com/Gra%20XYZ.iso') == 'Gra XYZ.iso' def test_api_auth_and_add(isolated, monkeypatch): from fastapi.testclient import TestClient from app import main monkeypatch.setenv('WEB_USER', 'admin') monkeypatch.setenv('WEB_PASSWORD', 'secretsecret1234') async def idle_worker(): await asyncio.sleep(10000) monkeypatch.setattr(main, 'worker', idle_worker) with TestClient(main.app) as client: assert client.get('/').status_code == 401 assert client.get('/api/jobs').status_code == 401 auth = ('admin', 'secretsecret1234') assert client.get('/', auth=auth).status_code == 200 r = client.post('/api/jobs', auth=auth, json={'url': 'https://example.com/DEMO.iso','format': 'xex'}) assert r.status_code == 200, r.text assert r.json()['title'] == 'DEMO.iso' assert client.get('/api/jobs', auth=auth).json()[0]['id'] == r.json()['id'] assert client.post('/api/jobs', auth=auth, json={'url':'file:///etc/passwd','format':'xex'}).status_code == 400 assert client.post('/api/jobs', auth=auth, json={'url':'https://example.org/foo.iso','format':'oops'}).status_code == 400