agentic-team-templates 0.3.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.
- package/README.md +280 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/src/index.js +521 -0
- package/templates/_shared/code-quality.md +162 -0
- package/templates/_shared/communication.md +114 -0
- package/templates/_shared/core-principles.md +62 -0
- package/templates/_shared/git-workflow.md +165 -0
- package/templates/_shared/security-fundamentals.md +173 -0
- package/templates/blockchain/.cursorrules/defi-patterns.md +520 -0
- package/templates/blockchain/.cursorrules/gas-optimization.md +339 -0
- package/templates/blockchain/.cursorrules/overview.md +130 -0
- package/templates/blockchain/.cursorrules/security.md +318 -0
- package/templates/blockchain/.cursorrules/smart-contracts.md +364 -0
- package/templates/blockchain/.cursorrules/testing.md +415 -0
- package/templates/blockchain/.cursorrules/web3-integration.md +538 -0
- package/templates/blockchain/CLAUDE.md +389 -0
- package/templates/cli-tools/.cursorrules/architecture.md +412 -0
- package/templates/cli-tools/.cursorrules/arguments.md +406 -0
- package/templates/cli-tools/.cursorrules/distribution.md +546 -0
- package/templates/cli-tools/.cursorrules/error-handling.md +455 -0
- package/templates/cli-tools/.cursorrules/overview.md +136 -0
- package/templates/cli-tools/.cursorrules/testing.md +537 -0
- package/templates/cli-tools/.cursorrules/user-experience.md +545 -0
- package/templates/cli-tools/CLAUDE.md +356 -0
- package/templates/data-engineering/.cursorrules/data-modeling.md +367 -0
- package/templates/data-engineering/.cursorrules/data-quality.md +455 -0
- package/templates/data-engineering/.cursorrules/overview.md +85 -0
- package/templates/data-engineering/.cursorrules/performance.md +339 -0
- package/templates/data-engineering/.cursorrules/pipeline-design.md +280 -0
- package/templates/data-engineering/.cursorrules/security.md +460 -0
- package/templates/data-engineering/.cursorrules/testing.md +452 -0
- package/templates/data-engineering/CLAUDE.md +974 -0
- package/templates/devops-sre/.cursorrules/capacity-planning.md +653 -0
- package/templates/devops-sre/.cursorrules/change-management.md +584 -0
- package/templates/devops-sre/.cursorrules/chaos-engineering.md +651 -0
- package/templates/devops-sre/.cursorrules/disaster-recovery.md +641 -0
- package/templates/devops-sre/.cursorrules/incident-management.md +565 -0
- package/templates/devops-sre/.cursorrules/observability.md +714 -0
- package/templates/devops-sre/.cursorrules/overview.md +230 -0
- package/templates/devops-sre/.cursorrules/postmortems.md +588 -0
- package/templates/devops-sre/.cursorrules/runbooks.md +760 -0
- package/templates/devops-sre/.cursorrules/slo-sli.md +617 -0
- package/templates/devops-sre/.cursorrules/toil-reduction.md +567 -0
- package/templates/devops-sre/CLAUDE.md +1007 -0
- package/templates/documentation/.cursorrules/adr.md +277 -0
- package/templates/documentation/.cursorrules/api-documentation.md +411 -0
- package/templates/documentation/.cursorrules/code-comments.md +253 -0
- package/templates/documentation/.cursorrules/maintenance.md +260 -0
- package/templates/documentation/.cursorrules/overview.md +82 -0
- package/templates/documentation/.cursorrules/readme-standards.md +306 -0
- package/templates/documentation/CLAUDE.md +120 -0
- package/templates/fullstack/.cursorrules/api-contracts.md +331 -0
- package/templates/fullstack/.cursorrules/architecture.md +298 -0
- package/templates/fullstack/.cursorrules/overview.md +109 -0
- package/templates/fullstack/.cursorrules/shared-types.md +348 -0
- package/templates/fullstack/.cursorrules/testing.md +386 -0
- package/templates/fullstack/CLAUDE.md +349 -0
- package/templates/ml-ai/.cursorrules/data-engineering.md +483 -0
- package/templates/ml-ai/.cursorrules/deployment.md +601 -0
- package/templates/ml-ai/.cursorrules/model-development.md +538 -0
- package/templates/ml-ai/.cursorrules/monitoring.md +658 -0
- package/templates/ml-ai/.cursorrules/overview.md +131 -0
- package/templates/ml-ai/.cursorrules/security.md +637 -0
- package/templates/ml-ai/.cursorrules/testing.md +678 -0
- package/templates/ml-ai/CLAUDE.md +1136 -0
- package/templates/mobile/.cursorrules/navigation.md +246 -0
- package/templates/mobile/.cursorrules/offline-first.md +302 -0
- package/templates/mobile/.cursorrules/overview.md +71 -0
- package/templates/mobile/.cursorrules/performance.md +345 -0
- package/templates/mobile/.cursorrules/testing.md +339 -0
- package/templates/mobile/CLAUDE.md +233 -0
- package/templates/platform-engineering/.cursorrules/ci-cd.md +778 -0
- package/templates/platform-engineering/.cursorrules/developer-experience.md +632 -0
- package/templates/platform-engineering/.cursorrules/infrastructure-as-code.md +600 -0
- package/templates/platform-engineering/.cursorrules/kubernetes.md +710 -0
- package/templates/platform-engineering/.cursorrules/observability.md +747 -0
- package/templates/platform-engineering/.cursorrules/overview.md +215 -0
- package/templates/platform-engineering/.cursorrules/security.md +855 -0
- package/templates/platform-engineering/.cursorrules/testing.md +878 -0
- package/templates/platform-engineering/CLAUDE.md +850 -0
- package/templates/utility-agent/.cursorrules/action-control.md +284 -0
- package/templates/utility-agent/.cursorrules/context-management.md +186 -0
- package/templates/utility-agent/.cursorrules/hallucination-prevention.md +253 -0
- package/templates/utility-agent/.cursorrules/overview.md +78 -0
- package/templates/utility-agent/.cursorrules/token-optimization.md +369 -0
- package/templates/utility-agent/CLAUDE.md +513 -0
- package/templates/web-backend/.cursorrules/api-design.md +255 -0
- package/templates/web-backend/.cursorrules/authentication.md +309 -0
- package/templates/web-backend/.cursorrules/database-patterns.md +298 -0
- package/templates/web-backend/.cursorrules/error-handling.md +366 -0
- package/templates/web-backend/.cursorrules/overview.md +69 -0
- package/templates/web-backend/.cursorrules/security.md +358 -0
- package/templates/web-backend/.cursorrules/testing.md +395 -0
- package/templates/web-backend/CLAUDE.md +366 -0
- package/templates/web-frontend/.cursorrules/accessibility.md +296 -0
- package/templates/web-frontend/.cursorrules/component-patterns.md +204 -0
- package/templates/web-frontend/.cursorrules/overview.md +72 -0
- package/templates/web-frontend/.cursorrules/performance.md +325 -0
- package/templates/web-frontend/.cursorrules/state-management.md +227 -0
- package/templates/web-frontend/.cursorrules/styling.md +271 -0
- package/templates/web-frontend/.cursorrules/testing.md +311 -0
- package/templates/web-frontend/CLAUDE.md +399 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
# CLI Testing
|
|
2
|
+
|
|
3
|
+
Strategies for testing command-line applications: unit tests, integration tests, and mocking I/O.
|
|
4
|
+
|
|
5
|
+
## Testing Philosophy
|
|
6
|
+
|
|
7
|
+
1. **Test behavior, not implementation** - Verify what the CLI does, not how
|
|
8
|
+
2. **Test at multiple levels** - Unit, integration, and end-to-end
|
|
9
|
+
3. **Test edge cases** - Invalid inputs, missing files, network errors
|
|
10
|
+
4. **Test non-interactively** - Support CI/CD environments
|
|
11
|
+
|
|
12
|
+
## Unit Testing
|
|
13
|
+
|
|
14
|
+
### Testing Business Logic
|
|
15
|
+
|
|
16
|
+
Separate business logic from CLI framework for easy testing:
|
|
17
|
+
|
|
18
|
+
```go
|
|
19
|
+
// Go: Testable business logic
|
|
20
|
+
// lib/build.go
|
|
21
|
+
func Build(config *BuildConfig) (*BuildResult, error) {
|
|
22
|
+
// Pure business logic, no CLI dependencies
|
|
23
|
+
// Easy to test in isolation
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// cmd/build.go
|
|
27
|
+
var buildCmd = &cobra.Command{
|
|
28
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
29
|
+
config := loadBuildConfig(cmd)
|
|
30
|
+
result, err := lib.Build(config)
|
|
31
|
+
if err != nil {
|
|
32
|
+
return err
|
|
33
|
+
}
|
|
34
|
+
printResult(result)
|
|
35
|
+
return nil
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```go
|
|
41
|
+
// Go: Unit test
|
|
42
|
+
func TestBuild(t *testing.T) {
|
|
43
|
+
config := &BuildConfig{
|
|
44
|
+
Source: "testdata/src",
|
|
45
|
+
Output: t.TempDir(),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
result, err := Build(config)
|
|
49
|
+
|
|
50
|
+
require.NoError(t, err)
|
|
51
|
+
assert.Equal(t, 5, result.FilesProcessed)
|
|
52
|
+
assert.FileExists(t, filepath.Join(config.Output, "main.js"))
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// TypeScript: Unit test
|
|
58
|
+
describe('build', () => {
|
|
59
|
+
it('processes all source files', async () => {
|
|
60
|
+
const config: BuildConfig = {
|
|
61
|
+
source: 'testdata/src',
|
|
62
|
+
output: await mkdtemp('build-test'),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const result = await build(config);
|
|
66
|
+
|
|
67
|
+
expect(result.filesProcessed).toBe(5);
|
|
68
|
+
expect(fs.existsSync(path.join(config.output, 'main.js'))).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Testing Argument Parsing
|
|
74
|
+
|
|
75
|
+
```go
|
|
76
|
+
// Go with Cobra: Test argument validation
|
|
77
|
+
func TestBuildArgsValidation(t *testing.T) {
|
|
78
|
+
tests := []struct {
|
|
79
|
+
name string
|
|
80
|
+
args []string
|
|
81
|
+
wantErr bool
|
|
82
|
+
}{
|
|
83
|
+
{
|
|
84
|
+
name: "valid single path",
|
|
85
|
+
args: []string{"./src"},
|
|
86
|
+
wantErr: false,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "no args uses current dir",
|
|
90
|
+
args: []string{},
|
|
91
|
+
wantErr: false,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "too many args",
|
|
95
|
+
args: []string{"./src", "./lib", "./extra"},
|
|
96
|
+
wantErr: true,
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for _, tt := range tests {
|
|
101
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
102
|
+
cmd := NewBuildCmd()
|
|
103
|
+
cmd.SetArgs(tt.args)
|
|
104
|
+
err := cmd.Execute()
|
|
105
|
+
|
|
106
|
+
if tt.wantErr {
|
|
107
|
+
assert.Error(t, err)
|
|
108
|
+
} else {
|
|
109
|
+
assert.NoError(t, err)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Integration Testing
|
|
117
|
+
|
|
118
|
+
### Testing Full Commands
|
|
119
|
+
|
|
120
|
+
```go
|
|
121
|
+
// Go: Integration test with captured output
|
|
122
|
+
func TestBuildCommand(t *testing.T) {
|
|
123
|
+
// Setup
|
|
124
|
+
tempDir := t.TempDir()
|
|
125
|
+
setupTestProject(t, tempDir)
|
|
126
|
+
|
|
127
|
+
// Capture stdout/stderr
|
|
128
|
+
stdout := new(bytes.Buffer)
|
|
129
|
+
stderr := new(bytes.Buffer)
|
|
130
|
+
|
|
131
|
+
// Create and run command
|
|
132
|
+
cmd := NewRootCmd()
|
|
133
|
+
cmd.SetOut(stdout)
|
|
134
|
+
cmd.SetErr(stderr)
|
|
135
|
+
cmd.SetArgs([]string{"build", tempDir, "-o", filepath.Join(tempDir, "dist")})
|
|
136
|
+
|
|
137
|
+
// Execute
|
|
138
|
+
err := cmd.Execute()
|
|
139
|
+
|
|
140
|
+
// Assert
|
|
141
|
+
require.NoError(t, err)
|
|
142
|
+
assert.Contains(t, stdout.String(), "Build complete")
|
|
143
|
+
assert.DirExists(t, filepath.Join(tempDir, "dist"))
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// TypeScript: Integration test
|
|
149
|
+
import { execSync } from 'child_process';
|
|
150
|
+
|
|
151
|
+
describe('build command', () => {
|
|
152
|
+
let tempDir: string;
|
|
153
|
+
|
|
154
|
+
beforeEach(async () => {
|
|
155
|
+
tempDir = await mkdtemp('build-test');
|
|
156
|
+
setupTestProject(tempDir);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
afterEach(() => {
|
|
160
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('builds project successfully', () => {
|
|
164
|
+
const result = execSync(`node ./bin/cli.js build ${tempDir}`, {
|
|
165
|
+
encoding: 'utf-8',
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(result).toContain('Build complete');
|
|
169
|
+
expect(fs.existsSync(path.join(tempDir, 'dist'))).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('returns error for invalid path', () => {
|
|
173
|
+
expect(() => {
|
|
174
|
+
execSync('node ./bin/cli.js build /nonexistent', {
|
|
175
|
+
encoding: 'utf-8',
|
|
176
|
+
});
|
|
177
|
+
}).toThrow();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Testing Exit Codes
|
|
183
|
+
|
|
184
|
+
```go
|
|
185
|
+
// Go: Test exit codes
|
|
186
|
+
func TestExitCodes(t *testing.T) {
|
|
187
|
+
tests := []struct {
|
|
188
|
+
name string
|
|
189
|
+
args []string
|
|
190
|
+
exitCode int
|
|
191
|
+
}{
|
|
192
|
+
{
|
|
193
|
+
name: "success",
|
|
194
|
+
args: []string{"build", "testdata/valid"},
|
|
195
|
+
exitCode: 0,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "invalid args",
|
|
199
|
+
args: []string{"build", "--invalid-flag"},
|
|
200
|
+
exitCode: 2,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: "file not found",
|
|
204
|
+
args: []string{"build", "/nonexistent"},
|
|
205
|
+
exitCode: 1,
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for _, tt := range tests {
|
|
210
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
211
|
+
cmd := exec.Command("./mytool", tt.args...)
|
|
212
|
+
err := cmd.Run()
|
|
213
|
+
|
|
214
|
+
exitErr, ok := err.(*exec.ExitError)
|
|
215
|
+
if tt.exitCode == 0 {
|
|
216
|
+
assert.NoError(t, err)
|
|
217
|
+
} else {
|
|
218
|
+
require.True(t, ok)
|
|
219
|
+
assert.Equal(t, tt.exitCode, exitErr.ExitCode())
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Mocking I/O
|
|
227
|
+
|
|
228
|
+
### Mocking stdin
|
|
229
|
+
|
|
230
|
+
```go
|
|
231
|
+
// Go: Mock stdin for testing
|
|
232
|
+
func TestInteractivePrompt(t *testing.T) {
|
|
233
|
+
// Create a pipe to simulate stdin
|
|
234
|
+
r, w, _ := os.Pipe()
|
|
235
|
+
oldStdin := os.Stdin
|
|
236
|
+
os.Stdin = r
|
|
237
|
+
|
|
238
|
+
// Write test input
|
|
239
|
+
go func() {
|
|
240
|
+
w.WriteString("y\n")
|
|
241
|
+
w.Close()
|
|
242
|
+
}()
|
|
243
|
+
|
|
244
|
+
// Run function that reads from stdin
|
|
245
|
+
result := confirmAction("Delete?")
|
|
246
|
+
|
|
247
|
+
// Restore stdin
|
|
248
|
+
os.Stdin = oldStdin
|
|
249
|
+
|
|
250
|
+
assert.True(t, result)
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// TypeScript: Mock stdin with mock-stdin
|
|
256
|
+
import mockStdin from 'mock-stdin';
|
|
257
|
+
|
|
258
|
+
describe('interactive prompts', () => {
|
|
259
|
+
let stdin: ReturnType<typeof mockStdin.stdin>;
|
|
260
|
+
|
|
261
|
+
beforeEach(() => {
|
|
262
|
+
stdin = mockStdin.stdin();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
afterEach(() => {
|
|
266
|
+
stdin.restore();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('accepts confirmation', async () => {
|
|
270
|
+
// Schedule input after prompt appears
|
|
271
|
+
setTimeout(() => {
|
|
272
|
+
stdin.send('y\n');
|
|
273
|
+
}, 10);
|
|
274
|
+
|
|
275
|
+
const result = await confirmAction('Delete?');
|
|
276
|
+
|
|
277
|
+
expect(result).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Mocking stdout/stderr
|
|
283
|
+
|
|
284
|
+
```go
|
|
285
|
+
// Go: Capture output
|
|
286
|
+
func TestOutputFormatting(t *testing.T) {
|
|
287
|
+
stdout := new(bytes.Buffer)
|
|
288
|
+
stderr := new(bytes.Buffer)
|
|
289
|
+
|
|
290
|
+
logger := NewLogger(stdout, stderr, LevelNormal)
|
|
291
|
+
logger.Info("Hello, world!")
|
|
292
|
+
logger.Error("Something went wrong")
|
|
293
|
+
|
|
294
|
+
assert.Contains(t, stdout.String(), "Hello, world!")
|
|
295
|
+
assert.Contains(t, stderr.String(), "Something went wrong")
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// TypeScript: Mock console
|
|
301
|
+
describe('output formatting', () => {
|
|
302
|
+
let consoleOutput: string[] = [];
|
|
303
|
+
let consoleError: string[] = [];
|
|
304
|
+
|
|
305
|
+
beforeEach(() => {
|
|
306
|
+
consoleOutput = [];
|
|
307
|
+
consoleError = [];
|
|
308
|
+
jest.spyOn(console, 'log').mockImplementation((...args) => {
|
|
309
|
+
consoleOutput.push(args.join(' '));
|
|
310
|
+
});
|
|
311
|
+
jest.spyOn(console, 'error').mockImplementation((...args) => {
|
|
312
|
+
consoleError.push(args.join(' '));
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
afterEach(() => {
|
|
317
|
+
jest.restoreAllMocks();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('prints info to stdout', () => {
|
|
321
|
+
logger.info('Hello, world!');
|
|
322
|
+
expect(consoleOutput).toContain('Hello, world!');
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Mocking File System
|
|
328
|
+
|
|
329
|
+
```go
|
|
330
|
+
// Go: Use interfaces for file system
|
|
331
|
+
type FileSystem interface {
|
|
332
|
+
ReadFile(path string) ([]byte, error)
|
|
333
|
+
WriteFile(path string, data []byte, perm os.FileMode) error
|
|
334
|
+
Exists(path string) bool
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Real implementation
|
|
338
|
+
type OSFileSystem struct{}
|
|
339
|
+
|
|
340
|
+
func (fs *OSFileSystem) ReadFile(path string) ([]byte, error) {
|
|
341
|
+
return os.ReadFile(path)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Mock implementation
|
|
345
|
+
type MockFileSystem struct {
|
|
346
|
+
Files map[string][]byte
|
|
347
|
+
Err error
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
func (fs *MockFileSystem) ReadFile(path string) ([]byte, error) {
|
|
351
|
+
if fs.Err != nil {
|
|
352
|
+
return nil, fs.Err
|
|
353
|
+
}
|
|
354
|
+
data, ok := fs.Files[path]
|
|
355
|
+
if !ok {
|
|
356
|
+
return nil, os.ErrNotExist
|
|
357
|
+
}
|
|
358
|
+
return data, nil
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Test
|
|
362
|
+
func TestLoadConfig(t *testing.T) {
|
|
363
|
+
mockFS := &MockFileSystem{
|
|
364
|
+
Files: map[string][]byte{
|
|
365
|
+
"/config.yaml": []byte("key: value"),
|
|
366
|
+
},
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
loader := NewConfigLoader(mockFS)
|
|
370
|
+
config, err := loader.Load("/config.yaml")
|
|
371
|
+
|
|
372
|
+
require.NoError(t, err)
|
|
373
|
+
assert.Equal(t, "value", config.Key)
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Mocking Network
|
|
378
|
+
|
|
379
|
+
```go
|
|
380
|
+
// Go: Use httptest for API mocking
|
|
381
|
+
func TestAPIClient(t *testing.T) {
|
|
382
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
383
|
+
assert.Equal(t, "/api/projects", r.URL.Path)
|
|
384
|
+
w.Header().Set("Content-Type", "application/json")
|
|
385
|
+
json.NewEncoder(w).Encode([]Project{{Name: "test"}})
|
|
386
|
+
}))
|
|
387
|
+
defer server.Close()
|
|
388
|
+
|
|
389
|
+
client := NewAPIClient(server.URL)
|
|
390
|
+
projects, err := client.ListProjects()
|
|
391
|
+
|
|
392
|
+
require.NoError(t, err)
|
|
393
|
+
assert.Len(t, projects, 1)
|
|
394
|
+
assert.Equal(t, "test", projects[0].Name)
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Snapshot Testing
|
|
399
|
+
|
|
400
|
+
### Output Snapshots
|
|
401
|
+
|
|
402
|
+
```go
|
|
403
|
+
// Go: Snapshot testing with golden files
|
|
404
|
+
func TestHelpOutput(t *testing.T) {
|
|
405
|
+
cmd := NewRootCmd()
|
|
406
|
+
cmd.SetArgs([]string{"--help"})
|
|
407
|
+
|
|
408
|
+
output := new(bytes.Buffer)
|
|
409
|
+
cmd.SetOut(output)
|
|
410
|
+
cmd.Execute()
|
|
411
|
+
|
|
412
|
+
golden := filepath.Join("testdata", "help.golden")
|
|
413
|
+
|
|
414
|
+
if *update {
|
|
415
|
+
// Update golden file
|
|
416
|
+
os.WriteFile(golden, output.Bytes(), 0644)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
expected, err := os.ReadFile(golden)
|
|
420
|
+
require.NoError(t, err)
|
|
421
|
+
assert.Equal(t, string(expected), output.String())
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
// TypeScript with Jest snapshots
|
|
427
|
+
describe('help output', () => {
|
|
428
|
+
it('matches snapshot', () => {
|
|
429
|
+
const output = execSync('node ./bin/cli.js --help', {
|
|
430
|
+
encoding: 'utf-8',
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
expect(output).toMatchSnapshot();
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Test Organization
|
|
439
|
+
|
|
440
|
+
### Directory Structure
|
|
441
|
+
|
|
442
|
+
```
|
|
443
|
+
tests/
|
|
444
|
+
├── unit/
|
|
445
|
+
│ ├── config_test.go
|
|
446
|
+
│ ├── build_test.go
|
|
447
|
+
│ └── output_test.go
|
|
448
|
+
├── integration/
|
|
449
|
+
│ ├── build_command_test.go
|
|
450
|
+
│ ├── deploy_command_test.go
|
|
451
|
+
│ └── interactive_test.go
|
|
452
|
+
├── e2e/
|
|
453
|
+
│ ├── full_workflow_test.go
|
|
454
|
+
│ └── ci_environment_test.go
|
|
455
|
+
├── fixtures/
|
|
456
|
+
│ ├── valid_project/
|
|
457
|
+
│ ├── invalid_config/
|
|
458
|
+
│ └── large_project/
|
|
459
|
+
└── testdata/
|
|
460
|
+
├── help.golden
|
|
461
|
+
└── version.golden
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Test Helpers
|
|
465
|
+
|
|
466
|
+
```go
|
|
467
|
+
// Go: Common test helpers
|
|
468
|
+
// testutil/setup.go
|
|
469
|
+
func SetupTestProject(t *testing.T) string {
|
|
470
|
+
t.Helper()
|
|
471
|
+
|
|
472
|
+
dir := t.TempDir()
|
|
473
|
+
|
|
474
|
+
// Create test files
|
|
475
|
+
os.WriteFile(filepath.Join(dir, "main.ts"), []byte("console.log('hi')"), 0644)
|
|
476
|
+
os.WriteFile(filepath.Join(dir, "package.json"), []byte("{}"), 0644)
|
|
477
|
+
|
|
478
|
+
return dir
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
func CaptureOutput(t *testing.T, cmd *cobra.Command) (stdout, stderr string) {
|
|
482
|
+
t.Helper()
|
|
483
|
+
|
|
484
|
+
outBuf := new(bytes.Buffer)
|
|
485
|
+
errBuf := new(bytes.Buffer)
|
|
486
|
+
|
|
487
|
+
cmd.SetOut(outBuf)
|
|
488
|
+
cmd.SetErr(errBuf)
|
|
489
|
+
|
|
490
|
+
return outBuf.String(), errBuf.String()
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## CI Considerations
|
|
495
|
+
|
|
496
|
+
### Environment Detection
|
|
497
|
+
|
|
498
|
+
```go
|
|
499
|
+
// Tests should handle CI environment
|
|
500
|
+
func TestProgressBar(t *testing.T) {
|
|
501
|
+
if os.Getenv("CI") != "" {
|
|
502
|
+
t.Skip("Skipping interactive test in CI")
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Test interactive progress bar
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Parallelization
|
|
510
|
+
|
|
511
|
+
```go
|
|
512
|
+
// Go: Run tests in parallel
|
|
513
|
+
func TestBuild(t *testing.T) {
|
|
514
|
+
t.Parallel()
|
|
515
|
+
|
|
516
|
+
// Test uses t.TempDir() which is safe for parallel tests
|
|
517
|
+
dir := t.TempDir()
|
|
518
|
+
// ...
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Test Timeouts
|
|
523
|
+
|
|
524
|
+
```go
|
|
525
|
+
// Go: Set reasonable timeouts
|
|
526
|
+
func TestLongRunningCommand(t *testing.T) {
|
|
527
|
+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
528
|
+
defer cancel()
|
|
529
|
+
|
|
530
|
+
cmd := exec.CommandContext(ctx, "./mytool", "slow-command")
|
|
531
|
+
err := cmd.Run()
|
|
532
|
+
|
|
533
|
+
if ctx.Err() == context.DeadlineExceeded {
|
|
534
|
+
t.Fatal("Command timed out")
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
```
|