start-command 0.25.1 → 0.25.3

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.
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Tests for --status lookup by session name and detached status enrichment
3
+ * Issue #101: --session name not usable with --status, and --detached reports immediate completion
4
+ */
5
+
6
+ const { describe, it, expect, beforeEach, afterEach } = require('bun:test');
7
+ const { spawnSync } = require('child_process');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+
12
+ const {
13
+ ExecutionStore,
14
+ ExecutionRecord,
15
+ ExecutionStatus,
16
+ } = require('../src/lib/execution-store');
17
+ const {
18
+ queryStatus,
19
+ isDetachedSessionAlive,
20
+ enrichDetachedStatus,
21
+ } = require('../src/lib/status-formatter');
22
+
23
+ // Use temp directory for tests
24
+ const TEST_APP_FOLDER = path.join(
25
+ os.tmpdir(),
26
+ `session-name-status-test-${Date.now()}`
27
+ );
28
+
29
+ // Path to CLI
30
+ const CLI_PATH = path.join(__dirname, '../src/bin/cli.js');
31
+
32
+ // Helper to clean up test directory
33
+ function cleanupTestDir() {
34
+ if (fs.existsSync(TEST_APP_FOLDER)) {
35
+ fs.rmSync(TEST_APP_FOLDER, { recursive: true, force: true });
36
+ }
37
+ }
38
+
39
+ // Helper to run CLI command
40
+ function runCli(args, env = {}) {
41
+ const result = spawnSync('bun', [CLI_PATH, ...args], {
42
+ encoding: 'utf8',
43
+ env: {
44
+ ...process.env,
45
+ START_APP_FOLDER: TEST_APP_FOLDER,
46
+ ...env,
47
+ },
48
+ timeout: 10000,
49
+ });
50
+ return {
51
+ stdout: result.stdout || '',
52
+ stderr: result.stderr || '',
53
+ exitCode: result.status,
54
+ };
55
+ }
56
+
57
+ describe('Issue #101: --status with session name lookup', () => {
58
+ let store;
59
+
60
+ beforeEach(() => {
61
+ cleanupTestDir();
62
+ store = new ExecutionStore({
63
+ appFolder: TEST_APP_FOLDER,
64
+ useLinks: false,
65
+ });
66
+ });
67
+
68
+ afterEach(() => {
69
+ cleanupTestDir();
70
+ });
71
+
72
+ describe('ExecutionStore.get() session name lookup', () => {
73
+ it('should find record by UUID', () => {
74
+ const record = new ExecutionRecord({
75
+ command: 'echo hello',
76
+ pid: 12345,
77
+ logPath: '/tmp/test.log',
78
+ options: { sessionName: 'my-custom-session' },
79
+ });
80
+ record.complete(0);
81
+ store.save(record);
82
+
83
+ const found = store.get(record.uuid);
84
+ expect(found).not.toBeNull();
85
+ expect(found.uuid).toBe(record.uuid);
86
+ });
87
+
88
+ it('should find record by session name', () => {
89
+ const record = new ExecutionRecord({
90
+ command: 'sleep 60',
91
+ pid: 12345,
92
+ logPath: '/tmp/test.log',
93
+ options: {
94
+ sessionName: 'my-custom-session',
95
+ isolated: 'screen',
96
+ isolationMode: 'detached',
97
+ },
98
+ });
99
+ store.save(record);
100
+
101
+ const found = store.get('my-custom-session');
102
+ expect(found).not.toBeNull();
103
+ expect(found.uuid).toBe(record.uuid);
104
+ expect(found.options.sessionName).toBe('my-custom-session');
105
+ });
106
+
107
+ it('should prefer UUID match over session name', () => {
108
+ // Create two records: one whose session name matches the UUID of another
109
+ const record1 = new ExecutionRecord({
110
+ command: 'echo first',
111
+ pid: 111,
112
+ logPath: '/tmp/first.log',
113
+ options: { sessionName: 'some-session' },
114
+ });
115
+ record1.complete(0);
116
+ store.save(record1);
117
+
118
+ const record2 = new ExecutionRecord({
119
+ command: 'echo second',
120
+ pid: 222,
121
+ logPath: '/tmp/second.log',
122
+ options: { sessionName: record1.uuid }, // session name matches record1's UUID
123
+ });
124
+ store.save(record2);
125
+
126
+ // Looking up by record1's UUID should return record1, not record2
127
+ const found = store.get(record1.uuid);
128
+ expect(found.command).toBe('echo first');
129
+ });
130
+
131
+ it('should return null for non-existent session name', () => {
132
+ const found = store.get('nonexistent-session');
133
+ expect(found).toBeNull();
134
+ });
135
+
136
+ it('should return null for record without session name', () => {
137
+ const record = new ExecutionRecord({
138
+ command: 'echo hello',
139
+ pid: 12345,
140
+ logPath: '/tmp/test.log',
141
+ });
142
+ store.save(record);
143
+
144
+ const found = store.get('some-session-name');
145
+ expect(found).toBeNull();
146
+ });
147
+ });
148
+
149
+ describe('queryStatus() with session name', () => {
150
+ it('should query status by session name', () => {
151
+ const record = new ExecutionRecord({
152
+ command: 'sleep 60',
153
+ pid: 12345,
154
+ logPath: '/tmp/test.log',
155
+ options: {
156
+ sessionName: 'my-test-session',
157
+ isolated: 'screen',
158
+ isolationMode: 'attached',
159
+ },
160
+ });
161
+ record.complete(0);
162
+ store.save(record);
163
+
164
+ const result = queryStatus(store, 'my-test-session', 'json');
165
+ expect(result.success).toBe(true);
166
+
167
+ const parsed = JSON.parse(result.output);
168
+ expect(parsed.uuid).toBe(record.uuid);
169
+ expect(parsed.command).toBe('sleep 60');
170
+ });
171
+
172
+ it('should show error for non-existent session name', () => {
173
+ const result = queryStatus(store, 'nonexistent-session', 'json');
174
+ expect(result.success).toBe(false);
175
+ expect(result.error).toContain('No execution found');
176
+ expect(result.error).toContain('nonexistent-session');
177
+ });
178
+ });
179
+
180
+ describe('CLI --status with session name', () => {
181
+ it('should query by session name via CLI', () => {
182
+ const record = new ExecutionRecord({
183
+ command: 'echo hello world',
184
+ pid: 12345,
185
+ logPath: '/tmp/test.log',
186
+ options: {
187
+ sessionName: 'cli-test-session',
188
+ isolated: 'screen',
189
+ isolationMode: 'attached',
190
+ },
191
+ });
192
+ record.complete(0);
193
+ store.save(record);
194
+
195
+ const result = runCli([
196
+ '--status',
197
+ 'cli-test-session',
198
+ '--output-format',
199
+ 'json',
200
+ ]);
201
+
202
+ expect(result.exitCode).toBe(0);
203
+ const parsed = JSON.parse(result.stdout);
204
+ expect(parsed.uuid).toBe(record.uuid);
205
+ expect(parsed.command).toBe('echo hello world');
206
+ expect(parsed.status).toBe('executed');
207
+ });
208
+
209
+ it('should show error for non-existent session name via CLI', () => {
210
+ const result = runCli(['--status', 'nonexistent-session']);
211
+ expect(result.exitCode).toBe(1);
212
+ expect(result.stderr).toContain('No execution found');
213
+ });
214
+ });
215
+ });
216
+
217
+ describe('Issue #101: Detached status enrichment', () => {
218
+ describe('isDetachedSessionAlive()', () => {
219
+ it('should return null for non-detached records', () => {
220
+ const record = new ExecutionRecord({
221
+ command: 'echo hello',
222
+ options: {
223
+ sessionName: 'test',
224
+ isolated: 'screen',
225
+ isolationMode: 'attached',
226
+ },
227
+ });
228
+ expect(isDetachedSessionAlive(record)).toBeNull();
229
+ });
230
+
231
+ it('should return null for records without session name', () => {
232
+ const record = new ExecutionRecord({
233
+ command: 'echo hello',
234
+ options: { isolationMode: 'detached' },
235
+ });
236
+ expect(isDetachedSessionAlive(record)).toBeNull();
237
+ });
238
+
239
+ it('should return false for non-existent screen session', () => {
240
+ const record = new ExecutionRecord({
241
+ command: 'sleep 60',
242
+ options: {
243
+ sessionName: 'nonexistent-screen-session-test-101',
244
+ isolated: 'screen',
245
+ isolationMode: 'detached',
246
+ },
247
+ });
248
+ const alive = isDetachedSessionAlive(record);
249
+ // May be false or null depending on whether screen is installed
250
+ if (alive !== null) {
251
+ expect(alive).toBe(false);
252
+ }
253
+ });
254
+
255
+ it('should return false for non-existent docker container', () => {
256
+ const record = new ExecutionRecord({
257
+ command: 'sleep 60',
258
+ options: {
259
+ sessionName: 'nonexistent-docker-container-test-101',
260
+ isolated: 'docker',
261
+ isolationMode: 'detached',
262
+ },
263
+ });
264
+ const alive = isDetachedSessionAlive(record);
265
+ // May be false or null depending on whether docker is installed
266
+ if (alive !== null) {
267
+ expect(alive).toBe(false);
268
+ }
269
+ });
270
+ });
271
+
272
+ describe('enrichDetachedStatus()', () => {
273
+ it('should not modify non-detached records', () => {
274
+ const record = new ExecutionRecord({
275
+ command: 'echo hello',
276
+ options: {
277
+ sessionName: 'test',
278
+ isolated: 'screen',
279
+ isolationMode: 'attached',
280
+ },
281
+ });
282
+ record.complete(0);
283
+
284
+ const enriched = enrichDetachedStatus(record);
285
+ expect(enriched.status).toBe('executed');
286
+ expect(enriched.exitCode).toBe(0);
287
+ });
288
+
289
+ it('should mark non-running detached session as executed', () => {
290
+ const record = new ExecutionRecord({
291
+ command: 'sleep 60',
292
+ options: {
293
+ sessionName: 'nonexistent-session-enrich-test-101',
294
+ isolated: 'screen',
295
+ isolationMode: 'detached',
296
+ },
297
+ });
298
+ // Record says executing, but session doesn't exist
299
+
300
+ const enriched = enrichDetachedStatus(record);
301
+ // If screen is available, should mark as executed
302
+ // If screen is not available, should return unchanged
303
+ if (enriched.status === 'executed') {
304
+ expect(enriched.exitCode).toBe(-1); // Unknown exit code
305
+ expect(enriched.endTime).not.toBeNull();
306
+ }
307
+ });
308
+ });
309
+ });
310
+
311
+ console.log('=== Session Name Status Tests (Issue #101) ===');