claude-code-templates 1.16.1 → 1.17.1

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.
Files changed (101) hide show
  1. package/README.md +7 -7
  2. package/bin/create-claude-config.js +17 -8
  3. package/package.json +2 -3
  4. package/src/analytics/core/AgentAnalyzer.js +17 -3
  5. package/src/analytics/core/ProcessDetector.js +23 -7
  6. package/src/analytics/core/StateCalculator.js +102 -33
  7. package/src/analytics/data/DataCache.js +7 -7
  8. package/src/analytics-web/chats_mobile.html +2590 -0
  9. package/src/analytics-web/components/App.js +10 -10
  10. package/src/analytics-web/components/SessionTimer.js +1 -1
  11. package/src/analytics-web/components/Sidebar.js +5 -14
  12. package/src/analytics-web/index.html +932 -78
  13. package/src/analytics.js +263 -5
  14. package/src/chats-mobile.js +682 -0
  15. package/src/claude-api-proxy.js +460 -0
  16. package/src/file-operations.js +239 -36
  17. package/src/health-check.js +310 -0
  18. package/src/index.js +1256 -36
  19. package/src/tracking-service.js +31 -34
  20. package/components/agents/api-security-audit.md +0 -92
  21. package/components/agents/database-optimization.md +0 -94
  22. package/components/agents/react-performance-optimization.md +0 -64
  23. package/components/commands/check-file.md +0 -53
  24. package/components/commands/generate-tests.md +0 -68
  25. package/components/mcps/deepgraph-nextjs.json +0 -12
  26. package/components/mcps/deepgraph-react.json +0 -12
  27. package/components/mcps/deepgraph-typescript.json +0 -12
  28. package/components/mcps/deepgraph-vue.json +0 -12
  29. package/components/mcps/filesystem-access.json +0 -12
  30. package/components/mcps/github-integration.json +0 -11
  31. package/components/mcps/memory-integration.json +0 -8
  32. package/components/mcps/mysql-integration.json +0 -11
  33. package/components/mcps/postgresql-integration.json +0 -11
  34. package/components/mcps/web-fetch.json +0 -8
  35. package/src/analytics-web/components/AgentsPage.js +0 -4761
  36. package/templates/common/.claude/commands/git-workflow.md +0 -239
  37. package/templates/common/.claude/commands/project-setup.md +0 -316
  38. package/templates/common/.mcp.json +0 -41
  39. package/templates/common/CLAUDE.md +0 -109
  40. package/templates/common/README.md +0 -96
  41. package/templates/go/.mcp.json +0 -78
  42. package/templates/go/README.md +0 -25
  43. package/templates/javascript-typescript/.claude/commands/api-endpoint.md +0 -51
  44. package/templates/javascript-typescript/.claude/commands/debug.md +0 -52
  45. package/templates/javascript-typescript/.claude/commands/lint.md +0 -48
  46. package/templates/javascript-typescript/.claude/commands/npm-scripts.md +0 -48
  47. package/templates/javascript-typescript/.claude/commands/refactor.md +0 -55
  48. package/templates/javascript-typescript/.claude/commands/test.md +0 -61
  49. package/templates/javascript-typescript/.claude/commands/typescript-migrate.md +0 -51
  50. package/templates/javascript-typescript/.claude/settings.json +0 -142
  51. package/templates/javascript-typescript/.mcp.json +0 -80
  52. package/templates/javascript-typescript/CLAUDE.md +0 -185
  53. package/templates/javascript-typescript/README.md +0 -259
  54. package/templates/javascript-typescript/examples/angular-app/.claude/commands/components.md +0 -63
  55. package/templates/javascript-typescript/examples/angular-app/.claude/commands/services.md +0 -62
  56. package/templates/javascript-typescript/examples/node-api/.claude/commands/api-endpoint.md +0 -46
  57. package/templates/javascript-typescript/examples/node-api/.claude/commands/database.md +0 -56
  58. package/templates/javascript-typescript/examples/node-api/.claude/commands/middleware.md +0 -61
  59. package/templates/javascript-typescript/examples/node-api/.claude/commands/route.md +0 -57
  60. package/templates/javascript-typescript/examples/node-api/CLAUDE.md +0 -102
  61. package/templates/javascript-typescript/examples/react-app/.claude/commands/component.md +0 -29
  62. package/templates/javascript-typescript/examples/react-app/.claude/commands/hooks.md +0 -44
  63. package/templates/javascript-typescript/examples/react-app/.claude/commands/state-management.md +0 -45
  64. package/templates/javascript-typescript/examples/react-app/CLAUDE.md +0 -81
  65. package/templates/javascript-typescript/examples/react-app/agents/react-performance-optimization.md +0 -530
  66. package/templates/javascript-typescript/examples/react-app/agents/react-state-management.md +0 -295
  67. package/templates/javascript-typescript/examples/vue-app/.claude/commands/components.md +0 -46
  68. package/templates/javascript-typescript/examples/vue-app/.claude/commands/composables.md +0 -51
  69. package/templates/python/.claude/commands/lint.md +0 -111
  70. package/templates/python/.claude/commands/test.md +0 -73
  71. package/templates/python/.claude/settings.json +0 -153
  72. package/templates/python/.mcp.json +0 -78
  73. package/templates/python/CLAUDE.md +0 -276
  74. package/templates/python/examples/django-app/.claude/commands/admin.md +0 -264
  75. package/templates/python/examples/django-app/.claude/commands/django-model.md +0 -124
  76. package/templates/python/examples/django-app/.claude/commands/views.md +0 -222
  77. package/templates/python/examples/django-app/CLAUDE.md +0 -313
  78. package/templates/python/examples/django-app/agents/django-api-security.md +0 -642
  79. package/templates/python/examples/django-app/agents/django-database-optimization.md +0 -752
  80. package/templates/python/examples/fastapi-app/.claude/commands/api-endpoints.md +0 -513
  81. package/templates/python/examples/fastapi-app/.claude/commands/auth.md +0 -775
  82. package/templates/python/examples/fastapi-app/.claude/commands/database.md +0 -657
  83. package/templates/python/examples/fastapi-app/.claude/commands/deployment.md +0 -160
  84. package/templates/python/examples/fastapi-app/.claude/commands/testing.md +0 -927
  85. package/templates/python/examples/fastapi-app/CLAUDE.md +0 -229
  86. package/templates/python/examples/flask-app/.claude/commands/app-factory.md +0 -384
  87. package/templates/python/examples/flask-app/.claude/commands/blueprint.md +0 -243
  88. package/templates/python/examples/flask-app/.claude/commands/database.md +0 -410
  89. package/templates/python/examples/flask-app/.claude/commands/deployment.md +0 -620
  90. package/templates/python/examples/flask-app/.claude/commands/flask-route.md +0 -217
  91. package/templates/python/examples/flask-app/.claude/commands/testing.md +0 -559
  92. package/templates/python/examples/flask-app/CLAUDE.md +0 -391
  93. package/templates/ruby/.claude/commands/model.md +0 -360
  94. package/templates/ruby/.claude/commands/test.md +0 -480
  95. package/templates/ruby/.claude/settings.json +0 -146
  96. package/templates/ruby/.mcp.json +0 -83
  97. package/templates/ruby/CLAUDE.md +0 -284
  98. package/templates/ruby/examples/rails-app/.claude/commands/authentication.md +0 -490
  99. package/templates/ruby/examples/rails-app/CLAUDE.md +0 -376
  100. package/templates/rust/.mcp.json +0 -78
  101. package/templates/rust/README.md +0 -26
@@ -1,927 +0,0 @@
1
- # FastAPI Testing Framework
2
-
3
- Comprehensive testing setup for FastAPI applications with pytest and async support.
4
-
5
- ## Usage
6
-
7
- ```bash
8
- # Run all tests
9
- pytest
10
-
11
- # Run with coverage
12
- pytest --cov=app --cov-report=html
13
-
14
- # Run specific test file
15
- pytest tests/test_api.py
16
-
17
- # Run with verbose output
18
- pytest -v -s
19
- ```
20
-
21
- ## Test Configuration
22
-
23
- ```python
24
- # pytest.ini
25
- [tool:pytest]
26
- testpaths = tests
27
- python_files = test_*.py
28
- python_classes = Test*
29
- python_functions = test_*
30
- addopts =
31
- --cov=app
32
- --cov-report=term-missing
33
- --cov-report=html:htmlcov
34
- --asyncio-mode=auto
35
- --strict-markers
36
- --disable-warnings
37
- markers =
38
- unit: Unit tests
39
- integration: Integration tests
40
- e2e: End-to-end tests
41
- slow: Slow running tests
42
- auth: Authentication tests
43
- api: API tests
44
- asyncio_mode = auto
45
- ```
46
-
47
- ## Test Dependencies
48
-
49
- ```python
50
- # requirements/test.txt
51
- pytest>=7.0.0
52
- pytest-asyncio>=0.21.0
53
- pytest-cov>=4.0.0
54
- httpx>=0.24.0
55
- factory-boy>=3.2.0
56
- faker>=18.0.0
57
- respx>=0.20.0
58
- pytest-mock>=3.10.0
59
- ```
60
-
61
- ## Test Fixtures
62
-
63
- ```python
64
- # tests/conftest.py
65
- import pytest
66
- import asyncio
67
- from typing import AsyncGenerator, Generator
68
- from fastapi.testclient import TestClient
69
- from httpx import AsyncClient
70
- from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
71
- from sqlalchemy.orm import sessionmaker
72
- from app.main import app
73
- from app.db.database import get_db, Base
74
- from app.models.user import User
75
- from app.core.security import get_password_hash
76
- from tests.factories import UserFactory
77
-
78
- # Test database URL
79
- TEST_DATABASE_URL = "sqlite+aiosqlite:///./test.db"
80
-
81
- @pytest.fixture(scope="session")
82
- def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
83
- """Create event loop for the test session."""
84
- loop = asyncio.get_event_loop_policy().new_event_loop()
85
- yield loop
86
- loop.close()
87
-
88
- @pytest.fixture(scope="session")
89
- async def test_engine():
90
- """Create test database engine."""
91
- engine = create_async_engine(
92
- TEST_DATABASE_URL,
93
- echo=False,
94
- future=True
95
- )
96
-
97
- # Create tables
98
- async with engine.begin() as conn:
99
- await conn.run_sync(Base.metadata.create_all)
100
-
101
- yield engine
102
-
103
- # Drop tables and dispose engine
104
- async with engine.begin() as conn:
105
- await conn.run_sync(Base.metadata.drop_all)
106
-
107
- await engine.dispose()
108
-
109
- @pytest.fixture
110
- async def db_session(test_engine) -> AsyncGenerator[AsyncSession, None]:
111
- """Create database session for testing."""
112
- TestSessionLocal = sessionmaker(
113
- test_engine,
114
- class_=AsyncSession,
115
- expire_on_commit=False
116
- )
117
-
118
- async with TestSessionLocal() as session:
119
- yield session
120
-
121
- @pytest.fixture
122
- def override_get_db(db_session: AsyncSession) -> Generator:
123
- """Override database dependency."""
124
- async def _override_get_db():
125
- yield db_session
126
-
127
- app.dependency_overrides[get_db] = _override_get_db
128
- yield
129
- app.dependency_overrides = {}
130
-
131
- @pytest.fixture
132
- def client(override_get_db) -> Generator[TestClient, None, None]:
133
- """Create test client."""
134
- with TestClient(app) as test_client:
135
- yield test_client
136
-
137
- @pytest.fixture
138
- async def async_client(override_get_db) -> AsyncGenerator[AsyncClient, None]:
139
- """Create async test client."""
140
- async with AsyncClient(app=app, base_url="http://test") as ac:
141
- yield ac
142
-
143
- @pytest.fixture
144
- async def test_user(db_session: AsyncSession) -> User:
145
- """Create test user."""
146
- user_data = {
147
- "username": "testuser",
148
- "email": "test@example.com",
149
- "hashed_password": get_password_hash("testpass123"),
150
- "first_name": "Test",
151
- "last_name": "User",
152
- "is_active": True,
153
- "is_superuser": False
154
- }
155
-
156
- user = User(**user_data)
157
- db_session.add(user)
158
- await db_session.commit()
159
- await db_session.refresh(user)
160
- return user
161
-
162
- @pytest.fixture
163
- async def superuser(db_session: AsyncSession) -> User:
164
- """Create superuser."""
165
- user_data = {
166
- "username": "admin",
167
- "email": "admin@example.com",
168
- "hashed_password": get_password_hash("adminpass123"),
169
- "first_name": "Admin",
170
- "last_name": "User",
171
- "is_active": True,
172
- "is_superuser": True
173
- }
174
-
175
- user = User(**user_data)
176
- db_session.add(user)
177
- await db_session.commit()
178
- await db_session.refresh(user)
179
- return user
180
-
181
- @pytest.fixture
182
- def user_token(test_user: User) -> str:
183
- """Create authentication token for test user."""
184
- from app.core.security import create_access_token
185
- return create_access_token(subject=test_user.id)
186
-
187
- @pytest.fixture
188
- def superuser_token(superuser: User) -> str:
189
- """Create authentication token for superuser."""
190
- from app.core.security import create_access_token
191
- return create_access_token(subject=superuser.id)
192
-
193
- @pytest.fixture
194
- def auth_headers(user_token: str) -> dict[str, str]:
195
- """Create authorization headers."""
196
- return {"Authorization": f"Bearer {user_token}"}
197
-
198
- @pytest.fixture
199
- def superuser_headers(superuser_token: str) -> dict[str, str]:
200
- """Create superuser authorization headers."""
201
- return {"Authorization": f"Bearer {superuser_token}"}
202
- ```
203
-
204
- ## Test Factories
205
-
206
- ```python
207
- # tests/factories.py
208
- import factory
209
- from factory import Faker, SubFactory
210
- from app.models.user import User
211
- from app.models.post import Post
212
- from app.core.security import get_password_hash
213
-
214
- class UserFactory(factory.Factory):
215
- """Factory for User model."""
216
-
217
- class Meta:
218
- model = User
219
-
220
- username = Faker('user_name')
221
- email = Faker('email')
222
- first_name = Faker('first_name')
223
- last_name = Faker('last_name')
224
- hashed_password = factory.LazyAttribute(lambda obj: get_password_hash('testpass123'))
225
- is_active = True
226
- is_superuser = False
227
- bio = Faker('text', max_nb_chars=200)
228
-
229
- class SuperUserFactory(UserFactory):
230
- """Factory for superuser."""
231
- username = 'admin'
232
- email = 'admin@example.com'
233
- first_name = 'Admin'
234
- last_name = 'User'
235
- is_superuser = True
236
-
237
- class PostFactory(factory.Factory):
238
- """Factory for Post model."""
239
-
240
- class Meta:
241
- model = Post
242
-
243
- title = Faker('sentence', nb_words=4)
244
- content = Faker('text', max_nb_chars=1000)
245
- slug = Faker('slug')
246
- is_published = True
247
- author = SubFactory(UserFactory)
248
-
249
- class InactiveUserFactory(UserFactory):
250
- """Factory for inactive user."""
251
- is_active = False
252
- ```
253
-
254
- ## API Testing
255
-
256
- ```python
257
- # tests/test_api/test_users.py
258
- import pytest
259
- from httpx import AsyncClient
260
- from app.models.user import User
261
- from tests.factories import UserFactory
262
-
263
- class TestUserAPI:
264
- """Test User API endpoints."""
265
-
266
- @pytest.mark.asyncio
267
- async def test_create_user(
268
- self,
269
- async_client: AsyncClient,
270
- superuser_headers: dict
271
- ):
272
- """Test POST /api/v1/users/."""
273
- user_data = {
274
- "username": "newuser",
275
- "email": "new@example.com",
276
- "password": "newpass123",
277
- "first_name": "New",
278
- "last_name": "User"
279
- }
280
-
281
- response = await async_client.post(
282
- "/api/v1/users/",
283
- json=user_data,
284
- headers=superuser_headers
285
- )
286
-
287
- assert response.status_code == 201
288
- data = response.json()
289
- assert data["username"] == user_data["username"]
290
- assert data["email"] == user_data["email"]
291
- assert "password" not in data
292
- assert "hashed_password" not in data
293
-
294
- @pytest.mark.asyncio
295
- async def test_get_users(
296
- self,
297
- async_client: AsyncClient,
298
- test_user: User,
299
- auth_headers: dict
300
- ):
301
- """Test GET /api/v1/users/."""
302
- response = await async_client.get(
303
- "/api/v1/users/",
304
- headers=auth_headers
305
- )
306
-
307
- assert response.status_code == 200
308
- data = response.json()
309
- assert "items" in data
310
- assert "total" in data
311
- assert "page" in data
312
- assert "size" in data
313
- assert len(data["items"]) >= 1
314
-
315
- @pytest.mark.asyncio
316
- async def test_get_user(
317
- self,
318
- async_client: AsyncClient,
319
- test_user: User,
320
- auth_headers: dict
321
- ):
322
- """Test GET /api/v1/users/{id}."""
323
- response = await async_client.get(
324
- f"/api/v1/users/{test_user.id}",
325
- headers=auth_headers
326
- )
327
-
328
- assert response.status_code == 200
329
- data = response.json()
330
- assert data["id"] == test_user.id
331
- assert data["username"] == test_user.username
332
- assert data["email"] == test_user.email
333
-
334
- @pytest.mark.asyncio
335
- async def test_update_user(
336
- self,
337
- async_client: AsyncClient,
338
- test_user: User,
339
- auth_headers: dict
340
- ):
341
- """Test PUT /api/v1/users/{id}."""
342
- update_data = {
343
- "first_name": "Updated",
344
- "last_name": "Name",
345
- "bio": "Updated bio"
346
- }
347
-
348
- response = await async_client.put(
349
- f"/api/v1/users/{test_user.id}",
350
- json=update_data,
351
- headers=auth_headers
352
- )
353
-
354
- assert response.status_code == 200
355
- data = response.json()
356
- assert data["first_name"] == update_data["first_name"]
357
- assert data["last_name"] == update_data["last_name"]
358
- assert data["bio"] == update_data["bio"]
359
-
360
- @pytest.mark.asyncio
361
- async def test_delete_user(
362
- self,
363
- async_client: AsyncClient,
364
- test_user: User,
365
- superuser_headers: dict
366
- ):
367
- """Test DELETE /api/v1/users/{id}."""
368
- response = await async_client.delete(
369
- f"/api/v1/users/{test_user.id}",
370
- headers=superuser_headers
371
- )
372
-
373
- assert response.status_code == 204
374
-
375
- # Verify user is deleted
376
- get_response = await async_client.get(
377
- f"/api/v1/users/{test_user.id}",
378
- headers=superuser_headers
379
- )
380
- assert get_response.status_code == 404
381
-
382
- @pytest.mark.asyncio
383
- async def test_unauthorized_access(self, async_client: AsyncClient):
384
- """Test unauthorized access to protected endpoints."""
385
- response = await async_client.get("/api/v1/users/")
386
- assert response.status_code == 401
387
-
388
- @pytest.mark.asyncio
389
- async def test_forbidden_access(
390
- self,
391
- async_client: AsyncClient,
392
- auth_headers: dict
393
- ):
394
- """Test forbidden access to admin endpoints."""
395
- user_data = {
396
- "username": "unauthorized",
397
- "email": "unauthorized@example.com",
398
- "password": "pass123"
399
- }
400
-
401
- response = await async_client.post(
402
- "/api/v1/users/",
403
- json=user_data,
404
- headers=auth_headers # Regular user, not superuser
405
- )
406
-
407
- assert response.status_code == 403
408
- ```
409
-
410
- ## Authentication Testing
411
-
412
- ```python
413
- # tests/test_api/test_auth.py
414
- import pytest
415
- from httpx import AsyncClient
416
- from app.models.user import User
417
- from app.core.security import create_access_token, decode_token
418
-
419
- class TestAuthAPI:
420
- """Test authentication API endpoints."""
421
-
422
- @pytest.mark.asyncio
423
- async def test_register(
424
- self,
425
- async_client: AsyncClient
426
- ):
427
- """Test user registration."""
428
- user_data = {
429
- "username": "newuser",
430
- "email": "new@example.com",
431
- "password": "newpass123",
432
- "first_name": "New",
433
- "last_name": "User"
434
- }
435
-
436
- response = await async_client.post(
437
- "/api/v1/auth/register",
438
- json=user_data
439
- )
440
-
441
- assert response.status_code == 201
442
- data = response.json()
443
- assert data["username"] == user_data["username"]
444
- assert data["email"] == user_data["email"]
445
- assert "password" not in data
446
-
447
- @pytest.mark.asyncio
448
- async def test_register_duplicate_email(
449
- self,
450
- async_client: AsyncClient,
451
- test_user: User
452
- ):
453
- """Test registration with duplicate email."""
454
- user_data = {
455
- "username": "different",
456
- "email": test_user.email, # Duplicate email
457
- "password": "pass123",
458
- "first_name": "Test",
459
- "last_name": "User"
460
- }
461
-
462
- response = await async_client.post(
463
- "/api/v1/auth/register",
464
- json=user_data
465
- )
466
-
467
- assert response.status_code == 400
468
- assert "Email already registered" in response.json()["detail"]
469
-
470
- @pytest.mark.asyncio
471
- async def test_login(
472
- self,
473
- async_client: AsyncClient,
474
- test_user: User
475
- ):
476
- """Test user login."""
477
- login_data = {
478
- "username": test_user.username,
479
- "password": "testpass123"
480
- }
481
-
482
- response = await async_client.post(
483
- "/api/v1/auth/login",
484
- data=login_data,
485
- headers={"Content-Type": "application/x-www-form-urlencoded"}
486
- )
487
-
488
- assert response.status_code == 200
489
- data = response.json()
490
- assert "access_token" in data
491
- assert "refresh_token" in data
492
- assert data["token_type"] == "bearer"
493
- assert "expires_in" in data
494
-
495
- # Verify token is valid
496
- payload = decode_token(data["access_token"])
497
- assert payload is not None
498
- assert payload["sub"] == str(test_user.id)
499
-
500
- @pytest.mark.asyncio
501
- async def test_login_invalid_credentials(
502
- self,
503
- async_client: AsyncClient,
504
- test_user: User
505
- ):
506
- """Test login with invalid credentials."""
507
- login_data = {
508
- "username": test_user.username,
509
- "password": "wrongpassword"
510
- }
511
-
512
- response = await async_client.post(
513
- "/api/v1/auth/login",
514
- data=login_data,
515
- headers={"Content-Type": "application/x-www-form-urlencoded"}
516
- )
517
-
518
- assert response.status_code == 401
519
- assert "Incorrect username or password" in response.json()["detail"]
520
-
521
- @pytest.mark.asyncio
522
- async def test_get_current_user(
523
- self,
524
- async_client: AsyncClient,
525
- test_user: User,
526
- auth_headers: dict
527
- ):
528
- """Test get current user endpoint."""
529
- response = await async_client.get(
530
- "/api/v1/auth/me",
531
- headers=auth_headers
532
- )
533
-
534
- assert response.status_code == 200
535
- data = response.json()
536
- assert data["id"] == test_user.id
537
- assert data["username"] == test_user.username
538
- assert data["email"] == test_user.email
539
-
540
- @pytest.mark.asyncio
541
- async def test_refresh_token(
542
- self,
543
- async_client: AsyncClient,
544
- test_user: User
545
- ):
546
- """Test token refresh."""
547
- from app.core.security import create_refresh_token
548
-
549
- refresh_token = create_refresh_token(subject=test_user.id)
550
-
551
- response = await async_client.post(
552
- "/api/v1/auth/refresh",
553
- json={"refresh_token": refresh_token}
554
- )
555
-
556
- assert response.status_code == 200
557
- data = response.json()
558
- assert "access_token" in data
559
- assert "refresh_token" in data
560
- assert data["token_type"] == "bearer"
561
-
562
- @pytest.mark.asyncio
563
- async def test_change_password(
564
- self,
565
- async_client: AsyncClient,
566
- test_user: User,
567
- auth_headers: dict
568
- ):
569
- """Test password change."""
570
- password_data = {
571
- "current_password": "testpass123",
572
- "new_password": "newpass123"
573
- }
574
-
575
- response = await async_client.post(
576
- "/api/v1/auth/change-password",
577
- json=password_data,
578
- headers=auth_headers
579
- )
580
-
581
- assert response.status_code == 200
582
- assert "Password changed successfully" in response.json()["message"]
583
- ```
584
-
585
- ## Model Testing
586
-
587
- ```python
588
- # tests/test_models/test_user.py
589
- import pytest
590
- from sqlalchemy.ext.asyncio import AsyncSession
591
- from app.models.user import User
592
- from app.core.security import verify_password, get_password_hash
593
-
594
- class TestUserModel:
595
- """Test User model."""
596
-
597
- @pytest.mark.asyncio
598
- async def test_create_user(self, db_session: AsyncSession):
599
- """Test user creation."""
600
- user_data = {
601
- "username": "testuser",
602
- "email": "test@example.com",
603
- "hashed_password": get_password_hash("password123"),
604
- "first_name": "Test",
605
- "last_name": "User"
606
- }
607
-
608
- user = User(**user_data)
609
- db_session.add(user)
610
- await db_session.commit()
611
- await db_session.refresh(user)
612
-
613
- assert user.id is not None
614
- assert user.username == "testuser"
615
- assert user.email == "test@example.com"
616
- assert user.full_name == "Test User"
617
- assert user.is_active is True
618
- assert user.is_superuser is False
619
- assert user.created_at is not None
620
- assert user.updated_at is not None
621
-
622
- def test_password_verification(self):
623
- """Test password verification."""
624
- password = "testpassword123"
625
- hashed = get_password_hash(password)
626
-
627
- user = User(
628
- username="test",
629
- email="test@example.com",
630
- hashed_password=hashed,
631
- first_name="Test",
632
- last_name="User"
633
- )
634
-
635
- assert user.verify_password(password)
636
- assert not user.verify_password("wrongpassword")
637
-
638
- def test_user_dict_excludes_password(self):
639
- """Test that dict() method excludes password."""
640
- user = User(
641
- username="test",
642
- email="test@example.com",
643
- hashed_password="hashed_password",
644
- first_name="Test",
645
- last_name="User"
646
- )
647
-
648
- user_dict = user.dict()
649
- assert "hashed_password" not in user_dict
650
- assert "username" in user_dict
651
- assert "email" in user_dict
652
-
653
- def test_user_repr(self):
654
- """Test user string representation."""
655
- user = User(
656
- id=1,
657
- username="test",
658
- email="test@example.com",
659
- hashed_password="hash",
660
- first_name="Test",
661
- last_name="User"
662
- )
663
-
664
- assert repr(user) == "<User(id=1)>"
665
- ```
666
-
667
- ## Repository Testing
668
-
669
- ```python
670
- # tests/test_repositories/test_user.py
671
- import pytest
672
- from sqlalchemy.ext.asyncio import AsyncSession
673
- from app.models.user import User
674
- from app.repositories.user import UserRepository
675
- from app.core.security import get_password_hash
676
-
677
- class TestUserRepository:
678
- """Test UserRepository."""
679
-
680
- @pytest.mark.asyncio
681
- async def test_create_user(self, db_session: AsyncSession):
682
- """Test user creation through repository."""
683
- repo = UserRepository(User, db_session)
684
-
685
- user_data = {
686
- "username": "repouser",
687
- "email": "repo@example.com",
688
- "hashed_password": get_password_hash("password123"),
689
- "first_name": "Repo",
690
- "last_name": "User"
691
- }
692
-
693
- user = await repo.create(user_data)
694
-
695
- assert user.id is not None
696
- assert user.username == "repouser"
697
- assert user.email == "repo@example.com"
698
-
699
- @pytest.mark.asyncio
700
- async def test_get_by_email(self, db_session: AsyncSession, test_user: User):
701
- """Test get user by email."""
702
- repo = UserRepository(User, db_session)
703
-
704
- found_user = await repo.get_by_email(test_user.email)
705
-
706
- assert found_user is not None
707
- assert found_user.id == test_user.id
708
- assert found_user.email == test_user.email
709
-
710
- @pytest.mark.asyncio
711
- async def test_get_by_username(self, db_session: AsyncSession, test_user: User):
712
- """Test get user by username."""
713
- repo = UserRepository(User, db_session)
714
-
715
- found_user = await repo.get_by_username(test_user.username)
716
-
717
- assert found_user is not None
718
- assert found_user.id == test_user.id
719
- assert found_user.username == test_user.username
720
-
721
- @pytest.mark.asyncio
722
- async def test_get_multi_with_pagination(
723
- self,
724
- db_session: AsyncSession,
725
- test_user: User
726
- ):
727
- """Test get multiple users with pagination."""
728
- repo = UserRepository(User, db_session)
729
-
730
- # Create additional users
731
- for i in range(5):
732
- user_data = {
733
- "username": f"user{i}",
734
- "email": f"user{i}@example.com",
735
- "hashed_password": get_password_hash("password123"),
736
- "first_name": f"User{i}",
737
- "last_name": "Test"
738
- }
739
- await repo.create(user_data)
740
-
741
- # Test pagination
742
- users = await repo.get_multi(skip=0, limit=3)
743
- assert len(users) == 3
744
-
745
- users_page_2 = await repo.get_multi(skip=3, limit=3)
746
- assert len(users_page_2) >= 1 # At least test_user
747
-
748
- @pytest.mark.asyncio
749
- async def test_update_user(self, db_session: AsyncSession, test_user: User):
750
- """Test user update."""
751
- repo = UserRepository(User, db_session)
752
-
753
- updated_user = await repo.update(test_user.id, {
754
- "first_name": "Updated",
755
- "bio": "Updated bio"
756
- })
757
-
758
- assert updated_user is not None
759
- assert updated_user.first_name == "Updated"
760
- assert updated_user.bio == "Updated bio"
761
-
762
- @pytest.mark.asyncio
763
- async def test_delete_user(self, db_session: AsyncSession, test_user: User):
764
- """Test user deletion."""
765
- repo = UserRepository(User, db_session)
766
-
767
- result = await repo.delete(test_user.id)
768
- assert result is True
769
-
770
- # Verify user is deleted
771
- deleted_user = await repo.get(test_user.id)
772
- assert deleted_user is None
773
- ```
774
-
775
- ## Performance Testing
776
-
777
- ```python
778
- # tests/test_performance.py
779
- import pytest
780
- import time
781
- import asyncio
782
- from httpx import AsyncClient
783
- from app.models.user import User
784
- from tests.factories import UserFactory
785
-
786
- @pytest.mark.slow
787
- class TestPerformance:
788
- """Test application performance."""
789
-
790
- @pytest.mark.asyncio
791
- async def test_concurrent_requests(
792
- self,
793
- async_client: AsyncClient,
794
- auth_headers: dict
795
- ):
796
- """Test concurrent API requests."""
797
-
798
- async def make_request():
799
- response = await async_client.get(
800
- "/api/v1/users/",
801
- headers=auth_headers
802
- )
803
- return response.status_code
804
-
805
- # Make 10 concurrent requests
806
- start_time = time.time()
807
- tasks = [make_request() for _ in range(10)]
808
- results = await asyncio.gather(*tasks)
809
- end_time = time.time()
810
-
811
- # All requests should succeed
812
- assert all(status == 200 for status in results)
813
-
814
- # Should complete within reasonable time
815
- assert (end_time - start_time) < 5.0
816
-
817
- @pytest.mark.asyncio
818
- async def test_large_dataset_pagination(
819
- self,
820
- async_client: AsyncClient,
821
- db_session,
822
- auth_headers: dict
823
- ):
824
- """Test pagination with large dataset."""
825
- # Create 100 users
826
- users = []
827
- for i in range(100):
828
- user = UserFactory.build()
829
- users.append(user)
830
-
831
- db_session.add_all(users)
832
- await db_session.commit()
833
-
834
- # Test pagination performance
835
- start_time = time.time()
836
- response = await async_client.get(
837
- "/api/v1/users/?skip=0&limit=50",
838
- headers=auth_headers
839
- )
840
- end_time = time.time()
841
-
842
- assert response.status_code == 200
843
- data = response.json()
844
- assert len(data["items"]) == 50
845
-
846
- # Should complete quickly
847
- assert (end_time - start_time) < 1.0
848
- ```
849
-
850
- ## Mocking External Services
851
-
852
- ```python
853
- # tests/test_external.py
854
- import pytest
855
- import respx
856
- import httpx
857
- from app.services.email import EmailService
858
-
859
- class TestExternalServices:
860
- """Test external service integrations."""
861
-
862
- @pytest.mark.asyncio
863
- @respx.mock
864
- async def test_email_service(
865
- self,
866
- async_client: AsyncClient
867
- ):
868
- """Test email service with mocked external API."""
869
- # Mock email service API
870
- respx.post("https://api.emailservice.com/send").mock(
871
- return_value=httpx.Response(
872
- 200,
873
- json={"message": "Email sent successfully"}
874
- )
875
- )
876
-
877
- email_service = EmailService()
878
- result = await email_service.send_email(
879
- to="test@example.com",
880
- subject="Test",
881
- body="Test email"
882
- )
883
-
884
- assert result["success"] is True
885
- ```
886
-
887
- ## Test Utilities
888
-
889
- ```python
890
- # tests/utils.py
891
- from typing import Dict, Any
892
- from httpx import Response
893
- import json
894
-
895
- def assert_response_status(response: Response, expected_status: int = 200):
896
- """Assert response status code."""
897
- assert response.status_code == expected_status, f"Expected {expected_status}, got {response.status_code}. Response: {response.text}"
898
-
899
- def assert_response_json(response: Response, expected_keys: list[str] = None):
900
- """Assert response is valid JSON with expected keys."""
901
- assert response.headers.get("content-type") == "application/json"
902
- data = response.json()
903
-
904
- if expected_keys:
905
- for key in expected_keys:
906
- assert key in data, f"Missing key '{key}' in response"
907
-
908
- return data
909
-
910
- def create_auth_headers(token: str) -> Dict[str, str]:
911
- """Create authorization headers with token."""
912
- return {"Authorization": f"Bearer {token}"}
913
-
914
- async def create_test_users(db_session, count: int = 5) -> list:
915
- """Create multiple test users."""
916
- from tests.factories import UserFactory
917
-
918
- users = []
919
- for i in range(count):
920
- user = UserFactory.build()
921
- users.append(user)
922
-
923
- db_session.add_all(users)
924
- await db_session.commit()
925
-
926
- return users
927
- ```