Files
pyrogram-bridge/tests/mock_config.py
T
claude code agent 444bd3e42f fix(stability): stage 2 — atomic media downloads, in-flight dedup, FloodWait->429, bounded semaphore
Fixes flaky static media serving: partial files served as 'ready', truncated
stubs living for an hour, and app-wide hangs under a saturated download path.

2.1 _download_atomic(file_id, final_path, timeout): downloads to a unique
    {final}.part.{hex}, validates size>0, and publishes solely via os.rename
    (atomic on POSIX); a finally ALWAYS removes the partial (timeout/cancel/
    zero-size/race-loser). Every downloader call routes through it, so a file at a
    FINAL name ({fid} / temp_{fid}) is GUARANTEED complete (grep-proven: the only
    safe_download_media caller passes a .part. path). Big-video timeout scales with
    size (min/max/min-speed knobs, documented in dockercompose.yml). The sweeper
    regex now matches both .part. and legacy .tmp. stubs.
2.2 In-flight dedup registry: the first request for a key runs the download in a
    DETACHED task sharing a Future; the task's finally sets the Future AND pops the
    key. Waiters await asyncio.shield(fut) so a client disconnect / waiter timeout
    cancels only the waiter, never the download — no hung waiters, no stuck key, a
    failed download frees the key for retry.
2.3 FloodWait->429: handler before except RPCError (FloodWait subclasses it),
    Retry-After = min(value + rand(1,30), 300); propagates from the detached task
    through the Future.
2.4 Serving a temp_* file touches its mtime so the 1h sweeper can't delete a
    video out from under a viewer.
2.5 The HTTP download semaphore acquire is bounded (wait_for 30 -> 503 +
    Retry-After); the permit is released only if the acquire succeeded. The
    request-scoped-permit trade-off (a disconnect can transiently exceed the
    download count) is documented inline for stage-7 prod observation.

Tests (tests/test_stage2_static.py, 13): atomic publish/clean on every exit incl.
FloodWait-through-the-finally; concurrent big-video serves no partial; dedup runs
one download, a cancelled waiter doesn't hang others, a failed download frees the
key; FloodWait -> 429; mtime touch; sweeper cleans .part./.tmp./stale temp_ but
keeps fresh files. 193 passed (180 baseline + 13).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 07:40:47 +03:00

40 lines
1.2 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
def setup_logging(level_name: str = "INFO") -> None:
"""No-op logging setup for tests (mirrors config.setup_logging signature)."""
return None
def get_settings():
"""
Mock config for testing without requiring TG_API_ID and TG_API_HASH
"""
return {
"tg_api_id": 12345,
"tg_api_hash": "test_hash",
"session_path": "tests/test_data",
"api_host": "127.0.0.1",
"api_port": 8080,
"pyrogram_bridge_url": "http://test.example.com",
"log_level": "DEBUG",
"debug": False,
"token": "test_token",
"time_based_merge": False,
"show_bridge_link": False,
"show_post_flags": True,
"proxy": None,
"trusted_proxies": [],
"tg_rpc_timeout": 60,
"tg_watchdog_enabled": True,
"tg_watchdog_interval": 60,
"tg_watchdog_timeout": 10,
"tg_watchdog_failures": 3,
"tg_watchdog_restart_timeout": 90,
"tg_watchdog_heartbeat_every": 30,
"tg_disconnect_flap_limit": 3,
"tg_disconnect_flap_window": 120,
"media_download_timeout_min": 120,
"media_download_timeout_max": 1800,
"media_download_min_speed": 256 * 1024,
}