1const t = require('tap')
2const mockNpm = require('../../fixtures/mock-npm')
3const { cleanCwd } = require('../../fixtures/clean-snapshot')
4
5const mockExplore = async (t, exec, {
6  PJ_ERROR = null,
7  RUN_SCRIPT_ERROR = null,
8  RUN_SCRIPT_EXIT_CODE = 0,
9  RUN_SCRIPT_SIGNAL = null,
10} = {}) => {
11  let PJ_CALLED = ''
12  const mockPJ = {
13    normalize: async path => {
14      if (PJ_ERROR) {
15        throw PJ_ERROR
16      }
17      PJ_CALLED = cleanCwd(path)
18      return { content: { some: 'package' } }
19    },
20  }
21
22  let RUN_SCRIPT_EXEC = null
23  const mockRunScript = ({ pkg, event }) => {
24    if (event !== '_explore') {
25      throw new Error('got wrong event name')
26    }
27
28    RUN_SCRIPT_EXEC = pkg.scripts._explore
29
30    if (RUN_SCRIPT_ERROR) {
31      return Promise.reject(RUN_SCRIPT_ERROR)
32    }
33
34    if (RUN_SCRIPT_EXIT_CODE || RUN_SCRIPT_SIGNAL) {
35      return Promise.reject(Object.assign(new Error('command failed'), {
36        code: RUN_SCRIPT_EXIT_CODE,
37        signal: RUN_SCRIPT_SIGNAL,
38      }))
39    }
40
41    return Promise.resolve({ code: 0, signal: null })
42  }
43
44  const mock = await mockNpm(t, {
45    mocks: {
46      '@npmcli/package-json': mockPJ,
47      '@npmcli/run-script': mockRunScript,
48    },
49    config: {
50      shell: 'shell-command',
51    },
52  })
53
54  await mock.npm.exec('explore', exec)
55
56  return {
57    ...mock,
58    PJ_CALLED,
59    RUN_SCRIPT_EXEC,
60    output: cleanCwd(mock.joinedOutput()).trim(),
61  }
62}
63
64t.test('basic interactive', async t => {
65  const {
66    output,
67    PJ_CALLED,
68    RUN_SCRIPT_EXEC,
69  } = await mockExplore(t, ['pkg'])
70
71  t.ok(PJ_CALLED.endsWith('/pkg'))
72  t.strictSame(RUN_SCRIPT_EXEC, 'shell-command')
73  t.match(output, /Exploring \{CWD\}\/[\w-_/]+\nType 'exit' or \^D when finished/)
74})
75
76t.test('interactive tracks exit code', async t => {
77  t.test('code', async t => {
78    const {
79      output,
80      PJ_CALLED,
81      RUN_SCRIPT_EXEC,
82    } = await mockExplore(t, ['pkg'], { RUN_SCRIPT_EXIT_CODE: 99 })
83
84    t.ok(PJ_CALLED.endsWith('/pkg'))
85    t.strictSame(RUN_SCRIPT_EXEC, 'shell-command')
86    t.match(output, /Exploring \{CWD\}\/[\w-_/]+\nType 'exit' or \^D when finished/)
87
88    t.equal(process.exitCode, 99)
89  })
90
91  t.test('spawn fail', async t => {
92    const RUN_SCRIPT_ERROR = Object.assign(new Error('glorb'), {
93      code: 33,
94    })
95    await t.rejects(
96      mockExplore(t, ['pkg'], { RUN_SCRIPT_ERROR }),
97      { message: 'glorb', code: 33 }
98    )
99    t.equal(process.exitCode, 33)
100  })
101
102  t.test('spawn fail, 0 exit code', async t => {
103    const RUN_SCRIPT_ERROR = Object.assign(new Error('glorb'), {
104      code: 0,
105    })
106    await t.rejects(
107      mockExplore(t, ['pkg'], { RUN_SCRIPT_ERROR }),
108      { message: 'glorb', code: 0 }
109    )
110    t.equal(process.exitCode, 1)
111  })
112
113  t.test('spawn fail, no exit code', async t => {
114    const RUN_SCRIPT_ERROR = Object.assign(new Error('command failed'), {
115      code: 'EPROBLEM',
116    })
117    await t.rejects(
118      mockExplore(t, ['pkg'], { RUN_SCRIPT_ERROR }),
119      { message: 'command failed', code: 'EPROBLEM' }
120    )
121    t.equal(process.exitCode, 1)
122  })
123})
124
125t.test('basic non-interactive', async t => {
126  const {
127    output,
128    PJ_CALLED,
129    RUN_SCRIPT_EXEC,
130  } = await mockExplore(t, ['pkg', 'ls'])
131
132  t.ok(PJ_CALLED.endsWith('/pkg'))
133  t.strictSame(RUN_SCRIPT_EXEC, 'ls')
134
135  t.strictSame(output, '')
136})
137
138t.test('signal fails non-interactive', async t => {
139  await t.rejects(
140    mockExplore(t, ['pkg', 'ls'], { RUN_SCRIPT_SIGNAL: 'SIGPROBLEM' }),
141    {
142      message: 'command failed',
143      signal: 'SIGPROBLEM',
144    }
145  )
146})
147
148t.test('usage if no pkg provided', async t => {
149  const noPkg = [
150    [],
151    ['foo/../..'],
152    ['asdf/..'],
153    ['.'],
154    ['..'],
155    ['../..'],
156  ]
157
158  for (const args of noPkg) {
159    t.test(JSON.stringify(args), async t => {
160      await t.rejects(
161        mockExplore(t, args),
162        'Usage:'
163      )
164    })
165  }
166})
167
168t.test('pkg not installed', async t => {
169  const PJ_ERROR = new Error('plurple')
170
171  await t.rejects(
172    mockExplore(t, ['pkg', 'ls'], { PJ_ERROR }),
173    { message: 'plurple' }
174  )
175})
176