plan-flow-skill 1.0.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 (124) hide show
  1. package/.claude/commands/create-contract.md +468 -0
  2. package/.claude/commands/create-plan.md +512 -0
  3. package/.claude/commands/discovery-plan.md +561 -0
  4. package/.claude/commands/execute-plan.md +682 -0
  5. package/.claude/commands/review-code.md +459 -0
  6. package/.claude/commands/review-pr.md +651 -0
  7. package/.claude/commands/setup.md +1609 -0
  8. package/.claude/commands/write-tests.md +543 -0
  9. package/.claude/rules/core/allowed-patterns.md +175 -0
  10. package/.claude/rules/core/complexity-scoring.md +225 -0
  11. package/.claude/rules/core/forbidden-patterns.md +253 -0
  12. package/.claude/rules/languages/python-patterns.md +6 -0
  13. package/.claude/rules/languages/typescript-patterns.md +7 -0
  14. package/.claude/rules/patterns/contract-patterns.md +332 -0
  15. package/.claude/rules/patterns/discovery-patterns.md +342 -0
  16. package/.claude/rules/patterns/discovery-templates.md +319 -0
  17. package/.claude/rules/patterns/jest-patterns.md +482 -0
  18. package/.claude/rules/patterns/plans-patterns.md +225 -0
  19. package/.claude/rules/patterns/plans-templates.md +227 -0
  20. package/.claude/rules/patterns/pytest-patterns.md +457 -0
  21. package/.claude/rules/patterns/review-code-templates.md +305 -0
  22. package/.claude/rules/patterns/review-pr-patterns.md +360 -0
  23. package/.claude/rules/tools/auth-pr-tool.md +30 -0
  24. package/.claude/rules/tools/interactive-questions-tool.md +235 -0
  25. package/.claude/rules/tools/jest-testing-tool.md +73 -0
  26. package/.claude/rules/tools/plan-mode-tool.md +164 -0
  27. package/.claude/rules/tools/pytest-testing-tool.md +121 -0
  28. package/.claude/rules/tools/reference-expansion-tool.md +326 -0
  29. package/LICENSE +21 -0
  30. package/README.md +167 -0
  31. package/dist/cli/commands/init.d.ts +6 -0
  32. package/dist/cli/commands/init.d.ts.map +1 -0
  33. package/dist/cli/commands/init.js +139 -0
  34. package/dist/cli/commands/init.js.map +1 -0
  35. package/dist/cli/handlers/claude.d.ts +9 -0
  36. package/dist/cli/handlers/claude.d.ts.map +1 -0
  37. package/dist/cli/handlers/claude.js +119 -0
  38. package/dist/cli/handlers/claude.js.map +1 -0
  39. package/dist/cli/handlers/codex.d.ts +9 -0
  40. package/dist/cli/handlers/codex.d.ts.map +1 -0
  41. package/dist/cli/handlers/codex.js +100 -0
  42. package/dist/cli/handlers/codex.js.map +1 -0
  43. package/dist/cli/handlers/cursor.d.ts +8 -0
  44. package/dist/cli/handlers/cursor.d.ts.map +1 -0
  45. package/dist/cli/handlers/cursor.js +34 -0
  46. package/dist/cli/handlers/cursor.js.map +1 -0
  47. package/dist/cli/handlers/openclaw.d.ts +8 -0
  48. package/dist/cli/handlers/openclaw.d.ts.map +1 -0
  49. package/dist/cli/handlers/openclaw.js +34 -0
  50. package/dist/cli/handlers/openclaw.js.map +1 -0
  51. package/dist/cli/handlers/shared.d.ts +9 -0
  52. package/dist/cli/handlers/shared.d.ts.map +1 -0
  53. package/dist/cli/handlers/shared.js +44 -0
  54. package/dist/cli/handlers/shared.js.map +1 -0
  55. package/dist/cli/index.d.ts +8 -0
  56. package/dist/cli/index.d.ts.map +1 -0
  57. package/dist/cli/index.js +43 -0
  58. package/dist/cli/index.js.map +1 -0
  59. package/dist/cli/types.d.ts +26 -0
  60. package/dist/cli/types.d.ts.map +1 -0
  61. package/dist/cli/types.js +5 -0
  62. package/dist/cli/types.js.map +1 -0
  63. package/dist/cli/utils/files.d.ts +37 -0
  64. package/dist/cli/utils/files.d.ts.map +1 -0
  65. package/dist/cli/utils/files.js +122 -0
  66. package/dist/cli/utils/files.js.map +1 -0
  67. package/dist/cli/utils/logger.d.ts +11 -0
  68. package/dist/cli/utils/logger.d.ts.map +1 -0
  69. package/dist/cli/utils/logger.js +34 -0
  70. package/dist/cli/utils/logger.js.map +1 -0
  71. package/dist/cli/utils/prompts.d.ts +10 -0
  72. package/dist/cli/utils/prompts.d.ts.map +1 -0
  73. package/dist/cli/utils/prompts.js +65 -0
  74. package/dist/cli/utils/prompts.js.map +1 -0
  75. package/dist/test/setup.d.ts +5 -0
  76. package/dist/test/setup.d.ts.map +1 -0
  77. package/dist/test/setup.js +7 -0
  78. package/dist/test/setup.js.map +1 -0
  79. package/package.json +63 -0
  80. package/rules/core/_index.mdc +89 -0
  81. package/rules/core/allowed-patterns.mdc +185 -0
  82. package/rules/core/complexity-scoring.mdc +235 -0
  83. package/rules/core/forbidden-patterns.mdc +263 -0
  84. package/rules/languages/_index.mdc +80 -0
  85. package/rules/languages/python-patterns.mdc +188 -0
  86. package/rules/languages/typescript-patterns.mdc +128 -0
  87. package/rules/patterns/_index.mdc +185 -0
  88. package/rules/patterns/contract-patterns.mdc +344 -0
  89. package/rules/patterns/discovery-patterns.mdc +354 -0
  90. package/rules/patterns/discovery-templates.mdc +329 -0
  91. package/rules/patterns/jest-patterns.mdc +492 -0
  92. package/rules/patterns/plans-patterns.mdc +237 -0
  93. package/rules/patterns/plans-templates.mdc +237 -0
  94. package/rules/patterns/pytest-patterns.mdc +467 -0
  95. package/rules/patterns/review-code-templates.mdc +315 -0
  96. package/rules/patterns/review-pr-patterns.mdc +370 -0
  97. package/rules/skills/_index.mdc +174 -0
  98. package/rules/skills/create-contract-skill.mdc +239 -0
  99. package/rules/skills/create-plan-skill.mdc +271 -0
  100. package/rules/skills/discovery-skill.mdc +295 -0
  101. package/rules/skills/execute-plan-skill.mdc +388 -0
  102. package/rules/skills/review-code-skill.mdc +308 -0
  103. package/rules/skills/review-pr-skill.mdc +496 -0
  104. package/rules/skills/setup-skill.mdc +923 -0
  105. package/rules/skills/write-tests-skill.mdc +294 -0
  106. package/rules/templates/index-template.mdc +126 -0
  107. package/rules/tools/_index.mdc +114 -0
  108. package/rules/tools/auth-pr-tool.mdc +362 -0
  109. package/rules/tools/interactive-questions-tool.mdc +337 -0
  110. package/rules/tools/jest-testing-tool.mdc +96 -0
  111. package/rules/tools/plan-mode-tool.mdc +229 -0
  112. package/rules/tools/pytest-testing-tool.mdc +144 -0
  113. package/rules/tools/reference-expansion-tool.mdc +338 -0
  114. package/skills/plan-flow/SKILL.md +109 -0
  115. package/skills/plan-flow/create-contract/SKILL.md +139 -0
  116. package/skills/plan-flow/create-plan/SKILL.md +93 -0
  117. package/skills/plan-flow/discovery/SKILL.md +85 -0
  118. package/skills/plan-flow/execute-plan/SKILL.md +89 -0
  119. package/skills/plan-flow/review-code/SKILL.md +100 -0
  120. package/skills/plan-flow/review-pr/SKILL.md +122 -0
  121. package/skills/plan-flow/setup/SKILL.md +73 -0
  122. package/skills/plan-flow/write-tests/SKILL.md +115 -0
  123. package/templates/shared/AGENTS.md.template +60 -0
  124. package/templates/shared/CLAUDE.md.template +62 -0
@@ -0,0 +1,227 @@
1
+
2
+ ## Plan Template
3
+
4
+ Use this template when creating new implementation plans:
5
+
6
+ ```markdown
7
+ # Plan: [Feature Name]
8
+
9
+ ## Overview
10
+
11
+ [Brief description of the feature and its purpose]
12
+
13
+ **Based on Discovery**: `flow/discovery/discovery_<feature>_v1.md` (or "Discovery skipped")
14
+
15
+ ## Goals
16
+
17
+ - [Goal 1]
18
+ - [Goal 2]
19
+ - [Goal 3]
20
+
21
+ ## Non-Goals
22
+
23
+ - [What this plan explicitly does NOT cover]
24
+
25
+ ## Requirements Summary
26
+
27
+ ### Functional Requirements
28
+
29
+ - [FR-1]: [Description]
30
+ - [FR-2]: [Description]
31
+
32
+ ### Non-Functional Requirements
33
+
34
+ - [NFR-1]: [Description]
35
+
36
+ ### Constraints
37
+
38
+ - [C-1]: [Description]
39
+
40
+ ## Risks
41
+
42
+ | Risk | Impact | Mitigation |
43
+ | -------- | --------------- | -------------------- |
44
+ | [Risk 1] | High/Medium/Low | [Mitigation strategy]|
45
+
46
+ ## Phases
47
+
48
+ ### Phase 1: [Phase Name]
49
+
50
+ **Scope**: [What this phase covers]
51
+ **Complexity**: X/10
52
+
53
+ - [ ] Task 1
54
+ - [ ] Task 2
55
+
56
+ **Build Verification**: Run `npm run build`
57
+
58
+ ### Phase 2: [Phase Name]
59
+
60
+ **Scope**: [What this phase covers]
61
+ **Complexity**: X/10
62
+
63
+ - [ ] Task 1
64
+ - [ ] Task 2
65
+
66
+ **Build Verification**: Run `npm run build`
67
+
68
+ ### Phase N: Tests (Final)
69
+
70
+ **Scope**: Write comprehensive tests
71
+ **Complexity**: X/10
72
+
73
+ - [ ] Unit tests for logic hooks
74
+ - [ ] Unit tests for utilities
75
+ - [ ] Integration tests
76
+
77
+ **Build Verification**: Run `npm run build && npm run test`
78
+
79
+ ## Key Changes
80
+
81
+ 1. **[Category]**: [Description of change]
82
+ 2. **[Category]**: [Description of change]
83
+ 3. **[Category]**: [Description of change]
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Phase Templates
89
+
90
+ ### Types and Schemas Phase
91
+
92
+ ```markdown
93
+ ### Phase 1: Types and Schemas
94
+
95
+ **Scope**: Define all TypeScript types and Zod schemas needed for the feature.
96
+ **Complexity**: 3/10
97
+
98
+ - [ ] Create type definitions in `/src/types/`
99
+ - [ ] Create Zod validation schemas in `/src/types/rest-inputs.ts`
100
+
101
+ **Build Verification**: Run `npm run build`
102
+ ```
103
+
104
+ ### Backend Implementation Phase
105
+
106
+ ```markdown
107
+ ### Phase 2: Backend Implementation
108
+
109
+ **Scope**: Implement API routes and commands.
110
+ **Complexity**: 7/10
111
+
112
+ - [ ] Create command in `/src/commands/`
113
+ - [ ] Create API route in `/src/app/api/`
114
+ - [ ] Add database operations if needed
115
+
116
+ **Build Verification**: Run `npm run build`
117
+ ```
118
+
119
+ ### Store and State Management Phase
120
+
121
+ ```markdown
122
+ ### Phase 3: Store and State Management
123
+
124
+ **Scope**: Implement Zustand stores for state management.
125
+ **Complexity**: 5/10
126
+
127
+ - [ ] Create or extend store in `/src/stores/`
128
+ - [ ] Add actions and selectors
129
+
130
+ **Build Verification**: Run `npm run build`
131
+ ```
132
+
133
+ ### UI Components Phase
134
+
135
+ ```markdown
136
+ ### Phase 4: UI Components
137
+
138
+ **Scope**: Build the user interface components.
139
+ **Complexity**: 6/10
140
+
141
+ - [ ] Create logic hook (`useFeatureLogic.internal.ts`)
142
+ - [ ] Create view component (`index.tsx`)
143
+ - [ ] Follow view/logic separation pattern
144
+
145
+ **Build Verification**: Run `npm run build`
146
+ ```
147
+
148
+ ### Integration Phase
149
+
150
+ ```markdown
151
+ ### Phase 5: Integration
152
+
153
+ **Scope**: Connect all pieces and verify functionality.
154
+ **Complexity**: 4/10
155
+
156
+ - [ ] Integrate components with pages
157
+ - [ ] Connect to API endpoints
158
+ - [ ] Verify end-to-end flow
159
+
160
+ **Build Verification**: Run `npm run build`
161
+ ```
162
+
163
+ ### Tests Phase (Always Last)
164
+
165
+ ```markdown
166
+ ### Phase N: Tests (Final)
167
+
168
+ **Scope**: Write comprehensive tests for all new code.
169
+ **Complexity**: 5/10
170
+
171
+ - [ ] Unit tests for logic hooks (`*.client.test.ts`)
172
+ - [ ] Unit tests for utility functions
173
+ - [ ] Integration tests for API routes
174
+ - [ ] Command tests
175
+
176
+ **Build Verification**: Run `npm run build && npm run test`
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Key Changes Examples
182
+
183
+ ```markdown
184
+ ## Key Changes
185
+
186
+ 1. **New API Endpoint**: Added `/api/v1/agents/workflow` for workflow management
187
+ 2. **Database Schema**: New `workflow_steps` table with foreign key to `agents`
188
+ 3. **Store Update**: Extended `agentStore` with workflow state management
189
+ 4. **Component Addition**: New `WorkflowEditor` component in `/src/components/agent/`
190
+ 5. **Type Definitions**: Added `WorkflowStep` and `WorkflowConfig` types in `/src/types/workflow.ts`
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Complexity Summary Example
196
+
197
+ ```markdown
198
+ ## Complexity Summary
199
+
200
+ | Phase | Complexity | Description |
201
+ | ----- | ---------- | ------------------------- |
202
+ | 1 | 3/10 | Types and schemas |
203
+ | 2 | 7/10 | Backend implementation |
204
+ | 3 | 5/10 | Store and state |
205
+ | 4 | 6/10 | UI components |
206
+ | 5 | 4/10 | Integration |
207
+ | 6 | 5/10 | Tests |
208
+
209
+ **Total Phases**: 6
210
+ **Average Complexity**: 5/10
211
+ **Highest Complexity**: Phase 2 at 7/10
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Execution Strategy Example
217
+
218
+ ```markdown
219
+ ## Execution Strategy
220
+
221
+ Based on complexity scores (per `.claude/rules/core/complexity-scoring.md`):
222
+
223
+ - **Phases 1-2**: Execute separately (Phase 2 is 7/10)
224
+ - **Phases 3-4**: Can be aggregated (combined complexity: 11, but cautious)
225
+ - **Phase 5**: Can aggregate with Phase 6 if simple
226
+ - **Phase 6**: Tests - always execute separately
227
+ ```
@@ -0,0 +1,457 @@
1
+
2
+ ## File Naming Conventions
3
+
4
+ | Pattern | Purpose |
5
+ | ------- | ------- |
6
+ | `test_*.py` | Test modules (preferred) |
7
+ | `*_test.py` | Alternative test module naming |
8
+ | `conftest.py` | Shared fixtures for a directory |
9
+
10
+ **Directory Structure**:
11
+
12
+ ```
13
+ src/
14
+ ├── mymodule/
15
+ │ ├── __init__.py
16
+ │ ├── service.py
17
+ │ └── utils.py
18
+ tests/
19
+ ├── conftest.py # Root fixtures
20
+ ├── test_service.py # Tests for service.py
21
+ ├── test_utils.py # Tests for utils.py
22
+ └── integration/
23
+ ├── conftest.py # Integration-specific fixtures
24
+ └── test_api.py
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Test Structure
30
+
31
+ Use descriptive function names and organize with classes when grouping:
32
+
33
+ ```python
34
+ import pytest
35
+ from mymodule.service import UserService
36
+ from mymodule.errors import NotFoundError
37
+
38
+
39
+ class TestUserService:
40
+ """Tests for UserService class."""
41
+
42
+ def test_get_user_returns_user_when_exists(self, mock_db):
43
+ """Should return user data when user exists in database."""
44
+ # Arrange
45
+ mock_db.find_user.return_value = {"id": "user-1", "name": "Test User"}
46
+ service = UserService(mock_db)
47
+
48
+ # Act
49
+ result = service.get_user("user-1")
50
+
51
+ # Assert
52
+ assert result["id"] == "user-1"
53
+ assert result["name"] == "Test User"
54
+ mock_db.find_user.assert_called_once_with("user-1")
55
+
56
+ def test_get_user_raises_not_found_when_missing(self, mock_db):
57
+ """Should raise NotFoundError when user doesn't exist."""
58
+ mock_db.find_user.return_value = None
59
+ service = UserService(mock_db)
60
+
61
+ with pytest.raises(NotFoundError) as exc_info:
62
+ service.get_user("nonexistent")
63
+
64
+ assert "user-1" in str(exc_info.value)
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Fixtures
70
+
71
+ Use fixtures for reusable test setup:
72
+
73
+ ```python
74
+ # conftest.py - Shared fixtures
75
+ import pytest
76
+ from unittest.mock import Mock, MagicMock, AsyncMock
77
+
78
+
79
+ @pytest.fixture
80
+ def mock_db():
81
+ """Provides a mocked database connection."""
82
+ db = Mock()
83
+ db.find_user = Mock()
84
+ db.create_user = Mock()
85
+ db.update_user = Mock()
86
+ return db
87
+
88
+
89
+ @pytest.fixture
90
+ def mock_user():
91
+ """Provides a mock user object."""
92
+ return {
93
+ "id": "user-1",
94
+ "email": "test@example.com",
95
+ "name": "Test User",
96
+ "created_at": "2024-01-01T00:00:00Z",
97
+ }
98
+
99
+
100
+ @pytest.fixture
101
+ def mock_chat(mock_user):
102
+ """Provides a mock chat with users."""
103
+ return {
104
+ "id": "chat-1",
105
+ "name": "Test Chat",
106
+ "tenant_id": "tenant-1",
107
+ "users": [mock_user],
108
+ }
109
+
110
+
111
+ @pytest.fixture
112
+ async def mock_async_client():
113
+ """Provides an async HTTP client mock."""
114
+ client = AsyncMock()
115
+ client.get = AsyncMock()
116
+ client.post = AsyncMock()
117
+ return client
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Factory Fixtures
123
+
124
+ Create parameterized factory fixtures for flexibility:
125
+
126
+ ```python
127
+ @pytest.fixture
128
+ def create_mock_user():
129
+ """Factory fixture for creating mock users with custom attributes."""
130
+ def _create_user(**overrides):
131
+ defaults = {
132
+ "id": "user-1",
133
+ "email": "test@example.com",
134
+ "name": "Test User",
135
+ "active": True,
136
+ "created_at": "2024-01-01T00:00:00Z",
137
+ }
138
+ return {**defaults, **overrides}
139
+ return _create_user
140
+
141
+
142
+ @pytest.fixture
143
+ def create_mock_message(create_mock_user):
144
+ """Factory fixture for creating mock messages."""
145
+ def _create_message(**overrides):
146
+ defaults = {
147
+ "id": "msg-1",
148
+ "chat_id": "chat-1",
149
+ "user": create_mock_user(),
150
+ "content": "Test message",
151
+ "role": "user",
152
+ }
153
+ return {**defaults, **overrides}
154
+ return _create_message
155
+
156
+
157
+ # Usage in tests
158
+ def test_message_creation(create_mock_message):
159
+ message = create_mock_message(content="Custom content", role="assistant")
160
+ assert message["content"] == "Custom content"
161
+ assert message["role"] == "assistant"
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Mocking
167
+
168
+ ### Basic Mocking with unittest.mock
169
+
170
+ ```python
171
+ from unittest.mock import Mock, MagicMock, patch, AsyncMock
172
+
173
+
174
+ class TestStreamService:
175
+ def test_stream_response_success(self):
176
+ """Should stream response from engine."""
177
+ mock_engine = Mock()
178
+ mock_engine.stream_response.return_value = iter(["chunk1", "chunk2"])
179
+
180
+ service = StreamService(mock_engine)
181
+ result = list(service.stream("Hello"))
182
+
183
+ assert result == ["chunk1", "chunk2"]
184
+ mock_engine.stream_response.assert_called_once_with("Hello")
185
+
186
+ @patch("mymodule.service.JupiterEngineAPI")
187
+ def test_with_patched_dependency(self, MockEngineAPI):
188
+ """Should use patched engine API."""
189
+ mock_instance = MockEngineAPI.return_value
190
+ mock_instance.get_agent.return_value = {"id": "agent-1"}
191
+
192
+ service = AgentService()
193
+ result = service.get_agent("agent-1")
194
+
195
+ assert result["id"] == "agent-1"
196
+ ```
197
+
198
+ ### Async Mocking
199
+
200
+ ```python
201
+ import pytest
202
+ from unittest.mock import AsyncMock, patch
203
+
204
+
205
+ class TestAsyncOperations:
206
+ @pytest.mark.asyncio
207
+ async def test_async_fetch_data(self):
208
+ """Should fetch data asynchronously."""
209
+ mock_client = AsyncMock()
210
+ mock_client.get.return_value = {"data": "test"}
211
+
212
+ service = AsyncService(mock_client)
213
+ result = await service.fetch_data("endpoint")
214
+
215
+ assert result["data"] == "test"
216
+ mock_client.get.assert_awaited_once_with("endpoint")
217
+
218
+ @pytest.mark.asyncio
219
+ @patch("mymodule.service.aiohttp.ClientSession")
220
+ async def test_with_patched_async_client(self, MockSession):
221
+ """Should work with patched async client."""
222
+ mock_session = AsyncMock()
223
+ mock_response = AsyncMock()
224
+ mock_response.json.return_value = {"status": "ok"}
225
+ mock_session.get.return_value.__aenter__.return_value = mock_response
226
+ MockSession.return_value.__aenter__.return_value = mock_session
227
+
228
+ result = await fetch_external_data()
229
+ assert result["status"] == "ok"
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Parametrized Tests
235
+
236
+ Use `@pytest.mark.parametrize` for testing multiple cases:
237
+
238
+ ```python
239
+ import pytest
240
+
241
+
242
+ class TestValidator:
243
+ @pytest.mark.parametrize("email,expected", [
244
+ ("test@example.com", True),
245
+ ("user@domain.org", True),
246
+ ("invalid-email", False),
247
+ ("@no-local-part.com", False),
248
+ ("no-domain@", False),
249
+ ("", False),
250
+ ])
251
+ def test_email_validation(self, email, expected):
252
+ """Should correctly validate email addresses."""
253
+ result = validate_email(email)
254
+ assert result == expected
255
+
256
+ @pytest.mark.parametrize("input_data,error_type", [
257
+ ({"chat_id": None}, ValueError),
258
+ ({"messages": []}, EmptyMessagesError),
259
+ ({"tenant_id": ""}, InvalidTenantError),
260
+ ])
261
+ def test_input_validation_errors(self, input_data, error_type):
262
+ """Should raise appropriate errors for invalid inputs."""
263
+ with pytest.raises(error_type):
264
+ validate_input(input_data)
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Testing Exceptions
270
+
271
+ ```python
272
+ import pytest
273
+ from mymodule.errors import NotFoundError, ForbiddenError
274
+
275
+
276
+ class TestErrorHandling:
277
+ def test_raises_not_found_error(self, mock_db):
278
+ """Should raise NotFoundError when resource doesn't exist."""
279
+ mock_db.find.return_value = None
280
+ service = MyService(mock_db)
281
+
282
+ with pytest.raises(NotFoundError) as exc_info:
283
+ service.get_item("nonexistent")
284
+
285
+ assert exc_info.value.resource_id == "nonexistent"
286
+ assert "not found" in str(exc_info.value).lower()
287
+
288
+ def test_raises_forbidden_with_details(self, mock_db):
289
+ """Should raise ForbiddenError with proper details."""
290
+ mock_db.check_permission.return_value = False
291
+ service = MyService(mock_db)
292
+
293
+ with pytest.raises(ForbiddenError) as exc_info:
294
+ service.access_resource("resource-1", user_id="user-1")
295
+
296
+ error = exc_info.value
297
+ assert error.reason == "INSUFFICIENT_PERMISSIONS"
298
+ assert error.user_id == "user-1"
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Testing Context Managers
304
+
305
+ ```python
306
+ from contextlib import contextmanager
307
+ from unittest.mock import Mock
308
+
309
+
310
+ class TestContextManager:
311
+ def test_database_transaction(self, mock_db):
312
+ """Should properly manage database transaction."""
313
+ mock_db.begin_transaction = Mock()
314
+ mock_db.commit = Mock()
315
+ mock_db.rollback = Mock()
316
+
317
+ with database_transaction(mock_db) as tx:
318
+ tx.execute("INSERT INTO users ...")
319
+
320
+ mock_db.begin_transaction.assert_called_once()
321
+ mock_db.commit.assert_called_once()
322
+ mock_db.rollback.assert_not_called()
323
+
324
+ def test_transaction_rollback_on_error(self, mock_db):
325
+ """Should rollback transaction on error."""
326
+ mock_db.begin_transaction = Mock()
327
+ mock_db.commit = Mock()
328
+ mock_db.rollback = Mock()
329
+
330
+ with pytest.raises(ValueError):
331
+ with database_transaction(mock_db) as tx:
332
+ raise ValueError("Simulated error")
333
+
334
+ mock_db.rollback.assert_called_once()
335
+ mock_db.commit.assert_not_called()
336
+ ```
337
+
338
+ ---
339
+
340
+ ## Async Testing
341
+
342
+ ```python
343
+ import pytest
344
+ import asyncio
345
+
346
+
347
+ class TestAsyncService:
348
+ @pytest.mark.asyncio
349
+ async def test_async_operation_success(self, mock_async_client):
350
+ """Should complete async operation successfully."""
351
+ mock_async_client.post.return_value = {"status": "success"}
352
+
353
+ service = AsyncService(mock_async_client)
354
+ result = await service.create_resource({"name": "test"})
355
+
356
+ assert result["status"] == "success"
357
+
358
+ @pytest.mark.asyncio
359
+ async def test_concurrent_operations(self):
360
+ """Should handle concurrent operations correctly."""
361
+ async def mock_operation(delay, value):
362
+ await asyncio.sleep(delay)
363
+ return value
364
+
365
+ results = await asyncio.gather(
366
+ mock_operation(0.1, "first"),
367
+ mock_operation(0.05, "second"),
368
+ mock_operation(0.15, "third"),
369
+ )
370
+
371
+ assert results == ["first", "second", "third"]
372
+
373
+ @pytest.mark.asyncio
374
+ async def test_async_timeout(self, mock_async_client):
375
+ """Should handle timeout correctly."""
376
+ mock_async_client.get.side_effect = asyncio.TimeoutError()
377
+
378
+ service = AsyncService(mock_async_client)
379
+
380
+ with pytest.raises(ServiceTimeoutError):
381
+ await service.fetch_with_timeout("endpoint", timeout=1.0)
382
+ ```
383
+
384
+ ---
385
+
386
+ ## Test Naming
387
+
388
+ - Use descriptive names that explain the expected behavior
389
+ - Describe the outcome in the function name
390
+ - Include context about the scenario
391
+
392
+ ```python
393
+ # Good
394
+ def test_raises_not_found_error_when_chat_missing(self):
395
+ def test_extracts_text_from_multipart_message(self):
396
+
397
+ # Bad
398
+ def test_1(self):
399
+ def test_chat(self):
400
+ ```
401
+
402
+ ---
403
+
404
+ ## Forbidden Patterns
405
+
406
+ ### DON'T Share State Between Tests
407
+
408
+ ```python
409
+ # BAD - Shared mutable state
410
+ class TestService:
411
+ shared_data = [] # Dangerous!
412
+
413
+ def test_one(self):
414
+ self.shared_data.append("item")
415
+
416
+ def test_two(self):
417
+ # Depends on test_one running first
418
+ assert len(self.shared_data) == 1
419
+
420
+ # GOOD - Fresh fixtures
421
+ @pytest.fixture
422
+ def data_list():
423
+ return []
424
+
425
+ def test_one(data_list):
426
+ data_list.append("item")
427
+ assert len(data_list) == 1
428
+
429
+ def test_two(data_list):
430
+ assert len(data_list) == 0 # Fresh list
431
+ ```
432
+
433
+ ### DON'T Write Tests That Always Pass
434
+
435
+ ```python
436
+ # BAD - No real assertion
437
+ def test_something():
438
+ some_function()
439
+ assert True
440
+
441
+ # GOOD - Meaningful assertion
442
+ def test_returns_expected_data():
443
+ result = some_function()
444
+ assert result.data == expected_data
445
+ ```
446
+
447
+ ---
448
+
449
+ ## Summary
450
+
451
+ Following these testing patterns ensures:
452
+
453
+ - **Consistency**: Same patterns across the codebase
454
+ - **Maintainability**: Tests are easy to understand and update
455
+ - **Reliability**: Tests are isolated and deterministic
456
+ - **Coverage**: Edge cases and errors are tested
457
+ - **Speed**: Tests run efficiently with proper mocking