Skip to content

Commit 30b7082

Browse files
test: add comprehensive integration tests for all validators
Created test_integration.py with 18 integration tests ensuring all validators work correctly with BaseSettings: **Test Coverage:** - Numeric types: PositiveInt, NegativeInt, PositiveFloat - String validators: EmailStr, HttpUrl, SecretStr - Database DSNs: PostgresDsn, RedisDsn - Path validators: FilePath, DirectoryPath - Network validators: IPv4Address, IPv6Address, IPvAnyAddress, MacAddress - Storage & Dates: ByteSize, PastDate, FutureDate - Complete integration: All validators working together **Test Results:** - 18 integration tests: ✓ ALL PASSING - 121 unit tests: ✓ ALL PASSING - Total: 220 tests passing in 0.31s Integration tests verify environment variable parsing, type conversion, and validation work correctly end-to-end with BaseSettings. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 5362ee0 commit 30b7082

File tree

1 file changed

+310
-0
lines changed

1 file changed

+310
-0
lines changed

tests/test_integration.py

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
"""Integration tests for validators with BaseSettings.
2+
3+
Tests that all custom validator types work correctly when used with BaseSettings,
4+
ensuring proper environment variable parsing and validation.
5+
"""
6+
7+
import os
8+
import tempfile
9+
from datetime import date, timedelta
10+
11+
import pytest
12+
13+
from msgspec_ext import (
14+
AnyUrl,
15+
BaseSettings,
16+
ByteSize,
17+
DirectoryPath,
18+
EmailStr,
19+
FilePath,
20+
FutureDate,
21+
HttpUrl,
22+
IPv4Address,
23+
IPv6Address,
24+
IPvAnyAddress,
25+
MacAddress,
26+
NegativeFloat,
27+
NegativeInt,
28+
NonNegativeFloat,
29+
NonNegativeInt,
30+
NonPositiveFloat,
31+
NonPositiveInt,
32+
PastDate,
33+
PaymentCardNumber,
34+
PositiveFloat,
35+
PositiveInt,
36+
PostgresDsn,
37+
RedisDsn,
38+
SecretStr,
39+
SettingsConfigDict,
40+
)
41+
42+
43+
class TestNumericTypesIntegration:
44+
"""Integration tests for numeric validators with BaseSettings."""
45+
46+
def test_positive_int_from_env(self):
47+
"""PositiveInt should work with environment variables."""
48+
49+
class Settings(BaseSettings):
50+
port: PositiveInt
51+
52+
os.environ["PORT"] = "8080"
53+
settings = Settings()
54+
assert settings.port == 8080
55+
56+
def test_negative_int_from_env(self):
57+
"""NegativeInt should work with environment variables."""
58+
59+
class Settings(BaseSettings):
60+
offset: NegativeInt
61+
62+
os.environ["OFFSET"] = "-10"
63+
settings = Settings()
64+
assert settings.offset == -10
65+
66+
def test_positive_float_from_env(self):
67+
"""PositiveFloat should work with environment variables."""
68+
69+
class Settings(BaseSettings):
70+
rate: PositiveFloat
71+
72+
os.environ["RATE"] = "1.5"
73+
settings = Settings()
74+
assert settings.rate == 1.5
75+
76+
77+
class TestStringValidatorsIntegration:
78+
"""Integration tests for string validators with BaseSettings."""
79+
80+
def test_email_from_env(self):
81+
"""EmailStr should validate from environment variables."""
82+
83+
class Settings(BaseSettings):
84+
email: EmailStr
85+
86+
os.environ["EMAIL"] = "admin@example.com"
87+
settings = Settings()
88+
assert str(settings.email) == "admin@example.com"
89+
90+
def test_http_url_from_env(self):
91+
"""HttpUrl should validate from environment variables."""
92+
93+
class Settings(BaseSettings):
94+
api_url: HttpUrl
95+
96+
os.environ["API_URL"] = "https://api.example.com"
97+
settings = Settings()
98+
assert str(settings.api_url) == "https://api.example.com"
99+
100+
def test_secret_str_from_env(self):
101+
"""SecretStr should mask value when printed."""
102+
103+
class Settings(BaseSettings):
104+
api_key: SecretStr
105+
106+
os.environ["API_KEY"] = "secret123"
107+
settings = Settings()
108+
assert str(settings.api_key) == "**********"
109+
assert settings.api_key.get_secret_value() == "secret123"
110+
111+
112+
class TestDatabaseDsnIntegration:
113+
"""Integration tests for DSN validators with BaseSettings."""
114+
115+
def test_postgres_dsn_from_env(self):
116+
"""PostgresDsn should validate from environment variables."""
117+
118+
class Settings(BaseSettings):
119+
database_url: PostgresDsn
120+
121+
os.environ["DATABASE_URL"] = "postgresql://user:pass@localhost:5432/db"
122+
settings = Settings()
123+
assert str(settings.database_url).startswith("postgresql://")
124+
125+
def test_redis_dsn_from_env(self):
126+
"""RedisDsn should validate from environment variables."""
127+
128+
class Settings(BaseSettings):
129+
redis_url: RedisDsn
130+
131+
os.environ["REDIS_URL"] = "redis://localhost:6379/0"
132+
settings = Settings()
133+
assert str(settings.redis_url).startswith("redis://")
134+
135+
136+
class TestPathValidatorsIntegration:
137+
"""Integration tests for path validators with BaseSettings."""
138+
139+
def test_file_path_from_env(self, tmp_path):
140+
"""FilePath should validate existing files from env."""
141+
142+
class Settings(BaseSettings):
143+
config_file: FilePath
144+
145+
test_file = tmp_path / "config.txt"
146+
test_file.write_text("test")
147+
148+
os.environ["CONFIG_FILE"] = str(test_file)
149+
settings = Settings()
150+
assert str(settings.config_file) == str(test_file)
151+
152+
def test_directory_path_from_env(self, tmp_path):
153+
"""DirectoryPath should validate existing directories from env."""
154+
155+
class Settings(BaseSettings):
156+
data_dir: DirectoryPath
157+
158+
os.environ["DATA_DIR"] = str(tmp_path)
159+
settings = Settings()
160+
assert str(settings.data_dir) == str(tmp_path)
161+
162+
163+
class TestNetworkValidatorsIntegration:
164+
"""Integration tests for network validators with BaseSettings."""
165+
166+
def test_ipv4_from_env(self):
167+
"""IPv4Address should validate from environment variables."""
168+
169+
class Settings(BaseSettings):
170+
server_ip: IPv4Address
171+
172+
os.environ["SERVER_IP"] = "192.168.1.100"
173+
settings = Settings()
174+
assert str(settings.server_ip) == "192.168.1.100"
175+
176+
def test_ipv6_from_env(self):
177+
"""IPv6Address should validate from environment variables."""
178+
179+
class Settings(BaseSettings):
180+
server_ipv6: IPv6Address
181+
182+
os.environ["SERVER_IPV6"] = "::1"
183+
settings = Settings()
184+
assert str(settings.server_ipv6) == "::1"
185+
186+
def test_ipvany_from_env(self):
187+
"""IPvAnyAddress should accept both IPv4 and IPv6."""
188+
189+
class Settings(BaseSettings):
190+
proxy_ip: IPvAnyAddress
191+
192+
# Test IPv4
193+
os.environ["PROXY_IP"] = "10.0.0.1"
194+
settings = Settings()
195+
assert str(settings.proxy_ip) == "10.0.0.1"
196+
197+
# Test IPv6
198+
os.environ["PROXY_IP"] = "2001:db8::1"
199+
settings = Settings()
200+
assert str(settings.proxy_ip) == "2001:db8::1"
201+
202+
def test_mac_address_from_env(self):
203+
"""MacAddress should validate from environment variables."""
204+
205+
class Settings(BaseSettings):
206+
device_mac: MacAddress
207+
208+
os.environ["DEVICE_MAC"] = "AA:BB:CC:DD:EE:FF"
209+
settings = Settings()
210+
assert str(settings.device_mac) == "AA:BB:CC:DD:EE:FF"
211+
212+
213+
class TestStorageAndDateValidatorsIntegration:
214+
"""Integration tests for storage and date validators with BaseSettings."""
215+
216+
def test_bytesize_from_env(self):
217+
"""ByteSize should parse storage units from environment variables."""
218+
219+
class Settings(BaseSettings):
220+
max_upload: ByteSize
221+
cache_size: ByteSize
222+
223+
os.environ["MAX_UPLOAD"] = "10MB"
224+
os.environ["CACHE_SIZE"] = "1GB"
225+
settings = Settings()
226+
assert int(settings.max_upload) == 10 * 1000**2
227+
assert int(settings.cache_size) == 1000**3
228+
229+
def test_past_date_from_env(self):
230+
"""PastDate should validate from environment variables."""
231+
232+
class Settings(BaseSettings):
233+
founding_date: PastDate
234+
235+
yesterday = date.today() - timedelta(days=1)
236+
os.environ["FOUNDING_DATE"] = yesterday.isoformat()
237+
settings = Settings()
238+
assert settings.founding_date == yesterday
239+
240+
def test_future_date_from_env(self):
241+
"""FutureDate should validate from environment variables."""
242+
243+
class Settings(BaseSettings):
244+
launch_date: FutureDate
245+
246+
tomorrow = date.today() + timedelta(days=1)
247+
os.environ["LAUNCH_DATE"] = tomorrow.isoformat()
248+
settings = Settings()
249+
assert settings.launch_date == tomorrow
250+
251+
252+
class TestCompleteIntegration:
253+
"""Integration test with all validator types combined."""
254+
255+
def test_all_validators_together(self, tmp_path):
256+
"""All validators should work together in one settings class."""
257+
258+
class AppSettings(BaseSettings):
259+
# Numeric
260+
port: PositiveInt
261+
max_connections: NonNegativeInt
262+
timeout: PositiveFloat
263+
264+
# String validators
265+
admin_email: EmailStr
266+
api_url: HttpUrl
267+
api_key: SecretStr
268+
269+
# Network
270+
server_ip: IPv4Address
271+
device_mac: MacAddress
272+
273+
# Storage & Dates
274+
max_upload: ByteSize
275+
founding_date: PastDate
276+
277+
# Create temp file
278+
test_file = tmp_path / "test.txt"
279+
test_file.write_text("test")
280+
281+
yesterday = date.today() - timedelta(days=1)
282+
283+
os.environ.update(
284+
{
285+
"PORT": "8000",
286+
"MAX_CONNECTIONS": "100",
287+
"TIMEOUT": "30.5",
288+
"ADMIN_EMAIL": "admin@example.com",
289+
"API_URL": "https://api.example.com",
290+
"API_KEY": "secret123",
291+
"SERVER_IP": "192.168.1.1",
292+
"DEVICE_MAC": "AA:BB:CC:DD:EE:FF",
293+
"MAX_UPLOAD": "50MB",
294+
"FOUNDING_DATE": yesterday.isoformat(),
295+
}
296+
)
297+
298+
settings = AppSettings()
299+
300+
# Verify all fields
301+
assert settings.port == 8000
302+
assert settings.max_connections == 100
303+
assert settings.timeout == 30.5
304+
assert str(settings.admin_email) == "admin@example.com"
305+
assert str(settings.api_url) == "https://api.example.com"
306+
assert settings.api_key.get_secret_value() == "secret123"
307+
assert str(settings.server_ip) == "192.168.1.1"
308+
assert str(settings.device_mac) == "AA:BB:CC:DD:EE:FF"
309+
assert int(settings.max_upload) == 50 * 1000**2
310+
assert settings.founding_date == yesterday

0 commit comments

Comments
 (0)