metacoding 1.0.0 → 1.1.0

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 (46) hide show
  1. package/CHANGELOG.md +68 -43
  2. package/LICENSE +1 -1
  3. package/lib/services/template-manager.d.ts +3 -0
  4. package/lib/services/template-manager.d.ts.map +1 -1
  5. package/lib/services/template-manager.js +126 -9
  6. package/lib/services/template-manager.js.map +1 -1
  7. package/lib/services/vscode.js +1 -1
  8. package/lib/services/vscode.js.map +1 -1
  9. package/package.json +4 -4
  10. package/templates/general/code-review.instructions.md +265 -0
  11. package/templates/general/{files/copilot-instructions.md.template → copilot-instructions.md} +97 -140
  12. package/templates/{python/files → general}/docs-update.instructions.md +45 -32
  13. package/templates/general/release.instructions.md +242 -0
  14. package/templates/general/test-runner.instructions.md +188 -0
  15. package/templates/node/nodejs.coding.instructions.md +249 -0
  16. package/templates/node/nodejs.docs.instructions.md +234 -0
  17. package/templates/node/nodejs.testing.instructions.md +373 -0
  18. package/templates/python/python.coding.instructions.md +339 -0
  19. package/templates/python/python.docs.instructions.md +1147 -0
  20. package/templates/python/python.testing.instructions.md +1074 -0
  21. package/templates/react/react.coding.instructions.md +695 -0
  22. package/templates/react/react.docs.instructions.md +427 -0
  23. package/templates/react/react.testing.instructions.md +193 -0
  24. package/templates/react/test-runner.instructions.md +135 -0
  25. package/templates/typescript/template.json +16 -0
  26. package/templates/typescript/typescript.coding.instructions.md +368 -0
  27. package/templates/typescript/typescript.docs.instructions.md +734 -0
  28. package/templates/typescript/typescript.testing.instructions.md +740 -0
  29. package/templates/general/files/code-review.instructions.md +0 -111
  30. package/templates/general/files/docs-update.instructions.md +0 -203
  31. package/templates/general/files/release.instructions.md +0 -72
  32. package/templates/general/files/test-runner.instructions.md +0 -107
  33. package/templates/node/files/code-review.instructions.md +0 -222
  34. package/templates/node/files/copilot-instructions.md.template +0 -391
  35. package/templates/node/files/docs-update.instructions.md +0 -203
  36. package/templates/node/files/release.instructions.md +0 -72
  37. package/templates/node/files/test-runner.instructions.md +0 -108
  38. package/templates/python/files/code-review.instructions.md +0 -215
  39. package/templates/python/files/copilot-instructions.md.template +0 -418
  40. package/templates/python/files/release.instructions.md +0 -72
  41. package/templates/python/files/test-runner.instructions.md +0 -108
  42. package/templates/react/files/code-review.instructions.md +0 -160
  43. package/templates/react/files/copilot-instructions.md.template +0 -472
  44. package/templates/react/files/docs-update.instructions.md +0 -203
  45. package/templates/react/files/release.instructions.md +0 -72
  46. package/templates/react/files/test-runner.instructions.md +0 -108
@@ -0,0 +1,1074 @@
1
+ ---
2
+ description: 'Python-specific testing patterns and frameworks (pytest, Django, FastAPI)'
3
+ applyTo: 'test/**/*.py,tests/**/*.py,**/test_*.py'
4
+ language: 'python'
5
+ ---
6
+
7
+ # Python Testing Standards
8
+
9
+ ## Test Case Naming Conventions
10
+
11
+ ### Test Case ID Format: `[AREA]-[TYPE]-[NUMBER]`
12
+
13
+ **Python/Django/FastAPI Area Prefixes:**
14
+
15
+ - `VIEW` - Django views/FastAPI endpoints tests
16
+ - `MODEL` - Django models/SQLAlchemy tests
17
+ - `FORM` - Django forms/Pydantic validators tests
18
+ - `API` - REST API endpoint tests
19
+ - `SRV` - Service layer tests
20
+ - `DB` - Database/ORM tests
21
+ - `AUTH` - Authentication/Authorization tests
22
+ - `UTIL` - Backend utility function tests
23
+ - `SERIALIZER` - DRF serializers/Pydantic schemas tests
24
+ - `MIDDLEWARE` - Django middleware tests
25
+
26
+ **Type Suffixes:**
27
+
28
+ - `UNIT` - Unit tests (isolated component testing)
29
+ - `INT` - Integration tests (component interaction testing)
30
+ - `E2E` - End-to-end tests (full API workflow testing)
31
+
32
+ **Examples:**
33
+
34
+ - `VIEW-UNIT-001` - First unit test for Django view
35
+ - `MODEL-UNIT-001` - First unit test for Django model
36
+ - `FORM-INT-001` - First integration test for Django form
37
+ - `API-E2E-001` - First end-to-end API test
38
+
39
+ ## Testing Framework and Setup
40
+
41
+ ### Primary Testing Framework: pytest
42
+
43
+ ```python
44
+ # pytest.ini
45
+ [tool:pytest]
46
+ testpaths = tests
47
+ python_files = test_*.py *_test.py
48
+ python_classes = Test*
49
+ python_functions = test_*
50
+ addopts =
51
+ --strict-markers
52
+ --strict-config
53
+ --cov=src
54
+ --cov-report=term-missing
55
+ --cov-report=html
56
+ --cov-fail-under=80
57
+ markers =
58
+ unit: Unit tests
59
+ integration: Integration tests
60
+ slow: Slow running tests
61
+ api: API endpoint tests
62
+ ```
63
+
64
+ ```python
65
+ # conftest.py
66
+ import pytest
67
+ from unittest.mock import Mock
68
+ from src.database import Database
69
+ from src.services.user_service import UserService
70
+
71
+ @pytest.fixture
72
+ def mock_database():
73
+ """Mock database connection for testing."""
74
+ return Mock(spec=Database)
75
+
76
+ @pytest.fixture
77
+ def user_service(mock_database):
78
+ """User service with mocked dependencies."""
79
+ return UserService(database=mock_database)
80
+
81
+ @pytest.fixture
82
+ def sample_user_data():
83
+ """Sample user data for testing."""
84
+ return {
85
+ 'name': 'John Doe',
86
+ 'email': 'john@example.com',
87
+ 'age': 30
88
+ }
89
+
90
+ @pytest.fixture(scope="session")
91
+ def test_database():
92
+ """Session-scoped test database."""
93
+ db = Database(DATABASE_TEST_URL)
94
+ db.create_tables()
95
+ yield db
96
+ db.drop_tables()
97
+ db.close()
98
+ ```
99
+
100
+ ### Test Case Naming Conventions
101
+
102
+ **Area Prefixes for Python/Django:**
103
+
104
+ - `VIEW` - View/Controller tests
105
+ - `MODEL` - Model/ORM tests
106
+ - `FORM` - Form validation tests
107
+ - `CMD` - Management command tests
108
+ - `API` - API endpoint tests
109
+ - `SRV` - Service layer tests
110
+ - `UTIL` - Utility function tests
111
+ - `DB` - Database operation tests
112
+ - `AUTH` - Authentication/Authorization tests
113
+ - `INT` - Integration tests
114
+ - `E2E` - End-to-end tests
115
+
116
+ **Examples:**
117
+
118
+ - `VIEW-UNIT-001` - First unit test for View layer
119
+ - `MODEL-UNIT-001` - First unit test for Model layer
120
+ - `API-INT-001` - First integration test for API
121
+ - `E2E-FLOW-001` - First end-to-end workflow test
122
+
123
+ ## Unit Testing Patterns
124
+
125
+ ### Service Layer Testing
126
+
127
+ ```python
128
+ # tests/unit/test_user_service.py
129
+ import pytest
130
+ from unittest.mock import Mock, patch, call
131
+ from src.services.user_service import UserService, UserNotFoundError
132
+ from src.models.user import User
133
+
134
+ class TestUserService:
135
+ """Test suite for UserService class."""
136
+
137
+ @pytest.fixture
138
+ def mock_repository(self):
139
+ """Mock user repository."""
140
+ return Mock()
141
+
142
+ @pytest.fixture
143
+ def user_service(self, mock_repository):
144
+ """UserService instance with mocked dependencies."""
145
+ return UserService(repository=mock_repository)
146
+
147
+ def test_get_user_by_id_success(self, user_service, mock_repository):
148
+ """Test successful user retrieval by ID."""
149
+ # Arrange
150
+ user_id = "123"
151
+ expected_user = User(id=user_id, name="John Doe", email="john@example.com")
152
+ mock_repository.find_by_id.return_value = expected_user
153
+
154
+ # Act
155
+ result = user_service.get_user_by_id(user_id)
156
+
157
+ # Assert
158
+ assert result == expected_user
159
+ mock_repository.find_by_id.assert_called_once_with(user_id)
160
+
161
+ def test_get_user_by_id_not_found(self, user_service, mock_repository):
162
+ """Test user retrieval when user doesn't exist."""
163
+ # Arrange
164
+ user_id = "999"
165
+ mock_repository.find_by_id.return_value = None
166
+
167
+ # Act & Assert
168
+ with pytest.raises(UserNotFoundError, match=f"User {user_id} not found"):
169
+ user_service.get_user_by_id(user_id)
170
+
171
+ mock_repository.find_by_id.assert_called_once_with(user_id)
172
+
173
+ @pytest.mark.parametrize("user_id,expected_calls", [
174
+ ("valid_id", 1),
175
+ ("another_id", 1),
176
+ ])
177
+ def test_get_user_by_id_repository_calls(
178
+ self, user_service, mock_repository, user_id, expected_calls
179
+ ):
180
+ """Test repository is called correct number of times."""
181
+ # Arrange
182
+ mock_repository.find_by_id.return_value = Mock()
183
+
184
+ # Act
185
+ user_service.get_user_by_id(user_id)
186
+
187
+ # Assert
188
+ assert mock_repository.find_by_id.call_count == expected_calls
189
+
190
+ def test_create_user_success(self, user_service, mock_repository):
191
+ """Test successful user creation."""
192
+ # Arrange
193
+ user_data = {
194
+ 'name': 'Jane Doe',
195
+ 'email': 'jane@example.com'
196
+ }
197
+ created_user = User(id="456", **user_data)
198
+ mock_repository.create.return_value = created_user
199
+
200
+ # Act
201
+ result = user_service.create_user(user_data)
202
+
203
+ # Assert
204
+ assert result == created_user
205
+ mock_repository.create.assert_called_once_with(user_data)
206
+
207
+ def test_create_user_validation_error(self, user_service):
208
+ """Test user creation with invalid data."""
209
+ # Arrange
210
+ invalid_data = {'name': ''} # Missing email
211
+
212
+ # Act & Assert
213
+ with pytest.raises(ValueError, match="Invalid user data"):
214
+ user_service.create_user(invalid_data)
215
+ ```
216
+
217
+ ### Model Testing (Django)
218
+
219
+ ```python
220
+ # tests/unit/test_user_model.py
221
+ import pytest
222
+ from django.test import TestCase
223
+ from django.core.exceptions import ValidationError
224
+ from django.db import IntegrityError
225
+ from src.models.user import User
226
+
227
+ class TestUserModel(TestCase):
228
+ """Test suite for User model."""
229
+
230
+ def setUp(self):
231
+ """Set up test data."""
232
+ self.valid_user_data = {
233
+ 'name': 'John Doe',
234
+ 'email': 'john@example.com',
235
+ 'age': 30
236
+ }
237
+
238
+ def test_create_user_success(self):
239
+ """Test successful user creation."""
240
+ # Act
241
+ user = User.objects.create(**self.valid_user_data)
242
+
243
+ # Assert
244
+ assert user.pk is not None
245
+ assert user.name == self.valid_user_data['name']
246
+ assert user.email == self.valid_user_data['email']
247
+ assert user.age == self.valid_user_data['age']
248
+ assert user.is_active is True # Default value
249
+
250
+ def test_user_str_representation(self):
251
+ """Test string representation of user."""
252
+ # Arrange
253
+ user = User(**self.valid_user_data)
254
+
255
+ # Act
256
+ result = str(user)
257
+
258
+ # Assert
259
+ expected = f"{self.valid_user_data['name']} ({self.valid_user_data['email']})"
260
+ assert result == expected
261
+
262
+ def test_email_uniqueness_constraint(self):
263
+ """Test email uniqueness is enforced."""
264
+ # Arrange
265
+ User.objects.create(**self.valid_user_data)
266
+
267
+ # Act & Assert
268
+ with pytest.raises(IntegrityError):
269
+ User.objects.create(**self.valid_user_data)
270
+
271
+ def test_email_validation(self):
272
+ """Test email format validation."""
273
+ # Arrange
274
+ invalid_data = self.valid_user_data.copy()
275
+ invalid_data['email'] = 'invalid-email'
276
+
277
+ # Act & Assert
278
+ user = User(**invalid_data)
279
+ with pytest.raises(ValidationError):
280
+ user.full_clean()
281
+
282
+ @pytest.mark.parametrize("age,is_valid", [
283
+ (0, False), # Too young
284
+ (13, True), # Minimum valid age
285
+ (120, True), # Maximum reasonable age
286
+ (150, False), # Too old
287
+ (-1, False), # Negative age
288
+ ])
289
+ def test_age_validation(self, age, is_valid):
290
+ """Test age validation with various values."""
291
+ # Arrange
292
+ user_data = self.valid_user_data.copy()
293
+ user_data['age'] = age
294
+ user = User(**user_data)
295
+
296
+ # Act & Assert
297
+ if is_valid:
298
+ user.full_clean() # Should not raise
299
+ else:
300
+ with pytest.raises(ValidationError):
301
+ user.full_clean()
302
+
303
+ def test_get_full_name_method(self):
304
+ """Test custom method for getting full name."""
305
+ # Arrange
306
+ user = User(name="John Doe", email="john@example.com")
307
+
308
+ # Act
309
+ result = user.get_full_name()
310
+
311
+ # Assert
312
+ assert result == "John Doe"
313
+
314
+ def test_is_adult_property(self):
315
+ """Test custom property for checking if user is adult."""
316
+ # Arrange
317
+ adult_user = User(age=25, name="Adult", email="adult@example.com")
318
+ minor_user = User(age=16, name="Minor", email="minor@example.com")
319
+
320
+ # Act & Assert
321
+ assert adult_user.is_adult is True
322
+ assert minor_user.is_adult is False
323
+ ```
324
+
325
+ ### View Testing (Django)
326
+
327
+ ```python
328
+ # tests/unit/test_user_views.py
329
+ import json
330
+ import pytest
331
+ from django.test import TestCase, Client
332
+ from django.contrib.auth.models import User as AuthUser
333
+ from django.urls import reverse
334
+ from src.models.user import User
335
+
336
+ class TestUserViews(TestCase):
337
+ """Test suite for user-related views."""
338
+
339
+ def setUp(self):
340
+ """Set up test data and client."""
341
+ self.client = Client()
342
+ self.auth_user = AuthUser.objects.create_user(
343
+ username='testuser',
344
+ password='testpass123'
345
+ )
346
+ self.user = User.objects.create(
347
+ name='John Doe',
348
+ email='john@example.com',
349
+ age=30
350
+ )
351
+
352
+ def test_user_list_view_success(self):
353
+ """Test user list view returns users."""
354
+ # Act
355
+ response = self.client.get(reverse('user:list'))
356
+
357
+ # Assert
358
+ assert response.status_code == 200
359
+ assert 'users' in response.context
360
+ assert self.user in response.context['users']
361
+
362
+ def test_user_detail_view_success(self):
363
+ """Test user detail view returns specific user."""
364
+ # Act
365
+ response = self.client.get(
366
+ reverse('user:detail', kwargs={'pk': self.user.pk})
367
+ )
368
+
369
+ # Assert
370
+ assert response.status_code == 200
371
+ assert response.context['user'] == self.user
372
+
373
+ def test_user_detail_view_not_found(self):
374
+ """Test user detail view with non-existent user."""
375
+ # Act
376
+ response = self.client.get(
377
+ reverse('user:detail', kwargs={'pk': 999})
378
+ )
379
+
380
+ # Assert
381
+ assert response.status_code == 404
382
+
383
+ def test_user_create_view_get(self):
384
+ """Test GET request to user create view."""
385
+ # Arrange
386
+ self.client.login(username='testuser', password='testpass123')
387
+
388
+ # Act
389
+ response = self.client.get(reverse('user:create'))
390
+
391
+ # Assert
392
+ assert response.status_code == 200
393
+ assert 'form' in response.context
394
+
395
+ def test_user_create_view_post_success(self):
396
+ """Test successful POST to user create view."""
397
+ # Arrange
398
+ self.client.login(username='testuser', password='testpass123')
399
+ user_data = {
400
+ 'name': 'Jane Doe',
401
+ 'email': 'jane@example.com',
402
+ 'age': 25
403
+ }
404
+
405
+ # Act
406
+ response = self.client.post(reverse('user:create'), data=user_data)
407
+
408
+ # Assert
409
+ assert response.status_code == 302 # Redirect after success
410
+ assert User.objects.filter(email='jane@example.com').exists()
411
+
412
+ def test_user_create_view_post_invalid_data(self):
413
+ """Test POST to user create view with invalid data."""
414
+ # Arrange
415
+ self.client.login(username='testuser', password='testpass123')
416
+ invalid_data = {
417
+ 'name': '', # Invalid: empty name
418
+ 'email': 'invalid-email', # Invalid: bad email format
419
+ 'age': -5 # Invalid: negative age
420
+ }
421
+
422
+ # Act
423
+ response = self.client.post(reverse('user:create'), data=invalid_data)
424
+
425
+ # Assert
426
+ assert response.status_code == 200 # Returns form with errors
427
+ assert 'form' in response.context
428
+ assert response.context['form'].errors
429
+
430
+ def test_user_create_view_requires_authentication(self):
431
+ """Test user create view requires authentication."""
432
+ # Act
433
+ response = self.client.get(reverse('user:create'))
434
+
435
+ # Assert
436
+ assert response.status_code == 302 # Redirect to login
437
+ assert '/login/' in response.url
438
+ ```
439
+
440
+ ### API Testing (FastAPI)
441
+
442
+ ```python
443
+ # tests/unit/test_user_api.py
444
+ import pytest
445
+ from fastapi.testclient import TestClient
446
+ from unittest.mock import Mock, patch
447
+ from src.main import app
448
+ from src.services.user_service import UserService
449
+ from src.models.user import User
450
+
451
+ client = TestClient(app)
452
+
453
+ class TestUserAPI:
454
+ """Test suite for User API endpoints."""
455
+
456
+ @patch('src.dependencies.get_user_service')
457
+ def test_get_user_success(self, mock_get_service):
458
+ """Test successful user retrieval via API."""
459
+ # Arrange
460
+ user_id = "123"
461
+ expected_user = User(id=user_id, name="John Doe", email="john@example.com")
462
+ mock_service = Mock(spec=UserService)
463
+ mock_service.get_user_by_id.return_value = expected_user
464
+ mock_get_service.return_value = mock_service
465
+
466
+ # Act
467
+ response = client.get(f"/users/{user_id}")
468
+
469
+ # Assert
470
+ assert response.status_code == 200
471
+ assert response.json()["id"] == user_id
472
+ assert response.json()["name"] == "John Doe"
473
+ mock_service.get_user_by_id.assert_called_once_with(user_id)
474
+
475
+ @patch('src.dependencies.get_user_service')
476
+ def test_get_user_not_found(self, mock_get_service):
477
+ """Test user not found via API."""
478
+ # Arrange
479
+ user_id = "999"
480
+ mock_service = Mock(spec=UserService)
481
+ mock_service.get_user_by_id.side_effect = UserNotFoundError("User not found")
482
+ mock_get_service.return_value = mock_service
483
+
484
+ # Act
485
+ response = client.get(f"/users/{user_id}")
486
+
487
+ # Assert
488
+ assert response.status_code == 404
489
+ assert "not found" in response.json()["detail"].lower()
490
+
491
+ @patch('src.dependencies.get_user_service')
492
+ def test_create_user_success(self, mock_get_service):
493
+ """Test successful user creation via API."""
494
+ # Arrange
495
+ user_data = {
496
+ "name": "Jane Doe",
497
+ "email": "jane@example.com",
498
+ "age": 25
499
+ }
500
+ created_user = User(id="456", **user_data)
501
+ mock_service = Mock(spec=UserService)
502
+ mock_service.create_user.return_value = created_user
503
+ mock_get_service.return_value = mock_service
504
+
505
+ # Act
506
+ response = client.post("/users/", json=user_data)
507
+
508
+ # Assert
509
+ assert response.status_code == 201
510
+ assert response.json()["id"] == "456"
511
+ assert response.json()["name"] == user_data["name"]
512
+ mock_service.create_user.assert_called_once_with(user_data)
513
+
514
+ def test_create_user_validation_error(self):
515
+ """Test user creation with invalid data."""
516
+ # Arrange
517
+ invalid_data = {
518
+ "name": "", # Invalid: empty name
519
+ "email": "invalid-email", # Invalid: bad format
520
+ "age": -5 # Invalid: negative age
521
+ }
522
+
523
+ # Act
524
+ response = client.post("/users/", json=invalid_data)
525
+
526
+ # Assert
527
+ assert response.status_code == 422
528
+ assert "detail" in response.json()
529
+
530
+ @pytest.mark.parametrize("user_data,expected_status", [
531
+ ({"name": "Valid User", "email": "valid@example.com", "age": 25}, 201),
532
+ ({"name": "", "email": "valid@example.com", "age": 25}, 422),
533
+ ({"name": "Valid User", "email": "invalid-email", "age": 25}, 422),
534
+ ({"name": "Valid User", "email": "valid@example.com", "age": -1}, 422),
535
+ ])
536
+ @patch('src.dependencies.get_user_service')
537
+ def test_create_user_various_data(
538
+ self, mock_get_service, user_data, expected_status
539
+ ):
540
+ """Test user creation with various data combinations."""
541
+ # Arrange
542
+ if expected_status == 201:
543
+ created_user = User(id="123", **user_data)
544
+ mock_service = Mock(spec=UserService)
545
+ mock_service.create_user.return_value = created_user
546
+ mock_get_service.return_value = mock_service
547
+
548
+ # Act
549
+ response = client.post("/users/", json=user_data)
550
+
551
+ # Assert
552
+ assert response.status_code == expected_status
553
+ ```
554
+
555
+ ## Integration Testing
556
+
557
+ ### Database Integration Testing
558
+
559
+ ```python
560
+ # tests/integration/test_user_repository.py
561
+ import pytest
562
+ import asyncio
563
+ from sqlalchemy import create_engine
564
+ from sqlalchemy.orm import sessionmaker
565
+ from src.database.base import Base
566
+ from src.repositories.user_repository import UserRepository
567
+ from src.models.user import User
568
+
569
+ class TestUserRepositoryIntegration:
570
+ """Integration tests for UserRepository with real database."""
571
+
572
+ @pytest.fixture(scope="class")
573
+ def db_engine(self):
574
+ """Create test database engine."""
575
+ engine = create_engine("sqlite:///:memory:", echo=False)
576
+ Base.metadata.create_all(engine)
577
+ yield engine
578
+ Base.metadata.drop_all(engine)
579
+
580
+ @pytest.fixture
581
+ def db_session(self, db_engine):
582
+ """Create database session for each test."""
583
+ Session = sessionmaker(bind=db_engine)
584
+ session = Session()
585
+ yield session
586
+ session.rollback()
587
+ session.close()
588
+
589
+ @pytest.fixture
590
+ def user_repository(self, db_session):
591
+ """Create UserRepository with real database session."""
592
+ return UserRepository(session=db_session)
593
+
594
+ def test_create_and_find_user(self, user_repository):
595
+ """Test creating and finding a user in database."""
596
+ # Arrange
597
+ user_data = {
598
+ 'name': 'John Doe',
599
+ 'email': 'john@example.com',
600
+ 'age': 30
601
+ }
602
+
603
+ # Act
604
+ created_user = user_repository.create(user_data)
605
+ found_user = user_repository.find_by_id(created_user.id)
606
+
607
+ # Assert
608
+ assert found_user is not None
609
+ assert found_user.id == created_user.id
610
+ assert found_user.name == user_data['name']
611
+ assert found_user.email == user_data['email']
612
+
613
+ def test_find_by_email(self, user_repository):
614
+ """Test finding user by email."""
615
+ # Arrange
616
+ user_data = {
617
+ 'name': 'Jane Doe',
618
+ 'email': 'jane@example.com',
619
+ 'age': 25
620
+ }
621
+ created_user = user_repository.create(user_data)
622
+
623
+ # Act
624
+ found_user = user_repository.find_by_email('jane@example.com')
625
+
626
+ # Assert
627
+ assert found_user is not None
628
+ assert found_user.id == created_user.id
629
+ assert found_user.email == 'jane@example.com'
630
+
631
+ def test_update_user(self, user_repository):
632
+ """Test updating user information."""
633
+ # Arrange
634
+ user_data = {
635
+ 'name': 'Original Name',
636
+ 'email': 'original@example.com',
637
+ 'age': 30
638
+ }
639
+ created_user = user_repository.create(user_data)
640
+
641
+ # Act
642
+ update_data = {'name': 'Updated Name', 'age': 31}
643
+ updated_user = user_repository.update(created_user.id, update_data)
644
+
645
+ # Assert
646
+ assert updated_user.name == 'Updated Name'
647
+ assert updated_user.age == 31
648
+ assert updated_user.email == 'original@example.com' # Unchanged
649
+
650
+ def test_delete_user(self, user_repository):
651
+ """Test deleting a user."""
652
+ # Arrange
653
+ user_data = {
654
+ 'name': 'To Be Deleted',
655
+ 'email': 'delete@example.com',
656
+ 'age': 25
657
+ }
658
+ created_user = user_repository.create(user_data)
659
+
660
+ # Act
661
+ result = user_repository.delete(created_user.id)
662
+ found_user = user_repository.find_by_id(created_user.id)
663
+
664
+ # Assert
665
+ assert result is True
666
+ assert found_user is None
667
+
668
+ def test_list_users_with_pagination(self, user_repository):
669
+ """Test listing users with pagination."""
670
+ # Arrange
671
+ users_data = [
672
+ {'name': f'User {i}', 'email': f'user{i}@example.com', 'age': 20 + i}
673
+ for i in range(5)
674
+ ]
675
+ created_users = [user_repository.create(data) for data in users_data]
676
+
677
+ # Act
678
+ page_1 = user_repository.list_users(page=1, per_page=2)
679
+ page_2 = user_repository.list_users(page=2, per_page=2)
680
+
681
+ # Assert
682
+ assert len(page_1) == 2
683
+ assert len(page_2) == 2
684
+ assert page_1[0].id != page_2[0].id # Different users
685
+ ```
686
+
687
+ ### API Integration Testing
688
+
689
+ ```python
690
+ # tests/integration/test_api_integration.py
691
+ import pytest
692
+ from fastapi.testclient import TestClient
693
+ from sqlalchemy import create_engine
694
+ from sqlalchemy.orm import sessionmaker
695
+ from src.main import app
696
+ from src.database.base import Base
697
+ from src.dependencies import get_database_session
698
+
699
+ class TestAPIIntegration:
700
+ """Integration tests for API with real database."""
701
+
702
+ @pytest.fixture(scope="class")
703
+ def test_engine(self):
704
+ """Create test database engine."""
705
+ engine = create_engine("sqlite:///:memory:", echo=False)
706
+ Base.metadata.create_all(engine)
707
+ yield engine
708
+ Base.metadata.drop_all(engine)
709
+
710
+ @pytest.fixture
711
+ def test_session(self, test_engine):
712
+ """Create test database session."""
713
+ Session = sessionmaker(bind=test_engine)
714
+ session = Session()
715
+ yield session
716
+ session.close()
717
+
718
+ @pytest.fixture
719
+ def client(self, test_session):
720
+ """Create test client with test database."""
721
+ def override_get_db():
722
+ yield test_session
723
+
724
+ app.dependency_overrides[get_database_session] = override_get_db
725
+ client = TestClient(app)
726
+ yield client
727
+ app.dependency_overrides.clear()
728
+
729
+ def test_full_user_crud_workflow(self, client):
730
+ """Test complete CRUD workflow for users."""
731
+ # Create user
732
+ user_data = {
733
+ "name": "Test User",
734
+ "email": "test@example.com",
735
+ "age": 30
736
+ }
737
+
738
+ create_response = client.post("/users/", json=user_data)
739
+ assert create_response.status_code == 201
740
+ created_user = create_response.json()
741
+ user_id = created_user["id"]
742
+
743
+ # Read user
744
+ get_response = client.get(f"/users/{user_id}")
745
+ assert get_response.status_code == 200
746
+ assert get_response.json()["name"] == user_data["name"]
747
+
748
+ # Update user
749
+ update_data = {"name": "Updated User", "age": 31}
750
+ update_response = client.patch(f"/users/{user_id}", json=update_data)
751
+ assert update_response.status_code == 200
752
+ assert update_response.json()["name"] == "Updated User"
753
+
754
+ # List users
755
+ list_response = client.get("/users/")
756
+ assert list_response.status_code == 200
757
+ users = list_response.json()
758
+ assert len(users) >= 1
759
+ assert any(user["id"] == user_id for user in users)
760
+
761
+ # Delete user
762
+ delete_response = client.delete(f"/users/{user_id}")
763
+ assert delete_response.status_code == 204
764
+
765
+ # Verify deletion
766
+ get_deleted_response = client.get(f"/users/{user_id}")
767
+ assert get_deleted_response.status_code == 404
768
+
769
+ def test_user_email_uniqueness_constraint(self, client):
770
+ """Test email uniqueness is enforced at API level."""
771
+ # Arrange
772
+ user_data = {
773
+ "name": "First User",
774
+ "email": "duplicate@example.com",
775
+ "age": 25
776
+ }
777
+
778
+ # Act - Create first user
779
+ first_response = client.post("/users/", json=user_data)
780
+ assert first_response.status_code == 201
781
+
782
+ # Act - Try to create second user with same email
783
+ user_data["name"] = "Second User"
784
+ second_response = client.post("/users/", json=user_data)
785
+
786
+ # Assert
787
+ assert second_response.status_code == 400
788
+ assert "email" in second_response.json()["detail"].lower()
789
+ ```
790
+
791
+ ## Async Testing
792
+
793
+ ### Async Function Testing
794
+
795
+ ```python
796
+ # tests/unit/test_async_service.py
797
+ import pytest
798
+ import asyncio
799
+ from unittest.mock import AsyncMock, Mock
800
+ from src.services.async_user_service import AsyncUserService
801
+
802
+ class TestAsyncUserService:
803
+ """Test suite for async user service."""
804
+
805
+ @pytest.fixture
806
+ def mock_async_repository(self):
807
+ """Mock async repository."""
808
+ mock = AsyncMock()
809
+ return mock
810
+
811
+ @pytest.fixture
812
+ def async_user_service(self, mock_async_repository):
813
+ """Async user service with mocked dependencies."""
814
+ return AsyncUserService(repository=mock_async_repository)
815
+
816
+ @pytest.mark.asyncio
817
+ async def test_get_user_by_id_async(self, async_user_service, mock_async_repository):
818
+ """Test async user retrieval."""
819
+ # Arrange
820
+ user_id = "123"
821
+ expected_user = {"id": user_id, "name": "John Doe"}
822
+ mock_async_repository.find_by_id.return_value = expected_user
823
+
824
+ # Act
825
+ result = await async_user_service.get_user_by_id(user_id)
826
+
827
+ # Assert
828
+ assert result == expected_user
829
+ mock_async_repository.find_by_id.assert_called_once_with(user_id)
830
+
831
+ @pytest.mark.asyncio
832
+ async def test_create_multiple_users_concurrently(
833
+ self, async_user_service, mock_async_repository
834
+ ):
835
+ """Test creating multiple users concurrently."""
836
+ # Arrange
837
+ users_data = [
838
+ {"name": f"User {i}", "email": f"user{i}@example.com"}
839
+ for i in range(3)
840
+ ]
841
+ mock_async_repository.create.side_effect = [
842
+ {"id": f"id_{i}", **data} for i, data in enumerate(users_data)
843
+ ]
844
+
845
+ # Act
846
+ tasks = [
847
+ async_user_service.create_user(data) for data in users_data
848
+ ]
849
+ results = await asyncio.gather(*tasks)
850
+
851
+ # Assert
852
+ assert len(results) == 3
853
+ assert all("id" in result for result in results)
854
+ assert mock_async_repository.create.call_count == 3
855
+
856
+ @pytest.mark.asyncio
857
+ async def test_timeout_handling(self, async_user_service, mock_async_repository):
858
+ """Test handling of timeout in async operations."""
859
+ # Arrange
860
+ async def slow_operation(*args, **kwargs):
861
+ await asyncio.sleep(2) # Simulate slow operation
862
+ return {"id": "123", "name": "Slow User"}
863
+
864
+ mock_async_repository.find_by_id.side_effect = slow_operation
865
+
866
+ # Act & Assert
867
+ with pytest.raises(asyncio.TimeoutError):
868
+ await asyncio.wait_for(
869
+ async_user_service.get_user_by_id("123"),
870
+ timeout=1.0
871
+ )
872
+ ```
873
+
874
+ ## Mocking and Fixtures
875
+
876
+ ### Complex Fixture Patterns
877
+
878
+ ```python
879
+ # tests/conftest.py
880
+ import pytest
881
+ from unittest.mock import Mock, patch
882
+ from datetime import datetime, timedelta
883
+ from src.models.user import User
884
+ from src.services.email_service import EmailService
885
+
886
+ @pytest.fixture
887
+ def freeze_time():
888
+ """Fixture to freeze time for consistent testing."""
889
+ frozen_time = datetime(2023, 1, 1, 12, 0, 0)
890
+ with patch('src.utils.datetime.datetime') as mock_datetime:
891
+ mock_datetime.now.return_value = frozen_time
892
+ mock_datetime.utcnow.return_value = frozen_time
893
+ yield frozen_time
894
+
895
+ @pytest.fixture
896
+ def mock_email_service():
897
+ """Mock email service for testing."""
898
+ mock = Mock(spec=EmailService)
899
+ mock.send_email.return_value = True
900
+ mock.send_bulk_email.return_value = {"sent": 5, "failed": 0}
901
+ return mock
902
+
903
+ @pytest.fixture(params=[
904
+ {"name": "John Doe", "age": 30, "email": "john@example.com"},
905
+ {"name": "Jane Smith", "age": 25, "email": "jane@example.com"},
906
+ ])
907
+ def user_data_variations(request):
908
+ """Parametrized fixture for different user data."""
909
+ return request.param
910
+
911
+ @pytest.fixture
912
+ def users_factory():
913
+ """Factory fixture for creating multiple users."""
914
+ def _create_users(count=3, **overrides):
915
+ users = []
916
+ for i in range(count):
917
+ user_data = {
918
+ "name": f"User {i}",
919
+ "email": f"user{i}@example.com",
920
+ "age": 20 + i,
921
+ **overrides
922
+ }
923
+ users.append(User(**user_data))
924
+ return users
925
+ return _create_users
926
+
927
+ @pytest.fixture(scope="session")
928
+ def test_config():
929
+ """Session-scoped configuration for tests."""
930
+ return {
931
+ "database_url": "sqlite:///:memory:",
932
+ "debug": True,
933
+ "testing": True,
934
+ "email_backend": "mock"
935
+ }
936
+ ```
937
+
938
+ ### Advanced Mocking Patterns
939
+
940
+ ```python
941
+ # tests/unit/test_advanced_mocking.py
942
+ import pytest
943
+ from unittest.mock import Mock, patch, MagicMock, call
944
+ from datetime import datetime
945
+ from src.services.notification_service import NotificationService
946
+
947
+ class TestAdvancedMocking:
948
+ """Test suite demonstrating advanced mocking patterns."""
949
+
950
+ def test_property_mocking(self):
951
+ """Test mocking object properties."""
952
+ # Arrange
953
+ mock_user = Mock()
954
+ type(mock_user).is_active = PropertyMock(return_value=True)
955
+
956
+ # Act & Assert
957
+ assert mock_user.is_active is True
958
+
959
+ def test_context_manager_mocking(self):
960
+ """Test mocking context managers."""
961
+ # Arrange
962
+ with patch('builtins.open', mock_open(read_data='file content')) as mock_file:
963
+ # Act
964
+ with open('test.txt', 'r') as f:
965
+ content = f.read()
966
+
967
+ # Assert
968
+ assert content == 'file content'
969
+ mock_file.assert_called_once_with('test.txt', 'r')
970
+
971
+ def test_side_effect_with_exception(self):
972
+ """Test using side_effect to raise exceptions."""
973
+ # Arrange
974
+ mock_service = Mock()
975
+ mock_service.get_user.side_effect = [
976
+ {"id": "1", "name": "User 1"}, # First call succeeds
977
+ Exception("Service unavailable"), # Second call fails
978
+ {"id": "3", "name": "User 3"}, # Third call succeeds
979
+ ]
980
+
981
+ # Act & Assert
982
+ result1 = mock_service.get_user("1")
983
+ assert result1["name"] == "User 1"
984
+
985
+ with pytest.raises(Exception, match="Service unavailable"):
986
+ mock_service.get_user("2")
987
+
988
+ result3 = mock_service.get_user("3")
989
+ assert result3["name"] == "User 3"
990
+
991
+ def test_mock_chain_calls(self):
992
+ """Test mocking chained method calls."""
993
+ # Arrange
994
+ mock_database = Mock()
995
+ mock_database.users.filter.return_value.order_by.return_value.limit.return_value = [
996
+ {"id": "1", "name": "User 1"}
997
+ ]
998
+
999
+ # Act
1000
+ result = mock_database.users.filter(active=True).order_by('name').limit(10)
1001
+
1002
+ # Assert
1003
+ assert len(result) == 1
1004
+ assert result[0]["name"] == "User 1"
1005
+
1006
+ @patch('src.services.notification_service.EmailService')
1007
+ @patch('src.services.notification_service.SMSService')
1008
+ def test_multiple_patches(self, mock_sms_service, mock_email_service):
1009
+ """Test using multiple patches."""
1010
+ # Arrange
1011
+ notification_service = NotificationService()
1012
+ mock_email_service.return_value.send.return_value = True
1013
+ mock_sms_service.return_value.send.return_value = True
1014
+
1015
+ # Act
1016
+ result = notification_service.send_notification(
1017
+ "user@example.com",
1018
+ "Test message",
1019
+ channels=["email", "sms"]
1020
+ )
1021
+
1022
+ # Assert
1023
+ assert result is True
1024
+ mock_email_service.return_value.send.assert_called_once()
1025
+ mock_sms_service.return_value.send.assert_called_once()
1026
+ ```
1027
+
1028
+ ## Performance and Load Testing
1029
+
1030
+ ### Performance Testing with pytest-benchmark
1031
+
1032
+ ```python
1033
+ # tests/performance/test_user_performance.py
1034
+ import pytest
1035
+ from src.services.user_service import UserService
1036
+ from src.utils.data_processor import DataProcessor
1037
+
1038
+ class TestUserServicePerformance:
1039
+ """Performance tests for user service operations."""
1040
+
1041
+ @pytest.fixture
1042
+ def large_user_dataset(self):
1043
+ """Generate large dataset for performance testing."""
1044
+ return [
1045
+ {"name": f"User {i}", "email": f"user{i}@example.com", "age": 20 + (i % 50)}
1046
+ for i in range(1000)
1047
+ ]
1048
+
1049
+ def test_user_creation_performance(self, benchmark, user_service, large_user_dataset):
1050
+ """Benchmark user creation performance."""
1051
+ # Act & Assert
1052
+ result = benchmark(user_service.create_bulk_users, large_user_dataset)
1053
+ assert len(result) == 1000
1054
+
1055
+ def test_user_search_performance(self, benchmark, user_service):
1056
+ """Benchmark user search performance."""
1057
+ # Arrange
1058
+ # Assume users are already created in database
1059
+
1060
+ # Act & Assert
1061
+ result = benchmark(user_service.search_users, query="User", limit=100)
1062
+ assert len(result) <= 100
1063
+
1064
+ @pytest.mark.parametrize("batch_size", [100, 500, 1000])
1065
+ def test_batch_processing_performance(self, benchmark, batch_size):
1066
+ """Test performance with different batch sizes."""
1067
+ # Arrange
1068
+ processor = DataProcessor(batch_size=batch_size)
1069
+ data = list(range(10000))
1070
+
1071
+ # Act & Assert
1072
+ result = benchmark(processor.process_batch, data)
1073
+ assert len(result) == 10000
1074
+ ```