red64-cli 0.1.0 → 0.2.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 (103) hide show
  1. package/dist/cli/parseArgs.d.ts.map +1 -1
  2. package/dist/cli/parseArgs.js +5 -0
  3. package/dist/cli/parseArgs.js.map +1 -1
  4. package/dist/components/init/CompleteStep.d.ts.map +1 -1
  5. package/dist/components/init/CompleteStep.js +2 -2
  6. package/dist/components/init/CompleteStep.js.map +1 -1
  7. package/dist/components/init/TestCheckStep.d.ts +16 -0
  8. package/dist/components/init/TestCheckStep.d.ts.map +1 -0
  9. package/dist/components/init/TestCheckStep.js +120 -0
  10. package/dist/components/init/TestCheckStep.js.map +1 -0
  11. package/dist/components/init/index.d.ts +1 -0
  12. package/dist/components/init/index.d.ts.map +1 -1
  13. package/dist/components/init/index.js +1 -0
  14. package/dist/components/init/index.js.map +1 -1
  15. package/dist/components/init/types.d.ts +9 -0
  16. package/dist/components/init/types.d.ts.map +1 -1
  17. package/dist/components/screens/InitScreen.d.ts.map +1 -1
  18. package/dist/components/screens/InitScreen.js +69 -6
  19. package/dist/components/screens/InitScreen.js.map +1 -1
  20. package/dist/components/screens/StartScreen.d.ts.map +1 -1
  21. package/dist/components/screens/StartScreen.js +89 -3
  22. package/dist/components/screens/StartScreen.js.map +1 -1
  23. package/dist/services/ConfigService.d.ts +1 -0
  24. package/dist/services/ConfigService.d.ts.map +1 -1
  25. package/dist/services/ConfigService.js.map +1 -1
  26. package/dist/services/ProjectDetector.d.ts +28 -0
  27. package/dist/services/ProjectDetector.d.ts.map +1 -0
  28. package/dist/services/ProjectDetector.js +236 -0
  29. package/dist/services/ProjectDetector.js.map +1 -0
  30. package/dist/services/TestRunner.d.ts +46 -0
  31. package/dist/services/TestRunner.d.ts.map +1 -0
  32. package/dist/services/TestRunner.js +85 -0
  33. package/dist/services/TestRunner.js.map +1 -0
  34. package/dist/services/index.d.ts +2 -0
  35. package/dist/services/index.d.ts.map +1 -1
  36. package/dist/services/index.js +2 -0
  37. package/dist/services/index.js.map +1 -1
  38. package/dist/types/index.d.ts +1 -0
  39. package/dist/types/index.d.ts.map +1 -1
  40. package/dist/types/index.js.map +1 -1
  41. package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
  42. package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
  43. package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
  44. package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
  45. package/framework/stacks/generic/feedback.md +80 -0
  46. package/framework/stacks/nextjs/accessibility.md +437 -0
  47. package/framework/stacks/nextjs/api.md +431 -0
  48. package/framework/stacks/nextjs/coding-style.md +282 -0
  49. package/framework/stacks/nextjs/commenting.md +226 -0
  50. package/framework/stacks/nextjs/components.md +411 -0
  51. package/framework/stacks/nextjs/conventions.md +333 -0
  52. package/framework/stacks/nextjs/css.md +310 -0
  53. package/framework/stacks/nextjs/error-handling.md +442 -0
  54. package/framework/stacks/nextjs/feedback.md +124 -0
  55. package/framework/stacks/nextjs/migrations.md +332 -0
  56. package/framework/stacks/nextjs/models.md +362 -0
  57. package/framework/stacks/nextjs/queries.md +410 -0
  58. package/framework/stacks/nextjs/responsive.md +338 -0
  59. package/framework/stacks/nextjs/tech-stack.md +177 -0
  60. package/framework/stacks/nextjs/test-writing.md +475 -0
  61. package/framework/stacks/nextjs/validation.md +467 -0
  62. package/framework/stacks/python/api.md +468 -0
  63. package/framework/stacks/python/authentication.md +342 -0
  64. package/framework/stacks/python/code-quality.md +283 -0
  65. package/framework/stacks/python/code-refactoring.md +315 -0
  66. package/framework/stacks/python/coding-style.md +462 -0
  67. package/framework/stacks/python/conventions.md +399 -0
  68. package/framework/stacks/python/error-handling.md +512 -0
  69. package/framework/stacks/python/feedback.md +92 -0
  70. package/framework/stacks/python/implement-ai-llm.md +468 -0
  71. package/framework/stacks/python/migrations.md +388 -0
  72. package/framework/stacks/python/models.md +399 -0
  73. package/framework/stacks/python/python.md +232 -0
  74. package/framework/stacks/python/queries.md +451 -0
  75. package/framework/stacks/python/structure.md +245 -58
  76. package/framework/stacks/python/tech.md +92 -35
  77. package/framework/stacks/python/testing.md +380 -0
  78. package/framework/stacks/python/validation.md +471 -0
  79. package/framework/stacks/rails/authentication.md +176 -0
  80. package/framework/stacks/rails/code-quality.md +287 -0
  81. package/framework/stacks/rails/code-refactoring.md +299 -0
  82. package/framework/stacks/rails/feedback.md +130 -0
  83. package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
  84. package/framework/stacks/rails/rails.md +301 -0
  85. package/framework/stacks/rails/rails8-best-practices.md +498 -0
  86. package/framework/stacks/rails/rails8-css.md +573 -0
  87. package/framework/stacks/rails/structure.md +140 -0
  88. package/framework/stacks/rails/tech.md +108 -0
  89. package/framework/stacks/react/code-quality.md +521 -0
  90. package/framework/stacks/react/components.md +625 -0
  91. package/framework/stacks/react/data-fetching.md +586 -0
  92. package/framework/stacks/react/feedback.md +110 -0
  93. package/framework/stacks/react/forms.md +694 -0
  94. package/framework/stacks/react/performance.md +640 -0
  95. package/framework/stacks/react/product.md +22 -9
  96. package/framework/stacks/react/state-management.md +472 -0
  97. package/framework/stacks/react/structure.md +351 -44
  98. package/framework/stacks/react/tech.md +219 -30
  99. package/framework/stacks/react/testing.md +690 -0
  100. package/package.json +1 -1
  101. package/framework/stacks/node/product.md +0 -27
  102. package/framework/stacks/node/structure.md +0 -82
  103. package/framework/stacks/node/tech.md +0 -63
@@ -0,0 +1,380 @@
1
+ # Testing Patterns
2
+
3
+ Comprehensive pytest patterns for modern Python projects.
4
+
5
+ ---
6
+
7
+ ## Philosophy
8
+
9
+ - **Fast feedback**: Unit tests run in milliseconds, no I/O
10
+ - **Realistic integration**: Test with real database when it matters
11
+ - **Readable tests**: Each test tells a story with arrange-act-assert
12
+ - **Fixtures over setup**: Composable pytest fixtures, not setUp/tearDown
13
+
14
+ ---
15
+
16
+ ## Test Organization
17
+
18
+ ```
19
+ tests/
20
+ conftest.py # Shared fixtures (db, client, factories)
21
+ unit/
22
+ services/
23
+ test_user_service.py
24
+ test_content_service.py
25
+ utils/
26
+ test_hashing.py
27
+ integration/
28
+ api/
29
+ test_users.py
30
+ test_content.py
31
+ repositories/
32
+ test_user_repo.py
33
+ factories/
34
+ __init__.py
35
+ user_factory.py
36
+ content_factory.py
37
+ ```
38
+
39
+ **Pattern**: Mirror `src/app/` structure. Prefix all test files with `test_`.
40
+
41
+ ---
42
+
43
+ ## Fixtures
44
+
45
+ ### Database Session Fixture
46
+
47
+ ```python
48
+ # tests/conftest.py
49
+ import pytest
50
+ from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
51
+
52
+ @pytest.fixture
53
+ async def db_engine():
54
+ engine = create_async_engine("sqlite+aiosqlite:///:memory:")
55
+ async with engine.begin() as conn:
56
+ await conn.run_sync(Base.metadata.create_all)
57
+ yield engine
58
+ await engine.dispose()
59
+
60
+ @pytest.fixture
61
+ async def db(db_engine) -> AsyncIterator[AsyncSession]:
62
+ session_factory = async_sessionmaker(db_engine, expire_on_commit=False)
63
+ async with session_factory() as session:
64
+ yield session
65
+ await session.rollback()
66
+ ```
67
+
68
+ ### Test Client Fixture (FastAPI)
69
+
70
+ ```python
71
+ import httpx
72
+ from app.main import app
73
+ from app.dependencies import get_db
74
+
75
+ @pytest.fixture
76
+ async def client(db: AsyncSession):
77
+ async def override_db():
78
+ yield db
79
+
80
+ app.dependency_overrides[get_db] = override_db
81
+ async with httpx.AsyncClient(app=app, base_url="http://test") as client:
82
+ yield client
83
+ app.dependency_overrides.clear()
84
+ ```
85
+
86
+ ### Factory Fixtures
87
+
88
+ ```python
89
+ # tests/factories/user_factory.py
90
+ import factory
91
+ from faker import Faker
92
+ from app.models.user import User
93
+
94
+ fake = Faker()
95
+
96
+ class UserFactory(factory.Factory):
97
+ class Meta:
98
+ model = User
99
+
100
+ id = factory.Sequence(lambda n: n + 1)
101
+ email = factory.LazyFunction(fake.email)
102
+ name = factory.LazyFunction(fake.name)
103
+ hashed_password = "hashed_test_pw"
104
+ is_active = True
105
+
106
+ # tests/conftest.py
107
+ @pytest.fixture
108
+ def user_factory():
109
+ return UserFactory
110
+
111
+ @pytest.fixture
112
+ def sample_user(user_factory):
113
+ return user_factory()
114
+ ```
115
+
116
+ ---
117
+
118
+ ## Unit Test Patterns
119
+
120
+ ### Service Testing with Mocks
121
+
122
+ ```python
123
+ from unittest.mock import AsyncMock, MagicMock
124
+
125
+ class TestUserService:
126
+ async def test_create_user_success(self):
127
+ # Arrange
128
+ repo = AsyncMock()
129
+ repo.get_by_email.return_value = None
130
+ repo.save.return_value = User(id=1, email="test@example.com", name="Test")
131
+
132
+ service = UserService(repo=repo)
133
+ data = CreateUserRequest(email="test@example.com", name="Test", password="secret123")
134
+
135
+ # Act
136
+ result = await service.create_user(data)
137
+
138
+ # Assert
139
+ assert result.is_ok
140
+ assert result.value.email == "test@example.com"
141
+ repo.save.assert_called_once()
142
+
143
+ async def test_create_user_duplicate_email(self):
144
+ repo = AsyncMock()
145
+ repo.get_by_email.return_value = User(id=1, email="taken@example.com")
146
+
147
+ service = UserService(repo=repo)
148
+ data = CreateUserRequest(email="taken@example.com", name="Test", password="secret123")
149
+
150
+ result = await service.create_user(data)
151
+
152
+ assert not result.is_ok
153
+ assert "already exists" in result.error
154
+ ```
155
+
156
+ ### Testing with `pytest.raises`
157
+
158
+ ```python
159
+ async def test_get_user_not_found_raises():
160
+ repo = AsyncMock()
161
+ repo.get.return_value = None
162
+ service = UserService(repo=repo)
163
+
164
+ with pytest.raises(NotFoundError, match="User 999 not found"):
165
+ await service.get_user(999)
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Parametrize
171
+
172
+ ### Basic Parametrize
173
+
174
+ ```python
175
+ @pytest.mark.parametrize("email,expected_valid", [
176
+ ("user@example.com", True),
177
+ ("user@sub.example.com", True),
178
+ ("invalid", False),
179
+ ("", False),
180
+ ("@example.com", False),
181
+ ])
182
+ def test_email_validation(email: str, expected_valid: bool):
183
+ if expected_valid:
184
+ user = CreateUserRequest(email=email, name="Test", password="pw123456")
185
+ assert user.email == email
186
+ else:
187
+ with pytest.raises(ValueError):
188
+ CreateUserRequest(email=email, name="Test", password="pw123456")
189
+ ```
190
+
191
+ ### Parametrize with IDs
192
+
193
+ ```python
194
+ @pytest.mark.parametrize("status,can_publish", [
195
+ pytest.param("draft", True, id="draft-can-publish"),
196
+ pytest.param("published", False, id="already-published"),
197
+ pytest.param("archived", False, id="archived-cannot-publish"),
198
+ ])
199
+ async def test_publish_eligibility(status: str, can_publish: bool):
200
+ content = ContentFactory(status=status)
201
+ assert content.can_publish() == can_publish
202
+ ```
203
+
204
+ ---
205
+
206
+ ## Mocking Patterns
207
+
208
+ ### `unittest.mock` Essentials
209
+
210
+ ```python
211
+ from unittest.mock import AsyncMock, MagicMock, patch
212
+
213
+ # AsyncMock for async functions
214
+ mock_repo = AsyncMock()
215
+ mock_repo.get.return_value = User(id=1, name="Test")
216
+
217
+ # MagicMock for sync objects
218
+ mock_cache = MagicMock()
219
+ mock_cache.get.return_value = None
220
+
221
+ # patch for module-level functions
222
+ @patch("app.services.content_service.send_notification")
223
+ async def test_create_sends_notification(mock_notify: AsyncMock):
224
+ mock_notify.return_value = None
225
+ await service.create(data)
226
+ mock_notify.assert_called_once_with(user_id=1, content_id=42)
227
+ ```
228
+
229
+ ### Mock Side Effects
230
+
231
+ ```python
232
+ # Simulate sequential return values
233
+ mock_repo.get.side_effect = [None, User(id=1, name="Created")]
234
+
235
+ # Simulate exception
236
+ mock_client.fetch.side_effect = ExternalServiceError("timeout")
237
+
238
+ # Custom side effect function
239
+ async def fake_save(entity):
240
+ entity.id = 42
241
+ return entity
242
+
243
+ mock_repo.save.side_effect = fake_save
244
+ ```
245
+
246
+ ---
247
+
248
+ ## Async Testing
249
+
250
+ ### pytest-asyncio (auto mode)
251
+
252
+ With `asyncio_mode = "auto"` in `pyproject.toml`, all `async def test_*` functions run automatically:
253
+
254
+ ```python
255
+ # No decorator needed with auto mode
256
+ async def test_async_service_call():
257
+ service = ContentService(repo=AsyncMock())
258
+ result = await service.get_all()
259
+ assert isinstance(result, list)
260
+ ```
261
+
262
+ ### Testing Async Generators
263
+
264
+ ```python
265
+ async def test_stream_content():
266
+ chunks = []
267
+ async for chunk in service.stream_content("prompt"):
268
+ chunks.append(chunk)
269
+ assert len(chunks) > 0
270
+ assert all(isinstance(c, str) for c in chunks)
271
+ ```
272
+
273
+ ---
274
+
275
+ ## Integration Test Patterns
276
+
277
+ ### API Endpoint Testing
278
+
279
+ ```python
280
+ class TestUsersAPI:
281
+ async def test_create_user(self, client: httpx.AsyncClient, db: AsyncSession):
282
+ response = await client.post("/api/v1/users", json={
283
+ "email": "new@example.com",
284
+ "name": "New User",
285
+ "password": "secure123",
286
+ })
287
+
288
+ assert response.status_code == 201
289
+ data = response.json()
290
+ assert data["email"] == "new@example.com"
291
+ assert "password" not in data
292
+
293
+ async def test_create_user_duplicate_email(
294
+ self, client: httpx.AsyncClient, sample_user: User, db: AsyncSession,
295
+ ):
296
+ db.add(sample_user)
297
+ await db.commit()
298
+
299
+ response = await client.post("/api/v1/users", json={
300
+ "email": sample_user.email,
301
+ "name": "Duplicate",
302
+ "password": "secure123",
303
+ })
304
+
305
+ assert response.status_code == 409
306
+
307
+ async def test_list_users_requires_auth(self, client: httpx.AsyncClient):
308
+ response = await client.get("/api/v1/users")
309
+ assert response.status_code == 401
310
+ ```
311
+
312
+ ### Repository Testing (Real DB)
313
+
314
+ ```python
315
+ class TestUserRepo:
316
+ async def test_save_and_retrieve(self, db: AsyncSession):
317
+ repo = UserRepo(db)
318
+ user = User(email="test@example.com", name="Test", hashed_password="hash")
319
+
320
+ saved = await repo.save(user)
321
+ assert saved.id is not None
322
+
323
+ found = await repo.get(saved.id)
324
+ assert found is not None
325
+ assert found.email == "test@example.com"
326
+
327
+ async def test_get_by_email_not_found(self, db: AsyncSession):
328
+ repo = UserRepo(db)
329
+ result = await repo.get_by_email("nonexistent@example.com")
330
+ assert result is None
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Test Markers
336
+
337
+ ```python
338
+ # Mark slow tests
339
+ @pytest.mark.slow
340
+ async def test_full_pipeline():
341
+ ...
342
+
343
+ # Mark integration tests
344
+ @pytest.mark.integration
345
+ async def test_database_migration():
346
+ ...
347
+
348
+ # Skip conditionally
349
+ @pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="No API key")
350
+ async def test_openai_integration():
351
+ ...
352
+ ```
353
+
354
+ ```bash
355
+ # Run only unit tests (exclude slow/integration)
356
+ uv run pytest tests/unit/ -m "not slow"
357
+
358
+ # Run integration tests
359
+ uv run pytest -m integration
360
+ ```
361
+
362
+ ---
363
+
364
+ ## Test Commands
365
+
366
+ ```bash
367
+ # Fast feedback
368
+ uv run pytest tests/unit/ -x # Stop on first failure
369
+ uv run pytest tests/unit/services/test_user.py -v # Single file, verbose
370
+
371
+ # Full suite
372
+ uv run pytest --cov=src/app --cov-report=term-missing
373
+
374
+ # Parallel execution
375
+ uv run pytest -n auto # Requires pytest-xdist
376
+ ```
377
+
378
+ ---
379
+
380
+ _Tests document behavior. Each test should read as a specification of what the code does._