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/CLAUDE.md +59 -0
- package/README.md +164 -121
- package/autofix.md +327 -0
- package/bin/install.js +23 -2
- package/bug.md +255 -0
- package/build.md +167 -21
- package/ci.md +414 -0
- package/claim.md +189 -0
- package/config.md +236 -0
- package/deploy.md +516 -0
- package/docs.md +494 -0
- package/edge-cases.md +340 -0
- package/export.md +456 -0
- package/help.md +84 -1
- package/init.md +56 -7
- package/issues.md +376 -0
- package/new-project.md +68 -4
- package/package.json +4 -2
- package/plan.md +15 -1
- package/progress.md +17 -0
- package/quality.md +273 -0
- package/release.md +135 -0
- package/server/dashboard/index.html +708 -0
- package/server/index.js +406 -0
- package/server/lib/plan-parser.js +146 -0
- package/server/lib/project-detector.js +301 -0
- package/server/package.json +19 -0
- package/server.md +742 -0
- package/who.md +151 -0
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
|