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,349 @@
1
+ # python-testing-patterns — detailed patterns and worked examples
2
+
3
+ ## Fundamental Patterns
4
+
5
+ ### Pattern 1: Basic pytest Tests
6
+
7
+ ```python
8
+ # test_calculator.py
9
+ import pytest
10
+
11
+ class Calculator:
12
+ """Simple calculator for testing."""
13
+
14
+ def add(self, a: float, b: float) -> float:
15
+ return a + b
16
+
17
+ def subtract(self, a: float, b: float) -> float:
18
+ return a - b
19
+
20
+ def multiply(self, a: float, b: float) -> float:
21
+ return a * b
22
+
23
+ def divide(self, a: float, b: float) -> float:
24
+ if b == 0:
25
+ raise ValueError("Cannot divide by zero")
26
+ return a / b
27
+
28
+
29
+ def test_addition():
30
+ """Test addition."""
31
+ calc = Calculator()
32
+ assert calc.add(2, 3) == 5
33
+ assert calc.add(-1, 1) == 0
34
+ assert calc.add(0, 0) == 0
35
+
36
+
37
+ def test_subtraction():
38
+ """Test subtraction."""
39
+ calc = Calculator()
40
+ assert calc.subtract(5, 3) == 2
41
+ assert calc.subtract(0, 5) == -5
42
+
43
+
44
+ def test_multiplication():
45
+ """Test multiplication."""
46
+ calc = Calculator()
47
+ assert calc.multiply(3, 4) == 12
48
+ assert calc.multiply(0, 5) == 0
49
+
50
+
51
+ def test_division():
52
+ """Test division."""
53
+ calc = Calculator()
54
+ assert calc.divide(6, 3) == 2
55
+ assert calc.divide(5, 2) == 2.5
56
+
57
+
58
+ def test_division_by_zero():
59
+ """Test division by zero raises error."""
60
+ calc = Calculator()
61
+ with pytest.raises(ValueError, match="Cannot divide by zero"):
62
+ calc.divide(5, 0)
63
+ ```
64
+
65
+ ### Pattern 2: Fixtures for Setup and Teardown
66
+
67
+ ```python
68
+ # test_database.py
69
+ import pytest
70
+ from typing import Generator
71
+
72
+ class Database:
73
+ """Simple database class."""
74
+
75
+ def __init__(self, connection_string: str):
76
+ self.connection_string = connection_string
77
+ self.connected = False
78
+
79
+ def connect(self):
80
+ """Connect to database."""
81
+ self.connected = True
82
+
83
+ def disconnect(self):
84
+ """Disconnect from database."""
85
+ self.connected = False
86
+
87
+ def query(self, sql: str) -> list:
88
+ """Execute query."""
89
+ if not self.connected:
90
+ raise RuntimeError("Not connected")
91
+ return [{"id": 1, "name": "Test"}]
92
+
93
+
94
+ @pytest.fixture
95
+ def db() -> Generator[Database, None, None]:
96
+ """Fixture that provides connected database."""
97
+ # Setup
98
+ database = Database("sqlite:///:memory:")
99
+ database.connect()
100
+
101
+ # Provide to test
102
+ yield database
103
+
104
+ # Teardown
105
+ database.disconnect()
106
+
107
+
108
+ def test_database_query(db):
109
+ """Test database query with fixture."""
110
+ results = db.query("SELECT * FROM users")
111
+ assert len(results) == 1
112
+ assert results[0]["name"] == "Test"
113
+
114
+
115
+ @pytest.fixture(scope="session")
116
+ def app_config():
117
+ """Session-scoped fixture - created once per test session."""
118
+ return {
119
+ "database_url": "postgresql://localhost/test",
120
+ "api_key": "test-key",
121
+ "debug": True
122
+ }
123
+
124
+
125
+ @pytest.fixture(scope="module")
126
+ def api_client(app_config):
127
+ """Module-scoped fixture - created once per test module."""
128
+ # Setup expensive resource
129
+ client = {"config": app_config, "session": "active"}
130
+ yield client
131
+ # Cleanup
132
+ client["session"] = "closed"
133
+
134
+
135
+ def test_api_client(api_client):
136
+ """Test using api client fixture."""
137
+ assert api_client["session"] == "active"
138
+ assert api_client["config"]["debug"] is True
139
+ ```
140
+
141
+ ### Pattern 3: Parameterized Tests
142
+
143
+ ```python
144
+ # test_validation.py
145
+ import pytest
146
+
147
+ def is_valid_email(email: str) -> bool:
148
+ """Check if email is valid."""
149
+ return "@" in email and "." in email.split("@")[1]
150
+
151
+
152
+ @pytest.mark.parametrize("email,expected", [
153
+ ("user@example.com", True),
154
+ ("test.user@domain.co.uk", True),
155
+ ("invalid.email", False),
156
+ ("@example.com", False),
157
+ ("user@domain", False),
158
+ ("", False),
159
+ ])
160
+ def test_email_validation(email, expected):
161
+ """Test email validation with various inputs."""
162
+ assert is_valid_email(email) == expected
163
+
164
+
165
+ @pytest.mark.parametrize("a,b,expected", [
166
+ (2, 3, 5),
167
+ (0, 0, 0),
168
+ (-1, 1, 0),
169
+ (100, 200, 300),
170
+ (-5, -5, -10),
171
+ ])
172
+ def test_addition_parameterized(a, b, expected):
173
+ """Test addition with multiple parameter sets."""
174
+ from test_calculator import Calculator
175
+ calc = Calculator()
176
+ assert calc.add(a, b) == expected
177
+
178
+
179
+ # Using pytest.param for special cases
180
+ @pytest.mark.parametrize("value,expected", [
181
+ pytest.param(1, True, id="positive"),
182
+ pytest.param(0, False, id="zero"),
183
+ pytest.param(-1, False, id="negative"),
184
+ ])
185
+ def test_is_positive(value, expected):
186
+ """Test with custom test IDs."""
187
+ assert (value > 0) == expected
188
+ ```
189
+
190
+ ### Pattern 4: Mocking with unittest.mock
191
+
192
+ ```python
193
+ # test_api_client.py
194
+ import pytest
195
+ from unittest.mock import Mock, patch, MagicMock
196
+ import requests
197
+
198
+ class APIClient:
199
+ """Simple API client."""
200
+
201
+ def __init__(self, base_url: str):
202
+ self.base_url = base_url
203
+
204
+ def get_user(self, user_id: int) -> dict:
205
+ """Fetch user from API."""
206
+ response = requests.get(f"{self.base_url}/users/{user_id}")
207
+ response.raise_for_status()
208
+ return response.json()
209
+
210
+ def create_user(self, data: dict) -> dict:
211
+ """Create new user."""
212
+ response = requests.post(f"{self.base_url}/users", json=data)
213
+ response.raise_for_status()
214
+ return response.json()
215
+
216
+
217
+ def test_get_user_success():
218
+ """Test successful API call with mock."""
219
+ client = APIClient("https://api.example.com")
220
+
221
+ mock_response = Mock()
222
+ mock_response.json.return_value = {"id": 1, "name": "John Doe"}
223
+ mock_response.raise_for_status.return_value = None
224
+
225
+ with patch("requests.get", return_value=mock_response) as mock_get:
226
+ user = client.get_user(1)
227
+
228
+ assert user["id"] == 1
229
+ assert user["name"] == "John Doe"
230
+ mock_get.assert_called_once_with("https://api.example.com/users/1")
231
+
232
+
233
+ def test_get_user_not_found():
234
+ """Test API call with 404 error."""
235
+ client = APIClient("https://api.example.com")
236
+
237
+ mock_response = Mock()
238
+ mock_response.raise_for_status.side_effect = requests.HTTPError("404 Not Found")
239
+
240
+ with patch("requests.get", return_value=mock_response):
241
+ with pytest.raises(requests.HTTPError):
242
+ client.get_user(999)
243
+
244
+
245
+ @patch("requests.post")
246
+ def test_create_user(mock_post):
247
+ """Test user creation with decorator syntax."""
248
+ client = APIClient("https://api.example.com")
249
+
250
+ mock_post.return_value.json.return_value = {"id": 2, "name": "Jane Doe"}
251
+ mock_post.return_value.raise_for_status.return_value = None
252
+
253
+ user_data = {"name": "Jane Doe", "email": "jane@example.com"}
254
+ result = client.create_user(user_data)
255
+
256
+ assert result["id"] == 2
257
+ mock_post.assert_called_once()
258
+ call_args = mock_post.call_args
259
+ assert call_args.kwargs["json"] == user_data
260
+ ```
261
+
262
+ ### Pattern 5: Testing Exceptions
263
+
264
+ ```python
265
+ # test_exceptions.py
266
+ import pytest
267
+
268
+ def divide(a: float, b: float) -> float:
269
+ """Divide a by b."""
270
+ if b == 0:
271
+ raise ZeroDivisionError("Division by zero")
272
+ if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
273
+ raise TypeError("Arguments must be numbers")
274
+ return a / b
275
+
276
+
277
+ def test_zero_division():
278
+ """Test exception is raised for division by zero."""
279
+ with pytest.raises(ZeroDivisionError):
280
+ divide(10, 0)
281
+
282
+
283
+ def test_zero_division_with_message():
284
+ """Test exception message."""
285
+ with pytest.raises(ZeroDivisionError, match="Division by zero"):
286
+ divide(5, 0)
287
+
288
+
289
+ def test_type_error():
290
+ """Test type error exception."""
291
+ with pytest.raises(TypeError, match="must be numbers"):
292
+ divide("10", 5)
293
+
294
+
295
+ def test_exception_info():
296
+ """Test accessing exception info."""
297
+ with pytest.raises(ValueError) as exc_info:
298
+ int("not a number")
299
+
300
+ assert "invalid literal" in str(exc_info.value)
301
+ ```
302
+
303
+ For advanced patterns including async testing, monkeypatching, temporary files, conftest setup, property-based testing, database testing, CI/CD integration, and configuration files, see [references/advanced-patterns.md](references/advanced-patterns.md)
304
+
305
+ ## Test Design Principles
306
+
307
+ ### One Behavior Per Test
308
+
309
+ Each test should verify exactly one behavior. This makes failures easy to diagnose and tests easy to maintain.
310
+
311
+ ```python
312
+ # BAD - testing multiple behaviors
313
+ def test_user_service():
314
+ user = service.create_user(data)
315
+ assert user.id is not None
316
+ assert user.email == data["email"]
317
+ updated = service.update_user(user.id, {"name": "New"})
318
+ assert updated.name == "New"
319
+
320
+ # GOOD - focused tests
321
+ def test_create_user_assigns_id():
322
+ user = service.create_user(data)
323
+ assert user.id is not None
324
+
325
+ def test_create_user_stores_email():
326
+ user = service.create_user(data)
327
+ assert user.email == data["email"]
328
+
329
+ def test_update_user_changes_name():
330
+ user = service.create_user(data)
331
+ updated = service.update_user(user.id, {"name": "New"})
332
+ assert updated.name == "New"
333
+ ```
334
+
335
+ ### Test Error Paths
336
+
337
+ Always test failure cases, not just happy paths.
338
+
339
+ ```python
340
+ def test_get_user_raises_not_found():
341
+ with pytest.raises(UserNotFoundError) as exc_info:
342
+ service.get_user("nonexistent-id")
343
+
344
+ assert "nonexistent-id" in str(exc_info.value)
345
+
346
+ def test_create_user_rejects_invalid_email():
347
+ with pytest.raises(ValueError, match="Invalid email format"):
348
+ service.create_user({"email": "not-an-email"})
349
+ ```