1const t = require('tap') 2const tmock = require('../../fixtures/tmock') 3const mockNpm = require('../../fixtures/mock-npm') 4 5const mockOpenUrl = async (t, args, { openerResult, ...config } = {}) => { 6 let openerUrl = null 7 let openerOpts = null 8 9 const open = async (url, options) => { 10 openerUrl = url 11 openerOpts = options 12 if (openerResult) { 13 throw openerResult 14 } 15 } 16 17 const mock = await mockNpm(t, { config }) 18 19 const openUrl = tmock(t, '{LIB}/utils/open-url.js', { 20 '@npmcli/promise-spawn': { open }, 21 }) 22 23 const openWithNpm = (...a) => openUrl(mock.npm, ...a) 24 25 if (args) { 26 await openWithNpm(...args) 27 } 28 29 return { 30 ...mock, 31 openUrl: openWithNpm, 32 openerUrl: () => openerUrl, 33 openerOpts: () => openerOpts, 34 } 35} 36 37t.test('opens a url', async t => { 38 const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t, 39 ['https://www.npmjs.com', 'npm home']) 40 t.equal(openerUrl(), 'https://www.npmjs.com', 'opened the given url') 41 t.same(openerOpts(), { command: null }, 'passed command as null (the default)') 42 t.same(joinedOutput(), '', 'printed no output') 43}) 44 45t.test('returns error for non-https url', async t => { 46 const { openUrl, openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t) 47 await t.rejects( 48 openUrl('ftp://www.npmjs.com', 'npm home'), 49 /Invalid URL/, 50 'got the correct error' 51 ) 52 t.equal(openerUrl(), null, 'did not open') 53 t.same(openerOpts(), null, 'did not open') 54 t.same(joinedOutput(), '', 'printed no output') 55}) 56 57t.test('returns error for file url', async t => { 58 const { openUrl, openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t) 59 await t.rejects( 60 openUrl('file:///usr/local/bin/ls', 'npm home'), 61 /Invalid URL/, 62 'got the correct error' 63 ) 64 t.equal(openerUrl(), null, 'did not open') 65 t.same(openerOpts(), null, 'did not open') 66 t.same(joinedOutput(), '', 'printed no output') 67}) 68 69t.test('file url allowed if explicitly asked for', async t => { 70 const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t, 71 ['file:///man/page/npm-install', 'npm home', true]) 72 t.equal(openerUrl(), 'file:///man/page/npm-install', 'opened the given url') 73 t.same(openerOpts(), { command: null }, 'passed command as null (the default)') 74 t.same(joinedOutput(), '', 'printed no output') 75}) 76 77t.test('returns error for non-parseable url', async t => { 78 const { openUrl, openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t) 79 await t.rejects( 80 openUrl('git+ssh://user@host:repo.git', 'npm home'), 81 /Invalid URL/, 82 'got the correct error' 83 ) 84 t.equal(openerUrl(), null, 'did not open') 85 t.same(openerOpts(), null, 'did not open') 86 t.same(joinedOutput(), '', 'printed no output') 87}) 88 89t.test('encodes non-URL-safe characters in url provided', async t => { 90 const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t, 91 ['https://www.npmjs.com/|cat', 'npm home']) 92 t.equal(openerUrl(), 'https://www.npmjs.com/%7Ccat', 'opened the encoded url') 93 t.same(openerOpts(), { command: null }, 'passed command as null (the default)') 94 t.same(joinedOutput(), '', 'printed no output') 95}) 96 97t.test('opens a url with the given browser', async t => { 98 const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t, 99 ['https://www.npmjs.com', 'npm home'], { browser: 'chrome' }) 100 t.equal(openerUrl(), 'https://www.npmjs.com', 'opened the given url') 101 // FIXME: browser string is parsed as a boolean in config layer 102 // this is a bug that should be fixed or the config should not allow it 103 t.same(openerOpts(), { command: null }, 'passed the given browser as command') 104 t.same(joinedOutput(), '', 'printed no output') 105}) 106 107t.test('prints where to go when browser is disabled', async t => { 108 const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t, 109 ['https://www.npmjs.com', 'npm home'], { browser: false }) 110 t.equal(openerUrl(), null, 'did not open') 111 t.same(openerOpts(), null, 'did not open') 112 t.matchSnapshot(joinedOutput(), 'printed expected message') 113}) 114 115t.test('prints where to go when browser is disabled and json is enabled', async t => { 116 const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t, 117 ['https://www.npmjs.com', 'npm home'], { browser: false, json: true }) 118 t.equal(openerUrl(), null, 'did not open') 119 t.same(openerOpts(), null, 'did not open') 120 t.matchSnapshot(joinedOutput(), 'printed expected message') 121}) 122 123t.test('prints where to go when given browser does not exist', async t => { 124 const { openerUrl, openerOpts, joinedOutput } = await mockOpenUrl(t, 125 ['https://www.npmjs.com', 'npm home'], 126 { 127 openerResult: Object.assign(new Error('failed'), { code: 127 }), 128 } 129 ) 130 131 t.equal(openerUrl(), 'https://www.npmjs.com', 'tried to open the correct url') 132 t.same(openerOpts(), { command: null }, 'tried to use the correct browser') 133 t.matchSnapshot(joinedOutput(), 'printed expected message') 134}) 135 136t.test('handles unknown opener error', async t => { 137 const { openUrl } = await mockOpenUrl(t, null, { 138 browser: 'firefox', 139 openerResult: Object.assign(new Error('failed'), { code: 'ENOBRIAN' }), 140 }) 141 142 await t.rejects(openUrl('https://www.npmjs.com', 'npm home'), 'failed', 'got the correct error') 143}) 144