belgie-alchemy 0.1.0a4__py3-none-any.whl → 0.3.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.
- belgie_alchemy/__init__.py +0 -26
- {belgie_alchemy-0.1.0a4.dist-info → belgie_alchemy-0.3.0.dist-info}/METADATA +43 -33
- belgie_alchemy-0.3.0.dist-info/RECORD +7 -0
- {belgie_alchemy-0.1.0a4.dist-info → belgie_alchemy-0.3.0.dist-info}/WHEEL +1 -1
- belgie_alchemy/__tests__/__init__.py +0 -0
- belgie_alchemy/__tests__/adapter/__init__.py +0 -0
- belgie_alchemy/__tests__/adapter/test_adapter.py +0 -493
- belgie_alchemy/__tests__/auth_models/__init__.py +0 -0
- belgie_alchemy/__tests__/auth_models/test_auth_models.py +0 -91
- belgie_alchemy/__tests__/base/__init__.py +0 -0
- belgie_alchemy/__tests__/base/test_base.py +0 -90
- belgie_alchemy/__tests__/conftest.py +0 -39
- belgie_alchemy/__tests__/fixtures/__init__.py +0 -12
- belgie_alchemy/__tests__/fixtures/database.py +0 -38
- belgie_alchemy/__tests__/fixtures/models.py +0 -119
- belgie_alchemy/__tests__/mixins/__init__.py +0 -0
- belgie_alchemy/__tests__/mixins/test_mixins.py +0 -80
- belgie_alchemy/__tests__/settings/__init__.py +0 -0
- belgie_alchemy/__tests__/settings/test_settings.py +0 -342
- belgie_alchemy/__tests__/settings/test_settings_integration.py +0 -416
- belgie_alchemy/__tests__/types/__init__.py +0 -0
- belgie_alchemy/__tests__/types/test_types.py +0 -155
- belgie_alchemy/base.py +0 -25
- belgie_alchemy/mixins.py +0 -83
- belgie_alchemy/types.py +0 -32
- belgie_alchemy-0.1.0a4.dist-info/RECORD +0 -28
|
@@ -1,493 +0,0 @@
|
|
|
1
|
-
from datetime import UTC, datetime, timedelta
|
|
2
|
-
from uuid import uuid4
|
|
3
|
-
|
|
4
|
-
import pytest
|
|
5
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
6
|
-
|
|
7
|
-
from belgie_alchemy import AlchemyAdapter
|
|
8
|
-
from belgie_alchemy.__tests__.fixtures.models import Account, OAuthState, Session, User
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@pytest.fixture
|
|
12
|
-
def adapter(alchemy_session: AsyncSession) -> AlchemyAdapter: # noqa: ARG001
|
|
13
|
-
return AlchemyAdapter(
|
|
14
|
-
user=User,
|
|
15
|
-
account=Account,
|
|
16
|
-
session=Session,
|
|
17
|
-
oauth_state=OAuthState,
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@pytest.mark.asyncio
|
|
22
|
-
async def test_create_user(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
23
|
-
user = await adapter.create_user(
|
|
24
|
-
alchemy_session,
|
|
25
|
-
email="test@example.com",
|
|
26
|
-
name="Test User",
|
|
27
|
-
email_verified=True,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
assert user.email == "test@example.com"
|
|
31
|
-
assert user.name == "Test User"
|
|
32
|
-
assert user.email_verified is True
|
|
33
|
-
assert user.id is not None
|
|
34
|
-
assert user.created_at is not None
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@pytest.mark.asyncio
|
|
38
|
-
async def test_get_user_by_id(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
39
|
-
created_user = await adapter.create_user(
|
|
40
|
-
alchemy_session,
|
|
41
|
-
email="test@example.com",
|
|
42
|
-
name="Test User",
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
found_user = await adapter.get_user_by_id(alchemy_session, created_user.id)
|
|
46
|
-
|
|
47
|
-
assert found_user is not None
|
|
48
|
-
assert found_user.id == created_user.id
|
|
49
|
-
assert found_user.email == "test@example.com"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@pytest.mark.asyncio
|
|
53
|
-
async def test_get_user_by_id_not_found(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
54
|
-
user = await adapter.get_user_by_id(alchemy_session, uuid4())
|
|
55
|
-
assert user is None
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
@pytest.mark.asyncio
|
|
59
|
-
async def test_get_user_by_email(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
60
|
-
await adapter.create_user(
|
|
61
|
-
alchemy_session,
|
|
62
|
-
email="test@example.com",
|
|
63
|
-
name="Test User",
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
user = await adapter.get_user_by_email(alchemy_session, "test@example.com")
|
|
67
|
-
|
|
68
|
-
assert user is not None
|
|
69
|
-
assert user.email == "test@example.com"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
@pytest.mark.asyncio
|
|
73
|
-
async def test_get_user_by_email_not_found(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
74
|
-
user = await adapter.get_user_by_email(alchemy_session, "nonexistent@example.com")
|
|
75
|
-
assert user is None
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@pytest.mark.asyncio
|
|
79
|
-
async def test_update_user(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
80
|
-
user = await adapter.create_user(
|
|
81
|
-
alchemy_session,
|
|
82
|
-
email="test@example.com",
|
|
83
|
-
name="Test User",
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
updated_user = await adapter.update_user(
|
|
87
|
-
alchemy_session,
|
|
88
|
-
user.id,
|
|
89
|
-
name="Updated Name",
|
|
90
|
-
email_verified=True,
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
assert updated_user is not None
|
|
94
|
-
assert updated_user.name == "Updated Name"
|
|
95
|
-
assert updated_user.email_verified is True
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@pytest.mark.asyncio
|
|
99
|
-
async def test_update_user_not_found(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
100
|
-
updated_user = await adapter.update_user(
|
|
101
|
-
alchemy_session,
|
|
102
|
-
uuid4(),
|
|
103
|
-
name="Updated Name",
|
|
104
|
-
)
|
|
105
|
-
assert updated_user is None
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
@pytest.mark.asyncio
|
|
109
|
-
async def test_create_account(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
110
|
-
user = await adapter.create_user(
|
|
111
|
-
alchemy_session,
|
|
112
|
-
email="test@example.com",
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
account = await adapter.create_account(
|
|
116
|
-
alchemy_session,
|
|
117
|
-
user_id=user.id,
|
|
118
|
-
provider="google",
|
|
119
|
-
provider_account_id="12345",
|
|
120
|
-
access_token="token",
|
|
121
|
-
refresh_token="refresh",
|
|
122
|
-
expires_at=datetime.now(UTC) + timedelta(hours=1),
|
|
123
|
-
token_type="Bearer",
|
|
124
|
-
scope="openid email",
|
|
125
|
-
id_token="id_token",
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
assert account.user_id == user.id
|
|
129
|
-
assert account.provider == "google"
|
|
130
|
-
assert account.provider_account_id == "12345"
|
|
131
|
-
assert account.access_token == "token" # noqa: S105
|
|
132
|
-
assert account.refresh_token == "refresh" # noqa: S105
|
|
133
|
-
assert account.token_type == "Bearer" # noqa: S105
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
@pytest.mark.asyncio
|
|
137
|
-
async def test_get_account(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
138
|
-
user = await adapter.create_user(
|
|
139
|
-
alchemy_session,
|
|
140
|
-
email="test@example.com",
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
await adapter.create_account(
|
|
144
|
-
alchemy_session,
|
|
145
|
-
user_id=user.id,
|
|
146
|
-
provider="google",
|
|
147
|
-
provider_account_id="12345",
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
account = await adapter.get_account(alchemy_session, "google", "12345")
|
|
151
|
-
|
|
152
|
-
assert account is not None
|
|
153
|
-
assert account.provider == "google"
|
|
154
|
-
assert account.provider_account_id == "12345"
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
@pytest.mark.asyncio
|
|
158
|
-
async def test_get_account_not_found(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
159
|
-
account = await adapter.get_account(alchemy_session, "google", "nonexistent")
|
|
160
|
-
assert account is None
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
@pytest.mark.asyncio
|
|
164
|
-
async def test_create_session(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
165
|
-
user = await adapter.create_user(
|
|
166
|
-
alchemy_session,
|
|
167
|
-
email="test@example.com",
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
expires_at = datetime.now(UTC) + timedelta(days=7)
|
|
171
|
-
session = await adapter.create_session(
|
|
172
|
-
alchemy_session,
|
|
173
|
-
user_id=user.id,
|
|
174
|
-
expires_at=expires_at,
|
|
175
|
-
ip_address="127.0.0.1",
|
|
176
|
-
user_agent="Test Agent",
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
assert session.user_id == user.id
|
|
180
|
-
assert session.expires_at.replace(tzinfo=UTC) == expires_at
|
|
181
|
-
assert session.ip_address == "127.0.0.1"
|
|
182
|
-
assert session.user_agent == "Test Agent"
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
@pytest.mark.asyncio
|
|
186
|
-
async def test_get_session(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
187
|
-
user = await adapter.create_user(
|
|
188
|
-
alchemy_session,
|
|
189
|
-
email="test@example.com",
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
created_session = await adapter.create_session(
|
|
193
|
-
alchemy_session,
|
|
194
|
-
user_id=user.id,
|
|
195
|
-
expires_at=datetime.now(UTC) + timedelta(days=7),
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
found_session = await adapter.get_session(alchemy_session, created_session.id)
|
|
199
|
-
|
|
200
|
-
assert found_session is not None
|
|
201
|
-
assert found_session.id == created_session.id
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
@pytest.mark.asyncio
|
|
205
|
-
async def test_get_session_not_found(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
206
|
-
session = await adapter.get_session(alchemy_session, uuid4())
|
|
207
|
-
assert session is None
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
@pytest.mark.asyncio
|
|
211
|
-
async def test_update_session(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
212
|
-
user = await adapter.create_user(
|
|
213
|
-
alchemy_session,
|
|
214
|
-
email="test@example.com",
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
session = await adapter.create_session(
|
|
218
|
-
alchemy_session,
|
|
219
|
-
user_id=user.id,
|
|
220
|
-
expires_at=datetime.now(UTC) + timedelta(days=7),
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
new_expires = datetime.now(UTC) + timedelta(days=14)
|
|
224
|
-
updated_session = await adapter.update_session(
|
|
225
|
-
alchemy_session,
|
|
226
|
-
session.id,
|
|
227
|
-
expires_at=new_expires,
|
|
228
|
-
ip_address="192.168.1.1",
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
assert updated_session is not None
|
|
232
|
-
assert updated_session.expires_at.replace(tzinfo=UTC) == new_expires
|
|
233
|
-
assert updated_session.ip_address == "192.168.1.1"
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
@pytest.mark.asyncio
|
|
237
|
-
async def test_update_session_not_found(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
238
|
-
updated_session = await adapter.update_session(
|
|
239
|
-
alchemy_session,
|
|
240
|
-
uuid4(),
|
|
241
|
-
expires_at=datetime.now(UTC) + timedelta(days=7),
|
|
242
|
-
)
|
|
243
|
-
assert updated_session is None
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
@pytest.mark.asyncio
|
|
247
|
-
async def test_delete_session(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
248
|
-
user = await adapter.create_user(
|
|
249
|
-
alchemy_session,
|
|
250
|
-
email="test@example.com",
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
session = await adapter.create_session(
|
|
254
|
-
alchemy_session,
|
|
255
|
-
user_id=user.id,
|
|
256
|
-
expires_at=datetime.now(UTC) + timedelta(days=7),
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
deleted = await adapter.delete_session(alchemy_session, session.id)
|
|
260
|
-
assert deleted is True
|
|
261
|
-
|
|
262
|
-
found = await adapter.get_session(alchemy_session, session.id)
|
|
263
|
-
assert found is None
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
@pytest.mark.asyncio
|
|
267
|
-
async def test_delete_session_not_found(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
268
|
-
deleted = await adapter.delete_session(alchemy_session, uuid4())
|
|
269
|
-
assert deleted is False
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
@pytest.mark.asyncio
|
|
273
|
-
async def test_delete_expired_sessions(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
274
|
-
user = await adapter.create_user(
|
|
275
|
-
alchemy_session,
|
|
276
|
-
email="test@example.com",
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
await adapter.create_session(
|
|
280
|
-
alchemy_session,
|
|
281
|
-
user_id=user.id,
|
|
282
|
-
expires_at=datetime.now(UTC) - timedelta(days=1),
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
await adapter.create_session(
|
|
286
|
-
alchemy_session,
|
|
287
|
-
user_id=user.id,
|
|
288
|
-
expires_at=datetime.now(UTC) + timedelta(days=7),
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
count = await adapter.delete_expired_sessions(alchemy_session)
|
|
292
|
-
assert count == 1
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
@pytest.mark.asyncio
|
|
296
|
-
async def test_create_oauth_state(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
297
|
-
expires_at = datetime.now(UTC) + timedelta(minutes=10)
|
|
298
|
-
oauth_state = await adapter.create_oauth_state(
|
|
299
|
-
alchemy_session,
|
|
300
|
-
state="random_state_123",
|
|
301
|
-
expires_at=expires_at,
|
|
302
|
-
code_verifier="verifier_abc",
|
|
303
|
-
redirect_url="/dashboard",
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
assert oauth_state.state == "random_state_123"
|
|
307
|
-
assert oauth_state.code_verifier == "verifier_abc"
|
|
308
|
-
assert oauth_state.redirect_url == "/dashboard"
|
|
309
|
-
assert oauth_state.expires_at.replace(tzinfo=UTC) == expires_at
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
@pytest.mark.asyncio
|
|
313
|
-
async def test_get_oauth_state(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
314
|
-
await adapter.create_oauth_state(
|
|
315
|
-
alchemy_session,
|
|
316
|
-
state="random_state_123",
|
|
317
|
-
expires_at=datetime.now(UTC) + timedelta(minutes=10),
|
|
318
|
-
)
|
|
319
|
-
|
|
320
|
-
oauth_state = await adapter.get_oauth_state(alchemy_session, "random_state_123")
|
|
321
|
-
|
|
322
|
-
assert oauth_state is not None
|
|
323
|
-
assert oauth_state.state == "random_state_123"
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
@pytest.mark.asyncio
|
|
327
|
-
async def test_get_oauth_state_not_found(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
328
|
-
oauth_state = await adapter.get_oauth_state(alchemy_session, "nonexistent")
|
|
329
|
-
assert oauth_state is None
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
@pytest.mark.asyncio
|
|
333
|
-
async def test_delete_oauth_state(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
334
|
-
await adapter.create_oauth_state(
|
|
335
|
-
alchemy_session,
|
|
336
|
-
state="random_state_123",
|
|
337
|
-
expires_at=datetime.now(UTC) + timedelta(minutes=10),
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
deleted = await adapter.delete_oauth_state(alchemy_session, "random_state_123")
|
|
341
|
-
assert deleted is True
|
|
342
|
-
|
|
343
|
-
found = await adapter.get_oauth_state(alchemy_session, "random_state_123")
|
|
344
|
-
assert found is None
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
@pytest.mark.asyncio
|
|
348
|
-
async def test_delete_oauth_state_not_found(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
349
|
-
deleted = await adapter.delete_oauth_state(alchemy_session, "nonexistent")
|
|
350
|
-
assert deleted is False
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
@pytest.mark.asyncio
|
|
354
|
-
async def test_user_with_custom_fields(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
355
|
-
user_data = User(
|
|
356
|
-
email="custom@example.com",
|
|
357
|
-
email_verified=True,
|
|
358
|
-
name="Custom User",
|
|
359
|
-
image=None,
|
|
360
|
-
custom_field="custom value",
|
|
361
|
-
)
|
|
362
|
-
alchemy_session.add(user_data)
|
|
363
|
-
await alchemy_session.commit()
|
|
364
|
-
|
|
365
|
-
found = await adapter.get_user_by_email(alchemy_session, "custom@example.com")
|
|
366
|
-
assert found is not None
|
|
367
|
-
assert found.custom_field == "custom value"
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
@pytest.mark.asyncio
|
|
371
|
-
async def test_delete_user_deletes_user(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
372
|
-
user = await adapter.create_user(alchemy_session, email="delete@example.com", name="Delete User")
|
|
373
|
-
|
|
374
|
-
deleted = await adapter.delete_user(alchemy_session, user.id)
|
|
375
|
-
|
|
376
|
-
assert deleted is True
|
|
377
|
-
assert await adapter.get_user_by_id(alchemy_session, user.id) is None
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
@pytest.mark.asyncio
|
|
381
|
-
async def test_delete_user_deletes_sessions(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
382
|
-
user = await adapter.create_user(alchemy_session, email="delete@example.com", name="Delete User")
|
|
383
|
-
|
|
384
|
-
expires_at = datetime.now(UTC) + timedelta(days=1)
|
|
385
|
-
session1 = await adapter.create_session(alchemy_session, user.id, expires_at.replace(tzinfo=None))
|
|
386
|
-
session2 = await adapter.create_session(alchemy_session, user.id, expires_at.replace(tzinfo=None))
|
|
387
|
-
|
|
388
|
-
await adapter.delete_user(alchemy_session, user.id)
|
|
389
|
-
|
|
390
|
-
assert await adapter.get_session(alchemy_session, session1.id) is None
|
|
391
|
-
assert await adapter.get_session(alchemy_session, session2.id) is None
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
@pytest.mark.asyncio
|
|
395
|
-
async def test_delete_user_deletes_accounts(adapter: AlchemyAdapter, alchemy_session: AsyncSession) -> None:
|
|
396
|
-
user = await adapter.create_user(alchemy_session, email="delete@example.com", name="Delete User")
|
|
397
|
-
|
|
398
|
-
await adapter.create_account(
|
|
399
|
-
alchemy_session,
|
|
400
|
-
user.id,
|
|
401
|
-
"google",
|
|
402
|
-
"google-123",
|
|
403
|
-
access_token="token123",
|
|
404
|
-
)
|
|
405
|
-
|
|
406
|
-
await adapter.delete_user(alchemy_session, user.id)
|
|
407
|
-
|
|
408
|
-
assert await adapter.get_account(alchemy_session, "google", "google-123") is None
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
@pytest.mark.asyncio
|
|
412
|
-
async def test_delete_user_deletes_all_related_data(
|
|
413
|
-
adapter: AlchemyAdapter,
|
|
414
|
-
alchemy_session: AsyncSession,
|
|
415
|
-
) -> None:
|
|
416
|
-
user = await adapter.create_user(alchemy_session, email="delete@example.com", name="Delete User")
|
|
417
|
-
|
|
418
|
-
expires_at = datetime.now(UTC) + timedelta(days=1)
|
|
419
|
-
session1 = await adapter.create_session(alchemy_session, user.id, expires_at.replace(tzinfo=None))
|
|
420
|
-
session2 = await adapter.create_session(alchemy_session, user.id, expires_at.replace(tzinfo=None))
|
|
421
|
-
|
|
422
|
-
await adapter.create_account(
|
|
423
|
-
alchemy_session,
|
|
424
|
-
user.id,
|
|
425
|
-
"google",
|
|
426
|
-
"google-123",
|
|
427
|
-
access_token="token123",
|
|
428
|
-
)
|
|
429
|
-
await adapter.create_account(
|
|
430
|
-
alchemy_session,
|
|
431
|
-
user.id,
|
|
432
|
-
"github",
|
|
433
|
-
"github-456",
|
|
434
|
-
access_token="token456",
|
|
435
|
-
)
|
|
436
|
-
|
|
437
|
-
deleted = await adapter.delete_user(alchemy_session, user.id)
|
|
438
|
-
|
|
439
|
-
assert deleted is True
|
|
440
|
-
assert await adapter.get_user_by_id(alchemy_session, user.id) is None
|
|
441
|
-
assert await adapter.get_session(alchemy_session, session1.id) is None
|
|
442
|
-
assert await adapter.get_session(alchemy_session, session2.id) is None
|
|
443
|
-
assert await adapter.get_account(alchemy_session, "google", "google-123") is None
|
|
444
|
-
assert await adapter.get_account(alchemy_session, "github", "github-456") is None
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
@pytest.mark.asyncio
|
|
448
|
-
async def test_delete_user_returns_false_if_user_not_found(
|
|
449
|
-
adapter: AlchemyAdapter,
|
|
450
|
-
alchemy_session: AsyncSession,
|
|
451
|
-
) -> None:
|
|
452
|
-
fake_user_id = uuid4()
|
|
453
|
-
deleted = await adapter.delete_user(alchemy_session, fake_user_id)
|
|
454
|
-
|
|
455
|
-
assert deleted is False
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
@pytest.mark.asyncio
|
|
459
|
-
async def test_delete_user_only_deletes_target_users_data(
|
|
460
|
-
adapter: AlchemyAdapter,
|
|
461
|
-
alchemy_session: AsyncSession,
|
|
462
|
-
) -> None:
|
|
463
|
-
user1 = await adapter.create_user(alchemy_session, email="user1@example.com", name="User 1")
|
|
464
|
-
user2 = await adapter.create_user(alchemy_session, email="user2@example.com", name="User 2")
|
|
465
|
-
|
|
466
|
-
expires_at = datetime.now(UTC) + timedelta(days=1)
|
|
467
|
-
session1 = await adapter.create_session(alchemy_session, user1.id, expires_at.replace(tzinfo=None))
|
|
468
|
-
session2 = await adapter.create_session(alchemy_session, user2.id, expires_at.replace(tzinfo=None))
|
|
469
|
-
|
|
470
|
-
await adapter.create_account(
|
|
471
|
-
alchemy_session,
|
|
472
|
-
user1.id,
|
|
473
|
-
"google",
|
|
474
|
-
"google-user1",
|
|
475
|
-
access_token="token1",
|
|
476
|
-
)
|
|
477
|
-
await adapter.create_account(
|
|
478
|
-
alchemy_session,
|
|
479
|
-
user2.id,
|
|
480
|
-
"google",
|
|
481
|
-
"google-user2",
|
|
482
|
-
access_token="token2",
|
|
483
|
-
)
|
|
484
|
-
|
|
485
|
-
await adapter.delete_user(alchemy_session, user1.id)
|
|
486
|
-
|
|
487
|
-
assert await adapter.get_user_by_id(alchemy_session, user1.id) is None
|
|
488
|
-
assert await adapter.get_session(alchemy_session, session1.id) is None
|
|
489
|
-
assert await adapter.get_account(alchemy_session, "google", "google-user1") is None
|
|
490
|
-
|
|
491
|
-
assert await adapter.get_user_by_id(alchemy_session, user2.id) is not None
|
|
492
|
-
assert await adapter.get_session(alchemy_session, session2.id) is not None
|
|
493
|
-
assert await adapter.get_account(alchemy_session, "google", "google-user2") is not None
|
|
File without changes
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
from datetime import UTC, datetime, timedelta
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from sqlalchemy import select
|
|
5
|
-
from sqlalchemy.exc import IntegrityError
|
|
6
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
7
|
-
|
|
8
|
-
from belgie_alchemy.__tests__.fixtures.models import Account, OAuthState, Session, User
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def test_user_model_structure() -> None:
|
|
12
|
-
"""Verify User model demonstrates proper structure."""
|
|
13
|
-
assert User.__tablename__ == "users"
|
|
14
|
-
assert not getattr(User, "__abstract__", False)
|
|
15
|
-
assert hasattr(User, "email")
|
|
16
|
-
assert hasattr(User, "scopes")
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def test_user_has_scopes_field() -> None:
|
|
20
|
-
"""Verify User has scopes field that accepts list of strings."""
|
|
21
|
-
user = User(email="test@example.com")
|
|
22
|
-
user.scopes = ["read", "write"]
|
|
23
|
-
assert user.scopes == ["read", "write"]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def test_user_relationships_defined() -> None:
|
|
27
|
-
"""Verify User has bidirectional relationships defined."""
|
|
28
|
-
assert hasattr(User, "accounts")
|
|
29
|
-
assert hasattr(User, "sessions")
|
|
30
|
-
assert hasattr(User, "oauth_states")
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@pytest.mark.asyncio
|
|
34
|
-
async def test_account_unique_constraint(alchemy_session: AsyncSession) -> None:
|
|
35
|
-
user = User(email="auth@example.com")
|
|
36
|
-
alchemy_session.add(user)
|
|
37
|
-
await alchemy_session.commit()
|
|
38
|
-
|
|
39
|
-
account = Account(
|
|
40
|
-
user_id=user.id,
|
|
41
|
-
provider="google",
|
|
42
|
-
provider_account_id="abc",
|
|
43
|
-
)
|
|
44
|
-
alchemy_session.add(account)
|
|
45
|
-
await alchemy_session.commit()
|
|
46
|
-
|
|
47
|
-
duplicate = Account(
|
|
48
|
-
user_id=user.id,
|
|
49
|
-
provider="google",
|
|
50
|
-
provider_account_id="abc",
|
|
51
|
-
)
|
|
52
|
-
alchemy_session.add(duplicate)
|
|
53
|
-
with pytest.raises(IntegrityError):
|
|
54
|
-
await alchemy_session.commit()
|
|
55
|
-
await alchemy_session.rollback()
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
@pytest.mark.asyncio
|
|
59
|
-
async def test_session_relationship(alchemy_session: AsyncSession) -> None:
|
|
60
|
-
user = User(email="session@example.com")
|
|
61
|
-
alchemy_session.add(user)
|
|
62
|
-
await alchemy_session.commit()
|
|
63
|
-
|
|
64
|
-
session = Session(
|
|
65
|
-
user_id=user.id,
|
|
66
|
-
expires_at=datetime.now(UTC) + timedelta(days=1),
|
|
67
|
-
)
|
|
68
|
-
alchemy_session.add(session)
|
|
69
|
-
await alchemy_session.commit()
|
|
70
|
-
|
|
71
|
-
refreshed = await alchemy_session.get(User, user.id)
|
|
72
|
-
assert refreshed is not None
|
|
73
|
-
await alchemy_session.refresh(refreshed, attribute_names=["sessions"])
|
|
74
|
-
assert len(refreshed.sessions) == 1
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@pytest.mark.asyncio
|
|
78
|
-
async def test_oauth_state_optional_user(alchemy_session: AsyncSession) -> None:
|
|
79
|
-
state = OAuthState(
|
|
80
|
-
state="abc",
|
|
81
|
-
code_verifier=None,
|
|
82
|
-
redirect_url=None,
|
|
83
|
-
expires_at=datetime.now(UTC) + timedelta(minutes=5),
|
|
84
|
-
user_id=None,
|
|
85
|
-
)
|
|
86
|
-
alchemy_session.add(state)
|
|
87
|
-
await alchemy_session.commit()
|
|
88
|
-
|
|
89
|
-
rows = await alchemy_session.execute(select(OAuthState))
|
|
90
|
-
stored = rows.scalar_one()
|
|
91
|
-
assert stored.user is None
|
|
File without changes
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
from datetime import UTC, datetime
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from tempfile import TemporaryDirectory
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
from sqlalchemy import Integer, event, select
|
|
7
|
-
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
8
|
-
from sqlalchemy.orm import Mapped, mapped_column
|
|
9
|
-
|
|
10
|
-
from belgie_alchemy.__tests__.fixtures.models import User
|
|
11
|
-
from belgie_alchemy.base import NAMING_CONVENTION, Base
|
|
12
|
-
from belgie_alchemy.types import DateTimeUTC
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def test_type_annotation_map_uses_datetimeutc() -> None:
|
|
16
|
-
mapping = Base.type_annotation_map
|
|
17
|
-
assert mapping[datetime] is DateTimeUTC
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def test_datetime_annotation_auto_uses_datetimeutc() -> None:
|
|
21
|
-
"""Verify that Mapped[datetime] automatically uses DateTimeUTC without explicit column type."""
|
|
22
|
-
|
|
23
|
-
class TestModel(Base):
|
|
24
|
-
__tablename__ = "test_auto_datetime"
|
|
25
|
-
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, init=False)
|
|
26
|
-
# No explicit DateTimeUTC type specified - should be inferred from type_annotation_map
|
|
27
|
-
timestamp: Mapped[datetime]
|
|
28
|
-
|
|
29
|
-
timestamp_column = TestModel.__table__.c.timestamp # type: ignore[attr-defined]
|
|
30
|
-
assert isinstance(timestamp_column.type, DateTimeUTC)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def test_naming_convention_applied() -> None:
|
|
34
|
-
assert Base.metadata.naming_convention == NAMING_CONVENTION
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def test_dataclass_kw_only_init() -> None:
|
|
38
|
-
user = User(email="a@b.com")
|
|
39
|
-
assert user.email == "a@b.com"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@pytest.mark.asyncio
|
|
43
|
-
async def test_file_based_sqlite_database() -> None:
|
|
44
|
-
"""Test that models work correctly with file-based SQLite database."""
|
|
45
|
-
with TemporaryDirectory() as tmpdir:
|
|
46
|
-
db_path = Path(tmpdir) / "test.db"
|
|
47
|
-
db_url = f"sqlite+aiosqlite:///{db_path}"
|
|
48
|
-
|
|
49
|
-
# Create engine with file-based database
|
|
50
|
-
engine = create_async_engine(db_url, echo=False)
|
|
51
|
-
|
|
52
|
-
# Enable foreign keys for SQLite
|
|
53
|
-
@event.listens_for(engine.sync_engine, "connect")
|
|
54
|
-
def _enable_fk(dbapi_conn, _connection_record) -> None:
|
|
55
|
-
cursor = dbapi_conn.cursor()
|
|
56
|
-
cursor.execute("PRAGMA foreign_keys=ON")
|
|
57
|
-
cursor.close()
|
|
58
|
-
|
|
59
|
-
# Create tables
|
|
60
|
-
async with engine.begin() as conn:
|
|
61
|
-
await conn.run_sync(Base.metadata.create_all)
|
|
62
|
-
|
|
63
|
-
# Create session factory
|
|
64
|
-
session_factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
|
65
|
-
|
|
66
|
-
# Test basic operations
|
|
67
|
-
async with session_factory() as session:
|
|
68
|
-
# Create user
|
|
69
|
-
user = User(email="file_db_test@example.com", name="Test User")
|
|
70
|
-
session.add(user)
|
|
71
|
-
await session.commit()
|
|
72
|
-
|
|
73
|
-
user_id = user.id
|
|
74
|
-
|
|
75
|
-
# Verify persistence by reading in new session
|
|
76
|
-
async with session_factory() as session:
|
|
77
|
-
result = await session.execute(select(User).where(User.id == user_id))
|
|
78
|
-
retrieved_user = result.scalar_one()
|
|
79
|
-
|
|
80
|
-
assert retrieved_user.email == "file_db_test@example.com"
|
|
81
|
-
assert retrieved_user.name == "Test User"
|
|
82
|
-
assert retrieved_user.created_at is not None
|
|
83
|
-
assert retrieved_user.created_at.tzinfo is UTC
|
|
84
|
-
|
|
85
|
-
# Cleanup
|
|
86
|
-
await engine.dispose()
|
|
87
|
-
|
|
88
|
-
# Verify database file was created
|
|
89
|
-
assert db_path.exists()
|
|
90
|
-
assert db_path.stat().st_size > 0
|