-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest-e2e.js
More file actions
309 lines (259 loc) Β· 9.55 KB
/
test-e2e.js
File metadata and controls
309 lines (259 loc) Β· 9.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
#!/usr/bin/env node
/**
* End-to-end test for MacroVox
* Tests the full pipeline: audio β Deepgram β mapper β AHK
*
* Usage: node test-e2e.js [--profile=premiere] [--duration=10]
*/
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { startAudioCapture, stopAudioCapture } from './src/audio.js';
import DeepgramStreamer from './src/deepgram.js';
import CommandMapper from './src/mapper.js';
import ProfileManager from './src/profiles.js';
import { executeCommand } from './src/ahk.js';
import Logger from './src/logger.js';
import { config } from './src/config.js';
const logger = new Logger('info');
class E2ETest {
constructor(options = {}) {
this.profile = options.profile || config.defaultProfile;
this.duration = options.duration || 15;
this.testResults = {
audio: null,
deepgram: null,
mapper: null,
ahk: null,
};
this.transcripts = [];
this.commands = [];
this.deepgramStreamer = null;
this.commandMapper = null;
this.profileManager = null;
}
async run() {
console.log('\nββββββββββββββββββββββββββββββββββββββββββ');
console.log('β MacroVox End-to-End Test Suite β');
console.log('ββββββββββββββββββββββββββββββββββββββββββ\n');
try {
// Step 1: Initialize
await this.initialize();
// Step 2: Test audio capture
await this.testAudioCapture();
// Step 3: Test Deepgram streaming
await this.testDeepgramStreaming();
// Step 4: Test command mapping
await this.testCommandMapping();
// Step 5: Test AHK execution
await this.testAHKExecution();
// Step 6: Summary
this.printSummary();
} catch (err) {
logger.error(`Test failed: ${err.message}`);
process.exit(1);
}
}
async initialize() {
console.log('π Initializing...\n');
// Initialize profile manager
this.profileManager = new ProfileManager();
process.env.CLI_PROFILE_OVERRIDE = this.profile;
this.profileManager.loadProfileState();
const currentProfile = this.profileManager.getCurrentProfile();
logger.info(`Profile: ${this.profileManager.getProfileName(currentProfile)}`);
logger.info(`Duration: ${this.duration}s`);
// Initialize mapper
this.commandMapper = new CommandMapper();
console.log('β Initialized\n');
}
async testAudioCapture() {
console.log('π€ Testing Audio Capture...\n');
try {
const stream = await startAudioCapture();
this.testResults.audio = { status: 'pass', details: 'Audio stream started' };
logger.info('β Audio capture initialized');
console.log();
return stream;
} catch (err) {
this.testResults.audio = { status: 'fail', error: err.message };
logger.error(`β Audio capture failed: ${err.message}`);
throw err;
}
}
async testDeepgramStreaming() {
console.log('π Testing Deepgram Streaming...\n');
return new Promise((resolve, reject) => {
try {
const audioStream = this.startAudioCapture();
this.deepgramStreamer = new DeepgramStreamer();
const transcriptHandler = (data) => {
const { transcript, isFinal, confidence } = data;
this.transcripts.push({ transcript, isFinal, confidence });
const type = isFinal ? 'FINAL' : 'interim';
console.log(` [${type}] "${transcript}" (confidence: ${confidence.toFixed(2)})`);
};
this.deepgramStreamer.startLiveTranscription(audioStream, transcriptHandler)
.then(() => {
logger.info('β Deepgram connection opened');
console.log(`\nListening for ${this.duration}s... Speak into your microphone.\n`);
setTimeout(() => {
this.deepgramStreamer.stopLiveTranscription();
stopAudioCapture();
if (this.transcripts.length > 0) {
this.testResults.deepgram = {
status: 'pass',
details: `Received ${this.transcripts.length} transcript events`,
};
logger.info(`β Deepgram streaming test passed (${this.transcripts.length} events)`);
} else {
this.testResults.deepgram = {
status: 'warn',
details: 'No transcripts received',
};
logger.warn('β No transcripts received (check microphone and API key)');
}
console.log();
resolve();
}, this.duration * 1000);
})
.catch(reject);
} catch (err) {
this.testResults.deepgram = { status: 'fail', error: err.message };
logger.error(`β Deepgram test failed: ${err.message}`);
reject(err);
}
});
}
async testCommandMapping() {
console.log('πΊοΈ Testing Command Mapping...\n');
const profile = this.profileManager.getCurrentProfile();
const testPhrases = [
'undo',
'undo please',
'cut',
'next frame',
'foobar', // Should not match
];
let passed = 0;
let failed = 0;
for (const phrase of testPhrases) {
const result = this.commandMapper.mapPhrase(phrase, profile);
if (result) {
console.log(` β "${phrase}" β "${result.keyword}" (confidence: ${result.confidence.toFixed(2)})`);
this.commands.push(result);
passed++;
} else {
console.log(` β "${phrase}" β no match (expected for non-commands)`);
}
}
this.testResults.mapper = {
status: 'pass',
details: `${passed} matches, ${failed} failures`,
};
logger.info(`β Command mapping test passed (${passed} matches)`);
console.log();
}
async testAHKExecution() {
console.log('βοΈ Testing AutoHotkey Execution...\n');
const profile = this.profileManager.getCurrentProfile();
try {
logger.info('Executing test command: "undo" (Ctrl+Z)');
console.log(' (Focus a text editor to see the effect)\n');
await executeCommand('undo', profile);
this.testResults.ahk = {
status: 'pass',
details: 'AutoHotkey executed successfully',
};
logger.info('β AutoHotkey execution test passed');
console.log();
} catch (err) {
this.testResults.ahk = {
status: 'fail',
error: err.message,
};
logger.error(`β AutoHotkey test failed: ${err.message}`);
console.log();
}
}
printSummary() {
console.log('ββββββββββββββββββββββββββββββββββββββββββ');
console.log('β Test Results Summary β');
console.log('ββββββββββββββββββββββββββββββββββββββββββ\n');
const results = this.testResults;
let totalTests = 0;
let passedTests = 0;
let warnedTests = 0;
for (const [name, result] of Object.entries(results)) {
totalTests++;
const status = result.status === 'pass' ? 'β' : result.status === 'warn' ? 'β ' : 'β';
const statusText = result.status === 'pass' ? 'PASS' : result.status === 'warn' ? 'WARN' : 'FAIL';
console.log(`${status} ${name.toUpperCase().padEnd(15)} [${statusText}]`);
if (result.details) {
console.log(` ββ ${result.details}`);
}
if (result.error) {
console.log(` ββ Error: ${result.error}`);
}
if (result.status === 'pass') passedTests++;
if (result.status === 'warn') warnedTests++;
}
console.log();
console.log(`Summary: ${passedTests}/${totalTests} passed, ${warnedTests} warnings`);
if (this.transcripts.length > 0) {
console.log(`\nTranscripts received: ${this.transcripts.length}`);
const finalTranscripts = this.transcripts.filter(t => t.isFinal);
if (finalTranscripts.length > 0) {
console.log(`Final transcripts: ${finalTranscripts.length}`);
finalTranscripts.forEach((t, i) => {
console.log(` ${i + 1}. "${t.transcript}"`);
});
}
}
if (this.commands.length > 0) {
console.log(`\nCommands matched: ${this.commands.length}`);
this.commands.forEach((cmd, i) => {
console.log(` ${i + 1}. ${cmd.keyword} (${cmd.method})`);
});
}
console.log();
// Exit code
const hasFailures = Object.values(results).some(r => r.status === 'fail');
if (hasFailures) {
console.log('β Some tests failed. Check the output above.');
process.exit(1);
} else {
console.log('β
All tests passed! MacroVox is ready to use.');
console.log('\nNext steps:');
console.log(' 1. npm start (start listening)');
console.log(' 2. Say a command (e.g., "undo", "cut")');
console.log(' 3. npm run add-command (add custom commands)');
process.exit(0);
}
}
}
async function main() {
const argv = await yargs(hideBin(process.argv))
.option('profile', {
alias: 'p',
describe: 'Profile to test',
type: 'string',
default: config.defaultProfile,
})
.option('duration', {
alias: 'd',
describe: 'Duration to listen (seconds)',
type: 'number',
default: 15,
})
.help()
.parse();
const test = new E2ETest({
profile: argv.profile,
duration: argv.duration,
});
await test.run();
}
main().catch(err => {
logger.error(`Fatal error: ${err.message}`);
process.exit(1);
});