python-skills 1.0.0__py3-none-any.whl

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 (105) hide show
  1. python_skills/__init__.py +10 -0
  2. python_skills/__main__.py +6 -0
  3. python_skills/adapters/__init__.py +48 -0
  4. python_skills/adapters/agent_skills.py +415 -0
  5. python_skills/adapters/aider_adapter.py +226 -0
  6. python_skills/adapters/base.py +153 -0
  7. python_skills/adapters/claude.py +474 -0
  8. python_skills/adapters/cline.py +332 -0
  9. python_skills/adapters/codex.py +24 -0
  10. python_skills/adapters/continue_adapter.py +198 -0
  11. python_skills/adapters/cursor.py +327 -0
  12. python_skills/adapters/gemini.py +26 -0
  13. python_skills/adapters/goose.py +26 -0
  14. python_skills/adapters/junie.py +25 -0
  15. python_skills/adapters/kiro.py +382 -0
  16. python_skills/adapters/opencode.py +27 -0
  17. python_skills/adapters/roo.py +25 -0
  18. python_skills/adapters/universal.py +203 -0
  19. python_skills/adapters/vscode.py +27 -0
  20. python_skills/adapters/windsurf.py +26 -0
  21. python_skills/adapters/zed.py +27 -0
  22. python_skills/cli.py +326 -0
  23. python_skills/config.py +160 -0
  24. python_skills/detector.py +152 -0
  25. python_skills/installer.py +163 -0
  26. python_skills/markers.py +115 -0
  27. python_skills/skills/__init__.py +14 -0
  28. python_skills/skills/loader.py +171 -0
  29. python_skills/skills/metadata.py +152 -0
  30. python_skills/skills/registry.py +101 -0
  31. python_skills/state.py +204 -0
  32. python_skills-1.0.0.dist-info/METADATA +99 -0
  33. python_skills-1.0.0.dist-info/RECORD +105 -0
  34. python_skills-1.0.0.dist-info/WHEEL +4 -0
  35. python_skills-1.0.0.dist-info/entry_points.txt +2 -0
  36. python_skills-1.0.0.dist-info/licenses/LICENSE +21 -0
  37. skills/advanced_python.md +239 -0
  38. skills/anti_patterns/index.md +406 -0
  39. skills/comprehensions.md +167 -0
  40. skills/control_flow.md +175 -0
  41. skills/data_structures.md +243 -0
  42. skills/debugging/common_bugs.md +222 -0
  43. skills/debugging/inspection_techniques.md +249 -0
  44. skills/debugging/root_cause.md +203 -0
  45. skills/engineering/application_logging.md +195 -0
  46. skills/engineering/cli_apps.md +207 -0
  47. skills/engineering/configuration.md +218 -0
  48. skills/engineering/database.md +240 -0
  49. skills/engineering/dependency_management.md +205 -0
  50. skills/engineering/http_clients.md +267 -0
  51. skills/engineering/modules_packages.md +211 -0
  52. skills/engineering/packaging.md +197 -0
  53. skills/engineering/project_structure.md +155 -0
  54. skills/engineering/pyproject_toml.md +302 -0
  55. skills/engineering/virtual_environments.md +206 -0
  56. skills/functions.md +244 -0
  57. skills/generation/async_concurrency.md +291 -0
  58. skills/generation/error_handling.md +276 -0
  59. skills/generation/protocols_generics.md +243 -0
  60. skills/generation/type_hints.md +290 -0
  61. skills/generation/validation_pipeline.md +274 -0
  62. skills/generation/workflow.md +190 -0
  63. skills/oop.md +228 -0
  64. skills/quality/abstractions.md +154 -0
  65. skills/quality/comments.md +177 -0
  66. skills/quality/documentation.md +176 -0
  67. skills/quality/duplication.md +137 -0
  68. skills/quality/maintainability.md +142 -0
  69. skills/quality/naming.md +171 -0
  70. skills/quality/quality_functions.md +245 -0
  71. skills/quality/readability.md +239 -0
  72. skills/quality/type_annotations.md +192 -0
  73. skills/refactoring/behavior_preservation.md +157 -0
  74. skills/refactoring/incremental.md +187 -0
  75. skills/refactoring/interface_stability.md +199 -0
  76. skills/refactoring/safe_refactoring.md +206 -0
  77. skills/security/auth_boundaries.md +200 -0
  78. skills/security/command_injection.md +207 -0
  79. skills/security/dependency_risks.md +282 -0
  80. skills/security/file_handling.md +156 -0
  81. skills/security/input_validation.md +190 -0
  82. skills/security/path_traversal.md +172 -0
  83. skills/security/secrets.md +171 -0
  84. skills/security/sql_injection.md +188 -0
  85. skills/security/unsafe_deserialization.md +164 -0
  86. skills/stdlib/argparse.md +178 -0
  87. skills/stdlib/collections.md +212 -0
  88. skills/stdlib/datetime.md +187 -0
  89. skills/stdlib/functools.md +238 -0
  90. skills/stdlib/itertools.md +183 -0
  91. skills/stdlib/json.md +162 -0
  92. skills/stdlib/logging.md +185 -0
  93. skills/stdlib/os_sys.md +184 -0
  94. skills/stdlib/pathlib.md +218 -0
  95. skills/stdlib/re.md +171 -0
  96. skills/stdlib/statistics.md +112 -0
  97. skills/stdlib/subprocess.md +211 -0
  98. skills/testing/async_tests.md +249 -0
  99. skills/testing/coverage.md +168 -0
  100. skills/testing/edge_cases.md +197 -0
  101. skills/testing/fixtures_mocks.md +203 -0
  102. skills/testing/organization.md +205 -0
  103. skills/testing/parameterized.md +174 -0
  104. skills/testing/regression_tests.md +165 -0
  105. skills/variables_types.md +107 -0
@@ -0,0 +1,249 @@
1
+ ---
2
+ name: testing_async_tests
3
+ purpose: Testing async code with pytest-asyncio
4
+ category: testing
5
+ triggers:
6
+ - async
7
+ - pytest-asyncio
8
+ - asyncio
9
+ - async test
10
+ - async fixture
11
+ - async mock
12
+ dependencies:
13
+ - generation/async_concurrency.md
14
+ - testing/organization.md
15
+ - testing/fixtures_mocks.md
16
+ - testing/edge_cases.md
17
+ priority: supporting
18
+ estimated_tokens: 2300
19
+ ---
20
+ # Testing: Async Tests
21
+
22
+ **Purpose**: Testing async code with pytest-asyncio.
23
+
24
+ **When to use**: Any async functions, services, or integrations.
25
+
26
+ ---
27
+
28
+ ## Core Rules
29
+
30
+ ### Setup
31
+ ```ini
32
+ # pytest.ini or pyproject.toml
33
+ [tool.pytest.ini_options]
34
+ asyncio_mode = "auto"
35
+ ```
36
+
37
+ ### Basic Async Test
38
+ ```python
39
+ import pytest
40
+ import pytest_asyncio
41
+
42
+ @pytest.mark.asyncio
43
+ async def test_async_function():
44
+ result = await async_function()
45
+ assert result == "expected"
46
+ ```
47
+
48
+ ### Async Fixtures
49
+ ```python
50
+ # conftest.py
51
+ import pytest_asyncio
52
+
53
+ @pytest_asyncio.fixture
54
+ async def async_client() -> AsyncClient:
55
+ client = AsyncClient()
56
+ yield client
57
+ await client.aclose()
58
+
59
+ @pytest_asyncio.fixture
60
+ async def db_pool() -> asyncpg.Pool:
61
+ pool = await asyncpg.create_pool("postgresql://test:test@localhost/test")
62
+ yield pool
63
+ await pool.close()
64
+ ```
65
+
66
+ ### Async Mocking
67
+ ```python
68
+ from unittest.mock import AsyncMock
69
+
70
+ @pytest.fixture
71
+ def mock_async_service():
72
+ service = AsyncMock()
73
+ service.fetch.return_value = {"data": "test"}
74
+ service.fetch.side_effect = [
75
+ {"data": "first"},
76
+ {"data": "second"},
77
+ Exception("error"),
78
+ ]
79
+ return service
80
+
81
+ @pytest.mark.asyncio
82
+ async def test_async_mock(mock_async_service):
83
+ result = await mock_async_service.fetch()
84
+ assert result == {"data": "first"}
85
+ mock_async_service.fetch.assert_awaited_once()
86
+ ```
87
+
88
+ ### Testing Async Generators
89
+ ```python
90
+ @pytest.mark.asyncio
91
+ async def test_async_generator():
92
+ results = []
93
+ async for item in async_generator():
94
+ results.append(item)
95
+ assert results == [1, 2, 3]
96
+
97
+ # Or collect
98
+ @pytest.mark.asyncio
99
+ async def test_async_generator_collect():
100
+ items = [item async for item in async_generator()]
101
+ assert items == [1, 2, 3]
102
+ ```
103
+
104
+ ### Testing Timeouts
105
+ ```python
106
+ import asyncio
107
+
108
+ @pytest.mark.asyncio
109
+ async def test_timeout():
110
+ async def slow():
111
+ await asyncio.sleep(10)
112
+ return "done"
113
+
114
+ with pytest.raises(asyncio.TimeoutError):
115
+ await asyncio.wait_for(slow(), timeout=0.1)
116
+ ```
117
+
118
+ ### Testing Concurrency
119
+ ```python
120
+ @pytest.mark.asyncio
121
+ async def test_concurrent_requests():
122
+ async def fetch(url):
123
+ await asyncio.sleep(0.1)
124
+ return url
125
+
126
+ urls = [f"http://example.com/{i}" for i in range(10)]
127
+ results = await asyncio.gather(*[fetch(u) for u in urls])
128
+ assert len(results) == 10
129
+
130
+ # With semaphore
131
+ @pytest.mark.asyncio
132
+ async def test_rate_limited():
133
+ sem = asyncio.Semaphore(2)
134
+
135
+ async def limited_fetch(url):
136
+ async with sem:
137
+ await asyncio.sleep(0.1)
138
+ return url
139
+
140
+ # Should take ~0.5s (10 tasks / 2 concurrent * 0.1s)
141
+ import time
142
+ start = time.monotonic()
143
+ await asyncio.gather(*[limited_fetch(f"url{i}") for i in range(10)])
144
+ elapsed = time.monotonic() - start
145
+ assert 0.4 < elapsed < 0.7
146
+ ```
147
+
148
+ ### Testing Cancellation
149
+ ```python
150
+ @pytest.mark.asyncio
151
+ async def test_cancellation():
152
+ async def long_running():
153
+ try:
154
+ await asyncio.sleep(10)
155
+ except asyncio.CancelledError:
156
+ # Cleanup
157
+ raise
158
+
159
+ task = asyncio.create_task(long_running())
160
+ await asyncio.sleep(0.01)
161
+ task.cancel()
162
+
163
+ with pytest.raises(asyncio.CancelledError):
164
+ await task
165
+ ```
166
+
167
+ ### TaskGroup (Python 3.11+)
168
+ ```python
169
+ @pytest.mark.asyncio
170
+ async def test_task_group():
171
+ async with asyncio.TaskGroup() as tg:
172
+ task1 = tg.create_task(fetch("url1"))
173
+ task2 = tg.create_task(fetch("url2"))
174
+
175
+ # All completed or exception raised
176
+ assert task1.result() == "url1"
177
+ assert task2.result() == "url2"
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Decision Rules
183
+
184
+ | Need | Pattern |
185
+ |------|---------|
186
+ | Async function | `@pytest.mark.asyncio` |
187
+ | Async fixture | `@pytest_asyncio.fixture` |
188
+ | External async service | `AsyncMock` |
189
+ | Timeout behavior | `asyncio.wait_for` |
190
+ | Cancellation | `task.cancel()` + `CancelledError` |
191
+ | Concurrent behavior | `asyncio.gather` / `TaskGroup` |
192
+
193
+ ---
194
+
195
+ ## Preferred Patterns
196
+
197
+ ```python
198
+ # Test with real async dependencies (integration)
199
+ @pytest.mark.asyncio
200
+ async def test_database_integration(db_pool):
201
+ async with db_pool.acquire() as conn:
202
+ await conn.execute("INSERT INTO test (value) VALUES ($1)", "test")
203
+ row = await conn.fetchrow("SELECT value FROM test WHERE value = $1", "test")
204
+ assert row["value"] == "test"
205
+
206
+ # Test retry logic
207
+ @pytest.mark.asyncio
208
+ async def test_retry_on_failure():
209
+ call_count = 0
210
+
211
+ async def flaky():
212
+ nonlocal call_count
213
+ call_count += 1
214
+ if call_count < 3:
215
+ raise ConnectionError("fail")
216
+ return "success"
217
+
218
+ result = await retry_async(flaky, attempts=3, base_delay=0.01)
219
+ assert result == "success"
220
+ assert call_count == 3
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Avoid
226
+
227
+ - Sync tests for async code
228
+ - `asyncio.run()` in tests (use pytest-asyncio)
229
+ - Blocking sleeps (`time.sleep`) in async tests
230
+ - Not awaiting mocks (`assert_awaited` vs `assert_called`)
231
+ - Shared event loop state between tests
232
+
233
+ ---
234
+
235
+ ## Validation Considerations
236
+
237
+ - `pytest-asyncio` handles event loop per test
238
+ - No "Event loop is closed" errors
239
+ - Timeout tests actually timeout (not hang)
240
+ - Cancellation cleanup verified
241
+
242
+ ---
243
+
244
+ ## Related Skills
245
+
246
+ - `generation/async_concurrency.md`
247
+ - `testing/organization.md`
248
+ - `testing/fixtures_mocks.md`
249
+ - `testing/edge_cases.md`
@@ -0,0 +1,168 @@
1
+ # Testing: Coverage
2
+
3
+ **Purpose**: Meaningful test coverage measurement and targets.
4
+
5
+ **When to use**: Configuring coverage, interpreting reports, setting thresholds.
6
+
7
+ ---
8
+
9
+ ## Core Rules
10
+
11
+ ### Coverage Types
12
+ | Type | Meaning |
13
+ |------|---------|
14
+ | **Line** | Executable lines executed |
15
+ | **Branch** | Decision branches taken (if/else, loops) |
16
+ | **Function** | Functions called |
17
+ | **Statement** | Similar to line |
18
+
19
+ ### Configuration
20
+ ```toml
21
+ # pyproject.toml
22
+ [tool.coverage.run]
23
+ source = ["src"]
24
+ omit = [
25
+ "tests/*",
26
+ "*/__main__.py",
27
+ "*/migrations/*",
28
+ "*/conftest.py",
29
+ ]
30
+ branch = true # Branch coverage (important!)
31
+
32
+ [tool.coverage.report]
33
+ exclude_lines = [
34
+ "pragma: no cover",
35
+ "def __repr__",
36
+ "raise AssertionError",
37
+ "raise NotImplementedError",
38
+ "if __name__ == .__main__.:",
39
+ "if TYPE_CHECKING:",
40
+ ]
41
+ precision = 2
42
+ show_missing = true
43
+
44
+ [tool.coverage.html]
45
+ directory = "htmlcov"
46
+ ```
47
+
48
+ ### Running Coverage
49
+ ```bash
50
+ # Basic
51
+ pytest --cov=mypackage --cov-report=term-missing
52
+
53
+ # With branch coverage
54
+ pytest --cov=mypackage --cov-branch --cov-report=term-missing
55
+
56
+ # HTML report
57
+ pytest --cov=mypackage --cov-report=html
58
+
59
+ # XML for CI
60
+ pytest --cov=mypackage --cov-report=xml
61
+ ```
62
+
63
+ ### Interpreting Coverage
64
+ ```text
65
+ Name Stmts Miss Branch BrMiss Cover Missing
66
+ ----------------------------------------------------------------------
67
+ src/mypackage/__init__ 5 0 0 0 100%
68
+ src/mypackage/models.py 50 2 10 2 92% 45-46
69
+ src/mypackage/service.py 120 15 30 8 85% 78-85, 92-95
70
+ src/mypackage/utils.py 30 0 4 0 100%
71
+ ----------------------------------------------------------------------
72
+ TOTAL 205 17 44 10 90%
73
+ ```
74
+
75
+ - **Line coverage**: 90% (188/205)
76
+ - **Branch coverage**: 77% (34/44) — more important!
77
+ - **Missing**: Shows exact lines/branches not covered
78
+
79
+ ### Coverage Targets
80
+ | Project Type | Line | Branch |
81
+ |--------------|------|--------|
82
+ | New project | 90%+ | 85%+ |
83
+ | Mature library | 95%+ | 90%+ |
84
+ | Application | 80%+ | 75%+ |
85
+ | Legacy | Improve incrementally | |
86
+
87
+ ### What Coverage Does NOT Measure
88
+ - **Logic correctness** — 100% coverage ≠ bug-free
89
+ - **Edge cases** — May cover line but not all values
90
+ - **Integration** — Unit coverage ≠ system works
91
+ - **Security** — Coverage doesn't check vulnerabilities
92
+
93
+ ---
94
+
95
+ ## Decision Rules
96
+
97
+ | Situation | Coverage Goal |
98
+ |-----------|---------------|
99
+ | New code (PR) | 100% line, 100% branch |
100
+ | Critical paths (auth, payments) | 100% branch |
101
+ | Legacy code | Incremental improvement |
102
+ | Generated code | Exclude (`pragma: no cover`) |
103
+ | Prototypes | No requirement |
104
+
105
+ ---
106
+
107
+ ## Preferred Patterns
108
+
109
+ ```python
110
+ # Exclude from coverage (rare, justify)
111
+ def __repr__(self) -> str: # pragma: no cover
112
+ return f"<{self.__class__.__name__}(id={self.id})>"
113
+
114
+ # Hard to test error paths
115
+ def connect(self): # pragma: no cover
116
+ try:
117
+ self._connect()
118
+ except OSError as e:
119
+ if e.errno == errno.ENETUNREACH:
120
+ raise NetworkUnreachable() from e
121
+ raise
122
+
123
+ # Branch coverage important here
124
+ def process(value: int) -> str:
125
+ if value > 0: # Branch 1
126
+ return "positive"
127
+ elif value < 0: # Branch 2
128
+ return "negative"
129
+ else: # Branch 3
130
+ return "zero"
131
+
132
+ # Test all 3 branches
133
+ @pytest.mark.parametrize("value,expected", [
134
+ (1, "positive"),
135
+ (-1, "negative"),
136
+ (0, "zero"),
137
+ ])
138
+ def test_process(value, expected):
139
+ assert process(value) == expected
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Avoid
145
+
146
+ - Coverage as only quality metric
147
+ - 100% coverage mandate (leads to bad tests)
148
+ - Excluding code without justification
149
+ - Not measuring branch coverage
150
+ - Testing getters/setters just for coverage
151
+
152
+ ---
153
+
154
+ ## Validation Considerations
155
+
156
+ - CI fails if coverage drops below threshold
157
+ - `pytest --cov-fail-under=80 --cov-branch --cov-fail-under-branch=75`
158
+ - Track coverage trends over time
159
+ - Focus on branch coverage for critical logic
160
+
161
+ ---
162
+
163
+ ## Related Skills
164
+
165
+ - `testing/organization.md`
166
+ - `testing/edge_cases.md`
167
+ - `testing/regression_tests.md`
168
+ - `quality/maintainability.md`
@@ -0,0 +1,197 @@
1
+ # Testing: Edge Cases
2
+
3
+ **Purpose**: Systematic edge case coverage for robust code.
4
+
5
+ **When to use**: Writing tests for any function with external input or complex logic.
6
+
7
+ ---
8
+
9
+ ## Core Rules
10
+
11
+ ### Edge Case Categories
12
+
13
+ | Category | Examples |
14
+ |----------|----------|
15
+ | **Empty/Zero** | `""`, `[]`, `{}`, `0`, `0.0`, `None` |
16
+ | **Boundary** | Min, max, min-1, max+1, off-by-one |
17
+ | **Special values** | `NaN`, `Inf`, `-0.0`, `True`/`False` as int |
18
+ | **Unicode** | Emoji, RTL, combining chars, null bytes |
19
+ | **Large** | Huge strings, deep nesting, many items |
20
+ | **Concurrent** | Race conditions, double-submit |
21
+ | **Failure** | Network error, timeout, disk full, permission denied |
22
+ | **Malformed** | Invalid encoding, truncated data, wrong type |
23
+
24
+ ### Parameterized Edge Cases
25
+ ```python
26
+ import pytest
27
+
28
+ # String edges
29
+ STRING_EDGES = [
30
+ ("", "empty"),
31
+ (" ", "whitespace"),
32
+ ("\t\n\r", "control-chars"),
33
+ ("a" * 10000, "long"),
34
+ ("🎉🎊", "emoji"),
35
+ ("\u200b", "zero-width"),
36
+ ("\x00", "null-byte"),
37
+ ("../../../etc/passwd", "traversal"),
38
+ ]
39
+
40
+ @pytest.mark.parametrize("value,case_id", STRING_EDGES, ids=lambda x: x[1])
41
+ def test_string_input(value: str, case_id: str):
42
+ result = process_string(value)
43
+ assert isinstance(result, str)
44
+
45
+ # Numeric edges
46
+ NUMERIC_EDGES = [
47
+ (0, "zero"),
48
+ (-1, "negative"),
49
+ (1, "positive"),
50
+ (2**63 - 1, "max-int64"),
51
+ (-(2**63), "min-int64"),
52
+ (float("inf"), "infinity"),
53
+ (float("-inf"), "neg-infinity"),
54
+ (float("nan"), "nan"),
55
+ (1.5, "float"),
56
+ ]
57
+
58
+ @pytest.mark.parametrize("value,case_id", NUMERIC_EDGES, ids=lambda x: x[1])
59
+ def test_numeric_input(value, case_id):
60
+ if isinstance(value, float) and (value != value or value in (float("inf"), float("-inf"))):
61
+ with pytest.raises(ValidationError):
62
+ process_number(value)
63
+ else:
64
+ result = process_number(value)
65
+ assert result == expected(value)
66
+ ```
67
+
68
+ ### Collection Edges
69
+ ```python
70
+ COLLECTION_EDGES = [
71
+ ([], "empty"),
72
+ ([1], "single"),
73
+ ([1, 2], "two"),
74
+ (list(range(10000)), "large"),
75
+ ([None, "", 0, False], "falsy-values"),
76
+ ([{"nested": {"deep": "value"}}], "nested"),
77
+ ]
78
+
79
+ @pytest.mark.parametrize("items,case_id", COLLECTION_EDGES, ids=lambda x: x[1])
80
+ def test_collection_processing(items, case_id):
81
+ result = process_items(items)
82
+ assert len(result) == len(items)
83
+ ```
84
+
85
+ ### None/Optional Edges
86
+ ```python
87
+ def test_optional_handling():
88
+ # Explicit None
89
+ assert process_optional(None) == default_value()
90
+
91
+ # Missing key vs None value
92
+ assert process_dict({}) == default_value()
93
+ assert process_dict({"key": None}) == default_value()
94
+ assert process_dict({"key": "value"}) == "value"
95
+ ```
96
+
97
+ ### Concurrency Edges
98
+ ```python
99
+ import threading
100
+ import time
101
+
102
+ def test_thread_safety(counter):
103
+ def increment():
104
+ for _ in range(1000):
105
+ counter.increment()
106
+
107
+ threads = [threading.Thread(target=increment) for _ in range(10)]
108
+ for t in threads: t.start()
109
+ for t in threads: t.join()
110
+
111
+ assert counter.value == 10000 # No race condition
112
+ ```
113
+
114
+ ### Time Edges
115
+ ```python
116
+ from freezegun import freeze_time
117
+
118
+ @freeze_time("2024-01-15 12:00:00")
119
+ def test_time_dependent():
120
+ assert get_current_timestamp() == "2024-01-15T12:00:00Z"
121
+
122
+ # Leap year, DST, timezone
123
+ @freeze_time("2024-02-29 23:59:59") # Leap day
124
+ def test_leap_year():
125
+ ...
126
+
127
+ @freeze_time("2024-03-10 02:30:00", tz_offset=-5) # DST transition
128
+ def test_dst():
129
+ ...
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Decision Rules
135
+
136
+ | Function Input | Edge Cases to Test |
137
+ |----------------|-------------------|
138
+ | String | Empty, whitespace, unicode, long, injection |
139
+ | Number | 0, negative, min/max, float special |
140
+ | List/Dict | Empty, single, large, nested, None elements |
141
+ | Optional | None, missing, empty string |
142
+ | Date/Time | Boundaries, DST, leap, timezone |
143
+ | File | Empty, large, missing, permission, symlink |
144
+ | Network | Timeout, 5xx, 4xx, malformed response |
145
+
146
+ ---
147
+
148
+ ## Preferred Patterns
149
+
150
+ ```python
151
+ # Property-based testing (hypothesis)
152
+ from hypothesis import given, strategies as st
153
+
154
+ @given(st.text())
155
+ def test_string_property(s: str):
156
+ # Should never crash on any string
157
+ result = safe_process(s)
158
+ assert isinstance(result, str)
159
+
160
+ @given(st.integers())
161
+ def test_int_property(n: int):
162
+ result = process_int(n)
163
+ assert result >= 0
164
+
165
+ @given(st.lists(st.integers()))
166
+ def test_list_property(items: list[int]):
167
+ result = process_list(items)
168
+ assert len(result) <= len(items)
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Avoid
174
+
175
+ - Only testing "happy path"
176
+ - Assuming input is always valid
177
+ - Missing boundary values (off-by-one)
178
+ - Not testing error handling paths
179
+ - Ignoring Unicode/timezone/concurrency
180
+
181
+ ---
182
+
183
+ ## Validation Considerations
184
+
185
+ - `pytest --cov` shows uncovered branches
186
+ - `hypothesis` finds unexpected edge cases
187
+ - Mutation testing (`mutmut`) verifies test quality
188
+ - Fuzzing for parsers/decoders
189
+
190
+ ---
191
+
192
+ ## Related Skills
193
+
194
+ - `testing/organization.md`
195
+ - `testing/parameterized.md`
196
+ - `security/input_validation.md`
197
+ - `generation/error_handling.md`