tlc-claude-code 0.6.4 → 0.7.1

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.
package/edge-cases.md ADDED
@@ -0,0 +1,340 @@
1
+ # /tlc:edge-cases - Generate Edge Case Tests
2
+
3
+ AI-generated edge case tests based on code analysis.
4
+
5
+ ## Usage
6
+
7
+ ```
8
+ /tlc:edge-cases [file-or-function]
9
+ ```
10
+
11
+ If no target specified, analyzes current phase tasks.
12
+
13
+ ## What This Does
14
+
15
+ 1. Analyzes function signatures and logic
16
+ 2. Identifies potential edge cases
17
+ 3. Generates test code for each edge case
18
+ 4. Optionally runs to verify they fail (Red phase)
19
+
20
+ ## Process
21
+
22
+ ### Step 1: Analyze Target Code
23
+
24
+ Read the function/file and extract:
25
+ - Parameter types and constraints
26
+ - Conditional branches
27
+ - Loop boundaries
28
+ - Error handling paths
29
+ - External dependencies
30
+
31
+ Example function:
32
+ ```typescript
33
+ async function login(email: string, password: string): Promise<User> {
34
+ if (!email || !password) {
35
+ throw new ValidationError('Email and password required');
36
+ }
37
+
38
+ const user = await db.findUserByEmail(email);
39
+ if (!user) {
40
+ throw new AuthError('User not found');
41
+ }
42
+
43
+ const valid = await bcrypt.compare(password, user.passwordHash);
44
+ if (!valid) {
45
+ throw new AuthError('Invalid password');
46
+ }
47
+
48
+ return user;
49
+ }
50
+ ```
51
+
52
+ ### Step 2: Generate Edge Cases
53
+
54
+ | Category | Edge Case | Test Description |
55
+ |----------|-----------|------------------|
56
+ | **Null/Undefined** | `email = null` | Should throw ValidationError |
57
+ | **Null/Undefined** | `password = undefined` | Should throw ValidationError |
58
+ | **Empty** | `email = ""` | Should throw ValidationError |
59
+ | **Empty** | `password = ""` | Should throw ValidationError |
60
+ | **Format** | `email = "not-an-email"` | Should throw ValidationError |
61
+ | **Format** | `email = "a@b"` | Minimal valid email - should work |
62
+ | **Whitespace** | `email = " user@test.com "` | Should trim or reject |
63
+ | **Case** | `email = "USER@TEST.COM"` | Should be case-insensitive |
64
+ | **Special Chars** | `email = "user+tag@test.com"` | Plus addressing valid |
65
+ | **Unicode** | `email = "用户@test.com"` | International email |
66
+ | **Length** | `password = "a"` | Too short - should reject |
67
+ | **Length** | `password = "a".repeat(1000)` | Very long - should handle |
68
+ | **SQL Injection** | `email = "'; DROP TABLE users;--"` | Should be safe |
69
+ | **XSS** | `email = "<script>alert(1)</script>"` | Should escape |
70
+ | **Not Found** | Valid email, user doesn't exist | Should throw AuthError |
71
+ | **Wrong Password** | Valid email, wrong password | Should throw AuthError |
72
+ | **Timing** | Multiple rapid attempts | Rate limiting check |
73
+ | **Async** | DB timeout | Should handle gracefully |
74
+
75
+ ### Step 3: Generate Test Code
76
+
77
+ ```javascript
78
+ const { expect } = require('chai');
79
+ const sinon = require('sinon');
80
+ const proxyquire = require('proxyquire');
81
+
82
+ describe('login - edge cases', () => {
83
+ let login, dbStub, bcryptStub;
84
+
85
+ beforeEach(() => {
86
+ dbStub = { findUserByEmail: sinon.stub() };
87
+ bcryptStub = { compare: sinon.stub() };
88
+ login = proxyquire('../src/auth/login', {
89
+ './db': dbStub,
90
+ 'bcrypt': bcryptStub
91
+ });
92
+ });
93
+
94
+ afterEach(() => sinon.restore());
95
+
96
+ describe('null/undefined inputs', () => {
97
+ it('throws ValidationError when email is null', async () => {
98
+ await expect(login(null, 'password'))
99
+ .to.be.rejectedWith('Email and password required');
100
+ });
101
+
102
+ it('throws ValidationError when email is undefined', async () => {
103
+ await expect(login(undefined, 'password'))
104
+ .to.be.rejectedWith('Email and password required');
105
+ });
106
+
107
+ it('throws ValidationError when password is null', async () => {
108
+ await expect(login('user@test.com', null))
109
+ .to.be.rejectedWith('Email and password required');
110
+ });
111
+
112
+ it('throws ValidationError when password is undefined', async () => {
113
+ await expect(login('user@test.com', undefined))
114
+ .to.be.rejectedWith('Email and password required');
115
+ });
116
+ });
117
+
118
+ describe('empty inputs', () => {
119
+ it('throws ValidationError when email is empty string', async () => {
120
+ await expect(login('', 'password'))
121
+ .to.be.rejectedWith('Email and password required');
122
+ });
123
+
124
+ it('throws ValidationError when password is empty string', async () => {
125
+ await expect(login('user@test.com', ''))
126
+ .to.be.rejectedWith('Email and password required');
127
+ });
128
+ });
129
+
130
+ describe('email format edge cases', () => {
131
+ it('accepts minimal valid email (a@b)', async () => {
132
+ dbStub.findUserByEmail.resolves({ passwordHash: 'hash' });
133
+ bcryptStub.compare.resolves(true);
134
+
135
+ const result = await login('a@b', 'password');
136
+ expect(result).to.exist;
137
+ });
138
+
139
+ it('accepts email with plus addressing (user+tag@test.com)', async () => {
140
+ dbStub.findUserByEmail.resolves({ passwordHash: 'hash' });
141
+ bcryptStub.compare.resolves(true);
142
+
143
+ await expect(login('user+tag@test.com', 'password'))
144
+ .to.not.be.rejected;
145
+ });
146
+
147
+ it('handles email with leading/trailing whitespace', async () => {
148
+ dbStub.findUserByEmail.resolves({ passwordHash: 'hash' });
149
+ bcryptStub.compare.resolves(true);
150
+
151
+ // Should either trim or reject consistently
152
+ await login(' user@test.com ', 'password');
153
+ expect(dbStub.findUserByEmail).to.have.been.calledWith('user@test.com');
154
+ });
155
+
156
+ it('handles email case-insensitively', async () => {
157
+ dbStub.findUserByEmail.resolves({ passwordHash: 'hash' });
158
+ bcryptStub.compare.resolves(true);
159
+
160
+ await login('USER@TEST.COM', 'password');
161
+ expect(dbStub.findUserByEmail).to.have.been.calledWith('user@test.com');
162
+ });
163
+ });
164
+
165
+ describe('security edge cases', () => {
166
+ it('safely handles SQL injection attempt in email', async () => {
167
+ dbStub.findUserByEmail.resolves(null);
168
+
169
+ await expect(login("'; DROP TABLE users;--", 'password'))
170
+ .to.be.rejectedWith('User not found');
171
+
172
+ // Verify the malicious string was passed as-is, not executed
173
+ expect(dbStub.findUserByEmail).to.have.been.calledWith("'; DROP TABLE users;--");
174
+ });
175
+
176
+ it('safely handles XSS attempt in email', async () => {
177
+ dbStub.findUserByEmail.resolves(null);
178
+
179
+ await expect(login('<script>alert(1)</script>@test.com', 'password'))
180
+ .to.be.rejectedWith('User not found');
181
+ });
182
+ });
183
+
184
+ describe('password edge cases', () => {
185
+ it('rejects very short password', async () => {
186
+ await expect(login('user@test.com', 'a'))
187
+ .to.be.rejectedWith(/too short|minimum/i);
188
+ });
189
+
190
+ it('handles very long password', async () => {
191
+ dbStub.findUserByEmail.resolves({ passwordHash: 'hash' });
192
+ bcryptStub.compare.resolves(true);
193
+
194
+ const longPassword = 'a'.repeat(1000);
195
+ await expect(login('user@test.com', longPassword))
196
+ .to.not.be.rejected;
197
+ });
198
+ });
199
+
200
+ describe('async/error edge cases', () => {
201
+ it('handles database timeout gracefully', async () => {
202
+ dbStub.findUserByEmail.rejects(new Error('Connection timeout'));
203
+
204
+ await expect(login('user@test.com', 'password'))
205
+ .to.be.rejectedWith('Connection timeout');
206
+ });
207
+
208
+ it('handles database connection error', async () => {
209
+ dbStub.findUserByEmail.rejects(new Error('ECONNREFUSED'));
210
+
211
+ await expect(login('user@test.com', 'password'))
212
+ .to.be.rejected;
213
+ });
214
+ });
215
+ });
216
+ ```
217
+
218
+ ### Step 4: Review and Customize
219
+
220
+ Present generated tests for review:
221
+
222
+ ```
223
+ Generated 18 edge case tests for login()
224
+
225
+ Categories:
226
+ ├── Null/Undefined: 4 tests
227
+ ├── Empty inputs: 2 tests
228
+ ├── Format: 4 tests
229
+ ├── Security: 2 tests
230
+ ├── Boundaries: 2 tests
231
+ └── Async/Error: 4 tests
232
+
233
+ Preview:
234
+ 1. ✓ throws when email is null
235
+ 2. ✓ throws when password is undefined
236
+ 3. ✓ accepts email with plus addressing
237
+ ...
238
+
239
+ Actions:
240
+ 1) Write all tests to tests/auth/login.edge-cases.test.js
241
+ 2) Select specific tests to include
242
+ 3) Preview full test code
243
+ 4) Cancel
244
+
245
+ Choice:
246
+ ```
247
+
248
+ ### Step 5: Write Tests
249
+
250
+ Save to appropriate test file:
251
+
252
+ ```
253
+ Writing edge case tests...
254
+
255
+ Created: tests/auth/login.edge-cases.test.js (18 tests)
256
+
257
+ Run tests? (Y/n) y
258
+
259
+ Running tests...
260
+ ✓ 12 passing
261
+ ✗ 6 failing (expected - edge cases not handled yet)
262
+
263
+ Failing tests reveal missing handling:
264
+ - No email trimming
265
+ - No case normalization
266
+ - No minimum password length check
267
+ - No rate limiting
268
+
269
+ These are now documented test cases to implement.
270
+ ```
271
+
272
+ ## Edge Case Patterns
273
+
274
+ ### Standard Patterns (Always Check)
275
+
276
+ | Pattern | Examples |
277
+ |---------|----------|
278
+ | **Null/Undefined** | `null`, `undefined` |
279
+ | **Empty** | `""`, `[]`, `{}` |
280
+ | **Whitespace** | `" "`, `"\t"`, `"\n"` |
281
+ | **Type Mismatch** | Number as string, object as array |
282
+ | **Boundaries** | 0, -1, MAX_INT, empty, single item |
283
+ | **Unicode** | Emojis, CJK characters, RTL text |
284
+ | **Special Chars** | `<>'"&;/\` |
285
+ | **Injection** | SQL, XSS, command injection patterns |
286
+
287
+ ### Domain-Specific Patterns
288
+
289
+ **Strings:**
290
+ - Very long (>1000 chars)
291
+ - Single character
292
+ - Only whitespace
293
+ - Only special characters
294
+
295
+ **Numbers:**
296
+ - Zero, negative, very large
297
+ - Decimal precision issues
298
+ - NaN, Infinity
299
+
300
+ **Arrays:**
301
+ - Empty, single item, very large
302
+ - Nested arrays
303
+ - Mixed types
304
+
305
+ **Dates:**
306
+ - Past, future, epoch
307
+ - Timezones, DST
308
+ - Invalid dates (Feb 30)
309
+
310
+ **Files:**
311
+ - Empty, very large
312
+ - Binary vs text
313
+ - Missing, permissions
314
+
315
+ ## Configuration
316
+
317
+ In `.tlc.json`:
318
+
319
+ ```json
320
+ {
321
+ "edgeCases": {
322
+ "patterns": ["null", "empty", "boundary", "security", "async"],
323
+ "customPatterns": [
324
+ {
325
+ "name": "currency",
326
+ "values": [0, 0.001, 0.009, 999999.99, -1]
327
+ }
328
+ ],
329
+ "outputDir": "tests/edge-cases",
330
+ "runAfterGenerate": true
331
+ }
332
+ }
333
+ ```
334
+
335
+ ## Notes
336
+
337
+ - Generated tests may reveal bugs (that's the point!)
338
+ - Review tests before committing - some may be invalid
339
+ - Use with `/tlc:quality` to track edge case coverage
340
+ - Re-run after significant code changes