This commit is contained in:
HuangHai
2026-01-20 14:05:19 +08:00
parent 646f3acd6d
commit d89388ab51
89 changed files with 12526 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
import os
import pytest
from config import ConfigLoader
def test_config_loader_merges_file_and_defaults(tmp_path, monkeypatch):
config_file = tmp_path / "config.yml"
config_file.write_text(
"""
link:
- https://www.douyin.com/video/1
path: ./Custom/
thread: 3
"""
)
monkeypatch.setenv('DOUYIN_THREAD', '8')
loader = ConfigLoader(str(config_file))
# Environment variable should override file
assert loader.get('thread') == 8
# File values should override defaults
assert loader.get('path') == './Custom/'
# Links should be normalized to list
assert loader.get_links() == ['https://www.douyin.com/video/1']
def test_config_validation_requires_links_and_path(tmp_path):
config_file = tmp_path / "config.yml"
config_file.write_text("{}")
loader = ConfigLoader(str(config_file))
assert not loader.validate()
loader.update(link=['https://www.douyin.com/video/1'], path='./Downloaded/')
assert loader.validate() is True

View File

@@ -0,0 +1,18 @@
from auth import CookieManager
def test_cookie_manager_validation_requires_all_keys(tmp_path):
cookie_file = tmp_path / '.cookies.json'
manager = CookieManager(str(cookie_file))
manager.set_cookies({'msToken': 'token', 'ttwid': 'id'})
assert manager.validate_cookies() is False
manager.set_cookies({
'msToken': 'token',
'ttwid': 'id',
'odin_tt': 'odin',
'passport_csrf_token': 'csrf',
})
assert manager.validate_cookies() is True

View File

@@ -0,0 +1,38 @@
import json
import pytest
from storage import Database
@pytest.mark.asyncio
async def test_database_aweme_lifecycle(tmp_path):
db_path = tmp_path / "test.db"
database = Database(str(db_path))
await database.initialize()
aweme_payload = {
'aweme_id': '123',
'aweme_type': 'video',
'title': 'test',
'author_id': 'author',
'author_name': 'Author',
'create_time': 1700000000,
'file_path': '/tmp',
'metadata': json.dumps({'a': 1}, ensure_ascii=False),
}
await database.add_aweme(aweme_payload)
assert await database.is_downloaded('123') is True
assert await database.get_aweme_count_by_author('author') == 1
assert await database.get_latest_aweme_time('author') == 1700000000
await database.add_history({
'url': 'https://www.douyin.com/video/123',
'url_type': 'video',
'total_count': 1,
'success_count': 1,
'config': json.dumps({'path': './Downloaded/'}, ensure_ascii=False),
})

View File

@@ -0,0 +1,25 @@
from core.url_parser import URLParser
def test_parse_video_url():
url = "https://www.douyin.com/video/7320876060210373923"
parsed = URLParser.parse(url)
assert parsed is not None
assert parsed['type'] == 'video'
assert parsed['aweme_id'] == '7320876060210373923'
def test_parse_gallery_url_sets_aweme_id():
url = "https://www.douyin.com/note/7320876060210373923"
parsed = URLParser.parse(url)
assert parsed is not None
assert parsed['type'] == 'gallery'
assert parsed['aweme_id'] == '7320876060210373923'
assert parsed['note_id'] == '7320876060210373923'
def test_parse_unsupported_url_returns_none():
url = "https://www.douyin.com/music/123456"
assert URLParser.parse(url) is None

View File

@@ -0,0 +1,81 @@
import pytest
from auth import CookieManager
from config import ConfigLoader
from control import QueueManager, RateLimiter, RetryHandler
from core.api_client import DouyinAPIClient
from core.video_downloader import VideoDownloader
from storage import FileManager
def _build_downloader(tmp_path):
config = ConfigLoader()
config.update(path=str(tmp_path))
file_manager = FileManager(str(tmp_path))
cookie_manager = CookieManager(str(tmp_path / '.cookies.json'))
api_client = DouyinAPIClient({})
downloader = VideoDownloader(
config,
api_client,
file_manager,
cookie_manager,
database=None,
rate_limiter=RateLimiter(max_per_second=5),
retry_handler=RetryHandler(max_retries=1),
queue_manager=QueueManager(max_workers=1),
)
return downloader, api_client
@pytest.mark.asyncio
async def test_video_downloader_skip_counts_total(tmp_path, monkeypatch):
downloader, api_client = _build_downloader(tmp_path)
async def _fake_should_download(self, _):
return False
downloader._should_download = _fake_should_download.__get__(downloader, VideoDownloader)
result = await downloader.download({'aweme_id': '123'})
assert result.total == 1
assert result.skipped == 1
assert result.success == 0
assert result.failed == 0
await api_client.close()
@pytest.mark.asyncio
async def test_build_no_watermark_url_signs_with_headers(tmp_path, monkeypatch):
downloader, api_client = _build_downloader(tmp_path)
signed_url = 'https://www.douyin.com/aweme/v1/play/?video_id=1&X-Bogus=signed'
def _fake_sign(url: str):
return signed_url, 'UnitTestAgent/1.0'
monkeypatch.setattr(api_client, 'sign_url', _fake_sign)
aweme = {
'aweme_id': '1',
'video': {
'play_addr': {
'url_list': [
'https://www.douyin.com/aweme/v1/play/?video_id=1&watermark=0'
]
}
},
}
url, headers = downloader._build_no_watermark_url(aweme)
assert url == signed_url
assert headers['User-Agent'] == 'UnitTestAgent/1.0'
assert headers['Accept'] == '*/*'
assert headers['Referer'].startswith('https://www.douyin.com')
await api_client.close()

View File

@@ -0,0 +1,11 @@
from utils.xbogus import generate_x_bogus
def test_generate_x_bogus_appends_parameter():
base_url = "https://www.douyin.com/aweme/v1/web/aweme/detail/?aweme_id=123"
signed_url, token, ua = generate_x_bogus(base_url)
assert signed_url.startswith(base_url)
assert "X-Bogus=" in signed_url
assert isinstance(token, str) and len(token) > 10
assert isinstance(ua, str) and "Mozilla" in ua