@cakemail-org/cakemail-cli 1.5.0 → 2.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.
- package/.claude/settings.local.json +12 -0
- package/.env.example +40 -0
- package/.env.test.example +45 -0
- package/CHANGELOG.md +1031 -0
- package/README.md +319 -15
- package/audit-formats.js +128 -0
- package/cakemail.rb +20 -0
- package/dist/cli.js +27 -10
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +16 -6
- package/dist/client.js.map +1 -1
- package/dist/commands/account.js +1 -1
- package/dist/commands/account.js.map +1 -1
- package/dist/commands/attributes.js +1 -1
- package/dist/commands/attributes.js.map +1 -1
- package/dist/commands/campaigns.d.ts.map +1 -1
- package/dist/commands/campaigns.js +103 -8
- package/dist/commands/campaigns.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +63 -4
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/contacts.d.ts.map +1 -1
- package/dist/commands/contacts.js +91 -12
- package/dist/commands/contacts.js.map +1 -1
- package/dist/commands/emails.js +1 -1
- package/dist/commands/emails.js.map +1 -1
- package/dist/commands/interests.d.ts +5 -0
- package/dist/commands/interests.d.ts.map +1 -0
- package/dist/commands/interests.js +172 -0
- package/dist/commands/interests.js.map +1 -0
- package/dist/commands/lists.d.ts.map +1 -1
- package/dist/commands/lists.js +6 -8
- package/dist/commands/lists.js.map +1 -1
- package/dist/commands/logs.d.ts +5 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +237 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/reports.js +1 -1
- package/dist/commands/reports.js.map +1 -1
- package/dist/commands/segments.js +1 -1
- package/dist/commands/segments.js.map +1 -1
- package/dist/commands/senders.d.ts.map +1 -1
- package/dist/commands/senders.js +11 -8
- package/dist/commands/senders.js.map +1 -1
- package/dist/commands/suppressed.js +1 -1
- package/dist/commands/suppressed.js.map +1 -1
- package/dist/commands/tags.d.ts +5 -0
- package/dist/commands/tags.d.ts.map +1 -0
- package/dist/commands/tags.js +124 -0
- package/dist/commands/tags.js.map +1 -0
- package/dist/commands/templates.js +1 -1
- package/dist/commands/templates.js.map +1 -1
- package/dist/commands/transactional-templates.d.ts +5 -0
- package/dist/commands/transactional-templates.d.ts.map +1 -0
- package/dist/commands/transactional-templates.js +354 -0
- package/dist/commands/transactional-templates.js.map +1 -0
- package/dist/commands/webhooks.js +1 -1
- package/dist/commands/webhooks.js.map +1 -1
- package/dist/utils/auth.d.ts +8 -1
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/auth.js +39 -11
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/config-file.d.ts +7 -0
- package/dist/utils/config-file.d.ts.map +1 -1
- package/dist/utils/config-file.js +15 -0
- package/dist/utils/config-file.js.map +1 -1
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +12 -4
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/errors.js +1 -1
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/list-defaults.d.ts +33 -0
- package/dist/utils/list-defaults.d.ts.map +1 -0
- package/dist/utils/list-defaults.js +52 -0
- package/dist/utils/list-defaults.js.map +1 -0
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +36 -13
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/progress.d.ts.map +1 -1
- package/dist/utils/progress.js +32 -4
- package/dist/utils/progress.js.map +1 -1
- package/dist/utils/spinner.d.ts +17 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/dist/utils/spinner.js +43 -0
- package/dist/utils/spinner.js.map +1 -0
- package/docs/DOCUMENTATION-STANDARD.md +1068 -0
- package/docs/README.md +161 -0
- package/docs/developer/ARCHITECTURE.md +516 -0
- package/docs/developer/AUTH.md +204 -0
- package/docs/developer/CONTRIBUTING.md +227 -0
- package/docs/developer/DOCUMENTATION_SUMMARY.md +346 -0
- package/docs/developer/PROJECT_INDEX.md +365 -0
- package/docs/planning/API_COVERAGE.md +1045 -0
- package/docs/planning/BACKLOG.md +1159 -0
- package/docs/planning/PROFILE_SYSTEM_TASKS.md +287 -0
- package/docs/planning/UX_IMPLEMENTATION_PLAN.md +691 -0
- package/docs/planning/archive/RELEASE_CHECKLIST_v1.3.0.md +332 -0
- package/docs/planning/archive/RELEASE_v1.3.0.md +428 -0
- package/docs/planning/archive/cakemail-cli-ux-improvements.md +438 -0
- package/docs/planning/cakemail-profile-system-plan.md +1121 -0
- package/docs/testing/AI_USER_SIMULATION_DESIGN.md +1342 -0
- package/docs/testing/KENOGAMI_BIDIRECTIONAL_FLOW.md +1517 -0
- package/docs/testing/KENOGAMI_TRUTH_RECONCILIATION_SYSTEM.md +1369 -0
- package/docs/user-manual/.obsidian/app.json +1 -0
- package/docs/user-manual/.obsidian/appearance.json +1 -0
- package/docs/user-manual/.obsidian/core-plugins.json +33 -0
- package/docs/user-manual/.obsidian/workspace.json +167 -0
- package/docs/user-manual/01-getting-started/01-installation.md +214 -0
- package/docs/user-manual/01-getting-started/02-quick-start.md +432 -0
- package/docs/user-manual/01-getting-started/03-authentication.md +448 -0
- package/docs/user-manual/01-getting-started/04-configuration.md +430 -0
- package/docs/user-manual/01-getting-started/05-output-formats.md +447 -0
- package/docs/user-manual/02-core-concepts/01-accounts.md +514 -0
- package/docs/user-manual/02-core-concepts/02-profile-system.md +771 -0
- package/docs/user-manual/02-core-concepts/03-smart-defaults.md +485 -0
- package/docs/user-manual/02-core-concepts/04-authentication-methods.md +435 -0
- package/docs/user-manual/02-core-concepts/05-pagination-filtering.md +600 -0
- package/docs/user-manual/02-core-concepts/06-error-handling.md +718 -0
- package/docs/user-manual/02-core-concepts/07-api-coverage.md +483 -0
- package/docs/user-manual/03-email-operations/01-senders.md +490 -0
- package/docs/user-manual/03-email-operations/02-templates.md +444 -0
- package/docs/user-manual/03-email-operations/03-transactional-emails.md +706 -0
- package/docs/user-manual/03-email-operations/04-email-tracking.md +407 -0
- package/docs/user-manual/04-campaign-management/01-campaigns-basics.md +394 -0
- package/docs/user-manual/04-campaign-management/02-campaign-scheduling.md +630 -0
- package/docs/user-manual/04-campaign-management/03-campaign-testing.md +997 -0
- package/docs/user-manual/04-campaign-management/04-campaign-lifecycle.md +709 -0
- package/docs/user-manual/04-campaign-management/05-campaign-links.md +934 -0
- package/docs/user-manual/05-contact-management/01-lists.md +836 -0
- package/docs/user-manual/05-contact-management/02-contacts.md +1035 -0
- package/docs/user-manual/05-contact-management/03-custom-attributes.md +788 -0
- package/docs/user-manual/05-contact-management/04-segments.md +1028 -0
- package/docs/user-manual/05-contact-management/05-contact-import-export.md +1031 -0
- package/docs/user-manual/06-analytics-reporting/01-campaign-analytics.md +867 -0
- package/docs/user-manual/06-analytics-reporting/02-account-reports.md +227 -0
- package/docs/user-manual/07-integrations/01-webhooks-integration.md +259 -0
- package/docs/user-manual/07-integrations/02-automation.md +326 -0
- package/docs/user-manual/08-advanced-usage/01-scripting-patterns.md +672 -0
- package/docs/user-manual/08-advanced-usage/02-bulk-operations.md +932 -0
- package/docs/user-manual/08-advanced-usage/03-ci-cd-integration.md +892 -0
- package/docs/user-manual/08-advanced-usage/04-performance-optimization.md +766 -0
- package/docs/user-manual/09-command-reference/01-config.md +776 -0
- package/docs/user-manual/09-command-reference/02-account.md +652 -0
- package/docs/user-manual/09-command-reference/03-lists.md +958 -0
- package/docs/user-manual/09-command-reference/04-contacts.md +1408 -0
- package/docs/user-manual/09-command-reference/05-attributes.md +617 -0
- package/docs/user-manual/09-command-reference/06-segments.md +894 -0
- package/docs/user-manual/09-command-reference/07-senders.md +803 -0
- package/docs/user-manual/09-command-reference/08-templates.md +818 -0
- package/docs/user-manual/09-command-reference/09-campaigns.md +1250 -0
- package/docs/user-manual/09-command-reference/10-emails.md +807 -0
- package/docs/user-manual/09-command-reference/11-reports.md +1135 -0
- package/docs/user-manual/09-command-reference/12-webhooks.md +773 -0
- package/docs/user-manual/09-command-reference/13-suppressed.md +797 -0
- package/docs/user-manual/09-command-reference/14-interests.md +630 -0
- package/docs/user-manual/09-command-reference/15-tags.md +584 -0
- package/docs/user-manual/09-command-reference/16-logs.md +656 -0
- package/docs/user-manual/09-command-reference/17-transactional-templates.md +850 -0
- package/docs/user-manual/10-troubleshooting/01-common-errors.md +457 -0
- package/docs/user-manual/10-troubleshooting/02-authentication-issues.md +558 -0
- package/docs/user-manual/10-troubleshooting/03-connection-problems.md +634 -0
- package/docs/user-manual/10-troubleshooting/04-debugging.md +725 -0
- package/docs/user-manual/11-appendix/04-faq.md +484 -0
- package/docs/user-manual/11-appendix/05-glossary.md +250 -0
- package/docs/user-manual/README.md +0 -0
- package/package.json +13 -47
- package/src/cli.ts +125 -0
- package/src/client.ts +16 -0
- package/src/commands/account.ts +267 -0
- package/src/commands/accounts.ts +78 -0
- package/src/commands/actions.ts +249 -0
- package/src/commands/attributes.ts +139 -0
- package/src/commands/campaign-blueprints.ts +106 -0
- package/src/commands/campaigns.ts +469 -0
- package/src/commands/config.ts +77 -0
- package/src/commands/contacts.ts +612 -0
- package/src/commands/custom-attributes.ts +127 -0
- package/src/commands/dkims.ts +117 -0
- package/src/commands/domains.ts +82 -0
- package/src/commands/email-apis.ts +569 -0
- package/src/commands/emails.ts +197 -0
- package/src/commands/forms.ts +283 -0
- package/src/commands/interests.ts +155 -0
- package/src/commands/links.ts +38 -0
- package/src/commands/lists.ts +406 -0
- package/src/commands/logos.ts +71 -0
- package/src/commands/logs.ts +386 -0
- package/src/commands/reports.ts +306 -0
- package/src/commands/segments.ts +158 -0
- package/src/commands/senders.ts +204 -0
- package/src/commands/sub-accounts.ts +271 -0
- package/src/commands/suppressed-emails.ts +234 -0
- package/src/commands/suppressed.ts +198 -0
- package/src/commands/system-emails.ts +85 -0
- package/src/commands/tags.ts +146 -0
- package/src/commands/tasks.ts +116 -0
- package/src/commands/templates.ts +189 -0
- package/src/commands/tokens.ts +83 -0
- package/src/commands/transactional-emails.ts +374 -0
- package/src/commands/transactional-templates.ts +385 -0
- package/src/commands/users.ts +506 -0
- package/src/commands/webhooks.ts +172 -0
- package/src/commands/workflow-blueprints.ts +123 -0
- package/src/commands/workflows.ts +265 -0
- package/src/types/profile.ts +93 -0
- package/src/utils/auth.ts +272 -0
- package/src/utils/config-file.ts +96 -0
- package/src/utils/config.ts +134 -0
- package/src/utils/confirm.ts +32 -0
- package/src/utils/defaults.ts +99 -0
- package/src/utils/errors.ts +116 -0
- package/src/utils/interactive.ts +91 -0
- package/src/utils/list-defaults.ts +74 -0
- package/src/utils/output.ts +190 -0
- package/src/utils/progress.ts +320 -0
- package/src/utils/spinner.ts +22 -0
- package/tests/IMPLEMENTATION_STATUS.md +258 -0
- package/tests/PTY_SETUP.md +118 -0
- package/tests/PTY_TESTING_GUIDE.md +507 -0
- package/tests/README.md +244 -0
- package/tests/fixtures/api-responses/campaigns.json +34 -0
- package/tests/fixtures/test-config.json +13 -0
- package/tests/helpers/cli-runner.ts +128 -0
- package/tests/helpers/mock-server.ts +301 -0
- package/tests/helpers/pty-runner.ts +181 -0
- package/tests/integration/campaigns-real-api.test.ts +196 -0
- package/tests/integration/setup-integration.ts +50 -0
- package/tests/pty/campaigns.test.ts +241 -0
- package/tests/setup.ts +34 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +28 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
# PTY Testing Guide: Simulating Real Users
|
|
2
|
+
|
|
3
|
+
## What is PTY Testing?
|
|
4
|
+
|
|
5
|
+
**PTY (Pseudo-Terminal)** creates a **real terminal session** for your CLI, exactly like when a user runs your command.
|
|
6
|
+
|
|
7
|
+
### What You Get:
|
|
8
|
+
- ✅ **Real terminal** - Just like `ssh` or Terminal.app
|
|
9
|
+
- ✅ **Colors** - ANSI escape codes work
|
|
10
|
+
- ✅ **Spinners** - Ora spinners animate
|
|
11
|
+
- ✅ **Interactive prompts** - Can respond to inquirer/prompts
|
|
12
|
+
- ✅ **Ctrl+C handling** - Test signal handling
|
|
13
|
+
- ✅ **Terminal size** - Responsive layouts
|
|
14
|
+
|
|
15
|
+
### What Makes It Special:
|
|
16
|
+
Unlike `execa` (subprocess), PTY gives your CLI a **real TTY (teletypewriter)**, which means:
|
|
17
|
+
- `process.stdout.isTTY === true`
|
|
18
|
+
- Colors don't get stripped
|
|
19
|
+
- Progress bars and spinners work
|
|
20
|
+
- You can test exactly what users see
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install --save-dev node-pty express @types/express
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Note**: `node-pty` requires build tools and has specific Node version requirements:
|
|
31
|
+
- **macOS**: Xcode Command Line Tools (`xcode-select --install`)
|
|
32
|
+
- **Linux**: `python3`, `make`, `g++`
|
|
33
|
+
- **Windows**: Visual Studio Build Tools
|
|
34
|
+
- **Node Version**: Currently supports Node 18.x and 20.x (Node 22+ may have compatibility issues)
|
|
35
|
+
|
|
36
|
+
**⚠️ If node-pty fails to build**: Use Node 18 LTS or Node 20 LTS. Node 24+ is too new and may not be supported yet.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Switch to Node 20 (recommended)
|
|
40
|
+
nvm install 20
|
|
41
|
+
nvm use 20
|
|
42
|
+
npm install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
### 1. Files Already Created
|
|
50
|
+
|
|
51
|
+
✅ `tests/helpers/mock-server.ts` - Mock API server
|
|
52
|
+
✅ `tests/helpers/pty-runner.ts` - PTY helper functions
|
|
53
|
+
✅ `tests/pty/campaigns.test.ts` - Example PTY test
|
|
54
|
+
✅ `tests/pty-test-example.ts` - Interactive examples
|
|
55
|
+
|
|
56
|
+
### 2. Run the Example
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Build CLI first
|
|
60
|
+
npm run build
|
|
61
|
+
|
|
62
|
+
# Run PTY test
|
|
63
|
+
npx vitest tests/pty/campaigns.test.ts
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## How It Works
|
|
69
|
+
|
|
70
|
+
### Architecture
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
┌─────────────────────────────────────────┐
|
|
74
|
+
│ Test Process (Vitest) │
|
|
75
|
+
│ │
|
|
76
|
+
│ ┌────────────────────────────────────┐ │
|
|
77
|
+
│ │ Mock HTTP Server │ │
|
|
78
|
+
│ │ http://localhost:RANDOM_PORT │ │
|
|
79
|
+
│ │ │ │
|
|
80
|
+
│ │ POST /token → Auth │ │
|
|
81
|
+
│ │ GET /campaigns → Mock data │ │
|
|
82
|
+
│ │ ... │ │
|
|
83
|
+
│ └────────────────────────────────────┘ │
|
|
84
|
+
│ │
|
|
85
|
+
│ ┌────────────────────────────────────┐ │
|
|
86
|
+
│ │ PTY (Real Terminal) │ │
|
|
87
|
+
│ │ │ │
|
|
88
|
+
│ │ $ node dist/cli.js campaigns list│ │
|
|
89
|
+
│ │ ↓ │ │
|
|
90
|
+
│ │ - Fetching campaigns... ⠋ │ │
|
|
91
|
+
│ │ ↓ │ │
|
|
92
|
+
│ │ ┌────┬─────────┬────────┐ │ │
|
|
93
|
+
│ │ │ ID │ Name │ Status │ │ │
|
|
94
|
+
│ │ ├────┼─────────┼────────┤ │ │
|
|
95
|
+
│ │ │ 1 │ Test │ draft │ │ │
|
|
96
|
+
│ │ └────┴─────────┴────────┘ │ │
|
|
97
|
+
│ │ │ │
|
|
98
|
+
│ │ (Makes HTTP requests to mock) │ │
|
|
99
|
+
│ └────────────────────────────────────┘ │
|
|
100
|
+
└─────────────────────────────────────────┘
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Key Differences from Current Tests
|
|
104
|
+
|
|
105
|
+
| Feature | Current (execa) | PTY Testing |
|
|
106
|
+
|---------|----------------|-------------|
|
|
107
|
+
| Terminal | ❌ No TTY | ✅ Real TTY |
|
|
108
|
+
| HTTP Mocks | ❌ Nock (doesn't work) | ✅ Real HTTP Server |
|
|
109
|
+
| Colors | ❌ Stripped | ✅ ANSI codes visible |
|
|
110
|
+
| Spinners | ❌ Don't work | ✅ Fully animated |
|
|
111
|
+
| Interactive | ❌ No | ✅ Yes |
|
|
112
|
+
| User Experience | ⚠️ Partial | ✅ Exact |
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Usage Examples
|
|
117
|
+
|
|
118
|
+
### Basic Test
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { runPTYSuccess } from '../helpers/pty-runner';
|
|
122
|
+
|
|
123
|
+
it('should list campaigns', async () => {
|
|
124
|
+
const { output, exitCode } = await runPTYSuccess(
|
|
125
|
+
['campaigns', 'list'],
|
|
126
|
+
{ mockServerPort }
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
expect(exitCode).toBe(0);
|
|
130
|
+
expect(output).toContain('Test Campaign');
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Test JSON Output
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
it('should output JSON', async () => {
|
|
138
|
+
const { cleanOutput } = await runPTYSuccess(
|
|
139
|
+
['-f', 'json', 'campaigns', 'list'],
|
|
140
|
+
{ mockServerPort }
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const data = JSON.parse(cleanOutput);
|
|
144
|
+
expect(data.data).toHaveLength(2);
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Test Colors
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
it('should show colored output', async () => {
|
|
152
|
+
const { output } = await runPTYSuccess(
|
|
153
|
+
['campaigns', 'list'],
|
|
154
|
+
{ mockServerPort, enableColors: true }
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Check for ANSI escape codes
|
|
158
|
+
expect(output).toMatch(/\u001b\[3\d+m/); // Color codes
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Test Spinners
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
it('should show spinner while loading', async () => {
|
|
166
|
+
const { output } = await runPTYSuccess(
|
|
167
|
+
['campaigns', 'list'],
|
|
168
|
+
{ mockServerPort }
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Check for loading message
|
|
172
|
+
expect(output).toContain('Fetching campaigns');
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Test Interactive Prompts
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { createInteraction } from '../helpers/pty-runner';
|
|
180
|
+
|
|
181
|
+
it('should handle interactive input', async () => {
|
|
182
|
+
const interaction = createInteraction([
|
|
183
|
+
{ prompt: 'List name:', response: 'My List\r' },
|
|
184
|
+
{ prompt: 'Language:', response: 'en\r' }
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
const { output } = await runPTYSuccess(
|
|
188
|
+
['lists', 'create'],
|
|
189
|
+
{
|
|
190
|
+
mockServerPort,
|
|
191
|
+
interactive: true,
|
|
192
|
+
onData: interaction.handler
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
expect(output).toContain('My List');
|
|
197
|
+
expect(output).toContain('created');
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Test Error Handling
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { runPTYFailure } from '../helpers/pty-runner';
|
|
205
|
+
|
|
206
|
+
it('should handle not found errors', async () => {
|
|
207
|
+
const { output, exitCode } = await runPTYFailure(
|
|
208
|
+
['campaigns', 'get', '999'],
|
|
209
|
+
{ mockServerPort }
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
expect(exitCode).toBe(1);
|
|
213
|
+
expect(output).toMatch(/not found/i);
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Converting Existing Tests
|
|
220
|
+
|
|
221
|
+
### Before (with execa and nock)
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { runCLISuccess } from '../helpers/cli-runner';
|
|
225
|
+
import { mockCampaignsList } from '../helpers/mock-api';
|
|
226
|
+
import nock from 'nock';
|
|
227
|
+
|
|
228
|
+
it('should list campaigns', async () => {
|
|
229
|
+
mockCampaignsList([/* data */]);
|
|
230
|
+
|
|
231
|
+
const result = await runCLISuccess(['campaigns', 'list']);
|
|
232
|
+
// ❌ This fails - nock doesn't work with subprocess
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### After (with PTY and mock server)
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { runPTYSuccess } from '../helpers/pty-runner';
|
|
240
|
+
|
|
241
|
+
it('should list campaigns', async () => {
|
|
242
|
+
// Mock server already running in beforeAll
|
|
243
|
+
const result = await runPTYSuccess(
|
|
244
|
+
['campaigns', 'list'],
|
|
245
|
+
{ mockServerPort }
|
|
246
|
+
);
|
|
247
|
+
// ✅ Works! Real HTTP to mock server
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Migration Checklist
|
|
252
|
+
|
|
253
|
+
For each test file:
|
|
254
|
+
1. ✅ Remove nock imports
|
|
255
|
+
2. ✅ Remove mock setup (e.g., `mockCampaignsList()`)
|
|
256
|
+
3. ✅ Import PTY helpers instead
|
|
257
|
+
4. ✅ Use `mockServerPort` from `beforeAll`
|
|
258
|
+
5. ✅ Update assertions to use `cleanOutput`
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Mock Server Customization
|
|
263
|
+
|
|
264
|
+
### Adding New Endpoints
|
|
265
|
+
|
|
266
|
+
Edit `tests/helpers/mock-server.ts`:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
export function createMockAPI(): Express {
|
|
270
|
+
const app = express();
|
|
271
|
+
|
|
272
|
+
// Your new endpoint
|
|
273
|
+
app.get('/my-endpoint', (req, res) => {
|
|
274
|
+
res.json({ data: 'your mock data' });
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return app;
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Testing Error Scenarios
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// In mock-server.ts
|
|
285
|
+
app.get('/campaigns/:id', (req, res) => {
|
|
286
|
+
const id = parseInt(req.params.id);
|
|
287
|
+
|
|
288
|
+
// Special IDs trigger errors
|
|
289
|
+
if (id === 999) {
|
|
290
|
+
return res.status(404).json({ error: 'Not found' });
|
|
291
|
+
}
|
|
292
|
+
if (id === 888) {
|
|
293
|
+
return res.status(500).json({ error: 'Server error' });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
res.json({ id, name: `Campaign ${id}` });
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// In test
|
|
300
|
+
it('should handle 404', async () => {
|
|
301
|
+
const { exitCode } = await runPTYFailure(
|
|
302
|
+
['campaigns', 'get', '999'], // Magic ID triggers 404
|
|
303
|
+
{ mockServerPort }
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
expect(exitCode).toBe(1);
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Dynamic Responses
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
// Track state in mock server
|
|
314
|
+
let campaignsData = [
|
|
315
|
+
{ id: 1, name: 'Campaign 1' }
|
|
316
|
+
];
|
|
317
|
+
|
|
318
|
+
app.post('/campaigns', (req, res) => {
|
|
319
|
+
const newCampaign = {
|
|
320
|
+
id: campaignsData.length + 1,
|
|
321
|
+
...req.body
|
|
322
|
+
};
|
|
323
|
+
campaignsData.push(newCampaign);
|
|
324
|
+
res.status(201).json(newCampaign);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
app.get('/campaigns', (req, res) => {
|
|
328
|
+
res.json({ data: campaignsData });
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Tips & Best Practices
|
|
335
|
+
|
|
336
|
+
### 1. Use `cleanOutput` for Assertions
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// ❌ BAD: Raw output has ANSI codes
|
|
340
|
+
expect(result.output).toContain('Campaign');
|
|
341
|
+
|
|
342
|
+
// ✅ GOOD: Clean output strips ANSI
|
|
343
|
+
expect(result.cleanOutput).toContain('Campaign');
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### 2. Test Colors Separately
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// Test functionality
|
|
350
|
+
expect(result.cleanOutput).toContain('success');
|
|
351
|
+
|
|
352
|
+
// Test presentation
|
|
353
|
+
it('should show green for success', async () => {
|
|
354
|
+
const { output } = await runPTYSuccess(
|
|
355
|
+
['campaigns', 'create', ...],
|
|
356
|
+
{ enableColors: true }
|
|
357
|
+
);
|
|
358
|
+
expect(output).toMatch(/\u001b\[32m/); // Green
|
|
359
|
+
});
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### 3. Set Reasonable Timeouts
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
const { output } = await runPTYSuccess(
|
|
366
|
+
['campaigns', 'list'],
|
|
367
|
+
{
|
|
368
|
+
mockServerPort,
|
|
369
|
+
timeout: 5000 // 5 seconds (default is 10s)
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### 4. Debug with onData
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
const { output } = await runPTYSuccess(
|
|
378
|
+
['campaigns', 'list'],
|
|
379
|
+
{
|
|
380
|
+
mockServerPort,
|
|
381
|
+
onData: (data) => console.log('[PTY]', data) // Debug output
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### 5. Clean Up PTY Processes
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
afterEach(() => {
|
|
390
|
+
// PTY helper already kills process
|
|
391
|
+
// But if you create custom PTY:
|
|
392
|
+
if (customPty) {
|
|
393
|
+
customPty.kill();
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## Common Issues
|
|
401
|
+
|
|
402
|
+
### Issue: "Cannot find module 'node-pty'"
|
|
403
|
+
|
|
404
|
+
**Solution**: Install native dependencies
|
|
405
|
+
```bash
|
|
406
|
+
# macOS
|
|
407
|
+
xcode-select --install
|
|
408
|
+
npm install node-pty
|
|
409
|
+
|
|
410
|
+
# Ubuntu/Debian
|
|
411
|
+
sudo apt-get install python3 make g++
|
|
412
|
+
npm install node-pty
|
|
413
|
+
|
|
414
|
+
# Windows
|
|
415
|
+
npm install --global windows-build-tools
|
|
416
|
+
npm install node-pty
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Issue: Tests timeout
|
|
420
|
+
|
|
421
|
+
**Solution**: Increase timeout or check mock server
|
|
422
|
+
```typescript
|
|
423
|
+
const result = await runPTYSuccess(args, {
|
|
424
|
+
mockServerPort,
|
|
425
|
+
timeout: 30000 // 30 seconds
|
|
426
|
+
});
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Issue: Can't parse JSON output
|
|
430
|
+
|
|
431
|
+
**Solution**: Use `-f json` flag
|
|
432
|
+
```typescript
|
|
433
|
+
const result = await runPTYSuccess(
|
|
434
|
+
['-f', 'json', 'campaigns', 'list'], // Force JSON
|
|
435
|
+
{ mockServerPort }
|
|
436
|
+
);
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Issue: Colors don't show
|
|
440
|
+
|
|
441
|
+
**Solution**: Enable colors explicitly
|
|
442
|
+
```typescript
|
|
443
|
+
const result = await runPTYSuccess(args, {
|
|
444
|
+
mockServerPort,
|
|
445
|
+
enableColors: true // Enable ANSI colors
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## Performance
|
|
452
|
+
|
|
453
|
+
### Speed Comparison
|
|
454
|
+
|
|
455
|
+
| Test Type | Speed | Tests/Second |
|
|
456
|
+
|-----------|-------|--------------|
|
|
457
|
+
| Direct Import + nock | 🚀 Very Fast | 50-100 |
|
|
458
|
+
| PTY + Mock Server | ⚡ Fast | 10-20 |
|
|
459
|
+
| Integration (Real API) | 🐌 Slow | 1-5 |
|
|
460
|
+
|
|
461
|
+
### Optimization Tips
|
|
462
|
+
|
|
463
|
+
1. **Reuse mock server** - Start once in `beforeAll`, not per test
|
|
464
|
+
2. **Parallel tests** - Vitest runs tests in parallel by default
|
|
465
|
+
3. **Disable colors** - Faster rendering without ANSI codes
|
|
466
|
+
4. **Short timeouts** - Fail fast on broken tests
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## Next Steps
|
|
471
|
+
|
|
472
|
+
### Phase 1: Get One Test Working (30 min)
|
|
473
|
+
```bash
|
|
474
|
+
npm install node-pty @types/node-pty express @types/express
|
|
475
|
+
npm run build
|
|
476
|
+
npx vitest tests/pty/campaigns.test.ts -t "should list campaigns"
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Phase 2: Convert High-Priority Tests (2-3 hours)
|
|
480
|
+
- Convert campaigns tests (25 tests)
|
|
481
|
+
- Convert lists tests (30 tests)
|
|
482
|
+
- Convert contacts tests (48 tests)
|
|
483
|
+
|
|
484
|
+
### Phase 3: Convert Remaining Tests (3-4 hours)
|
|
485
|
+
- Convert senders tests (20 tests)
|
|
486
|
+
- Convert templates tests (24 tests)
|
|
487
|
+
|
|
488
|
+
### Total Effort: 6-8 hours
|
|
489
|
+
✅ **Result**: 147 tests simulating real user experience!
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Summary
|
|
494
|
+
|
|
495
|
+
**PTY Testing gives you**:
|
|
496
|
+
- ✅ Real terminal simulation
|
|
497
|
+
- ✅ HTTP mocks that work (real server)
|
|
498
|
+
- ✅ Test colors, spinners, interactivity
|
|
499
|
+
- ✅ Exact user experience verification
|
|
500
|
+
|
|
501
|
+
**Perfect for**:
|
|
502
|
+
- E2E testing CLI behavior
|
|
503
|
+
- Verifying user-facing features
|
|
504
|
+
- Testing error messages and formatting
|
|
505
|
+
- Ensuring professional UX
|
|
506
|
+
|
|
507
|
+
**Start with**: `tests/pty/campaigns.test.ts` example! 🚀
|