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