summaryrefslogtreecommitdiff
path: root/integration-tests/shell-service.test.ts
blob: 387d635c13fbcada76c5845cec5d195565d20c3a (plain)
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
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect, beforeAll } from 'vitest';
import { ShellExecutionService } from '../packages/core/src/services/shellExecutionService.js';
import * as fs from 'fs/promises';
import * as path from 'path';
import { vi } from 'vitest';

describe('ShellExecutionService programmatic integration tests', () => {
  let testDir: string;

  beforeAll(async () => {
    // Create a dedicated directory for this test suite to avoid conflicts.
    testDir = path.join(
      process.env['INTEGRATION_TEST_FILE_DIR']!,
      'shell-service-tests',
    );
    await fs.mkdir(testDir, { recursive: true });
  });

  it('should execute a simple cross-platform command (echo)', async () => {
    const command = 'echo "hello from the service"';
    const onOutputEvent = vi.fn();
    const abortController = new AbortController();

    const handle = await ShellExecutionService.execute(
      command,
      testDir,
      onOutputEvent,
      abortController.signal,
      false,
    );

    const result = await handle.result;

    expect(result.error).toBeNull();
    expect(result.exitCode).toBe(0);
    // Output can vary slightly between shells (e.g., quotes), so check for inclusion.
    expect(result.output).toContain('hello from the service');
  });

  it.runIf(process.platform === 'win32')(
    'should execute "dir" on Windows',
    async () => {
      const testFile = 'test-file-windows.txt';
      await fs.writeFile(path.join(testDir, testFile), 'windows test');

      const command = 'dir';
      const onOutputEvent = vi.fn();
      const abortController = new AbortController();

      const handle = await ShellExecutionService.execute(
        command,
        testDir,
        onOutputEvent,
        abortController.signal,
        false,
      );

      const result = await handle.result;

      expect(result.error).toBeNull();
      expect(result.exitCode).toBe(0);
      expect(result.output).toContain(testFile);
    },
  );

  it.skipIf(process.platform === 'win32')(
    'should execute "ls -l" on Unix',
    async () => {
      const testFile = 'test-file-unix.txt';
      await fs.writeFile(path.join(testDir, testFile), 'unix test');

      const command = 'ls -l';
      const onOutputEvent = vi.fn();
      const abortController = new AbortController();

      const handle = await ShellExecutionService.execute(
        command,
        testDir,
        onOutputEvent,
        abortController.signal,
        false,
      );

      const result = await handle.result;

      expect(result.error).toBeNull();
      expect(result.exitCode).toBe(0);
      expect(result.output).toContain(testFile);
    },
  );

  it('should abort a running process', async () => {
    // A command that runs for a bit. 'sleep' on unix, 'timeout' on windows.
    const command = process.platform === 'win32' ? 'timeout /t 20' : 'sleep 20';
    const onOutputEvent = vi.fn();
    const abortController = new AbortController();

    const handle = await ShellExecutionService.execute(
      command,
      testDir,
      onOutputEvent,
      abortController.signal,
      false,
    );

    // Abort shortly after starting
    setTimeout(() => abortController.abort(), 50);

    const result = await handle.result;

    // For debugging the flaky test.
    console.log('Abort test result:', result);

    expect(result.aborted).toBe(true);
    // A clean exit is exitCode 0 and no signal. If the process was truly
    // aborted, it should not have exited cleanly.
    const exitedCleanly = result.exitCode === 0 && result.signal === null;
    expect(exitedCleanly, 'Process should not have exited cleanly').toBe(false);
  });
});