File size: 3,594 Bytes
69fb140
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""
測試 core/retry.py 重試機制
"""

import pytest
import asyncio
from core.retry import (
    RetryConfig,
    retry_async,
    with_retry,
    default_retry_config,
    api_retry_config,
)


class TestRetryConfig:
    """測試重試配置"""

    def test_default_config(self):
        """測試預設配置"""
        config = RetryConfig()
        assert config.max_retries == 3
        assert config.base_delay == 1.0
        assert config.max_delay == 30.0

    def test_custom_config(self):
        """測試自訂配置"""
        config = RetryConfig(max_retries=5, base_delay=0.5)
        assert config.max_retries == 5
        assert config.base_delay == 0.5

    def test_calculate_delay(self):
        """測試延遲計算"""
        config = RetryConfig(base_delay=1.0, jitter=False)
        
        # 第一次重試
        delay0 = config.calculate_delay(0)
        assert delay0 == 1.0
        
        # 第二次重試(指數退避)
        delay1 = config.calculate_delay(1)
        assert delay1 == 2.0

    def test_max_delay_cap(self):
        """測試最大延遲上限"""
        config = RetryConfig(base_delay=10.0, max_delay=15.0, jitter=False)
        delay = config.calculate_delay(5)  # 10 * 2^5 = 320,應被限制
        assert delay == 15.0


class TestRetryAsync:
    """測試異步重試"""

    @pytest.mark.asyncio
    async def test_success_no_retry(self):
        """測試成功時不重試"""
        call_count = 0

        async def success_func():
            nonlocal call_count
            call_count += 1
            return "success"

        result = await retry_async(success_func)
        assert result == "success"
        assert call_count == 1

    @pytest.mark.asyncio
    async def test_retry_then_success(self):
        """測試重試後成功"""
        call_count = 0

        async def fail_then_success():
            nonlocal call_count
            call_count += 1
            if call_count < 3:
                raise ValueError("temporary error")
            return "success"

        config = RetryConfig(max_retries=3, base_delay=0.01)
        result = await retry_async(fail_then_success, config=config)
        assert result == "success"
        assert call_count == 3

    @pytest.mark.asyncio
    async def test_all_retries_fail(self):
        """測試所有重試都失敗"""
        call_count = 0

        async def always_fail():
            nonlocal call_count
            call_count += 1
            raise ValueError("always fails")

        config = RetryConfig(max_retries=2, base_delay=0.01)
        
        with pytest.raises(ValueError):
            await retry_async(always_fail, config=config)
        
        assert call_count == 3  # 初始 + 2 次重試


class TestWithRetryDecorator:
    """測試重試裝飾器"""

    @pytest.mark.asyncio
    async def test_decorator_success(self):
        """測試裝飾器成功"""
        @with_retry(max_retries=2, base_delay=0.01)
        async def decorated_func():
            return "decorated"

        result = await decorated_func()
        assert result == "decorated"


class TestPredefinedConfigs:
    """測試預定義配置"""

    def test_default_config_exists(self):
        """測試預設配置存在"""
        assert default_retry_config is not None
        assert default_retry_config.max_retries == 3

    def test_api_config_exists(self):
        """測試 API 配置存在"""
        assert api_retry_config is not None
        assert api_retry_config.max_retries == 2
        assert api_retry_config.base_delay == 0.5