ndomo 0.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 (247) hide show
  1. package/.bun-version +1 -0
  2. package/.dockerignore +79 -0
  3. package/.editorconfig +18 -0
  4. package/.env.example +19 -0
  5. package/.github/CODEOWNERS +8 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.yml +62 -0
  7. package/.github/ISSUE_TEMPLATE/config.yml +2 -0
  8. package/.github/ISSUE_TEMPLATE/feature_request.yml +34 -0
  9. package/.github/dependabot.yml +36 -0
  10. package/.github/pull_request_template.md +24 -0
  11. package/.github/release.yml +30 -0
  12. package/.github/workflows/gitleaks.yml +28 -0
  13. package/.github/workflows/release-please.yml +27 -0
  14. package/.github/workflows/smoke.yml +29 -0
  15. package/.husky/commit-msg +1 -0
  16. package/CHANGELOG.md +114 -0
  17. package/Dockerfile +32 -0
  18. package/README.es.md +174 -0
  19. package/README.md +187 -0
  20. package/agents/chronicler.md +98 -0
  21. package/agents/ci-smith.md +136 -0
  22. package/agents/craftsman.md +341 -0
  23. package/agents/deploy-smith.md +138 -0
  24. package/agents/foreman.md +377 -0
  25. package/agents/go-smith.md +164 -0
  26. package/agents/guild.md +188 -0
  27. package/agents/inspector.md +83 -0
  28. package/agents/js-smith.md +127 -0
  29. package/agents/ops-scout.md +173 -0
  30. package/agents/painter.md +200 -0
  31. package/agents/python-smith.md +120 -0
  32. package/agents/ranger.md +307 -0
  33. package/agents/release-smith.md +165 -0
  34. package/agents/rust-smith.md +159 -0
  35. package/agents/sage.md +178 -0
  36. package/agents/scout.md +144 -0
  37. package/agents/scribe.md +156 -0
  38. package/agents/smith.md +201 -0
  39. package/agents/vue-smith.md +155 -0
  40. package/agents/warden.md +216 -0
  41. package/agents/zig-smith.md +156 -0
  42. package/bin/ndomo-analyses.ts +4 -0
  43. package/bin/ndomo-status.ts +4 -0
  44. package/biome.json +57 -0
  45. package/bun.lock +514 -0
  46. package/commitlint.config.js +3 -0
  47. package/config/ndomo.config.json +258 -0
  48. package/config/ndomo.schema.json +166 -0
  49. package/docs/agents.md +375 -0
  50. package/docs/bugs/plan-create-orphan-fk.md +131 -0
  51. package/docs/bugs/task_create_batch-order-index-collision.md +158 -0
  52. package/docs/configuration.md +276 -0
  53. package/docs/database.md +364 -0
  54. package/docs/features/feature-flexible-builder-v1.md +724 -0
  55. package/docs/features/feature-flexible-builder-v2.md +882 -0
  56. package/docs/features/feature-flexible-builder.md +974 -0
  57. package/docs/http-server.md +244 -0
  58. package/docs/installation.md +259 -0
  59. package/docs/integrations.md +129 -0
  60. package/docs/operations/anti-pattern-sub-agent-verify-2026-06-21.md +32 -0
  61. package/docs/operations/audit-v1.md +417 -0
  62. package/docs/operations/audit-v2.md +197 -0
  63. package/docs/operations/audit-v3.md +306 -0
  64. package/docs/operations/db-optimize-foundations.md +123 -0
  65. package/docs/operations/verify-gate-architecture.md +82 -0
  66. package/docs/workflows.md +448 -0
  67. package/opencode.json +5 -0
  68. package/package.json +65 -0
  69. package/release-please-config.json +11 -0
  70. package/scripts/dev-bust-cache.sh +164 -0
  71. package/scripts/install.sh +688 -0
  72. package/scripts/smoke-e2e.ts +704 -0
  73. package/scripts/smoke-hot.ts +417 -0
  74. package/scripts/smoke-http.sh +228 -0
  75. package/scripts/smoke-v4.ts +256 -0
  76. package/scripts/smoke-v5.ts +397 -0
  77. package/scripts/smoke.sh +9 -0
  78. package/scripts/uninstall.sh +224 -0
  79. package/skills/api-security-best-practices/SKILL.md +915 -0
  80. package/skills/bash-scripting/SKILL.md +201 -0
  81. package/skills/bun/SKILL.md +313 -0
  82. package/skills/cavecrew/SKILL.md +82 -0
  83. package/skills/caveman/SKILL.md +74 -0
  84. package/skills/caveman-review/README.md +33 -0
  85. package/skills/caveman-review/SKILL.md +55 -0
  86. package/skills/find-skills/SKILL.md +142 -0
  87. package/skills/frontend-design/LICENSE.txt +177 -0
  88. package/skills/frontend-design/SKILL.md +55 -0
  89. package/skills/golang-patterns/SKILL.md +674 -0
  90. package/skills/golang-security/SKILL.md +185 -0
  91. package/skills/golang-security/evals/evals.json +595 -0
  92. package/skills/golang-security/references/architecture.md +268 -0
  93. package/skills/golang-security/references/checklist.md +80 -0
  94. package/skills/golang-security/references/cookies.md +200 -0
  95. package/skills/golang-security/references/cryptography.md +424 -0
  96. package/skills/golang-security/references/filesystem.md +285 -0
  97. package/skills/golang-security/references/injection.md +315 -0
  98. package/skills/golang-security/references/logging.md +163 -0
  99. package/skills/golang-security/references/memory-safety.md +241 -0
  100. package/skills/golang-security/references/network.md +253 -0
  101. package/skills/golang-security/references/secrets.md +189 -0
  102. package/skills/golang-security/references/third-party.md +159 -0
  103. package/skills/golang-security/references/threat-modeling.md +189 -0
  104. package/skills/golang-testing/SKILL.md +720 -0
  105. package/skills/grill-me/SKILL.md +7 -0
  106. package/skills/javascript-testing-patterns/SKILL.md +537 -0
  107. package/skills/javascript-testing-patterns/references/advanced-testing-patterns.md +513 -0
  108. package/skills/modern-javascript-patterns/SKILL.md +43 -0
  109. package/skills/modern-javascript-patterns/references/advanced-patterns.md +487 -0
  110. package/skills/modern-javascript-patterns/references/details.md +457 -0
  111. package/skills/python-anti-patterns/SKILL.md +349 -0
  112. package/skills/python-design-patterns/SKILL.md +85 -0
  113. package/skills/python-design-patterns/references/details.md +353 -0
  114. package/skills/python-error-handling/SKILL.md +193 -0
  115. package/skills/python-error-handling/references/details.md +171 -0
  116. package/skills/python-testing-patterns/SKILL.md +278 -0
  117. package/skills/python-testing-patterns/references/advanced-patterns.md +411 -0
  118. package/skills/python-testing-patterns/references/details.md +349 -0
  119. package/skills/rust-patterns/SKILL.md +500 -0
  120. package/skills/rust-testing/SKILL.md +501 -0
  121. package/skills/security-review/SKILL.md +504 -0
  122. package/skills/security-review/cloud-infrastructure-security.md +361 -0
  123. package/skills/vue-best-practices/SKILL.md +154 -0
  124. package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
  125. package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
  126. package/skills/vue-best-practices/references/component-async.md +97 -0
  127. package/skills/vue-best-practices/references/component-data-flow.md +307 -0
  128. package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
  129. package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
  130. package/skills/vue-best-practices/references/component-slots.md +216 -0
  131. package/skills/vue-best-practices/references/component-suspense.md +228 -0
  132. package/skills/vue-best-practices/references/component-teleport.md +108 -0
  133. package/skills/vue-best-practices/references/component-transition-group.md +128 -0
  134. package/skills/vue-best-practices/references/component-transition.md +125 -0
  135. package/skills/vue-best-practices/references/composables.md +290 -0
  136. package/skills/vue-best-practices/references/directives.md +162 -0
  137. package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
  138. package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
  139. package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
  140. package/skills/vue-best-practices/references/plugins.md +166 -0
  141. package/skills/vue-best-practices/references/reactivity.md +344 -0
  142. package/skills/vue-best-practices/references/render-functions.md +201 -0
  143. package/skills/vue-best-practices/references/sfc.md +310 -0
  144. package/skills/vue-best-practices/references/state-management.md +135 -0
  145. package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
  146. package/skills/vue-pinia-best-practices/SKILL.md +21 -0
  147. package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
  148. package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
  149. package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
  150. package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
  151. package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
  152. package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
  153. package/skills/zig-0.16/SKILL.md +840 -0
  154. package/skills/zig-0.16/scripts/check-zig-version.sh +21 -0
  155. package/src/cli/analyses.ts +280 -0
  156. package/src/cli/index.ts +108 -0
  157. package/src/cli/serve.ts +192 -0
  158. package/src/cli/smoke.ts +131 -0
  159. package/src/cli/status.test.ts +204 -0
  160. package/src/cli/status.ts +263 -0
  161. package/src/cli/vacuum.test.ts +82 -0
  162. package/src/cli/vacuum.ts +96 -0
  163. package/src/config/schema.test.ts +88 -0
  164. package/src/config/schema.ts +64 -0
  165. package/src/db/analyses-migration.test.ts +210 -0
  166. package/src/db/analyses.test.ts +466 -0
  167. package/src/db/analyses.ts +375 -0
  168. package/src/db/auto-checkpoint.ts +131 -0
  169. package/src/db/client.test.ts +129 -0
  170. package/src/db/client.ts +55 -0
  171. package/src/db/fts-escape.ts +20 -0
  172. package/src/db/incidents.test.ts +201 -0
  173. package/src/db/incidents.ts +93 -0
  174. package/src/db/index.ts +86 -0
  175. package/src/db/migrations-v13.test.ts +141 -0
  176. package/src/db/migrations-v8.test.ts +301 -0
  177. package/src/db/migrations.ts +147 -0
  178. package/src/db/plan-archive.test.ts +180 -0
  179. package/src/db/plan-archive.ts +274 -0
  180. package/src/db/plan-create.test.ts +276 -0
  181. package/src/db/plan-create.ts +78 -0
  182. package/src/db/plan-files.test.ts +289 -0
  183. package/src/db/plan-update-status.ts +287 -0
  184. package/src/db/plans.test.ts +490 -0
  185. package/src/db/plans.ts +534 -0
  186. package/src/db/resolve-project-dir.test.ts +143 -0
  187. package/src/db/resolve-project-dir.ts +75 -0
  188. package/src/db/rollbacks.test.ts +150 -0
  189. package/src/db/rollbacks.ts +67 -0
  190. package/src/db/schema.ts +907 -0
  191. package/src/db/sessions.test.ts +80 -0
  192. package/src/db/sessions.ts +135 -0
  193. package/src/db/shutdown.test.ts +147 -0
  194. package/src/db/shutdown.ts +45 -0
  195. package/src/db/tasks.test.ts +921 -0
  196. package/src/db/tasks.ts +747 -0
  197. package/src/db/types.ts +619 -0
  198. package/src/http/__tests__/auth.test.ts +196 -0
  199. package/src/http/__tests__/routes.test.ts +465 -0
  200. package/src/http/__tests__/sse.test.ts +317 -0
  201. package/src/http/auth.ts +72 -0
  202. package/src/http/middleware/cors.ts +53 -0
  203. package/src/http/middleware/security-headers.ts +21 -0
  204. package/src/http/routes/events.ts +112 -0
  205. package/src/http/routes/health.ts +51 -0
  206. package/src/http/routes/plans.ts +66 -0
  207. package/src/http/routes/sessions.ts +50 -0
  208. package/src/http/routes/tasks.ts +60 -0
  209. package/src/http/server.ts +95 -0
  210. package/src/http/sse.ts +116 -0
  211. package/src/index.ts +37 -0
  212. package/src/lib.ts +65 -0
  213. package/src/mem/scoped.ts +65 -0
  214. package/src/orchestrator/background.test.ts +268 -0
  215. package/src/orchestrator/background.ts +293 -0
  216. package/src/orchestrator/memory-hook.ts +182 -0
  217. package/src/orchestrator/reconciler.ts +123 -0
  218. package/src/orchestrator/scheduler.test.ts +300 -0
  219. package/src/orchestrator/scheduler.ts +243 -0
  220. package/src/plugin.test.ts +2574 -0
  221. package/src/plugin.ts +1690 -0
  222. package/src/sdk/client.ts +66 -0
  223. package/src/worktrees/manager.ts +236 -0
  224. package/src/worktrees/state.ts +87 -0
  225. package/tests/integration/ranger-flow.test.ts +257 -0
  226. package/tools/analysis_archive.ts +28 -0
  227. package/tools/analysis_create.ts +55 -0
  228. package/tools/analysis_get.ts +33 -0
  229. package/tools/analysis_link_plan.ts +44 -0
  230. package/tools/analysis_list.ts +48 -0
  231. package/tools/analysis_search.ts +36 -0
  232. package/tools/analysis_update.ts +44 -0
  233. package/tools/plan_approve.ts +31 -0
  234. package/tools/plan_create.ts +58 -0
  235. package/tools/plan_get.ts +40 -0
  236. package/tools/plan_list.ts +37 -0
  237. package/tools/plan_search.ts +34 -0
  238. package/tools/plan_update_status.ts +71 -0
  239. package/tools/session_checkpoint.ts +31 -0
  240. package/tools/session_end.ts +26 -0
  241. package/tools/session_start.ts +43 -0
  242. package/tools/task_create_batch.ts +70 -0
  243. package/tools/task_list.ts +35 -0
  244. package/tools/task_next_for_agent.ts +30 -0
  245. package/tools/task_search.ts +34 -0
  246. package/tools/task_update_status.ts +37 -0
  247. package/tsconfig.json +31 -0
@@ -0,0 +1,278 @@
1
+ ---
2
+ name: python-testing-patterns
3
+ description: Implement comprehensive testing strategies with pytest, fixtures, mocking, and test-driven development. Use when writing Python tests, setting up test suites, or implementing testing best practices.
4
+ ---
5
+
6
+ # Python Testing Patterns
7
+
8
+ Comprehensive guide to implementing robust testing strategies in Python using pytest, fixtures, mocking, parameterization, and test-driven development practices.
9
+
10
+ ## When to Use This Skill
11
+
12
+ - Writing unit tests for Python code
13
+ - Setting up test suites and test infrastructure
14
+ - Implementing test-driven development (TDD)
15
+ - Creating integration tests for APIs and services
16
+ - Mocking external dependencies and services
17
+ - Testing async code and concurrent operations
18
+ - Setting up continuous testing in CI/CD
19
+ - Implementing property-based testing
20
+ - Testing database operations
21
+ - Debugging failing tests
22
+
23
+ ## Core Concepts
24
+
25
+ ### 1. Test Types
26
+
27
+ - **Unit Tests**: Test individual functions/classes in isolation
28
+ - **Integration Tests**: Test interaction between components
29
+ - **Functional Tests**: Test complete features end-to-end
30
+ - **Performance Tests**: Measure speed and resource usage
31
+
32
+ ### 2. Test Structure (AAA Pattern)
33
+
34
+ - **Arrange**: Set up test data and preconditions
35
+ - **Act**: Execute the code under test
36
+ - **Assert**: Verify the results
37
+
38
+ ### 3. Test Coverage
39
+
40
+ - Measure what code is exercised by tests
41
+ - Identify untested code paths
42
+ - Aim for meaningful coverage, not just high percentages
43
+
44
+ ### 4. Test Isolation
45
+
46
+ - Tests should be independent
47
+ - No shared state between tests
48
+ - Each test should clean up after itself
49
+
50
+ ## Quick Start
51
+
52
+ ```python
53
+ # test_example.py
54
+ def add(a, b):
55
+ return a + b
56
+
57
+ def test_add():
58
+ """Basic test example."""
59
+ result = add(2, 3)
60
+ assert result == 5
61
+
62
+ def test_add_negative():
63
+ """Test with negative numbers."""
64
+ assert add(-1, 1) == 0
65
+
66
+ # Run with: pytest test_example.py
67
+ ```
68
+
69
+ ## Detailed patterns and worked examples
70
+
71
+ Detailed pattern documentation lives in `references/details.md`. Read that file when the navigation tier above is insufficient.
72
+
73
+ ## Testing Best Practices
74
+
75
+ ### Test Organization
76
+
77
+ ```python
78
+ # tests/
79
+ # __init__.py
80
+ # conftest.py # Shared fixtures
81
+ # test_unit/ # Unit tests
82
+ # test_models.py
83
+ # test_utils.py
84
+ # test_integration/ # Integration tests
85
+ # test_api.py
86
+ # test_database.py
87
+ # test_e2e/ # End-to-end tests
88
+ # test_workflows.py
89
+ ```
90
+
91
+ ### Test Naming Convention
92
+
93
+ A common pattern: `test_<unit>_<scenario>_<expected_outcome>`. Adapt to your team's preferences.
94
+
95
+ ```python
96
+ # Pattern: test_<unit>_<scenario>_<expected>
97
+ def test_create_user_with_valid_data_returns_user():
98
+ ...
99
+
100
+ def test_create_user_with_duplicate_email_raises_conflict():
101
+ ...
102
+
103
+ def test_get_user_with_unknown_id_returns_none():
104
+ ...
105
+
106
+ # Good test names - clear and descriptive
107
+ def test_user_creation_with_valid_data():
108
+ """Clear name describes what is being tested."""
109
+ pass
110
+
111
+ def test_login_fails_with_invalid_password():
112
+ """Name describes expected behavior."""
113
+ pass
114
+
115
+ def test_api_returns_404_for_missing_resource():
116
+ """Specific about inputs and expected outcomes."""
117
+ pass
118
+
119
+ # Bad test names - avoid these
120
+ def test_1(): # Not descriptive
121
+ pass
122
+
123
+ def test_user(): # Too vague
124
+ pass
125
+
126
+ def test_function(): # Doesn't explain what's tested
127
+ pass
128
+ ```
129
+
130
+ ### Testing Retry Behavior
131
+
132
+ Verify that retry logic works correctly using mock side effects.
133
+
134
+ ```python
135
+ from unittest.mock import Mock
136
+
137
+ def test_retries_on_transient_error():
138
+ """Test that service retries on transient failures."""
139
+ client = Mock()
140
+ # Fail twice, then succeed
141
+ client.request.side_effect = [
142
+ ConnectionError("Failed"),
143
+ ConnectionError("Failed"),
144
+ {"status": "ok"},
145
+ ]
146
+
147
+ service = ServiceWithRetry(client, max_retries=3)
148
+ result = service.fetch()
149
+
150
+ assert result == {"status": "ok"}
151
+ assert client.request.call_count == 3
152
+
153
+ def test_gives_up_after_max_retries():
154
+ """Test that service stops retrying after max attempts."""
155
+ client = Mock()
156
+ client.request.side_effect = ConnectionError("Failed")
157
+
158
+ service = ServiceWithRetry(client, max_retries=3)
159
+
160
+ with pytest.raises(ConnectionError):
161
+ service.fetch()
162
+
163
+ assert client.request.call_count == 3
164
+
165
+ def test_does_not_retry_on_permanent_error():
166
+ """Test that permanent errors are not retried."""
167
+ client = Mock()
168
+ client.request.side_effect = ValueError("Invalid input")
169
+
170
+ service = ServiceWithRetry(client, max_retries=3)
171
+
172
+ with pytest.raises(ValueError):
173
+ service.fetch()
174
+
175
+ # Only called once - no retry for ValueError
176
+ assert client.request.call_count == 1
177
+ ```
178
+
179
+ ### Mocking Time with Freezegun
180
+
181
+ Use freezegun to control time in tests for predictable time-dependent behavior.
182
+
183
+ ```python
184
+ from freezegun import freeze_time
185
+ from datetime import datetime, timedelta
186
+
187
+ @freeze_time("2026-01-15 10:00:00")
188
+ def test_token_expiry():
189
+ """Test token expires at correct time."""
190
+ token = create_token(expires_in_seconds=3600)
191
+ assert token.expires_at == datetime(2026, 1, 15, 11, 0, 0)
192
+
193
+ @freeze_time("2026-01-15 10:00:00")
194
+ def test_is_expired_returns_false_before_expiry():
195
+ """Test token is not expired when within validity period."""
196
+ token = create_token(expires_in_seconds=3600)
197
+ assert not token.is_expired()
198
+
199
+ @freeze_time("2026-01-15 12:00:00")
200
+ def test_is_expired_returns_true_after_expiry():
201
+ """Test token is expired after validity period."""
202
+ token = Token(expires_at=datetime(2026, 1, 15, 11, 30, 0))
203
+ assert token.is_expired()
204
+
205
+ def test_with_time_travel():
206
+ """Test behavior across time using freeze_time context."""
207
+ with freeze_time("2026-01-01") as frozen_time:
208
+ item = create_item()
209
+ assert item.created_at == datetime(2026, 1, 1)
210
+
211
+ # Move forward in time
212
+ frozen_time.move_to("2026-01-15")
213
+ assert item.age_days == 14
214
+ ```
215
+
216
+ ### Test Markers
217
+
218
+ ```python
219
+ # test_markers.py
220
+ import pytest
221
+
222
+ @pytest.mark.slow
223
+ def test_slow_operation():
224
+ """Mark slow tests."""
225
+ import time
226
+ time.sleep(2)
227
+
228
+
229
+ @pytest.mark.integration
230
+ def test_database_integration():
231
+ """Mark integration tests."""
232
+ pass
233
+
234
+
235
+ @pytest.mark.skip(reason="Feature not implemented yet")
236
+ def test_future_feature():
237
+ """Skip tests temporarily."""
238
+ pass
239
+
240
+
241
+ @pytest.mark.skipif(os.name == "nt", reason="Unix only test")
242
+ def test_unix_specific():
243
+ """Conditional skip."""
244
+ pass
245
+
246
+
247
+ @pytest.mark.xfail(reason="Known bug #123")
248
+ def test_known_bug():
249
+ """Mark expected failures."""
250
+ assert False
251
+
252
+
253
+ # Run with:
254
+ # pytest -m slow # Run only slow tests
255
+ # pytest -m "not slow" # Skip slow tests
256
+ # pytest -m integration # Run integration tests
257
+ ```
258
+
259
+ ### Coverage Reporting
260
+
261
+ ```bash
262
+ # Install coverage
263
+ pip install pytest-cov
264
+
265
+ # Run tests with coverage
266
+ pytest --cov=myapp tests/
267
+
268
+ # Generate HTML report
269
+ pytest --cov=myapp --cov-report=html tests/
270
+
271
+ # Fail if coverage below threshold
272
+ pytest --cov=myapp --cov-fail-under=80 tests/
273
+
274
+ # Show missing lines
275
+ pytest --cov=myapp --cov-report=term-missing tests/
276
+ ```
277
+
278
+ For advanced patterns (async testing, monkeypatching, property-based testing, database testing, CI/CD integration, and configuration), see [references/advanced-patterns.md](references/advanced-patterns.md)
@@ -0,0 +1,411 @@
1
+ # Python Testing Patterns — Advanced Reference
2
+
3
+ Advanced testing patterns including async code, monkeypatching, temporary files, conftest setup, property-based testing, database testing, CI/CD integration, and configuration.
4
+
5
+ ## Pattern 6: Testing Async Code
6
+
7
+ ```python
8
+ # test_async.py
9
+ import pytest
10
+ import asyncio
11
+
12
+ async def fetch_data(url: str) -> dict:
13
+ """Fetch data asynchronously."""
14
+ await asyncio.sleep(0.1)
15
+ return {"url": url, "data": "result"}
16
+
17
+
18
+ @pytest.mark.asyncio
19
+ async def test_fetch_data():
20
+ """Test async function."""
21
+ result = await fetch_data("https://api.example.com")
22
+ assert result["url"] == "https://api.example.com"
23
+ assert "data" in result
24
+
25
+
26
+ @pytest.mark.asyncio
27
+ async def test_concurrent_fetches():
28
+ """Test concurrent async operations."""
29
+ urls = ["url1", "url2", "url3"]
30
+ tasks = [fetch_data(url) for url in urls]
31
+ results = await asyncio.gather(*tasks)
32
+
33
+ assert len(results) == 3
34
+ assert all("data" in r for r in results)
35
+
36
+
37
+ @pytest.fixture
38
+ async def async_client():
39
+ """Async fixture."""
40
+ client = {"connected": True}
41
+ yield client
42
+ client["connected"] = False
43
+
44
+
45
+ @pytest.mark.asyncio
46
+ async def test_with_async_fixture(async_client):
47
+ """Test using async fixture."""
48
+ assert async_client["connected"] is True
49
+ ```
50
+
51
+ ## Pattern 7: Monkeypatch for Testing
52
+
53
+ ```python
54
+ # test_environment.py
55
+ import os
56
+ import pytest
57
+
58
+ def get_database_url() -> str:
59
+ """Get database URL from environment."""
60
+ return os.environ.get("DATABASE_URL", "sqlite:///:memory:")
61
+
62
+
63
+ def test_database_url_default():
64
+ """Test default database URL."""
65
+ # Will use actual environment variable if set
66
+ url = get_database_url()
67
+ assert url
68
+
69
+
70
+ def test_database_url_custom(monkeypatch):
71
+ """Test custom database URL with monkeypatch."""
72
+ monkeypatch.setenv("DATABASE_URL", "postgresql://localhost/test")
73
+ assert get_database_url() == "postgresql://localhost/test"
74
+
75
+
76
+ def test_database_url_not_set(monkeypatch):
77
+ """Test when env var is not set."""
78
+ monkeypatch.delenv("DATABASE_URL", raising=False)
79
+ assert get_database_url() == "sqlite:///:memory:"
80
+
81
+
82
+ class Config:
83
+ """Configuration class."""
84
+
85
+ def __init__(self):
86
+ self.api_key = "production-key"
87
+
88
+ def get_api_key(self):
89
+ return self.api_key
90
+
91
+
92
+ def test_monkeypatch_attribute(monkeypatch):
93
+ """Test monkeypatching object attributes."""
94
+ config = Config()
95
+ monkeypatch.setattr(config, "api_key", "test-key")
96
+ assert config.get_api_key() == "test-key"
97
+ ```
98
+
99
+ ## Pattern 8: Temporary Files and Directories
100
+
101
+ ```python
102
+ # test_file_operations.py
103
+ import pytest
104
+ from pathlib import Path
105
+
106
+ def save_data(filepath: Path, data: str):
107
+ """Save data to file."""
108
+ filepath.write_text(data)
109
+
110
+
111
+ def load_data(filepath: Path) -> str:
112
+ """Load data from file."""
113
+ return filepath.read_text()
114
+
115
+
116
+ def test_file_operations(tmp_path):
117
+ """Test file operations with temporary directory."""
118
+ # tmp_path is a pathlib.Path object
119
+ test_file = tmp_path / "test_data.txt"
120
+
121
+ # Save data
122
+ save_data(test_file, "Hello, World!")
123
+
124
+ # Verify file exists
125
+ assert test_file.exists()
126
+
127
+ # Load and verify data
128
+ data = load_data(test_file)
129
+ assert data == "Hello, World!"
130
+
131
+
132
+ def test_multiple_files(tmp_path):
133
+ """Test with multiple temporary files."""
134
+ files = {
135
+ "file1.txt": "Content 1",
136
+ "file2.txt": "Content 2",
137
+ "file3.txt": "Content 3"
138
+ }
139
+
140
+ for filename, content in files.items():
141
+ filepath = tmp_path / filename
142
+ save_data(filepath, content)
143
+
144
+ # Verify all files created
145
+ assert len(list(tmp_path.iterdir())) == 3
146
+
147
+ # Verify contents
148
+ for filename, expected_content in files.items():
149
+ filepath = tmp_path / filename
150
+ assert load_data(filepath) == expected_content
151
+ ```
152
+
153
+ ## Pattern 9: Custom Fixtures and Conftest
154
+
155
+ ```python
156
+ # conftest.py
157
+ """Shared fixtures for all tests."""
158
+ import pytest
159
+
160
+ @pytest.fixture(scope="session")
161
+ def database_url():
162
+ """Provide database URL for all tests."""
163
+ return "postgresql://localhost/test_db"
164
+
165
+
166
+ @pytest.fixture(autouse=True)
167
+ def reset_database(database_url):
168
+ """Auto-use fixture that runs before each test."""
169
+ # Setup: Clear database
170
+ print(f"Clearing database: {database_url}")
171
+ yield
172
+ # Teardown: Clean up
173
+ print("Test completed")
174
+
175
+
176
+ @pytest.fixture
177
+ def sample_user():
178
+ """Provide sample user data."""
179
+ return {
180
+ "id": 1,
181
+ "name": "Test User",
182
+ "email": "test@example.com"
183
+ }
184
+
185
+
186
+ @pytest.fixture
187
+ def sample_users():
188
+ """Provide list of sample users."""
189
+ return [
190
+ {"id": 1, "name": "User 1"},
191
+ {"id": 2, "name": "User 2"},
192
+ {"id": 3, "name": "User 3"},
193
+ ]
194
+
195
+
196
+ # Parametrized fixture
197
+ @pytest.fixture(params=["sqlite", "postgresql", "mysql"])
198
+ def db_backend(request):
199
+ """Fixture that runs tests with different database backends."""
200
+ return request.param
201
+
202
+
203
+ def test_with_db_backend(db_backend):
204
+ """This test will run 3 times with different backends."""
205
+ print(f"Testing with {db_backend}")
206
+ assert db_backend in ["sqlite", "postgresql", "mysql"]
207
+ ```
208
+
209
+ ## Pattern 10: Property-Based Testing
210
+
211
+ ```python
212
+ # test_properties.py
213
+ from hypothesis import given, strategies as st
214
+ import pytest
215
+
216
+ def reverse_string(s: str) -> str:
217
+ """Reverse a string."""
218
+ return s[::-1]
219
+
220
+
221
+ @given(st.text())
222
+ def test_reverse_twice_is_original(s):
223
+ """Property: reversing twice returns original."""
224
+ assert reverse_string(reverse_string(s)) == s
225
+
226
+
227
+ @given(st.text())
228
+ def test_reverse_length(s):
229
+ """Property: reversed string has same length."""
230
+ assert len(reverse_string(s)) == len(s)
231
+
232
+
233
+ @given(st.integers(), st.integers())
234
+ def test_addition_commutative(a, b):
235
+ """Property: addition is commutative."""
236
+ assert a + b == b + a
237
+
238
+
239
+ @given(st.lists(st.integers()))
240
+ def test_sorted_list_properties(lst):
241
+ """Property: sorted list is ordered."""
242
+ sorted_lst = sorted(lst)
243
+
244
+ # Same length
245
+ assert len(sorted_lst) == len(lst)
246
+
247
+ # All elements present
248
+ assert set(sorted_lst) == set(lst)
249
+
250
+ # Is ordered
251
+ for i in range(len(sorted_lst) - 1):
252
+ assert sorted_lst[i] <= sorted_lst[i + 1]
253
+ ```
254
+
255
+ ## Testing Database Code
256
+
257
+ ```python
258
+ # test_database_models.py
259
+ import pytest
260
+ from sqlalchemy import create_engine, Column, Integer, String
261
+ from sqlalchemy.ext.declarative import declarative_base
262
+ from sqlalchemy.orm import sessionmaker, Session
263
+
264
+ Base = declarative_base()
265
+
266
+
267
+ class User(Base):
268
+ """User model."""
269
+ __tablename__ = "users"
270
+
271
+ id = Column(Integer, primary_key=True)
272
+ name = Column(String(50))
273
+ email = Column(String(100), unique=True)
274
+
275
+
276
+ @pytest.fixture(scope="function")
277
+ def db_session() -> Session:
278
+ """Create in-memory database for testing."""
279
+ engine = create_engine("sqlite:///:memory:")
280
+ Base.metadata.create_all(engine)
281
+
282
+ SessionLocal = sessionmaker(bind=engine)
283
+ session = SessionLocal()
284
+
285
+ yield session
286
+
287
+ session.close()
288
+
289
+
290
+ def test_create_user(db_session):
291
+ """Test creating a user."""
292
+ user = User(name="Test User", email="test@example.com")
293
+ db_session.add(user)
294
+ db_session.commit()
295
+
296
+ assert user.id is not None
297
+ assert user.name == "Test User"
298
+
299
+
300
+ def test_query_user(db_session):
301
+ """Test querying users."""
302
+ user1 = User(name="User 1", email="user1@example.com")
303
+ user2 = User(name="User 2", email="user2@example.com")
304
+
305
+ db_session.add_all([user1, user2])
306
+ db_session.commit()
307
+
308
+ users = db_session.query(User).all()
309
+ assert len(users) == 2
310
+
311
+
312
+ def test_unique_email_constraint(db_session):
313
+ """Test unique email constraint."""
314
+ from sqlalchemy.exc import IntegrityError
315
+
316
+ user1 = User(name="User 1", email="same@example.com")
317
+ user2 = User(name="User 2", email="same@example.com")
318
+
319
+ db_session.add(user1)
320
+ db_session.commit()
321
+
322
+ db_session.add(user2)
323
+
324
+ with pytest.raises(IntegrityError):
325
+ db_session.commit()
326
+ ```
327
+
328
+ ## CI/CD Integration
329
+
330
+ ```yaml
331
+ # .github/workflows/test.yml
332
+ name: Tests
333
+
334
+ on: [push, pull_request]
335
+
336
+ jobs:
337
+ test:
338
+ runs-on: ubuntu-latest
339
+
340
+ strategy:
341
+ matrix:
342
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
343
+
344
+ steps:
345
+ - uses: actions/checkout@v3
346
+
347
+ - name: Set up Python
348
+ uses: actions/setup-python@v4
349
+ with:
350
+ python-version: ${{ matrix.python-version }}
351
+
352
+ - name: Install dependencies
353
+ run: |
354
+ pip install -e ".[dev]"
355
+ pip install pytest pytest-cov
356
+
357
+ - name: Run tests
358
+ run: |
359
+ pytest --cov=myapp --cov-report=xml
360
+
361
+ - name: Upload coverage
362
+ uses: codecov/codecov-action@v3
363
+ with:
364
+ file: ./coverage.xml
365
+ ```
366
+
367
+ ## Configuration Files
368
+
369
+ ```ini
370
+ # pytest.ini
371
+ [pytest]
372
+ testpaths = tests
373
+ python_files = test_*.py
374
+ python_classes = Test*
375
+ python_functions = test_*
376
+ addopts =
377
+ -v
378
+ --strict-markers
379
+ --tb=short
380
+ --cov=myapp
381
+ --cov-report=term-missing
382
+ markers =
383
+ slow: marks tests as slow
384
+ integration: marks integration tests
385
+ unit: marks unit tests
386
+ e2e: marks end-to-end tests
387
+ ```
388
+
389
+ ```toml
390
+ # pyproject.toml
391
+ [tool.pytest.ini_options]
392
+ testpaths = ["tests"]
393
+ python_files = ["test_*.py"]
394
+ addopts = [
395
+ "-v",
396
+ "--cov=myapp",
397
+ "--cov-report=term-missing",
398
+ ]
399
+
400
+ [tool.coverage.run]
401
+ source = ["myapp"]
402
+ omit = ["*/tests/*", "*/migrations/*"]
403
+
404
+ [tool.coverage.report]
405
+ exclude_lines = [
406
+ "pragma: no cover",
407
+ "def __repr__",
408
+ "raise AssertionError",
409
+ "raise NotImplementedError",
410
+ ]
411
+ ```