codeforge-dev 1.4.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 (131) hide show
  1. package/.devcontainer/.env +22 -0
  2. package/.devcontainer/CHANGELOG.md +197 -0
  3. package/.devcontainer/CLAUDE.md +117 -0
  4. package/.devcontainer/README.md +222 -0
  5. package/.devcontainer/config/main-system-prompt.md +502 -0
  6. package/.devcontainer/config/settings.json +47 -0
  7. package/.devcontainer/devcontainer.json +94 -0
  8. package/.devcontainer/features/README.md +113 -0
  9. package/.devcontainer/features/agent-browser/README.md +65 -0
  10. package/.devcontainer/features/agent-browser/devcontainer-feature.json +23 -0
  11. package/.devcontainer/features/agent-browser/install.sh +79 -0
  12. package/.devcontainer/features/ast-grep/README.md +24 -0
  13. package/.devcontainer/features/ast-grep/devcontainer-feature.json +24 -0
  14. package/.devcontainer/features/ast-grep/install.sh +51 -0
  15. package/.devcontainer/features/ccstatusline/README.md +296 -0
  16. package/.devcontainer/features/ccstatusline/devcontainer-feature.json +19 -0
  17. package/.devcontainer/features/ccstatusline/install.sh +290 -0
  18. package/.devcontainer/features/ccusage/README.md +205 -0
  19. package/.devcontainer/features/ccusage/devcontainer-feature.json +38 -0
  20. package/.devcontainer/features/ccusage/install.sh +132 -0
  21. package/.devcontainer/features/claude-code/README.md +498 -0
  22. package/.devcontainer/features/claude-code/config/settings.json +36 -0
  23. package/.devcontainer/features/claude-code/config/system-prompt.md +118 -0
  24. package/.devcontainer/features/claude-code/config/world-building-sp.md +1432 -0
  25. package/.devcontainer/features/claude-code/devcontainer-feature.json +42 -0
  26. package/.devcontainer/features/claude-code/install.sh +466 -0
  27. package/.devcontainer/features/claude-monitor/README.md +74 -0
  28. package/.devcontainer/features/claude-monitor/devcontainer-feature.json +38 -0
  29. package/.devcontainer/features/claude-monitor/install.sh +99 -0
  30. package/.devcontainer/features/lsp-servers/README.md +85 -0
  31. package/.devcontainer/features/lsp-servers/devcontainer-feature.json +40 -0
  32. package/.devcontainer/features/lsp-servers/install.sh +116 -0
  33. package/.devcontainer/features/mcp-qdrant/CHANGES.md +399 -0
  34. package/.devcontainer/features/mcp-qdrant/README.md +474 -0
  35. package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +57 -0
  36. package/.devcontainer/features/mcp-qdrant/install.sh +295 -0
  37. package/.devcontainer/features/mcp-qdrant/poststart-hook.sh +129 -0
  38. package/.devcontainer/features/mcp-reasoner/README.md +177 -0
  39. package/.devcontainer/features/mcp-reasoner/devcontainer-feature.json +20 -0
  40. package/.devcontainer/features/mcp-reasoner/install.sh +177 -0
  41. package/.devcontainer/features/mcp-reasoner/poststart-hook.sh +67 -0
  42. package/.devcontainer/features/notify-hook/README.md +86 -0
  43. package/.devcontainer/features/notify-hook/devcontainer-feature.json +23 -0
  44. package/.devcontainer/features/notify-hook/install.sh +38 -0
  45. package/.devcontainer/features/splitrail/README.md +140 -0
  46. package/.devcontainer/features/splitrail/devcontainer-feature.json +34 -0
  47. package/.devcontainer/features/splitrail/install.sh +129 -0
  48. package/.devcontainer/features/tree-sitter/README.md +138 -0
  49. package/.devcontainer/features/tree-sitter/devcontainer-feature.json +52 -0
  50. package/.devcontainer/features/tree-sitter/install.sh +173 -0
  51. package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +106 -0
  52. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/.claude-plugin/plugin.json +7 -0
  53. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/hooks/hooks.json +17 -0
  54. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/format-file.py +101 -0
  55. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/.claude-plugin/plugin.json +7 -0
  56. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/hooks/hooks.json +17 -0
  57. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/lint-file.py +137 -0
  58. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/.claude-plugin/plugin.json +8 -0
  59. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/SKILL.md +387 -0
  60. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/cli-flags-and-output.md +312 -0
  61. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/sdk-and-mcp.md +569 -0
  62. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/SKILL.md +309 -0
  63. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/compose-services.md +438 -0
  64. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/dockerfile-patterns.md +340 -0
  65. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/SKILL.md +412 -0
  66. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/container-lifecycle.md +388 -0
  67. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/resources-and-security.md +444 -0
  68. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/SKILL.md +344 -0
  69. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/middleware-and-lifespan.md +254 -0
  70. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/pydantic-models.md +245 -0
  71. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/routing-and-dependencies.md +255 -0
  72. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/sse-and-streaming.md +318 -0
  73. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/SKILL.md +345 -0
  74. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/agents-and-tools.md +271 -0
  75. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/models-and-streaming.md +422 -0
  76. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/SKILL.md +220 -0
  77. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/cross-vendor-principles.md +139 -0
  78. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/patterns-and-antipatterns.md +376 -0
  79. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/skill-authoring-patterns.md +356 -0
  80. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/SKILL.md +329 -0
  81. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/advanced-queries.md +314 -0
  82. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/javascript-patterns.md +323 -0
  83. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/python-patterns.md +354 -0
  84. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/schema-and-pragmas.md +326 -0
  85. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/SKILL.md +356 -0
  86. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/ai-sdk-svelte.md +128 -0
  87. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/component-patterns.md +332 -0
  88. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/layercake.md +203 -0
  89. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/migration-guide.md +350 -0
  90. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/runes-and-reactivity.md +328 -0
  91. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/spa-and-routing.md +262 -0
  92. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/svelte-dnd-action.md +181 -0
  93. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/SKILL.md +414 -0
  94. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/fastapi-testing.md +411 -0
  95. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/svelte-testing.md +538 -0
  96. package/.devcontainer/plugins/devs-marketplace/plugins/codeforge-lsp/.claude-plugin/plugin.json +7 -0
  97. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/.claude-plugin/plugin.json +7 -0
  98. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/hooks/hooks.json +17 -0
  99. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/scripts/block-dangerous.py +110 -0
  100. package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/.claude-plugin/plugin.json +7 -0
  101. package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/hooks/hooks.json +17 -0
  102. package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/.claude-plugin/plugin.json +7 -0
  103. package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/hooks/hooks.json +17 -0
  104. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/.claude-plugin/plugin.json +7 -0
  105. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/hooks/hooks.json +17 -0
  106. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +108 -0
  107. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272create-pr.md +337 -0
  108. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272new.md +166 -0
  109. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272review-commit.md +290 -0
  110. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272work.md +257 -0
  111. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/plugin.json +8 -0
  112. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/system-prompt.md +184 -0
  113. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/.claude-plugin/plugin.json +6 -0
  114. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/config/planning-instructions.md +14 -0
  115. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/functional-conjuring-map.md +989 -0
  116. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/hooks/hooks.json +33 -0
  117. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/__pycache__/post-enhance-task.cpython-314.pyc +0 -0
  118. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhance-planning.py +71 -0
  119. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-plan.sh +68 -0
  120. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-task.sh +120 -0
  121. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-plan.py +133 -0
  122. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-task.py +253 -0
  123. package/.devcontainer/scripts/setup-aliases.sh +80 -0
  124. package/.devcontainer/scripts/setup-config.sh +28 -0
  125. package/.devcontainer/scripts/setup-irie-claude.sh +32 -0
  126. package/.devcontainer/scripts/setup-plugins.sh +80 -0
  127. package/.devcontainer/scripts/setup.sh +58 -0
  128. package/LICENSE.txt +674 -0
  129. package/README.md +267 -0
  130. package/package.json +44 -0
  131. package/setup.js +83 -0
@@ -0,0 +1,411 @@
1
+ # FastAPI Testing -- Deep Dive
2
+
3
+ ## 1. AsyncClient Setup
4
+
5
+ ### Basic Fixture
6
+
7
+ ```python
8
+ import pytest
9
+ from httpx import ASGITransport, AsyncClient
10
+ from app.main import app
11
+
12
+ @pytest.fixture
13
+ async def client():
14
+ transport = ASGITransport(app=app)
15
+ async with AsyncClient(transport=transport, base_url="http://test") as ac:
16
+ yield ac
17
+ ```
18
+
19
+ ### With Lifespan Events
20
+
21
+ `AsyncClient` does not trigger ASGI lifespan events. Install `asgi-lifespan` to handle startup/shutdown:
22
+
23
+ ```python
24
+ from asgi_lifespan import LifespanManager
25
+
26
+ @pytest.fixture
27
+ async def client():
28
+ async with LifespanManager(app) as manager:
29
+ transport = ASGITransport(app=manager.app)
30
+ async with AsyncClient(transport=transport, base_url="http://test") as ac:
31
+ yield ac
32
+ ```
33
+
34
+ ### pytest Configuration
35
+
36
+ ```toml
37
+ # pyproject.toml
38
+ [tool.pytest.ini_options]
39
+ asyncio_mode = "auto"
40
+ testpaths = ["tests"]
41
+ ```
42
+
43
+ With pytest-anyio (alternative to pytest-asyncio):
44
+
45
+ ```toml
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
48
+ ```
49
+
50
+ Mark async tests explicitly when not using `asyncio_mode = "auto"`:
51
+
52
+ ```python
53
+ @pytest.mark.anyio
54
+ async def test_endpoint(client: AsyncClient):
55
+ response = await client.get("/")
56
+ assert response.status_code == 200
57
+ ```
58
+
59
+ ---
60
+
61
+ ## 2. Dependency Override Patterns
62
+
63
+ ### Basic Override
64
+
65
+ ```python
66
+ from app.dependencies import get_db
67
+
68
+ async def mock_db():
69
+ yield FakeDatabase()
70
+
71
+ @pytest.fixture
72
+ async def client():
73
+ app.dependency_overrides[get_db] = mock_db
74
+ transport = ASGITransport(app=app)
75
+ async with AsyncClient(transport=transport, base_url="http://test") as ac:
76
+ yield ac
77
+ app.dependency_overrides.clear()
78
+ ```
79
+
80
+ ### Authentication Bypass
81
+
82
+ ```python
83
+ from app.dependencies import get_current_user
84
+ from app.models import User
85
+
86
+ async def mock_admin():
87
+ return User(id=1, email="admin@test.com", role="admin")
88
+
89
+ async def mock_regular_user():
90
+ return User(id=2, email="user@test.com", role="user")
91
+
92
+ @pytest.fixture
93
+ async def admin_client():
94
+ app.dependency_overrides[get_current_user] = mock_admin
95
+ async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
96
+ yield ac
97
+ app.dependency_overrides.clear()
98
+
99
+ @pytest.fixture
100
+ async def user_client():
101
+ app.dependency_overrides[get_current_user] = mock_regular_user
102
+ async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
103
+ yield ac
104
+ app.dependency_overrides.clear()
105
+ ```
106
+
107
+ ### Parameterized Override Fixture
108
+
109
+ ```python
110
+ @pytest.fixture
111
+ def override_user():
112
+ """Factory fixture for custom user overrides."""
113
+ def _override(user: User):
114
+ app.dependency_overrides[get_current_user] = lambda: user
115
+ return _override
116
+
117
+ @pytest.mark.anyio
118
+ async def test_owner_access(client, override_user):
119
+ override_user(User(id=5, email="owner@test.com", role="owner"))
120
+ response = await client.get("/admin/settings")
121
+ assert response.status_code == 200
122
+ ```
123
+
124
+ ---
125
+
126
+ ## 3. SSE Stream Testing
127
+
128
+ ### With httpx-sse
129
+
130
+ ```bash
131
+ pip install httpx-sse
132
+ ```
133
+
134
+ ```python
135
+ import pytest
136
+ from httpx import ASGITransport, AsyncClient
137
+ from httpx_sse import aconnect_sse
138
+ from app.main import app
139
+
140
+ @pytest.mark.anyio
141
+ async def test_sse_stream():
142
+ async with AsyncClient(
143
+ transport=ASGITransport(app=app),
144
+ base_url="http://test"
145
+ ) as ac:
146
+ async with aconnect_sse(ac, "GET", "/events") as event_source:
147
+ events = []
148
+ async for sse in event_source.aiter_sse():
149
+ events.append(sse)
150
+ if len(events) >= 3:
151
+ break
152
+
153
+ assert len(events) == 3
154
+ assert events[0].event == "update"
155
+ assert events[0].data # non-empty data
156
+ ```
157
+
158
+ ### Asserting Event Structure
159
+
160
+ ```python
161
+ @pytest.mark.anyio
162
+ async def test_sse_event_types():
163
+ async with AsyncClient(
164
+ transport=ASGITransport(app=app),
165
+ base_url="http://test"
166
+ ) as ac:
167
+ async with aconnect_sse(ac, "POST", "/chat", json={"prompt": "Hello"}) as event_source:
168
+ events = []
169
+ async for sse in event_source.aiter_sse():
170
+ events.append({"event": sse.event, "data": sse.data})
171
+
172
+ # Verify event sequence
173
+ event_types = [e["event"] for e in events]
174
+ assert "token" in event_types
175
+ assert event_types[-1] == "done"
176
+ ```
177
+
178
+ ### Raw Stream Testing (Without httpx-sse)
179
+
180
+ ```python
181
+ @pytest.mark.anyio
182
+ async def test_sse_raw_stream():
183
+ async with AsyncClient(
184
+ transport=ASGITransport(app=app),
185
+ base_url="http://test"
186
+ ) as ac:
187
+ async with ac.stream("GET", "/events") as response:
188
+ assert response.status_code == 200
189
+ assert "text/event-stream" in response.headers["content-type"]
190
+
191
+ lines = []
192
+ async for line in response.aiter_lines():
193
+ if line.startswith("data:"):
194
+ lines.append(line.removeprefix("data: "))
195
+ assert len(lines) >= 1
196
+ ```
197
+
198
+ ---
199
+
200
+ ## 4. WebSocket Testing
201
+
202
+ WebSocket testing uses the synchronous `TestClient`:
203
+
204
+ ```python
205
+ from fastapi.testclient import TestClient
206
+ from app.main import app
207
+
208
+ def test_websocket_echo():
209
+ client = TestClient(app)
210
+ with client.websocket_connect("/ws") as ws:
211
+ ws.send_text("hello")
212
+ data = ws.receive_text()
213
+ assert data == "Echo: hello"
214
+
215
+ def test_websocket_json():
216
+ client = TestClient(app)
217
+ with client.websocket_connect("/ws/json") as ws:
218
+ ws.send_json({"action": "ping"})
219
+ response = ws.receive_json()
220
+ assert response["action"] == "pong"
221
+
222
+ def test_websocket_disconnect():
223
+ client = TestClient(app)
224
+ with client.websocket_connect("/ws") as ws:
225
+ ws.send_text("hello")
226
+ ws.receive_text()
227
+ # Connection is closed after the context manager exits
228
+ ```
229
+
230
+ Available WebSocket test methods:
231
+
232
+ | Method | Purpose |
233
+ |--------|---------|
234
+ | `ws.send_text(data)` | Send text frame |
235
+ | `ws.send_bytes(data)` | Send binary frame |
236
+ | `ws.send_json(data)` | Send JSON |
237
+ | `ws.receive_text()` | Receive text frame |
238
+ | `ws.receive_bytes()` | Receive binary frame |
239
+ | `ws.receive_json()` | Receive JSON |
240
+ | `ws.close(code=1000)` | Close connection |
241
+
242
+ ---
243
+
244
+ ## 5. Background Task Testing
245
+
246
+ ### Assert Task Was Scheduled
247
+
248
+ ```python
249
+ from unittest.mock import AsyncMock, patch
250
+
251
+ @pytest.mark.anyio
252
+ async def test_order_sends_notification(client: AsyncClient):
253
+ with patch("app.tasks.send_notification", new_callable=AsyncMock) as mock_notify:
254
+ response = await client.post("/orders", json={"item": "Widget", "email": "a@b.com"})
255
+ assert response.status_code == 201
256
+
257
+ # Background task executes before TestClient returns
258
+ mock_notify.assert_called_once_with("a@b.com", "Order confirmed")
259
+ ```
260
+
261
+ ### Prevent Side Effects
262
+
263
+ ```python
264
+ @pytest.mark.anyio
265
+ async def test_order_no_email(client: AsyncClient, monkeypatch):
266
+ monkeypatch.setattr("app.tasks.send_notification", lambda *a, **kw: None)
267
+ response = await client.post("/orders", json={"item": "Widget", "email": "a@b.com"})
268
+ assert response.status_code == 201
269
+ ```
270
+
271
+ Note: With `TestClient` (synchronous), background tasks typically execute synchronously before the response returns. With `AsyncClient`, background tasks may or may not complete before the test assertion -- use mocking to control behavior deterministically.
272
+
273
+ ---
274
+
275
+ ## 6. Database Fixtures with aiosqlite
276
+
277
+ ### Session-Scoped Engine + Function-Scoped Transactions
278
+
279
+ ```python
280
+ import pytest
281
+ from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
282
+ from httpx import ASGITransport, AsyncClient
283
+ from app.main import app
284
+ from app.database import get_db, Base
285
+
286
+ @pytest.fixture(scope="session")
287
+ def anyio_backend():
288
+ return "asyncio"
289
+
290
+ @pytest.fixture(scope="session")
291
+ async def engine():
292
+ engine = create_async_engine("sqlite+aiosqlite:///:memory:")
293
+ async with engine.begin() as conn:
294
+ await conn.run_sync(Base.metadata.create_all)
295
+ yield engine
296
+ await engine.dispose()
297
+
298
+ @pytest.fixture
299
+ async def db_session(engine):
300
+ session_factory = async_sessionmaker(engine, class_=AsyncSession)
301
+ async with session_factory() as session:
302
+ async with session.begin():
303
+ yield session
304
+ await session.rollback()
305
+
306
+ @pytest.fixture
307
+ async def client(db_session):
308
+ async def override_get_db():
309
+ yield db_session
310
+
311
+ app.dependency_overrides[get_db] = override_get_db
312
+ async with AsyncClient(
313
+ transport=ASGITransport(app=app),
314
+ base_url="http://test"
315
+ ) as ac:
316
+ yield ac
317
+ app.dependency_overrides.clear()
318
+ ```
319
+
320
+ ### Seeded Data Fixture
321
+
322
+ ```python
323
+ @pytest.fixture
324
+ async def seeded_db(db_session):
325
+ """Pre-populate the database with test data."""
326
+ db_session.add_all([
327
+ Item(name="Widget", price=9.99),
328
+ Item(name="Gadget", price=19.99),
329
+ ])
330
+ await db_session.flush()
331
+ return db_session
332
+
333
+ @pytest.mark.anyio
334
+ async def test_list_items(client: AsyncClient, seeded_db):
335
+ response = await client.get("/items/")
336
+ assert response.status_code == 200
337
+ items = response.json()
338
+ assert len(items) == 2
339
+ assert items[0]["name"] == "Widget"
340
+ ```
341
+
342
+ ### Raw aiosqlite (Without SQLAlchemy)
343
+
344
+ ```python
345
+ import aiosqlite
346
+
347
+ @pytest.fixture
348
+ async def db():
349
+ async with aiosqlite.connect(":memory:") as db:
350
+ await db.execute("CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT, price REAL)")
351
+ await db.commit()
352
+ yield db
353
+
354
+ @pytest.fixture
355
+ async def client(db):
356
+ async def override_get_db():
357
+ yield db
358
+ app.dependency_overrides[get_db] = override_get_db
359
+ async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
360
+ yield ac
361
+ app.dependency_overrides.clear()
362
+ ```
363
+
364
+ ---
365
+
366
+ ## 7. Testing Patterns
367
+
368
+ ### Error Response Testing
369
+
370
+ ```python
371
+ @pytest.mark.anyio
372
+ async def test_item_not_found(client: AsyncClient):
373
+ response = await client.get("/items/99999")
374
+ assert response.status_code == 404
375
+ assert response.json()["detail"] == "Item not found"
376
+
377
+ @pytest.mark.anyio
378
+ async def test_validation_error(client: AsyncClient):
379
+ response = await client.post("/items/", json={"name": ""})
380
+ assert response.status_code == 422
381
+ ```
382
+
383
+ ### Header and Cookie Testing
384
+
385
+ ```python
386
+ @pytest.mark.anyio
387
+ async def test_auth_header_required(client: AsyncClient):
388
+ response = await client.get("/protected")
389
+ assert response.status_code == 401
390
+
391
+ @pytest.mark.anyio
392
+ async def test_with_auth(client: AsyncClient):
393
+ response = await client.get(
394
+ "/protected",
395
+ headers={"Authorization": "Bearer test-token"},
396
+ )
397
+ assert response.status_code == 200
398
+ ```
399
+
400
+ ### File Upload Testing
401
+
402
+ ```python
403
+ @pytest.mark.anyio
404
+ async def test_file_upload(client: AsyncClient):
405
+ response = await client.post(
406
+ "/upload",
407
+ files={"file": ("test.txt", b"file content", "text/plain")},
408
+ )
409
+ assert response.status_code == 200
410
+ assert response.json()["filename"] == "test.txt"
411
+ ```