File size: 13,510 Bytes
5009cb8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
"""Unit tests for VoiceSettings value object."""

import pytest
from src.domain.models.voice_settings import VoiceSettings


class TestVoiceSettings:
    """Test cases for VoiceSettings value object."""
    
    def test_valid_voice_settings_creation(self):
        """Test creating valid VoiceSettings instance."""
        settings = VoiceSettings(
            voice_id="en_male_001",
            speed=1.2,
            language="en",
            pitch=0.1,
            volume=0.8
        )
        
        assert settings.voice_id == "en_male_001"
        assert settings.speed == 1.2
        assert settings.language == "en"
        assert settings.pitch == 0.1
        assert settings.volume == 0.8
    
    def test_voice_settings_with_optional_none(self):
        """Test creating VoiceSettings with optional parameters as None."""
        settings = VoiceSettings(
            voice_id="en_male_001",
            speed=1.0,
            language="en"
        )
        
        assert settings.pitch is None
        assert settings.volume is None
    
    def test_non_string_voice_id_raises_error(self):
        """Test that non-string voice_id raises TypeError."""
        with pytest.raises(TypeError, match="Voice ID must be a string"):
            VoiceSettings(
                voice_id=123,  # type: ignore
                speed=1.0,
                language="en"
            )
    
    def test_empty_voice_id_raises_error(self):
        """Test that empty voice_id raises ValueError."""
        with pytest.raises(ValueError, match="Voice ID cannot be empty"):
            VoiceSettings(
                voice_id="",
                speed=1.0,
                language="en"
            )
    
    def test_whitespace_voice_id_raises_error(self):
        """Test that whitespace-only voice_id raises ValueError."""
        with pytest.raises(ValueError, match="Voice ID cannot be empty"):
            VoiceSettings(
                voice_id="   ",
                speed=1.0,
                language="en"
            )
    
    def test_invalid_voice_id_format_raises_error(self):
        """Test that invalid voice_id format raises ValueError."""
        invalid_ids = ["voice id", "voice@id", "voice.id", "voice/id", "voice\\id"]
        
        for voice_id in invalid_ids:
            with pytest.raises(ValueError, match="Invalid voice ID format"):
                VoiceSettings(
                    voice_id=voice_id,
                    speed=1.0,
                    language="en"
                )
    
    def test_valid_voice_id_formats(self):
        """Test valid voice_id formats."""
        valid_ids = ["voice1", "voice_1", "voice-1", "en_male_001", "female-voice", "Voice123"]
        
        for voice_id in valid_ids:
            settings = VoiceSettings(
                voice_id=voice_id,
                speed=1.0,
                language="en"
            )
            assert settings.voice_id == voice_id
    
    def test_non_numeric_speed_raises_error(self):
        """Test that non-numeric speed raises TypeError."""
        with pytest.raises(TypeError, match="Speed must be a number"):
            VoiceSettings(
                voice_id="voice1",
                speed="fast",  # type: ignore
                language="en"
            )
    
    def test_speed_too_low_raises_error(self):
        """Test that speed below 0.1 raises ValueError."""
        with pytest.raises(ValueError, match="Speed must be between 0.1 and 3.0"):
            VoiceSettings(
                voice_id="voice1",
                speed=0.05,
                language="en"
            )
    
    def test_speed_too_high_raises_error(self):
        """Test that speed above 3.0 raises ValueError."""
        with pytest.raises(ValueError, match="Speed must be between 0.1 and 3.0"):
            VoiceSettings(
                voice_id="voice1",
                speed=3.1,
                language="en"
            )
    
    def test_valid_speed_boundaries(self):
        """Test valid speed boundaries."""
        # Test minimum valid speed
        settings_min = VoiceSettings(
            voice_id="voice1",
            speed=0.1,
            language="en"
        )
        assert settings_min.speed == 0.1
        
        # Test maximum valid speed
        settings_max = VoiceSettings(
            voice_id="voice1",
            speed=3.0,
            language="en"
        )
        assert settings_max.speed == 3.0
    
    def test_non_string_language_raises_error(self):
        """Test that non-string language raises TypeError."""
        with pytest.raises(TypeError, match="Language must be a string"):
            VoiceSettings(
                voice_id="voice1",
                speed=1.0,
                language=123  # type: ignore
            )
    
    def test_empty_language_raises_error(self):
        """Test that empty language raises ValueError."""
        with pytest.raises(ValueError, match="Language cannot be empty"):
            VoiceSettings(
                voice_id="voice1",
                speed=1.0,
                language=""
            )
    
    def test_invalid_language_code_format_raises_error(self):
        """Test that invalid language code format raises ValueError."""
        invalid_codes = ["e", "ENG", "en-us", "en-USA", "123", "en_US"]
        
        for code in invalid_codes:
            with pytest.raises(ValueError, match="Invalid language code format"):
                VoiceSettings(
                    voice_id="voice1",
                    speed=1.0,
                    language=code
                )
    
    def test_valid_language_codes(self):
        """Test valid language code formats."""
        valid_codes = ["en", "fr", "de", "es", "zh", "ja", "en-US", "fr-FR", "zh-CN"]
        
        for code in valid_codes:
            settings = VoiceSettings(
                voice_id="voice1",
                speed=1.0,
                language=code
            )
            assert settings.language == code
    
    def test_non_numeric_pitch_raises_error(self):
        """Test that non-numeric pitch raises TypeError."""
        with pytest.raises(TypeError, match="Pitch must be a number"):
            VoiceSettings(
                voice_id="voice1",
                speed=1.0,
                language="en",
                pitch="high"  # type: ignore
            )
    
    def test_pitch_too_low_raises_error(self):
        """Test that pitch below -2.0 raises ValueError."""
        with pytest.raises(ValueError, match="Pitch must be between -2.0 and 2.0"):
            VoiceSettings(
                voice_id="voice1",
                speed=1.0,
                language="en",
                pitch=-2.1
            )
    
    def test_pitch_too_high_raises_error(self):
        """Test that pitch above 2.0 raises ValueError."""
        with pytest.raises(ValueError, match="Pitch must be between -2.0 and 2.0"):
            VoiceSettings(
                voice_id="voice1",
                speed=1.0,
                language="en",
                pitch=2.1
            )
    
    def test_valid_pitch_boundaries(self):
        """Test valid pitch boundaries."""
        # Test minimum valid pitch
        settings_min = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en",
            pitch=-2.0
        )
        assert settings_min.pitch == -2.0
        
        # Test maximum valid pitch
        settings_max = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en",
            pitch=2.0
        )
        assert settings_max.pitch == 2.0
    
    def test_non_numeric_volume_raises_error(self):
        """Test that non-numeric volume raises TypeError."""
        with pytest.raises(TypeError, match="Volume must be a number"):
            VoiceSettings(
                voice_id="voice1",
                speed=1.0,
                language="en",
                volume="loud"  # type: ignore
            )
    
    def test_volume_too_low_raises_error(self):
        """Test that volume below 0.0 raises ValueError."""
        with pytest.raises(ValueError, match="Volume must be between 0.0 and 2.0"):
            VoiceSettings(
                voice_id="voice1",
                speed=1.0,
                language="en",
                volume=-0.1
            )
    
    def test_volume_too_high_raises_error(self):
        """Test that volume above 2.0 raises ValueError."""
        with pytest.raises(ValueError, match="Volume must be between 0.0 and 2.0"):
            VoiceSettings(
                voice_id="voice1",
                speed=1.0,
                language="en",
                volume=2.1
            )
    
    def test_valid_volume_boundaries(self):
        """Test valid volume boundaries."""
        # Test minimum valid volume
        settings_min = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en",
            volume=0.0
        )
        assert settings_min.volume == 0.0
        
        # Test maximum valid volume
        settings_max = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en",
            volume=2.0
        )
        assert settings_max.volume == 2.0
    
    def test_is_default_speed_property(self):
        """Test is_default_speed property."""
        # Default speed (1.0)
        settings_default = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en"
        )
        assert settings_default.is_default_speed is True
        
        # Non-default speed
        settings_non_default = VoiceSettings(
            voice_id="voice1",
            speed=1.5,
            language="en"
        )
        assert settings_non_default.is_default_speed is False
    
    def test_is_default_pitch_property(self):
        """Test is_default_pitch property."""
        # Default pitch (None)
        settings_none = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en"
        )
        assert settings_none.is_default_pitch is True
        
        # Default pitch (0.0)
        settings_zero = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en",
            pitch=0.0
        )
        assert settings_zero.is_default_pitch is True
        
        # Non-default pitch
        settings_non_default = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en",
            pitch=0.5
        )
        assert settings_non_default.is_default_pitch is False
    
    def test_is_default_volume_property(self):
        """Test is_default_volume property."""
        # Default volume (None)
        settings_none = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en"
        )
        assert settings_none.is_default_volume is True
        
        # Default volume (1.0)
        settings_one = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en",
            volume=1.0
        )
        assert settings_one.is_default_volume is True
        
        # Non-default volume
        settings_non_default = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en",
            volume=0.5
        )
        assert settings_non_default.is_default_volume is False
    
    def test_with_speed_method(self):
        """Test with_speed method creates new instance."""
        original = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en",
            pitch=0.1,
            volume=0.8
        )
        
        new_settings = original.with_speed(1.5)
        
        assert new_settings.speed == 1.5
        assert new_settings.voice_id == original.voice_id
        assert new_settings.language == original.language
        assert new_settings.pitch == original.pitch
        assert new_settings.volume == original.volume
        assert new_settings is not original  # Different instances
    
    def test_with_pitch_method(self):
        """Test with_pitch method creates new instance."""
        original = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en",
            pitch=0.1,
            volume=0.8
        )
        
        new_settings = original.with_pitch(0.5)
        
        assert new_settings.pitch == 0.5
        assert new_settings.voice_id == original.voice_id
        assert new_settings.speed == original.speed
        assert new_settings.language == original.language
        assert new_settings.volume == original.volume
        assert new_settings is not original  # Different instances
    
    def test_with_pitch_none(self):
        """Test with_pitch method with None value."""
        original = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en",
            pitch=0.1
        )
        
        new_settings = original.with_pitch(None)
        assert new_settings.pitch is None
    
    def test_voice_settings_is_immutable(self):
        """Test that VoiceSettings is immutable (frozen dataclass)."""
        settings = VoiceSettings(
            voice_id="voice1",
            speed=1.0,
            language="en"
        )
        
        with pytest.raises(AttributeError):
            settings.speed = 1.5  # type: ignore