11cb0ef41Sopenharmony_ciconst t = require('tap')
21cb0ef41Sopenharmony_ciconst { basename } = require('path')
31cb0ef41Sopenharmony_ciconst tmock = require('../../fixtures/tmock')
41cb0ef41Sopenharmony_ciconst mockNpm = require('../../fixtures/mock-npm')
51cb0ef41Sopenharmony_ci
61cb0ef41Sopenharmony_ciconst CURRENT_VERSION = '123.420.69'
71cb0ef41Sopenharmony_ciconst CURRENT_MAJOR = '122.420.69'
81cb0ef41Sopenharmony_ciconst CURRENT_MINOR = '123.419.69'
91cb0ef41Sopenharmony_ciconst CURRENT_PATCH = '123.420.68'
101cb0ef41Sopenharmony_ciconst NEXT_VERSION = '123.421.70'
111cb0ef41Sopenharmony_ciconst NEXT_MINOR = '123.420.70'
121cb0ef41Sopenharmony_ciconst NEXT_PATCH = '123.421.69'
131cb0ef41Sopenharmony_ciconst CURRENT_BETA = '124.0.0-beta.99999'
141cb0ef41Sopenharmony_ciconst HAVE_BETA = '124.0.0-beta.0'
151cb0ef41Sopenharmony_ci
161cb0ef41Sopenharmony_ciconst runUpdateNotifier = async (t, {
171cb0ef41Sopenharmony_ci  STAT_ERROR,
181cb0ef41Sopenharmony_ci  WRITE_ERROR,
191cb0ef41Sopenharmony_ci  PACOTE_ERROR,
201cb0ef41Sopenharmony_ci  STAT_MTIME = 0,
211cb0ef41Sopenharmony_ci  mocks: _mocks = {},
221cb0ef41Sopenharmony_ci  command = 'help',
231cb0ef41Sopenharmony_ci  prefixDir,
241cb0ef41Sopenharmony_ci  version = CURRENT_VERSION,
251cb0ef41Sopenharmony_ci  argv = [],
261cb0ef41Sopenharmony_ci  wroteFile = false,
271cb0ef41Sopenharmony_ci  ...config
281cb0ef41Sopenharmony_ci} = {}) => {
291cb0ef41Sopenharmony_ci  const mockFs = {
301cb0ef41Sopenharmony_ci    ...require('fs/promises'),
311cb0ef41Sopenharmony_ci    stat: async (path) => {
321cb0ef41Sopenharmony_ci      if (basename(path) !== '_update-notifier-last-checked') {
331cb0ef41Sopenharmony_ci        t.fail('no stat allowed for non upate notifier files')
341cb0ef41Sopenharmony_ci      }
351cb0ef41Sopenharmony_ci      if (STAT_ERROR) {
361cb0ef41Sopenharmony_ci        throw STAT_ERROR
371cb0ef41Sopenharmony_ci      }
381cb0ef41Sopenharmony_ci      return { mtime: new Date(STAT_MTIME) }
391cb0ef41Sopenharmony_ci    },
401cb0ef41Sopenharmony_ci    writeFile: async (path, content) => {
411cb0ef41Sopenharmony_ci      wroteFile = true
421cb0ef41Sopenharmony_ci      if (content !== '') {
431cb0ef41Sopenharmony_ci        t.fail('no write file content allowed')
441cb0ef41Sopenharmony_ci      }
451cb0ef41Sopenharmony_ci      if (basename(path) !== '_update-notifier-last-checked') {
461cb0ef41Sopenharmony_ci        t.fail('no writefile allowed for non upate notifier files')
471cb0ef41Sopenharmony_ci      }
481cb0ef41Sopenharmony_ci      if (WRITE_ERROR) {
491cb0ef41Sopenharmony_ci        throw WRITE_ERROR
501cb0ef41Sopenharmony_ci      }
511cb0ef41Sopenharmony_ci    },
521cb0ef41Sopenharmony_ci  }
531cb0ef41Sopenharmony_ci
541cb0ef41Sopenharmony_ci  const MANIFEST_REQUEST = []
551cb0ef41Sopenharmony_ci  const mockPacote = {
561cb0ef41Sopenharmony_ci    manifest: async (spec) => {
571cb0ef41Sopenharmony_ci      if (!spec.match(/^npm@/)) {
581cb0ef41Sopenharmony_ci        t.fail('no pacote manifest allowed for non npm packages')
591cb0ef41Sopenharmony_ci      }
601cb0ef41Sopenharmony_ci      MANIFEST_REQUEST.push(spec)
611cb0ef41Sopenharmony_ci      if (PACOTE_ERROR) {
621cb0ef41Sopenharmony_ci        throw PACOTE_ERROR
631cb0ef41Sopenharmony_ci      }
641cb0ef41Sopenharmony_ci      const manifestV = spec === 'npm@latest' ? CURRENT_VERSION
651cb0ef41Sopenharmony_ci        : /-/.test(spec) ? CURRENT_BETA : NEXT_VERSION
661cb0ef41Sopenharmony_ci      return { version: manifestV }
671cb0ef41Sopenharmony_ci    },
681cb0ef41Sopenharmony_ci  }
691cb0ef41Sopenharmony_ci
701cb0ef41Sopenharmony_ci  const mocks = {
711cb0ef41Sopenharmony_ci    pacote: mockPacote,
721cb0ef41Sopenharmony_ci    'fs/promises': mockFs,
731cb0ef41Sopenharmony_ci    '{ROOT}/package.json': { version },
741cb0ef41Sopenharmony_ci    'ci-info': { isCI: false, name: null },
751cb0ef41Sopenharmony_ci    ..._mocks,
761cb0ef41Sopenharmony_ci  }
771cb0ef41Sopenharmony_ci
781cb0ef41Sopenharmony_ci  const mock = await mockNpm(t, {
791cb0ef41Sopenharmony_ci    command,
801cb0ef41Sopenharmony_ci    mocks,
811cb0ef41Sopenharmony_ci    config,
821cb0ef41Sopenharmony_ci    exec: true,
831cb0ef41Sopenharmony_ci    prefixDir,
841cb0ef41Sopenharmony_ci    argv,
851cb0ef41Sopenharmony_ci  })
861cb0ef41Sopenharmony_ci  const updateNotifier = tmock(t, '{LIB}/utils/update-notifier.js', mocks)
871cb0ef41Sopenharmony_ci
881cb0ef41Sopenharmony_ci  const result = await updateNotifier(mock.npm)
891cb0ef41Sopenharmony_ci
901cb0ef41Sopenharmony_ci  return {
911cb0ef41Sopenharmony_ci    wroteFile,
921cb0ef41Sopenharmony_ci    result,
931cb0ef41Sopenharmony_ci    MANIFEST_REQUEST,
941cb0ef41Sopenharmony_ci  }
951cb0ef41Sopenharmony_ci}
961cb0ef41Sopenharmony_ci
971cb0ef41Sopenharmony_cit.test('duration has elapsed, no updates', async t => {
981cb0ef41Sopenharmony_ci  const { wroteFile, result, MANIFEST_REQUEST } = await runUpdateNotifier(t)
991cb0ef41Sopenharmony_ci  t.equal(wroteFile, true)
1001cb0ef41Sopenharmony_ci  t.not(result)
1011cb0ef41Sopenharmony_ci  t.equal(MANIFEST_REQUEST.length, 1)
1021cb0ef41Sopenharmony_ci})
1031cb0ef41Sopenharmony_ci
1041cb0ef41Sopenharmony_cit.test('situations in which we do not notify', t => {
1051cb0ef41Sopenharmony_ci  t.test('nothing to do if notifier disabled', async t => {
1061cb0ef41Sopenharmony_ci    const { wroteFile, result, MANIFEST_REQUEST } = await runUpdateNotifier(t, {
1071cb0ef41Sopenharmony_ci      'update-notifier': false,
1081cb0ef41Sopenharmony_ci    })
1091cb0ef41Sopenharmony_ci    t.equal(wroteFile, false)
1101cb0ef41Sopenharmony_ci    t.equal(result, null)
1111cb0ef41Sopenharmony_ci    t.strictSame(MANIFEST_REQUEST, [], 'no requests for manifests')
1121cb0ef41Sopenharmony_ci  })
1131cb0ef41Sopenharmony_ci
1141cb0ef41Sopenharmony_ci  t.test('do not suggest update if already updating', async t => {
1151cb0ef41Sopenharmony_ci    const { wroteFile, result, MANIFEST_REQUEST } = await runUpdateNotifier(t, {
1161cb0ef41Sopenharmony_ci      command: 'install',
1171cb0ef41Sopenharmony_ci      prefixDir: { 'package.json': `{"name":"${t.testName}"}` },
1181cb0ef41Sopenharmony_ci      argv: ['npm'],
1191cb0ef41Sopenharmony_ci      global: true,
1201cb0ef41Sopenharmony_ci    })
1211cb0ef41Sopenharmony_ci    t.equal(wroteFile, false)
1221cb0ef41Sopenharmony_ci    t.equal(result, null)
1231cb0ef41Sopenharmony_ci    t.strictSame(MANIFEST_REQUEST, [], 'no requests for manifests')
1241cb0ef41Sopenharmony_ci  })
1251cb0ef41Sopenharmony_ci
1261cb0ef41Sopenharmony_ci  t.test('do not suggest update if already updating with spec', async t => {
1271cb0ef41Sopenharmony_ci    const { wroteFile, result, MANIFEST_REQUEST } = await runUpdateNotifier(t, {
1281cb0ef41Sopenharmony_ci      command: 'install',
1291cb0ef41Sopenharmony_ci      prefixDir: { 'package.json': `{"name":"${t.testName}"}` },
1301cb0ef41Sopenharmony_ci      argv: ['npm@latest'],
1311cb0ef41Sopenharmony_ci      global: true,
1321cb0ef41Sopenharmony_ci    })
1331cb0ef41Sopenharmony_ci    t.equal(wroteFile, false)
1341cb0ef41Sopenharmony_ci    t.equal(result, null)
1351cb0ef41Sopenharmony_ci    t.strictSame(MANIFEST_REQUEST, [], 'no requests for manifests')
1361cb0ef41Sopenharmony_ci  })
1371cb0ef41Sopenharmony_ci
1381cb0ef41Sopenharmony_ci  t.test('do not update if same as latest', async t => {
1391cb0ef41Sopenharmony_ci    const { wroteFile, result, MANIFEST_REQUEST } = await runUpdateNotifier(t)
1401cb0ef41Sopenharmony_ci    t.equal(wroteFile, true)
1411cb0ef41Sopenharmony_ci    t.equal(result, null)
1421cb0ef41Sopenharmony_ci    t.strictSame(MANIFEST_REQUEST, ['npm@latest'], 'requested latest version')
1431cb0ef41Sopenharmony_ci  })
1441cb0ef41Sopenharmony_ci  t.test('check if stat errors (here for coverage)', async t => {
1451cb0ef41Sopenharmony_ci    const STAT_ERROR = new Error('blorg')
1461cb0ef41Sopenharmony_ci    const { wroteFile, result, MANIFEST_REQUEST } = await runUpdateNotifier(t, { STAT_ERROR })
1471cb0ef41Sopenharmony_ci    t.equal(wroteFile, true)
1481cb0ef41Sopenharmony_ci    t.equal(result, null)
1491cb0ef41Sopenharmony_ci    t.strictSame(MANIFEST_REQUEST, ['npm@latest'], 'requested latest version')
1501cb0ef41Sopenharmony_ci  })
1511cb0ef41Sopenharmony_ci  t.test('ok if write errors (here for coverage)', async t => {
1521cb0ef41Sopenharmony_ci    const WRITE_ERROR = new Error('grolb')
1531cb0ef41Sopenharmony_ci    const { wroteFile, result, MANIFEST_REQUEST } = await runUpdateNotifier(t, { WRITE_ERROR })
1541cb0ef41Sopenharmony_ci    t.equal(wroteFile, true)
1551cb0ef41Sopenharmony_ci    t.equal(result, null)
1561cb0ef41Sopenharmony_ci    t.strictSame(MANIFEST_REQUEST, ['npm@latest'], 'requested latest version')
1571cb0ef41Sopenharmony_ci  })
1581cb0ef41Sopenharmony_ci  t.test('ignore pacote failures (here for coverage)', async t => {
1591cb0ef41Sopenharmony_ci    const PACOTE_ERROR = new Error('pah-KO-tchay')
1601cb0ef41Sopenharmony_ci    const { wroteFile, result, MANIFEST_REQUEST } = await runUpdateNotifier(t, { PACOTE_ERROR })
1611cb0ef41Sopenharmony_ci    t.equal(result, null)
1621cb0ef41Sopenharmony_ci    t.equal(wroteFile, true)
1631cb0ef41Sopenharmony_ci    t.strictSame(MANIFEST_REQUEST, ['npm@latest'], 'requested latest version')
1641cb0ef41Sopenharmony_ci  })
1651cb0ef41Sopenharmony_ci  t.test('do not update if newer than latest, but same as next', async t => {
1661cb0ef41Sopenharmony_ci    const {
1671cb0ef41Sopenharmony_ci      wroteFile,
1681cb0ef41Sopenharmony_ci      result,
1691cb0ef41Sopenharmony_ci      MANIFEST_REQUEST,
1701cb0ef41Sopenharmony_ci    } = await runUpdateNotifier(t, { version: NEXT_VERSION })
1711cb0ef41Sopenharmony_ci    t.equal(result, null)
1721cb0ef41Sopenharmony_ci    t.equal(wroteFile, true)
1731cb0ef41Sopenharmony_ci    const reqs = ['npm@latest', `npm@^${NEXT_VERSION}`]
1741cb0ef41Sopenharmony_ci    t.strictSame(MANIFEST_REQUEST, reqs, 'requested latest and next versions')
1751cb0ef41Sopenharmony_ci  })
1761cb0ef41Sopenharmony_ci  t.test('do not update if on the latest beta', async t => {
1771cb0ef41Sopenharmony_ci    const {
1781cb0ef41Sopenharmony_ci      wroteFile,
1791cb0ef41Sopenharmony_ci      result,
1801cb0ef41Sopenharmony_ci      MANIFEST_REQUEST,
1811cb0ef41Sopenharmony_ci    } = await runUpdateNotifier(t, { version: CURRENT_BETA })
1821cb0ef41Sopenharmony_ci    t.equal(result, null)
1831cb0ef41Sopenharmony_ci    t.equal(wroteFile, true)
1841cb0ef41Sopenharmony_ci    const reqs = [`npm@^${CURRENT_BETA}`]
1851cb0ef41Sopenharmony_ci    t.strictSame(MANIFEST_REQUEST, reqs, 'requested latest and next versions')
1861cb0ef41Sopenharmony_ci  })
1871cb0ef41Sopenharmony_ci
1881cb0ef41Sopenharmony_ci  t.test('do not update in CI', async t => {
1891cb0ef41Sopenharmony_ci    const { wroteFile, result, MANIFEST_REQUEST } = await runUpdateNotifier(t, { mocks: {
1901cb0ef41Sopenharmony_ci      'ci-info': { isCI: true, name: 'something' },
1911cb0ef41Sopenharmony_ci    } })
1921cb0ef41Sopenharmony_ci    t.equal(wroteFile, false)
1931cb0ef41Sopenharmony_ci    t.equal(result, null)
1941cb0ef41Sopenharmony_ci    t.strictSame(MANIFEST_REQUEST, [], 'no requests for manifests')
1951cb0ef41Sopenharmony_ci  })
1961cb0ef41Sopenharmony_ci
1971cb0ef41Sopenharmony_ci  t.test('only check weekly for GA releases', async t => {
1981cb0ef41Sopenharmony_ci    // One week (plus five minutes to account for test environment fuzziness)
1991cb0ef41Sopenharmony_ci    const STAT_MTIME = Date.now() - 1000 * 60 * 60 * 24 * 7 + 1000 * 60 * 5
2001cb0ef41Sopenharmony_ci    const { wroteFile, result, MANIFEST_REQUEST } = await runUpdateNotifier(t, { STAT_MTIME })
2011cb0ef41Sopenharmony_ci    t.equal(wroteFile, false, 'duration was not reset')
2021cb0ef41Sopenharmony_ci    t.equal(result, null)
2031cb0ef41Sopenharmony_ci    t.strictSame(MANIFEST_REQUEST, [], 'no requests for manifests')
2041cb0ef41Sopenharmony_ci  })
2051cb0ef41Sopenharmony_ci
2061cb0ef41Sopenharmony_ci  t.test('only check daily for betas', async t => {
2071cb0ef41Sopenharmony_ci    // One day (plus five minutes to account for test environment fuzziness)
2081cb0ef41Sopenharmony_ci    const STAT_MTIME = Date.now() - 1000 * 60 * 60 * 24 + 1000 * 60 * 5
2091cb0ef41Sopenharmony_ci    const {
2101cb0ef41Sopenharmony_ci      wroteFile,
2111cb0ef41Sopenharmony_ci      result,
2121cb0ef41Sopenharmony_ci      MANIFEST_REQUEST,
2131cb0ef41Sopenharmony_ci    } = await runUpdateNotifier(t, { STAT_MTIME, version: HAVE_BETA })
2141cb0ef41Sopenharmony_ci    t.equal(wroteFile, false, 'duration was not reset')
2151cb0ef41Sopenharmony_ci    t.equal(result, null)
2161cb0ef41Sopenharmony_ci    t.strictSame(MANIFEST_REQUEST, [], 'no requests for manifests')
2171cb0ef41Sopenharmony_ci  })
2181cb0ef41Sopenharmony_ci
2191cb0ef41Sopenharmony_ci  t.end()
2201cb0ef41Sopenharmony_ci})
2211cb0ef41Sopenharmony_ci
2221cb0ef41Sopenharmony_cit.test('notification situations', async t => {
2231cb0ef41Sopenharmony_ci  const cases = {
2241cb0ef41Sopenharmony_ci    [HAVE_BETA]: [`^{V}`],
2251cb0ef41Sopenharmony_ci    [NEXT_PATCH]: [`latest`, `^{V}`],
2261cb0ef41Sopenharmony_ci    [NEXT_MINOR]: [`latest`, `^{V}`],
2271cb0ef41Sopenharmony_ci    [CURRENT_PATCH]: ['latest'],
2281cb0ef41Sopenharmony_ci    [CURRENT_MINOR]: ['latest'],
2291cb0ef41Sopenharmony_ci    [CURRENT_MAJOR]: ['latest'],
2301cb0ef41Sopenharmony_ci  }
2311cb0ef41Sopenharmony_ci
2321cb0ef41Sopenharmony_ci  for (const [version, reqs] of Object.entries(cases)) {
2331cb0ef41Sopenharmony_ci    for (const color of [false, 'always']) {
2341cb0ef41Sopenharmony_ci      await t.test(`${version} - color=${color}`, async t => {
2351cb0ef41Sopenharmony_ci        const {
2361cb0ef41Sopenharmony_ci          wroteFile,
2371cb0ef41Sopenharmony_ci          result,
2381cb0ef41Sopenharmony_ci          MANIFEST_REQUEST,
2391cb0ef41Sopenharmony_ci        } = await runUpdateNotifier(t, { version, color })
2401cb0ef41Sopenharmony_ci        t.matchSnapshot(result)
2411cb0ef41Sopenharmony_ci        t.equal(wroteFile, true)
2421cb0ef41Sopenharmony_ci        t.strictSame(MANIFEST_REQUEST, reqs.map(r => `npm@${r.replace('{V}', version)}`))
2431cb0ef41Sopenharmony_ci      })
2441cb0ef41Sopenharmony_ci    }
2451cb0ef41Sopenharmony_ci  }
2461cb0ef41Sopenharmony_ci})
247