belgie-alchemy 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,342 @@
1
+ import os
2
+ from importlib.util import find_spec
3
+ from urllib.parse import urlparse
4
+
5
+ import pytest
6
+ from sqlalchemy import text
7
+ from sqlalchemy.exc import IntegrityError
8
+ from sqlalchemy.ext.asyncio import AsyncSession
9
+
10
+ from belgie_alchemy.settings import DatabaseSettings
11
+
12
+ ASYNC_PG_AVAILABLE = find_spec("asyncpg") is not None
13
+
14
+
15
+ @pytest.mark.asyncio
16
+ async def test_sqlite_engine_fk_enabled() -> None:
17
+ db = DatabaseSettings(dialect={"type": "sqlite", "database": ":memory:", "enable_foreign_keys": True})
18
+
19
+ async with db.engine.connect() as conn:
20
+ result = await conn.execute(text("PRAGMA foreign_keys"))
21
+ value = result.scalar_one()
22
+
23
+ assert value == 1
24
+ await db.engine.dispose()
25
+
26
+
27
+ @pytest.mark.asyncio
28
+ async def test_session_maker_expire_disabled() -> None:
29
+ db = DatabaseSettings(dialect={"type": "sqlite", "database": ":memory:"})
30
+
31
+ session_factory = db.session_maker
32
+ assert session_factory.kw["expire_on_commit"] is False
33
+
34
+ async with session_factory() as session:
35
+ assert isinstance(session, AsyncSession)
36
+
37
+ await db.engine.dispose()
38
+
39
+
40
+ def test_postgres_url_creation() -> None:
41
+ if not ASYNC_PG_AVAILABLE:
42
+ pytest.skip("asyncpg not installed; skip postgres engine test")
43
+
44
+ db = DatabaseSettings(
45
+ dialect={
46
+ "type": "postgres",
47
+ "host": "localhost",
48
+ "port": 5432,
49
+ "database": "belgie",
50
+ "username": "user",
51
+ "password": "secret",
52
+ },
53
+ )
54
+
55
+ assert db.engine.url.get_backend_name() == "postgresql"
56
+ assert db.engine.url.get_driver_name() == "asyncpg"
57
+ assert db.engine.url.username == "user"
58
+ assert db.engine.url.host == "localhost"
59
+ assert db.engine.url.database == "belgie"
60
+ assert db.engine.url.port == 5432
61
+
62
+
63
+ @pytest.mark.asyncio
64
+ async def test_sqlite_fk_violations_raise_error() -> None:
65
+ """Test that foreign key constraints are enforced when enabled."""
66
+ db = DatabaseSettings(dialect={"type": "sqlite", "database": ":memory:", "enable_foreign_keys": True})
67
+
68
+ # Try to insert a child record with invalid FK - should raise IntegrityError
69
+ async with db.engine.begin() as conn:
70
+ # Create simple test tables
71
+ await conn.execute(text("CREATE TABLE test_parents (id INTEGER PRIMARY KEY, name TEXT)"))
72
+ await conn.execute(
73
+ text(
74
+ "CREATE TABLE test_children "
75
+ "(id INTEGER PRIMARY KEY, parent_id INTEGER NOT NULL REFERENCES test_parents(id), name TEXT)",
76
+ ),
77
+ )
78
+
79
+ async with db.session_maker() as session:
80
+ # Try to insert child without parent - should raise IntegrityError
81
+ with pytest.raises(IntegrityError): # noqa: PT012
82
+ await session.execute(text("INSERT INTO test_children (id, parent_id, name) VALUES (1, 999, 'orphan')"))
83
+ await session.commit()
84
+
85
+ await db.engine.dispose()
86
+
87
+
88
+ @pytest.mark.asyncio
89
+ async def test_dependency_yields_different_sessions() -> None:
90
+ """Test that dependency yields different session instances."""
91
+ db = DatabaseSettings(dialect={"type": "sqlite", "database": ":memory:"})
92
+
93
+ sessions = []
94
+ async for session1 in db.dependency():
95
+ sessions.append(session1)
96
+ break
97
+
98
+ async for session2 in db.dependency():
99
+ sessions.append(session2)
100
+ break
101
+
102
+ assert len(sessions) == 2
103
+ assert sessions[0] is not sessions[1]
104
+ await db.engine.dispose()
105
+
106
+
107
+ @pytest.mark.asyncio
108
+ async def test_dependency_handles_exceptions() -> None:
109
+ """Test that dependency properly handles exceptions."""
110
+ db = DatabaseSettings(dialect={"type": "sqlite", "database": ":memory:"})
111
+
112
+ # Simulate exception during request handling
113
+ simulated_error = "Simulated error"
114
+ try:
115
+ async for _session in db.dependency():
116
+ # Force an error
117
+ raise ValueError(simulated_error) # noqa: TRY301
118
+ except ValueError:
119
+ pass # Expected
120
+
121
+ # Verify we can still get new sessions
122
+ async for session in db.dependency():
123
+ assert isinstance(session, AsyncSession)
124
+ break
125
+
126
+ await db.engine.dispose()
127
+
128
+
129
+ def test_postgres_settings_validation() -> None:
130
+ """Test that PostgreSQL settings validates all required fields."""
131
+ # Test with valid data
132
+ db = DatabaseSettings(
133
+ dialect={
134
+ "type": "postgres",
135
+ "host": "db.example.com",
136
+ "port": 5433,
137
+ "database": "testdb",
138
+ "username": "testuser",
139
+ "password": "testpass",
140
+ "pool_size": 10,
141
+ "max_overflow": 20,
142
+ },
143
+ )
144
+
145
+ assert db.dialect.type == "postgres"
146
+ assert db.dialect.host == "db.example.com"
147
+ assert db.dialect.port == 5433
148
+ assert db.dialect.database == "testdb"
149
+ assert db.dialect.username == "testuser"
150
+ assert db.dialect.password.get_secret_value() == "testpass"
151
+ assert db.dialect.pool_size == 10
152
+ assert db.dialect.max_overflow == 20
153
+
154
+
155
+ def test_sqlite_settings_validation() -> None:
156
+ """Test that SQLite settings validates correctly."""
157
+ db = DatabaseSettings(
158
+ dialect={
159
+ "type": "sqlite",
160
+ "database": "/tmp/test.db", # noqa: S108
161
+ "enable_foreign_keys": False,
162
+ "echo": True,
163
+ },
164
+ )
165
+
166
+ assert db.dialect.type == "sqlite"
167
+ assert db.dialect.database == "/tmp/test.db" # noqa: S108
168
+ assert db.dialect.enable_foreign_keys is False
169
+ assert db.dialect.echo is True
170
+
171
+
172
+ # ==================== PostgreSQL Integration Tests ====================
173
+
174
+
175
+ @pytest.mark.asyncio
176
+ @pytest.mark.integration
177
+ async def test_postgres_engine_connection() -> None:
178
+ """Test actual PostgreSQL connection and session creation.
179
+
180
+ This test requires a PostgreSQL instance to be available.
181
+ Set POSTGRES_TEST_URL environment variable or skip.
182
+ """
183
+ if not ASYNC_PG_AVAILABLE:
184
+ pytest.skip("asyncpg not installed")
185
+
186
+ # Allow configuring test database via environment
187
+ test_url = os.getenv("POSTGRES_TEST_URL")
188
+ if not test_url:
189
+ pytest.skip("POSTGRES_TEST_URL not set - skipping integration test")
190
+
191
+ # Parse URL components (format: postgresql://user:pass@host:port/db)
192
+ try:
193
+ parsed = urlparse(test_url)
194
+ db = DatabaseSettings(
195
+ dialect={
196
+ "type": "postgres",
197
+ "host": parsed.hostname or "localhost",
198
+ "port": parsed.port or 5432,
199
+ "database": parsed.path.lstrip("/") if parsed.path else "postgres",
200
+ "username": parsed.username or "postgres",
201
+ "password": parsed.password or "",
202
+ },
203
+ )
204
+
205
+ # Test basic connection
206
+ async with db.engine.connect() as conn:
207
+ result = await conn.execute(text("SELECT 1 as test"))
208
+ value = result.scalar_one()
209
+ assert value == 1
210
+
211
+ await db.engine.dispose()
212
+
213
+ except (OSError, IntegrityError) as e:
214
+ pytest.skip(f"Could not connect to PostgreSQL: {e}")
215
+
216
+
217
+ @pytest.mark.asyncio
218
+ @pytest.mark.integration
219
+ async def test_postgres_session_creation() -> None:
220
+ """Test that PostgreSQL session factory works correctly."""
221
+ if not ASYNC_PG_AVAILABLE:
222
+ pytest.skip("asyncpg not installed")
223
+
224
+ test_url = os.getenv("POSTGRES_TEST_URL")
225
+ if not test_url:
226
+ pytest.skip("POSTGRES_TEST_URL not set - skipping integration test")
227
+
228
+ try:
229
+ parsed = urlparse(test_url)
230
+ db = DatabaseSettings(
231
+ dialect={
232
+ "type": "postgres",
233
+ "host": parsed.hostname or "localhost",
234
+ "port": parsed.port or 5432,
235
+ "database": parsed.path.lstrip("/") if parsed.path else "postgres",
236
+ "username": parsed.username or "postgres",
237
+ "password": parsed.password or "",
238
+ },
239
+ )
240
+
241
+ # Test session creation
242
+ async with db.session_maker() as session:
243
+ assert isinstance(session, AsyncSession)
244
+ result = await session.execute(text("SELECT version()"))
245
+ version = result.scalar_one()
246
+ assert "PostgreSQL" in version
247
+
248
+ await db.engine.dispose()
249
+
250
+ except (OSError, IntegrityError) as e:
251
+ pytest.skip(f"Could not connect to PostgreSQL: {e}")
252
+
253
+
254
+ @pytest.mark.asyncio
255
+ @pytest.mark.integration
256
+ async def test_postgres_dependency_yields_sessions() -> None:
257
+ """Test that PostgreSQL dependency generator works correctly."""
258
+ if not ASYNC_PG_AVAILABLE:
259
+ pytest.skip("asyncpg not installed")
260
+
261
+ test_url = os.getenv("POSTGRES_TEST_URL")
262
+ if not test_url:
263
+ pytest.skip("POSTGRES_TEST_URL not set - skipping integration test")
264
+
265
+ try:
266
+ parsed = urlparse(test_url)
267
+ db = DatabaseSettings(
268
+ dialect={
269
+ "type": "postgres",
270
+ "host": parsed.hostname or "localhost",
271
+ "port": parsed.port or 5432,
272
+ "database": parsed.path.lstrip("/") if parsed.path else "postgres",
273
+ "username": parsed.username or "postgres",
274
+ "password": parsed.password or "",
275
+ },
276
+ )
277
+
278
+ # Test dependency yields working sessions
279
+ sessions = []
280
+ async for session in db.dependency():
281
+ sessions.append(session)
282
+ # Verify session works
283
+ result = await session.execute(text("SELECT 1"))
284
+ assert result.scalar_one() == 1
285
+ break
286
+
287
+ async for session in db.dependency():
288
+ sessions.append(session)
289
+ break
290
+
291
+ # Verify different session instances
292
+ assert len(sessions) == 2
293
+ assert sessions[0] is not sessions[1]
294
+
295
+ await db.engine.dispose()
296
+
297
+ except (OSError, IntegrityError) as e:
298
+ pytest.skip(f"Could not connect to PostgreSQL: {e}")
299
+
300
+
301
+ @pytest.mark.asyncio
302
+ @pytest.mark.integration
303
+ async def test_postgres_connection_pooling() -> None:
304
+ """Test that PostgreSQL connection pooling is configured correctly."""
305
+ if not ASYNC_PG_AVAILABLE:
306
+ pytest.skip("asyncpg not installed")
307
+
308
+ test_url = os.getenv("POSTGRES_TEST_URL")
309
+ if not test_url:
310
+ pytest.skip("POSTGRES_TEST_URL not set - skipping integration test")
311
+
312
+ try:
313
+ parsed = urlparse(test_url)
314
+ db = DatabaseSettings(
315
+ dialect={
316
+ "type": "postgres",
317
+ "host": parsed.hostname or "localhost",
318
+ "port": parsed.port or 5432,
319
+ "database": parsed.path.lstrip("/") if parsed.path else "postgres",
320
+ "username": parsed.username or "postgres",
321
+ "password": parsed.password or "",
322
+ "pool_size": 5,
323
+ "max_overflow": 10,
324
+ },
325
+ )
326
+
327
+ # Verify pool settings
328
+ assert db.dialect.pool_size == 5
329
+ assert db.dialect.max_overflow == 10
330
+
331
+ # Create multiple sessions to test pooling
332
+ sessions = []
333
+ for _ in range(3):
334
+ async with db.session_maker() as session:
335
+ sessions.append(session)
336
+ result = await session.execute(text("SELECT 1"))
337
+ assert result.scalar_one() == 1
338
+
339
+ await db.engine.dispose()
340
+
341
+ except (OSError, IntegrityError) as e:
342
+ pytest.skip(f"Could not connect to PostgreSQL: {e}")