1const t = require('tap')
2const { load: loadMockNpm } = require('../../fixtures/mock-npm')
3
4t.test('exec commands', async t => {
5  await t.test('with args, dev=true', async t => {
6    const SCRIPTS = []
7    let ARB_ARGS = null
8    let REIFY_CALLED = false
9    let ARB_OBJ = null
10
11    const { npm } = await loadMockNpm(t, {
12      mocks: {
13        '@npmcli/run-script': ({ event }) => {
14          SCRIPTS.push(event)
15        },
16        '@npmcli/arborist': function (args) {
17          ARB_ARGS = args
18          ARB_OBJ = this
19          this.reify = () => {
20            REIFY_CALLED = true
21          }
22        },
23        '{LIB}/utils/reify-finish.js': (_, arb) => {
24          if (arb !== ARB_OBJ) {
25            throw new Error('got wrong object passed to reify-finish')
26          }
27        },
28      },
29      config: {
30        // This is here because CI calls tests with `--ignore-scripts`, which config
31        // picks up from argv
32        'ignore-scripts': false,
33        'audit-level': 'low',
34        dev: true,
35      },
36    })
37
38    await npm.exec('install', ['fizzbuzz'])
39
40    t.match(
41      ARB_ARGS,
42      { global: false, path: npm.prefix, auditLevel: null },
43      'Arborist gets correct args and ignores auditLevel'
44    )
45    t.equal(REIFY_CALLED, true, 'called reify')
46    t.strictSame(SCRIPTS, [], 'no scripts when adding dep')
47  })
48
49  await t.test('without args', async t => {
50    const SCRIPTS = []
51    let ARB_ARGS = null
52    let REIFY_CALLED = false
53    let ARB_OBJ = null
54
55    const { npm } = await loadMockNpm(t, {
56      mocks: {
57        '@npmcli/run-script': ({ event }) => {
58          SCRIPTS.push(event)
59        },
60        '@npmcli/arborist': function (args) {
61          ARB_ARGS = args
62          ARB_OBJ = this
63          this.reify = () => {
64            REIFY_CALLED = true
65          }
66        },
67        '{LIB}/utils/reify-finish.js': (_, arb) => {
68          if (arb !== ARB_OBJ) {
69            throw new Error('got wrong object passed to reify-finish')
70          }
71        },
72      },
73      config: {
74
75      },
76    })
77
78    await npm.exec('install', [])
79    t.match(ARB_ARGS, { global: false, path: npm.prefix })
80    t.equal(REIFY_CALLED, true, 'called reify')
81    t.strictSame(SCRIPTS, [
82      'preinstall',
83      'install',
84      'postinstall',
85      'prepublish',
86      'preprepare',
87      'prepare',
88      'postprepare',
89    ], 'exec scripts when doing local build')
90  })
91
92  await t.test('should ignore scripts with --ignore-scripts', async t => {
93    const SCRIPTS = []
94    let REIFY_CALLED = false
95    const { npm } = await loadMockNpm(t, {
96      mocks: {
97        '{LIB}/utils/reify-finish.js': async () => {},
98        '@npmcli/run-script': ({ event }) => {
99          SCRIPTS.push(event)
100        },
101        '@npmcli/arborist': function () {
102          this.reify = () => {
103            REIFY_CALLED = true
104          }
105        },
106      },
107      config: {
108        'ignore-scripts': true,
109      },
110    })
111
112    await npm.exec('install', [])
113    t.equal(REIFY_CALLED, true, 'called reify')
114    t.strictSame(SCRIPTS, [], 'no scripts when adding dep')
115  })
116
117  await t.test('should install globally using Arborist', async t => {
118    const SCRIPTS = []
119    let ARB_ARGS = null
120    let REIFY_CALLED
121    const { npm } = await loadMockNpm(t, {
122      mocks: {
123        '@npmcli/run-script': ({ event }) => {
124          SCRIPTS.push(event)
125        },
126        '{LIB}/utils/reify-finish.js': async () => {},
127        '@npmcli/arborist': function (args) {
128          ARB_ARGS = args
129          this.reify = () => {
130            REIFY_CALLED = true
131          }
132        },
133      },
134      config: {
135        global: true,
136      },
137    })
138    await npm.exec('install', [])
139    t.match(
140      ARB_ARGS,
141      { global: true, path: npm.globalPrefix }
142    )
143    t.equal(REIFY_CALLED, true, 'called reify')
144    t.strictSame(SCRIPTS, [], 'no scripts when installing globally')
145    t.notOk(npm.config.get('audit', 'cli'))
146  })
147
148  await t.test('should not install invalid global package name', async t => {
149    const { npm } = await loadMockNpm(t, {
150      mocks: {
151        '@npmcli/run-script': () => {},
152        '{LIB}/utils/reify-finish.js': async () => {},
153        '@npmcli/arborist': function (args) {
154          throw new Error('should not reify')
155        },
156      },
157      config: {
158        global: true,
159      },
160    })
161    await t.rejects(
162      npm.exec('install', ['']),
163      /Usage:/,
164      'should not install invalid package name'
165    )
166  })
167
168  await t.test('npm i -g npm engines check success', async t => {
169    const { npm } = await loadMockNpm(t, {
170      mocks: {
171        '{LIB}/utils/reify-finish.js': async () => {},
172        '@npmcli/arborist': function () {
173          this.reify = () => {}
174        },
175        pacote: {
176          manifest: () => {
177            return {
178              version: '100.100.100',
179              engines: {
180                node: '>1',
181              },
182            }
183          },
184        },
185      },
186      config: {
187        global: true,
188      },
189    })
190    await npm.exec('install', ['npm'])
191    t.ok('No exceptions happen')
192  })
193
194  await t.test('npm i -g npm engines check failure', async t => {
195    const { npm } = await loadMockNpm(t, {
196      mocks: {
197        pacote: {
198          manifest: () => {
199            return {
200              _id: 'npm@1.2.3',
201              version: '100.100.100',
202              engines: {
203                node: '>1000',
204              },
205            }
206          },
207        },
208      },
209      config: {
210        global: true,
211      },
212    })
213    await t.rejects(
214      npm.exec('install', ['npm']),
215      {
216        message: 'Unsupported engine',
217        pkgid: 'npm@1.2.3',
218        current: {
219          node: process.version,
220          npm: '100.100.100',
221        },
222        required: {
223          node: '>1000',
224        },
225        code: 'EBADENGINE',
226      }
227    )
228  })
229
230  await t.test('npm i -g npm engines check failure forced override', async t => {
231    const { npm } = await loadMockNpm(t, {
232      mocks: {
233        '{LIB}/utils/reify-finish.js': async () => {},
234        '@npmcli/arborist': function () {
235          this.reify = () => {}
236        },
237        pacote: {
238          manifest: () => {
239            return {
240              _id: 'npm@1.2.3',
241              version: '100.100.100',
242              engines: {
243                node: '>1000',
244              },
245            }
246          },
247        },
248      },
249      config: {
250        global: true,
251        force: true,
252      },
253    })
254    await npm.exec('install', ['npm'])
255    t.ok('Does not throw')
256  })
257
258  await t.test('npm i -g npm@version engines check failure', async t => {
259    const { npm } = await loadMockNpm(t, {
260      mocks: {
261        '{LIB}/utils/reify-finish.js': async () => {},
262        '@npmcli/arborist': function () {
263          this.reify = () => {}
264        },
265        pacote: {
266          manifest: () => {
267            return {
268              _id: 'npm@1.2.3',
269              version: '100.100.100',
270              engines: {
271                node: '>1000',
272              },
273            }
274          },
275        },
276      },
277      config: {
278        global: true,
279      },
280    })
281    await t.rejects(
282      npm.exec('install', ['npm@100']),
283      {
284        message: 'Unsupported engine',
285        pkgid: 'npm@1.2.3',
286        current: {
287          node: process.version,
288          npm: '100.100.100',
289        },
290        required: {
291          node: '>1000',
292        },
293        code: 'EBADENGINE',
294      }
295    )
296  })
297})
298
299t.test('completion', async t => {
300  const mockComp = async (t, { noChdir } = {}) => loadMockNpm(t, {
301    command: 'install',
302    prefixDir: {
303      arborist: {
304        'package.json': '{}',
305      },
306      'arborist.txt': 'just a file',
307      'other-dir': { a: 'a' },
308    },
309    ...(noChdir ? { chdir: false } : {}),
310  })
311
312  await t.test('completion to folder - has a match', async t => {
313    const { install } = await mockComp(t)
314    const res = await install.completion({ partialWord: './ar' })
315    t.strictSame(res, ['arborist'], 'package dir match')
316  })
317
318  await t.test('completion to folder - invalid dir', async t => {
319    const { install } = await mockComp(t, { noChdir: true })
320    const res = await install.completion({ partialWord: '/does/not/exist' })
321    t.strictSame(res, [], 'invalid dir: no matching')
322  })
323
324  await t.test('completion to folder - no matches', async t => {
325    const { install } = await mockComp(t)
326    const res = await install.completion({ partialWord: './pa' })
327    t.strictSame(res, [], 'no name match')
328  })
329
330  await t.test('completion to folder - match is not a package', async t => {
331    const { install } = await mockComp(t)
332    const res = await install.completion({ partialWord: './othe' })
333    t.strictSame(res, [], 'no name match')
334  })
335
336  await t.test('completion to url', async t => {
337    const { install } = await mockComp(t)
338    const res = await install.completion({ partialWord: 'http://path/to/url' })
339    t.strictSame(res, [])
340  })
341
342  await t.test('no /', async t => {
343    const { install } = await mockComp(t)
344    const res = await install.completion({ partialWord: 'toto' })
345    t.notOk(res)
346  })
347
348  await t.test('only /', async t => {
349    const { install } = await mockComp(t)
350    const res = await install.completion({ partialWord: '/' })
351    t.strictSame(res, [])
352  })
353})
354
355t.test('location detection and audit', async (t) => {
356  await t.test('audit false without package.json', async t => {
357    const { npm } = await loadMockNpm(t, {
358      command: 'install',
359      prefixDir: {
360        // no package.json
361        'readme.txt': 'just a file',
362        'other-dir': { a: 'a' },
363      },
364    })
365    t.equal(npm.config.get('location'), 'user')
366    t.equal(npm.config.get('audit'), false)
367  })
368
369  await t.test('audit true with package.json', async t => {
370    const { npm } = await loadMockNpm(t, {
371      command: 'install',
372      prefixDir: {
373        'package.json': '{ "name": "testpkg", "version": "1.0.0" }',
374        'readme.txt': 'just a file',
375      },
376    })
377    t.equal(npm.config.get('location'), 'user')
378    t.equal(npm.config.get('audit'), true)
379  })
380
381  await t.test('audit true without package.json when set', async t => {
382    const { npm } = await loadMockNpm(t, {
383      command: 'install',
384      prefixDir: {
385        // no package.json
386        'readme.txt': 'just a file',
387        'other-dir': { a: 'a' },
388      },
389      config: {
390        audit: true,
391      },
392    })
393    t.equal(npm.config.get('location'), 'user')
394    t.equal(npm.config.get('audit'), true)
395  })
396
397  await t.test('audit true in root config without package.json', async t => {
398    const { npm } = await loadMockNpm(t, {
399      command: 'install',
400      prefixDir: {
401        // no package.json
402        'readme.txt': 'just a file',
403        'other-dir': { a: 'a' },
404      },
405      // change npmRoot to get it to use a builtin rc file
406      otherDirs: { npmrc: 'audit=true' },
407      npm: ({ other }) => ({ npmRoot: other }),
408    })
409    t.equal(npm.config.get('location'), 'user')
410    t.equal(npm.config.get('audit'), true)
411  })
412
413  await t.test('test for warning when --global & --audit', async t => {
414    const { npm, logs } = await loadMockNpm(t, {
415      command: 'install',
416      prefixDir: {
417        // no package.json
418        'readme.txt': 'just a file',
419        'other-dir': { a: 'a' },
420      },
421      config: {
422        audit: true,
423        global: true,
424      },
425    })
426    t.equal(npm.config.get('location'), 'user')
427    t.equal(npm.config.get('audit'), true)
428    t.equal(logs.warn[0][0], 'config')
429    t.equal(logs.warn[0][1], 'includes both --global and --audit, which is currently unsupported.')
430  })
431})
432