1const t = require('tap') 2const fs = require('fs') 3const path = require('path') 4 5const { load: loadMockNpm } = require('../../fixtures/mock-npm') 6const tnock = require('../../fixtures/tnock.js') 7const mockGlobals = require('@npmcli/mock-globals') 8const { cleanCwd, cleanDate } = require('../../fixtures/clean-snapshot.js') 9 10const cleanCacheSha = (str) => 11 str.replace(/content-v2\/sha512\/[^"]+/g, 'content-v2/sha512/{sha}') 12 13t.cleanSnapshot = p => cleanCacheSha(cleanDate(cleanCwd(p))) 14 15const npmManifest = (version) => { 16 return { 17 name: 'npm', 18 versions: { 19 [version]: { 20 name: 'npm', 21 version: version, 22 }, 23 }, 24 time: { 25 [version]: new Date(), 26 }, 27 'dist-tags': { latest: version }, 28 } 29} 30 31const nodeVersions = [ 32 { version: 'v2.0.1', lts: false }, 33 { version: 'v2.0.0', lts: false }, 34 { version: 'v1.0.0', lts: 'NpmTestium' }, 35] 36 37const dirs = { 38 prefixDir: { 39 node_modules: { 40 testLink: t.fixture('symlink', './testDir'), 41 testDir: { 42 testFile: 'test contents', 43 }, 44 '.bin': {}, 45 }, 46 }, 47 globalPrefixDir: { 48 bin: {}, 49 node_modules: {}, 50 }, 51} 52 53const globals = ({ globalPrefix }) => { 54 return { 55 process: { 56 'env.PATH': `${globalPrefix}:${path.join(globalPrefix, 'bin')}`, 57 version: 'v1.0.0', 58 }, 59 } 60} 61 62mockGlobals(t, { 63 process: { 64 // set platform to not-windows before any tests because mockNpm 65 // sets the platform specific location of node_modules based on it 66 platform: 'test-not-windows', 67 // getuid and getgid do not exist in windows, so we shim them 68 // to return 0, as that is the value that lstat will assign the 69 // gid and uid properties for fs.Stats objects 70 ...(process.platform === 'win32' ? { getuid: () => 0, getgid: () => 0 } : {}), 71 }, 72}) 73 74const mocks = { 75 '{ROOT}/package.json': { version: '1.0.0' }, 76 which: async () => '/path/to/git', 77 cacache: { 78 verify: () => { 79 return { badContentCount: 0, reclaimedCount: 0, missingContent: 0, verifiedContent: 0 } 80 }, 81 }, 82} 83 84t.test('all clear', async t => { 85 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 86 mocks, 87 globals, 88 ...dirs, 89 }) 90 tnock(t, npm.config.get('registry')) 91 .get('/-/ping?write=true').reply(200, '{}') 92 .get('/npm').reply(200, npmManifest(npm.version)) 93 tnock(t, 'https://nodejs.org') 94 .get('/dist/index.json').reply(200, nodeVersions) 95 await npm.exec('doctor', []) 96 t.matchSnapshot(joinedOutput(), 'output') 97 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 98}) 99 100t.test('all clear in color', async t => { 101 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 102 mocks, 103 globals, 104 ...dirs, 105 config: { 106 color: 'always', 107 }, 108 }) 109 tnock(t, npm.config.get('registry')) 110 .get('/-/ping?write=true').reply(200, '{}') 111 .get('/npm').reply(200, npmManifest(npm.version)) 112 tnock(t, 'https://nodejs.org') 113 .get('/dist/index.json').reply(200, nodeVersions) 114 await npm.exec('doctor', []) 115 t.matchSnapshot(joinedOutput(), 'everything is ok in color') 116 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 117}) 118 119t.test('silent success', async t => { 120 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 121 mocks, 122 globals, 123 config: { 124 loglevel: 'silent', 125 }, 126 ...dirs, 127 }) 128 tnock(t, npm.config.get('registry')) 129 .get('/-/ping?write=true').reply(200, '{}') 130 .get('/npm').reply(200, npmManifest(npm.version)) 131 tnock(t, 'https://nodejs.org') 132 .get('/dist/index.json').reply(200, nodeVersions) 133 await npm.exec('doctor', []) 134 t.matchSnapshot(joinedOutput(), 'output') 135 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 136}) 137 138t.test('silent errors', async t => { 139 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 140 mocks, 141 globals, 142 config: { 143 loglevel: 'silent', 144 }, 145 ...dirs, 146 }) 147 tnock(t, npm.config.get('registry')) 148 .get('/-/ping?write=true').reply(404, '{}') 149 await t.rejects(npm.exec('doctor', ['ping']), { 150 message: /Check logs/, 151 }) 152 t.matchSnapshot(joinedOutput(), 'output') 153 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 154}) 155 156t.test('ping 404', async t => { 157 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 158 mocks, 159 globals, 160 ...dirs, 161 }) 162 tnock(t, npm.config.get('registry')) 163 .get('/-/ping?write=true').reply(404, '{}') 164 .get('/npm').reply(200, npmManifest(npm.version)) 165 tnock(t, 'https://nodejs.org') 166 .get('/dist/index.json').reply(200, nodeVersions) 167 await t.rejects(npm.exec('doctor', []), { 168 message: /See above/, 169 }) 170 t.matchSnapshot(joinedOutput(), 'ping 404') 171 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 172}) 173 174t.test('ping 404 in color', async t => { 175 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 176 mocks, 177 globals, 178 ...dirs, 179 config: { 180 color: 'always', 181 }, 182 }) 183 tnock(t, npm.config.get('registry')) 184 .get('/-/ping?write=true').reply(404, '{}') 185 .get('/npm').reply(200, npmManifest(npm.version)) 186 tnock(t, 'https://nodejs.org') 187 .get('/dist/index.json').reply(200, nodeVersions) 188 await t.rejects(npm.exec('doctor', [])) 189 t.matchSnapshot(joinedOutput(), 'ping 404 in color') 190 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 191}) 192 193t.test('ping exception with code', async t => { 194 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 195 mocks, 196 globals, 197 ...dirs, 198 }) 199 tnock(t, npm.config.get('registry')) 200 .get('/-/ping?write=true').replyWithError({ message: 'Test Error', code: 'TEST' }) 201 .get('/npm').reply(200, npmManifest(npm.version)) 202 tnock(t, 'https://nodejs.org') 203 .get('/dist/index.json').reply(200, nodeVersions) 204 await t.rejects(npm.exec('doctor', [])) 205 t.matchSnapshot(joinedOutput(), 'ping failure') 206 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 207}) 208 209t.test('ping exception without code', async t => { 210 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 211 mocks, 212 globals, 213 ...dirs, 214 }) 215 tnock(t, npm.config.get('registry')) 216 .get('/-/ping?write=true').replyWithError({ message: 'Test Error', code: false }) 217 .get('/npm').reply(200, npmManifest(npm.version)) 218 tnock(t, 'https://nodejs.org') 219 .get('/dist/index.json').reply(200, nodeVersions) 220 await t.rejects(npm.exec('doctor', [])) 221 t.matchSnapshot(joinedOutput(), 'ping failure') 222 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 223}) 224 225t.test('npm out of date', async t => { 226 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 227 mocks, 228 globals, 229 ...dirs, 230 }) 231 tnock(t, npm.config.get('registry')) 232 .get('/-/ping?write=true').reply(200, '{}') 233 .get('/npm').reply(200, npmManifest('2.0.0')) 234 tnock(t, 'https://nodejs.org') 235 .get('/dist/index.json').reply(200, nodeVersions) 236 await t.rejects(npm.exec('doctor', [])) 237 t.matchSnapshot(joinedOutput(), 'npm is out of date') 238 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 239}) 240 241t.test('node out of date - lts', async t => { 242 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 243 mocks, 244 globals: (context) => { 245 const g = globals(context) 246 return { 247 ...g, 248 process: { 249 ...g.process, 250 version: 'v0.0.1', 251 }, 252 } 253 }, 254 ...dirs, 255 }) 256 tnock(t, npm.config.get('registry')) 257 .get('/-/ping?write=true').reply(200, '{}') 258 .get('/npm').reply(200, npmManifest(npm.version)) 259 tnock(t, 'https://nodejs.org') 260 .get('/dist/index.json').reply(200, nodeVersions) 261 await t.rejects(npm.exec('doctor', [])) 262 t.matchSnapshot(joinedOutput(), 'node is out of date') 263 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 264}) 265 266t.test('node out of date - current', async t => { 267 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 268 mocks, 269 globals: (context) => { 270 const g = globals(context) 271 return { 272 ...g, 273 process: { 274 ...g.process, 275 version: 'v2.0.0', 276 }, 277 } 278 }, 279 ...dirs, 280 }) 281 tnock(t, npm.config.get('registry')) 282 .get('/-/ping?write=true').reply(200, '{}') 283 .get('/npm').reply(200, npmManifest(npm.version)) 284 tnock(t, 'https://nodejs.org') 285 .get('/dist/index.json').reply(200, nodeVersions) 286 await t.rejects(npm.exec('doctor', [])) 287 t.matchSnapshot(joinedOutput(), 'node is out of date') 288 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 289}) 290 291t.test('non-default registry', async t => { 292 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 293 mocks, 294 globals, 295 config: { registry: 'http://some-other-url.npmjs.org' }, 296 ...dirs, 297 }) 298 tnock(t, npm.config.get('registry')) 299 .get('/-/ping?write=true').reply(200, '{}') 300 .get('/npm').reply(200, npmManifest(npm.version)) 301 tnock(t, 'https://nodejs.org') 302 .get('/dist/index.json').reply(200, nodeVersions) 303 await t.rejects(npm.exec('doctor', [])) 304 t.matchSnapshot(joinedOutput(), 'non default registry') 305 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 306}) 307 308t.test('missing git', async t => { 309 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 310 mocks: { 311 ...mocks, 312 which: async () => { 313 throw new Error('test error') 314 }, 315 }, 316 globals, 317 ...dirs, 318 }) 319 tnock(t, npm.config.get('registry')) 320 .get('/-/ping?write=true').reply(200, '{}') 321 .get('/npm').reply(200, npmManifest(npm.version)) 322 tnock(t, 'https://nodejs.org') 323 .get('/dist/index.json').reply(200, nodeVersions) 324 await t.rejects(npm.exec('doctor', [])) 325 t.matchSnapshot(joinedOutput(), 'missing git') 326 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 327}) 328 329t.test('windows skips permissions checks', async t => { 330 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 331 mocks, 332 globals: (context) => { 333 const g = globals(context) 334 return { 335 ...g, 336 process: { 337 ...g.process, 338 platform: 'win32', 339 }, 340 } 341 }, 342 prefixDir: {}, 343 globalPrefixDir: {}, 344 }) 345 tnock(t, npm.config.get('registry')) 346 .get('/-/ping?write=true').reply(200, '{}') 347 .get('/npm').reply(200, npmManifest(npm.version)) 348 tnock(t, 'https://nodejs.org') 349 .get('/dist/index.json').reply(200, nodeVersions) 350 await npm.exec('doctor', []) 351 t.matchSnapshot(joinedOutput(), 'no permissions checks') 352 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 353}) 354 355t.test('missing global directories', async t => { 356 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 357 mocks, 358 globals, 359 prefixDir: dirs.prefixDir, 360 globalPrefixDir: {}, 361 }) 362 tnock(t, npm.config.get('registry')) 363 .get('/-/ping?write=true').reply(200, '{}') 364 .get('/npm').reply(200, npmManifest(npm.version)) 365 tnock(t, 'https://nodejs.org') 366 .get('/dist/index.json').reply(200, nodeVersions) 367 await t.rejects(npm.exec('doctor', [])) 368 t.matchSnapshot(joinedOutput(), 'missing global directories') 369 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 370}) 371 372t.test('missing local node_modules', async t => { 373 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 374 mocks, 375 globals, 376 globalPrefixDir: dirs.globalPrefixDir, 377 }) 378 tnock(t, npm.config.get('registry')) 379 .get('/-/ping?write=true').reply(200, '{}') 380 .get('/npm').reply(200, npmManifest(npm.version)) 381 tnock(t, 'https://nodejs.org') 382 .get('/dist/index.json').reply(200, nodeVersions) 383 await npm.exec('doctor', []) 384 t.matchSnapshot(joinedOutput(), 'missing local node_modules') 385 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 386}) 387 388t.test('incorrect owner', async t => { 389 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 390 mocks: { 391 ...mocks, 392 fs: { 393 ...fs, 394 lstat: (p, cb) => { 395 const stat = fs.lstatSync(p) 396 if (p.endsWith('_cacache')) { 397 stat.uid += 1 398 stat.gid += 1 399 } 400 return cb(null, stat) 401 }, 402 }, 403 }, 404 globals, 405 ...dirs, 406 }) 407 tnock(t, npm.config.get('registry')) 408 .get('/-/ping?write=true').reply(200, '{}') 409 .get('/npm').reply(200, npmManifest(npm.version)) 410 tnock(t, 'https://nodejs.org') 411 .get('/dist/index.json').reply(200, nodeVersions) 412 await t.rejects(npm.exec('doctor', [])) 413 t.matchSnapshot(joinedOutput(), 'incorrect owner') 414 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 415}) 416 417t.test('incorrect permissions', async t => { 418 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 419 mocks: { 420 ...mocks, 421 fs: { 422 ...fs, 423 access: () => { 424 throw new Error('Test Error') 425 }, 426 }, 427 }, 428 globals, 429 ...dirs, 430 }) 431 tnock(t, npm.config.get('registry')) 432 .get('/-/ping?write=true').reply(200, '{}') 433 .get('/npm').reply(200, npmManifest(npm.version)) 434 tnock(t, 'https://nodejs.org') 435 .get('/dist/index.json').reply(200, nodeVersions) 436 await t.rejects(npm.exec('doctor', [])) 437 t.matchSnapshot(joinedOutput(), 'incorrect owner') 438 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 439}) 440 441t.test('error reading directory', async t => { 442 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 443 mocks: { 444 ...mocks, 445 fs: { 446 ...fs, 447 readdir: () => { 448 throw new Error('Test Error') 449 }, 450 }, 451 }, 452 globals, 453 ...dirs, 454 }) 455 tnock(t, npm.config.get('registry')) 456 .get('/-/ping?write=true').reply(200, '{}') 457 .get('/npm').reply(200, npmManifest(npm.version)) 458 tnock(t, 'https://nodejs.org') 459 .get('/dist/index.json').reply(200, nodeVersions) 460 await t.rejects(npm.exec('doctor', [])) 461 t.matchSnapshot(joinedOutput(), 'readdir error') 462 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 463}) 464 465t.test('cacache badContent', async t => { 466 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 467 mocks: { 468 ...mocks, 469 cacache: { 470 verify: async () => { 471 return { badContentCount: 1, reclaimedCount: 0, missingContent: 0, verifiedContent: 2 } 472 }, 473 }, 474 }, 475 globals, 476 ...dirs, 477 }) 478 tnock(t, npm.config.get('registry')) 479 .get('/-/ping?write=true').reply(200, '{}') 480 .get('/npm').reply(200, npmManifest(npm.version)) 481 tnock(t, 'https://nodejs.org') 482 .get('/dist/index.json').reply(200, nodeVersions) 483 await npm.exec('doctor', []) 484 t.matchSnapshot(joinedOutput(), 'corrupted cache content') 485 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 486}) 487 488t.test('cacache reclaimedCount', async t => { 489 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 490 mocks: { 491 ...mocks, 492 cacache: { 493 verify: async () => { 494 return { badContentCount: 0, reclaimedCount: 1, missingContent: 0, verifiedContent: 2 } 495 }, 496 }, 497 }, 498 globals, 499 ...dirs, 500 }) 501 tnock(t, npm.config.get('registry')) 502 .get('/-/ping?write=true').reply(200, '{}') 503 .get('/npm').reply(200, npmManifest(npm.version)) 504 tnock(t, 'https://nodejs.org') 505 .get('/dist/index.json').reply(200, nodeVersions) 506 await npm.exec('doctor', []) 507 t.matchSnapshot(joinedOutput(), 'content garbage collected') 508 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 509}) 510 511t.test('cacache missingContent', async t => { 512 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 513 mocks: { 514 ...mocks, 515 cacache: { 516 verify: async () => { 517 return { badContentCount: 0, reclaimedCount: 0, missingContent: 1, verifiedContent: 2 } 518 }, 519 }, 520 }, 521 globals, 522 ...dirs, 523 }) 524 tnock(t, npm.config.get('registry')) 525 .get('/-/ping?write=true').reply(200, '{}') 526 .get('/npm').reply(200, npmManifest(npm.version)) 527 tnock(t, 'https://nodejs.org') 528 .get('/dist/index.json').reply(200, nodeVersions) 529 await npm.exec('doctor', []) 530 t.matchSnapshot(joinedOutput(), 'missing content') 531 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 532}) 533 534t.test('bad proxy', async t => { 535 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 536 mocks, 537 globals, 538 config: { 539 proxy: 'ssh://npmjs.org', 540 }, 541 ...dirs, 542 }) 543 await t.rejects(npm.exec('doctor', [])) 544 t.matchSnapshot(joinedOutput(), 'output') 545 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 546}) 547 548t.test('discrete checks', t => { 549 t.test('ping', async t => { 550 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 551 mocks, 552 globals, 553 ...dirs, 554 }) 555 tnock(t, npm.config.get('registry')) 556 .get('/-/ping?write=true').reply(200, '{}') 557 await npm.exec('doctor', ['ping']) 558 t.matchSnapshot(joinedOutput(), 'output') 559 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 560 }) 561 562 t.test('versions', async t => { 563 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 564 mocks, 565 globals, 566 ...dirs, 567 }) 568 tnock(t, npm.config.get('registry')) 569 .get('/npm').reply(200, npmManifest(npm.version)) 570 tnock(t, 'https://nodejs.org') 571 .get('/dist/index.json').reply(200, nodeVersions) 572 await npm.exec('doctor', ['versions']) 573 t.matchSnapshot(joinedOutput(), 'output') 574 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 575 }) 576 577 t.test('registry', async t => { 578 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 579 mocks, 580 globals, 581 ...dirs, 582 }) 583 tnock(t, npm.config.get('registry')) 584 .get('/-/ping?write=true').reply(200, '{}') 585 await npm.exec('doctor', ['registry']) 586 t.matchSnapshot(joinedOutput(), 'output') 587 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 588 }) 589 590 t.test('git', async t => { 591 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 592 mocks, 593 globals, 594 ...dirs, 595 }) 596 await npm.exec('doctor', ['git']) 597 t.matchSnapshot(joinedOutput(), 'output') 598 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 599 }) 600 601 t.test('permissions - not windows', async t => { 602 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 603 mocks, 604 globals, 605 ...dirs, 606 }) 607 await npm.exec('doctor', ['permissions']) 608 t.matchSnapshot(joinedOutput(), 'output') 609 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 610 }) 611 612 t.test('cache', async t => { 613 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 614 mocks, 615 globals, 616 ...dirs, 617 }) 618 await npm.exec('doctor', ['cache']) 619 t.matchSnapshot(joinedOutput(), 'output') 620 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 621 }) 622 623 t.test('permissions - windows', async t => { 624 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 625 mocks, 626 globals: (context) => { 627 const g = globals(context) 628 return { 629 ...g, 630 process: { 631 ...g.process, 632 platform: 'win32', 633 }, 634 } 635 }, 636 prefixDir: {}, 637 globalPrefixDir: {}, 638 }) 639 await npm.exec('doctor', ['permissions']) 640 t.matchSnapshot(joinedOutput(), 'output') 641 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 642 }) 643 644 t.test('invalid environment', async t => { 645 const { joinedOutput, logs, npm } = await loadMockNpm(t, { 646 mocks, 647 globals: (context) => { 648 const g = globals(context) 649 return { 650 ...g, 651 process: { 652 ...g.process, 653 'env.PATH': '/nope', 654 }, 655 } 656 }, 657 prefixDir: {}, 658 globalPrefixDir: {}, 659 }) 660 await t.rejects(npm.exec('doctor', ['environment'])) 661 t.matchSnapshot(joinedOutput(), 'output') 662 t.matchSnapshot({ info: logs.info, warn: logs.warn, error: logs.error }, 'logs') 663 }) 664 665 t.end() 666}) 667