1'use strict' 2 3const cacheFile = require('npm-cache-filename') 4const mkdirp = require('mkdirp') 5const mr = require('npm-registry-mock') 6const path = require('path') 7const qs = require('querystring') 8const test = require('tap').test 9 10const Tacks = require('tacks') 11const File = Tacks.File 12 13const common = require('../common-tap.js') 14 15// this test uses a fresh cache for each test block 16// create them all in common.cache so that we can verify 17// them for root-owned files in sudotest 18let CACHE_DIR 19let cacheBase 20let cachePath 21let cacheCounter = 1 22function setup () { 23 CACHE_DIR = common.cache + '/' + cacheCounter++ 24 cacheBase = cacheFile(CACHE_DIR)(common.registry + '/-/all') 25 cachePath = path.join(cacheBase, '.cache.json') 26 mkdirp.sync(cacheBase) 27 fixOwner(CACHE_DIR) 28} 29 30const chownr = require('chownr') 31const fixOwner = ( 32 process.getuid && process.getuid() === 0 && 33 process.env.SUDO_UID && process.env.SUDO_GID 34) ? (path) => chownr.sync(path, +process.env.SUDO_UID, +process.env.SUDO_GID) 35 : () => {} 36 37let server 38 39test('setup', function (t) { 40 mr({port: common.port, throwOnUnmatched: true}, function (err, s) { 41 t.ifError(err, 'registry mocked successfully') 42 server = s 43 t.pass('all set up') 44 t.done() 45 }) 46}) 47 48test('notifies when there are no results', function (t) { 49 setup() 50 const query = qs.stringify({ 51 text: 'none', 52 size: 20, 53 from: 0, 54 quality: 0.65, 55 popularity: 0.98, 56 maintenance: 0.5 57 }) 58 server.get(`/-/v1/search?${query}`).once().reply(200, { 59 objects: [] 60 }) 61 common.npm([ 62 'search', 'none', 63 '--registry', common.registry, 64 '--loglevel', 'error', 65 '--cache', CACHE_DIR 66 ], {}, function (err, code, stdout, stderr) { 67 if (err) throw err 68 t.equal(stderr, '', 'no error output') 69 t.equal(code, 0, 'search gives 0 error code even if no matches') 70 t.match(stdout, /No matches found/, 'Useful message on search failure') 71 t.done() 72 }) 73}) 74 75test('spits out a useful error when no cache nor network', function (t) { 76 setup() 77 const query = qs.stringify({ 78 text: 'foo', 79 size: 20, 80 from: 0, 81 quality: 0.65, 82 popularity: 0.98, 83 maintenance: 0.5 84 }) 85 server.get(`/-/v1/search?${query}`).once().reply(404, {}) 86 server.get('/-/all').many().reply(404, {}) 87 const cacheContents = {} 88 const fixture = new Tacks(File(cacheContents)) 89 fixture.create(cachePath) 90 fixOwner(cachePath) 91 common.npm([ 92 'search', 'foo', 93 '--registry', common.registry, 94 '--loglevel', 'silly', 95 '--json', 96 '--fetch-retry-mintimeout', 0, 97 '--fetch-retry-maxtimeout', 0, 98 '--cache', CACHE_DIR 99 ], {}, function (err, code, stdout, stderr) { 100 if (err) throw err 101 t.equal(code, 1, 'non-zero exit code') 102 t.match(JSON.parse(stdout).error.summary, /No search sources available/) 103 t.match(stderr, /No search sources available/, 'useful error') 104 t.done() 105 }) 106}) 107 108test('can switch to JSON mode', function (t) { 109 setup() 110 const query = qs.stringify({ 111 text: 'oo', 112 size: 20, 113 from: 0, 114 quality: 0.65, 115 popularity: 0.98, 116 maintenance: 0.5 117 }) 118 server.get(`/-/v1/search?${query}`).once().reply(200, { 119 objects: [ 120 { package: { name: 'cool', version: '1.0.0' } }, 121 { package: { name: 'foo', version: '2.0.0' } } 122 ] 123 }) 124 common.npm([ 125 'search', 'oo', 126 '--json', 127 '--registry', common.registry, 128 '--loglevel', 'error', 129 '--cache', CACHE_DIR 130 ], {}, function (err, code, stdout, stderr) { 131 if (err) throw err 132 t.equal(stderr, '', 'no error output') 133 t.equal(code, 0, 'search gives 0 error code even if no matches') 134 t.similar(JSON.parse(stdout), [ 135 { 136 name: 'cool', 137 version: '1.0.0' 138 }, 139 { 140 name: 'foo', 141 version: '2.0.0' 142 } 143 ], 'results returned as valid json') 144 t.done() 145 }) 146}) 147 148test('JSON mode does not notify on empty', function (t) { 149 setup() 150 const query = qs.stringify({ 151 text: 'oo', 152 size: 20, 153 from: 0, 154 quality: 0.65, 155 popularity: 0.98, 156 maintenance: 0.5 157 }) 158 server.get(`/-/v1/search?${query}`).once().reply(200, { 159 objects: [] 160 }) 161 common.npm([ 162 'search', 'oo', 163 '--json', 164 '--registry', common.registry, 165 '--loglevel', 'error', 166 '--cache', CACHE_DIR 167 ], {}, function (err, code, stdout, stderr) { 168 if (err) throw err 169 t.deepEquals(JSON.parse(stdout), [], 'no notification about no results') 170 t.equal(stderr, '', 'no error output') 171 t.equal(code, 0, 'search gives 0 error code even if no matches') 172 t.done() 173 }) 174}) 175 176test('can switch to tab separated mode', function (t) { 177 setup() 178 const query = qs.stringify({ 179 text: 'oo', 180 size: 20, 181 from: 0, 182 quality: 0.65, 183 popularity: 0.98, 184 maintenance: 0.5 185 }) 186 server.get(`/-/v1/search?${query}`).once().reply(200, { 187 objects: [ 188 { package: { name: 'cool', version: '1.0.0' } }, 189 { package: { name: 'foo', description: 'this\thas\ttabs', version: '2.0.0' } } 190 ] 191 }) 192 common.npm([ 193 'search', 'oo', 194 '--parseable', 195 '--registry', common.registry, 196 '--loglevel', 'error', 197 '--cache', CACHE_DIR 198 ], {}, function (err, code, stdout, stderr) { 199 if (err) throw err 200 t.equal(stdout, 'cool\t\t\tprehistoric\t1.0.0\t\nfoo\tthis has tabs\t\tprehistoric\t2.0.0\t\n', 'correct output, including replacing tabs in descriptions') 201 t.equal(stderr, '', 'no error output') 202 t.equal(code, 0, 'search gives 0 error code even if no matches') 203 t.done() 204 }) 205}) 206 207test('tab mode does not notify on empty', function (t) { 208 setup() 209 const query = qs.stringify({ 210 text: 'oo', 211 size: 20, 212 from: 0, 213 quality: 0.65, 214 popularity: 0.98, 215 maintenance: 0.5 216 }) 217 server.get(`/-/v1/search?${query}`).once().reply(200, { 218 objects: [] 219 }) 220 common.npm([ 221 'search', 'oo', 222 '--parseable', 223 '--registry', common.registry, 224 '--loglevel', 'error', 225 '--cache', CACHE_DIR 226 ], {}, function (err, code, stdout, stderr) { 227 if (err) throw err 228 t.equal(stdout, '', 'no notification about no results') 229 t.equal(stderr, '', 'no error output') 230 t.equal(code, 0, 'search gives 0 error code even if no matches') 231 t.done() 232 }) 233}) 234 235test('no arguments provided should error', function (t) { 236 setup() 237 common.npm(['search', '--cache', CACHE_DIR], {}, function (err, code, stdout, stderr) { 238 if (err) throw err 239 t.equal(code, 1, 'search finished unsuccessfully') 240 241 t.match( 242 stderr, 243 /search must be called with arguments/, 244 'should have correct error message' 245 ) 246 t.end() 247 }) 248}) 249 250test('cleanup', function (t) { 251 server.done() 252 server.close() 253 t.end() 254}) 255