@ 260902.1
current
import { describe, test } from 'node:test'
import Validator from '../lib/validator.js'
import { readFileSync } from 'node:fs'

// Note, these are not necessarily all real commit messages
const str = `commit e7c077c610afa371430180fbd447bfef60ebc5ea
Author:     Calvin Metcalf <cmetcalf@appgeo.com>
AuthorDate: Tue Apr 12 15:42:23 2016 -0400
Commit:     James M Snell <jasnell@gmail.com>
CommitDate: Wed Apr 20 13:28:35 2016 -0700

    stream: make null an invalid chunk to write in object mode

    this harmonizes behavior between readable, writable, and transform
    streams so that they all handle nulls in object mode the same way by
    considering them invalid chunks.

    Signed-off-by: Calvin Metcalf <cmetcalf@appgeo.com>
    PR-URL: https://github.com/nodejs/node/pull/6170
    Reviewed-By: James M Snell <jasnell@gmail.com>
    Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
`

const str2 = `commit b6475b9a9d0da0971eec7eb5559dff4d18a0e721
Author: Evan Lucas <evanlucas@me.com>
Date:   Tue Mar 29 08:09:37 2016 -0500

    Revert "tty: do not read from the console stream upon creation"

    This reverts commit 461138929498f31bd35bea61aa4375a2f56cceb7.

    The offending commit broke certain usages of piping from stdin.

    Fixes: https://github.com/nodejs/node/issues/5927
    Signed-off-by: Evan Lucas <evanlucas@me.com>
    PR-URL: https://github.com/nodejs/node/pull/5947
    Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
    Reviewed-By: Alexis Campailla <orangemocha@nodejs.org>
    Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
`

/* eslint-disable */
const str3 = `commit 75487f0db80e70a3e27fabfe323a33258dfbbea8
Author: Michaël Zasso <targos@protonmail.com>
Date:   Fri Apr 15 13:32:36 2016 +0200

    module: fix resolution of filename with trailing slash - make this tile too long

    A recent optimization of module loading performance [1] forgot to check that
    extensions were set in a certain code path.

    [1] https://github.com/nodejs/node/pull/5172/commits/ae18bbef48d87d9c641df85369f62cfd5ed8c250

    Fixes: https://github.com/nodejs/node/issues/6214
    Signed-off-by: Michaël Zasso <targos@protonmail.com>
    PR-URL: https://github.com/nodejs/node/pull/6215
    Reviewed-By: James M Snell <jasnell@gmail.com>
    Reviewed-By: Brian White <mscdex@mscdex.net>`
/* eslint-enable */

const str4 = `commit 7d3a7ea0d7df9b6f11df723dec370f49f4f87e99
Author: Wyatt Preul <wpreul@gmail.com>
Date:   Thu Mar 3 10:10:46 2016 -0600

    check memoryUsage properties
    The properties on memoryUsage were not checked before,
    this commit checks them.

    Signed-off-by: Wyatt Preul <wpreul@gmail.com>
    PR-URL: #5546
    Reviewed-By: Colin Ihrig <cjihrig@gmail.com>`

const str5 = `commit 7d3a7ea0d7df9b6f11df723dec370f49f4f87e99
Author: Wyatt Preul <wpreul@gmail.com>
Date:   Thu Mar 3 10:10:46 2016 -0600

    test: check memoryUsage properties

    The properties on memoryUsage were not checked before,
    this commit checks them.

    Signed-off-by: Wyatt Preul <wpreul@gmail.com>`

/* eslint-enable */

const str7 = `commit 7d3a7ea0d7df9b6f11df723dec370f49f4f87e99
Author: Wyatt Preul <wpreul@gmail.com>
Date:   Thu Mar 3 10:10:46 2016 -0600

    test: check memoryUsage properties.

    Signed-off-by: Wyatt Preul <wpreul@gmail.com>
`

const str8 = `commit 7d3a7ea0d7df9b6f11df723dec370f49f4f87e99
Author: Wyatt Preul <wpreul@gmail.com>
Date:   Thu Mar 3 10:10:46 2016 -0600

    test: Check memoryUsage properties

    Signed-off-by: Wyatt Preul <wpreul@gmail.com>
`

const str9 = `commit 7d3a7ea0d7df9b6f11df723dec370f49f4f87e99
Author: Wyatt Preul <wpreul@gmail.com>
Date:   Thu Mar 3 10:10:46 2016 -0600

    test: Check memoryUsage properties.

    Signed-off-by: Wyatt Preul <wpreul@gmail.com>
`

const str10 = `commit b04fe688d5859f707cf1a5e0206967268118bf7a
Author: Darshan Sen <raisinten@gmail.com>
Date:   Sun May 1 21:10:21 2022 +0530

    Revert "bootstrap: delay the instantiation of maps in per-context scripts"

    The linked issue, https://bugs.chromium.org/p/v8/issues/detail?id=6593,
    is marked as "Fixed", so I think we can revert this now.

    This reverts commit 08a9c4a996964aca909cd75fa8ecafd652c54885.

    Signed-off-by: Darshan Sen <raisinten@gmail.com>
    PR-URL: https://github.com/nodejs/node/pull/42934
    Refs: https://bugs.chromium.org/p/v8/issues/detail?id=9187
    Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
    Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
`

const str11 = `commit b04fe688d5859f707cf1a5e0206967268118bf7a
Author: Darshan Sen <raisinten@gmail.com>
Date:   Sun May 1 21:10:21 2022 +0530

    Revert "bootstrap: delay the instantiation of all maps in the per-context scripts"

    The linked issue, https://bugs.chromium.org/p/v8/issues/detail?id=6593,
    is marked as "Fixed", so I think we can revert this now.

    This reverts commit 08a9c4a996964aca909cd75fa8ecafd652c54885.

    Signed-off-by: Darshan Sen <raisinten@gmail.com>
    PR-URL: https://github.com/nodejs/node/pull/42934
    Refs: https://bugs.chromium.org/p/v8/issues/detail?id=9187
    Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
    Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
`

const str12 = `commit cbb404503c9df13aaeb3dd8b345cb3f34c8c07e4
Author: Michaël Zasso <targos@protonmail.com>
Date:   Sat Oct 22 10:22:43 2022 +0200

    Revert "deps: V8: forward declaration of \`Rtl*FunctionTable\`"

    This reverts commit 01bc8e6fd81314e76c7fb0d09e5310f609e48bee.

    Signed-off-by: Michaël Zasso <targos@protonmail.com>
`

function waitForCommits (validator, count, callback) {
  return new Promise((resolve, reject) => {
    const onCommit = (data) => {
      try {
        callback(data)
        if (--count === 0) {
          validator.removeListener('commit', onCommit)
          resolve()
        }
      } catch (error) {
        validator.removeListener('commit', onCommit)
        reject(error)
      }
    }

    validator.on('commit', onCommit)
  })
}

test('Validator - misc', (t) => {
  const v = new Validator()

  t.assert.throws(() => {
    v.disableRule('biscuits')
  }, /Invalid rule: "biscuits"/)

  v.disableRule('line-length')
  t.assert.strictEqual(v.rules.get('line-length').disabled, true, 'disabled')
  v.rules.get('line-length').disabled = false
})

describe('Validator - real commits', () => {
  test('basic', async (t) => {
    const commit = JSON.parse(readFileSync(new URL('fixtures/commit.json', import.meta.url), { encoding: 'utf8' }))
    const pr = JSON.parse(readFileSync(new URL('fixtures/pr.json', import.meta.url), { encoding: 'utf8' }))
    t.plan(21)
    const v = new Validator()
    // run against the output of git show --quiet
    // run against the output of github's get commit api request
    // run against the output of github's list commits for pr api request
    v.lint(str)
    v.lint(commit)
    v.lint(pr)
    await waitForCommits(v, 3, (data) => {
      const c = data.commit
      t.assert.strictEqual(c.sha, 'e7c077c610afa371430180fbd447bfef60ebc5ea', 'sha')
      t.assert.deepStrictEqual(c.subsystems, ['stream'], 'subsystems')
      t.assert.strictEqual(c.prUrl, 'https://github.com/nodejs/node/pull/6170', 'pr')
      const msgs = data.messages
      const failed = msgs.filter((item) => {
        return item.level === 'fail'
      })
      t.assert.strictEqual(failed.length, 0, 'failed.length')
      const warned = msgs.filter((item) => {
        return item.level === 'warn'
      })
      t.assert.strictEqual(warned.length, 3, 'warned.length')
      t.assert.strictEqual(warned[0].level, 'warn')
      t.assert.strictEqual(warned[0].id, 'title-length')
    })
  })

  test('basic revert', async (t) => {
    const v = new Validator()
    v.lint(str2)
    await waitForCommits(v, 1, (data) => {
      const c = data.commit.toJSON()
      t.assert.strictEqual(c.sha, 'b6475b9a9d0da0971eec7eb5559dff4d18a0e721', 'sha')
      t.assert.strictEqual(c.date, 'Tue Mar 29 08:09:37 2016 -0500', 'date')
      t.assert.deepStrictEqual(c.subsystems, ['tty'], 'subsystems')
      t.assert.strictEqual(c.prUrl, 'https://github.com/nodejs/node/pull/5947', 'pr')
      t.assert.strictEqual(c.revert, true, 'revert')
      const msgs = data.messages
      const filtered = msgs.filter((item) => {
        return item.level === 'warn'
      })
      t.assert.strictEqual(filtered.length, 1, 'messages.length')
      t.assert.strictEqual(filtered[0].level, 'warn')
      t.assert.strictEqual(filtered[0].id, 'title-length')
    })
  })

  test('more basic', async (t) => {
    const v = new Validator()
    v.lint(str3)
    await waitForCommits(v, 1, (data) => {
      const c = data.commit.toJSON()
      t.assert.strictEqual(c.sha, '75487f0db80e70a3e27fabfe323a33258dfbbea8', 'sha')
      t.assert.strictEqual(c.date, 'Fri Apr 15 13:32:36 2016 +0200', 'date')
      t.assert.deepStrictEqual(c.subsystems, ['module'], 'subsystems')
      t.assert.strictEqual(c.prUrl, 'https://github.com/nodejs/node/pull/6215', 'pr')
      t.assert.strictEqual(c.revert, false, 'revert')
      const msgs = data.messages
      const filtered = msgs.filter((item) => {
        return item.level === 'fail'
      })
      t.assert.strictEqual(filtered.length, 2, 'messages.length')
      const ids = filtered.map((item) => {
        return item.id
      })
      const exp = ['line-length', 'title-length']
      t.assert.deepStrictEqual(ids.sort(), exp.sort(), 'message ids')
    })
  })

  test('accept revert commit titles that are elongated by git', async (t) => {
    const v = new Validator()
    v.lint(str10)
    await waitForCommits(v, 1, (data) => {
      const c = data.commit.toJSON()
      t.assert.strictEqual(c.sha, 'b04fe688d5859f707cf1a5e0206967268118bf7a', 'sha')
      t.assert.strictEqual(c.date, 'Sun May 1 21:10:21 2022 +0530', 'date')
      t.assert.deepStrictEqual(c.subsystems, ['bootstrap'], 'subsystems')
      t.assert.strictEqual(c.prUrl, 'https://github.com/nodejs/node/pull/42934', 'pr')
      t.assert.strictEqual(c.revert, true, 'revert')
      const msgs = data.messages
      const filtered = msgs.filter((item) => {
        return item.level === 'fail'
      })
      t.assert.deepStrictEqual(filtered, [], 'messages.length')
    })
  })

  test('reject revert commit titles whose original titles are really long', async (t) => {
    const v = new Validator()
    v.lint(str11)
    await waitForCommits(v, 1, (data) => {
      const c = data.commit.toJSON()
      t.assert.strictEqual(c.sha, 'b04fe688d5859f707cf1a5e0206967268118bf7a', 'sha')
      t.assert.strictEqual(c.date, 'Sun May 1 21:10:21 2022 +0530', 'date')
      t.assert.deepStrictEqual(c.subsystems, ['bootstrap'], 'subsystems')
      t.assert.strictEqual(c.prUrl, 'https://github.com/nodejs/node/pull/42934', 'pr')
      t.assert.strictEqual(c.revert, true, 'revert')
      const msgs = data.messages
      const filtered = msgs.filter((item) => {
        return item.level === 'fail'
      })
      t.assert.strictEqual(filtered.length, 1, 'messages.length')
      const ids = filtered.map((item) => {
        return item.id
      })
      const exp = ['title-length']
      t.assert.deepStrictEqual(ids.sort(), exp.sort(), 'message ids')
    })
  })

  test('accept deps: V8 as the subsystem for revert commits', async (t) => {
    const v = new Validator({
      'validate-metadata': false
    })
    v.lint(str12)
    await waitForCommits(v, 1, (data) => {
      const c = data.commit.toJSON()
      t.assert.strictEqual(c.sha, 'cbb404503c9df13aaeb3dd8b345cb3f34c8c07e4', 'sha')
      t.assert.strictEqual(c.date, 'Sat Oct 22 10:22:43 2022 +0200', 'date')
      t.assert.deepStrictEqual(c.subsystems, ['deps'], 'subsystems')
      t.assert.strictEqual(c.revert, true, 'revert')
      const msgs = data.messages
      const filtered = msgs.filter((item) => {
        return item.level === 'fail'
      })
      t.assert.strictEqual(filtered.length, 0, 'messages.length')
    })
  })

  test('invalid pr-url, missing subsystem', async (t) => {
    const v = new Validator()
    v.lint(str4)
    await waitForCommits(v, 1, (data) => {
      const c = data.commit.toJSON()
      t.assert.strictEqual(c.sha, '7d3a7ea0d7df9b6f11df723dec370f49f4f87e99', 'sha')
      t.assert.strictEqual(c.date, 'Thu Mar 3 10:10:46 2016 -0600', 'date')
      t.assert.deepStrictEqual(c.subsystems, [], 'subsystems')
      t.assert.strictEqual(c.prUrl, '#5546', 'pr')
      t.assert.strictEqual(c.revert, false, 'revert')
      const msgs = data.messages
      msgs.sort((a, b) => {
        return a.id < b.id
          ? -1
          : a.id > b.id
            ? 1
            : 0
      })
      const filtered = msgs.filter((item) => {
        return item.level === 'fail'
      })
      t.assert.strictEqual(filtered.length, 3, 'messages.length')
      t.assert.strictEqual(filtered[0].id, 'line-after-title', 'message id')
      t.assert.strictEqual(filtered[1].id, 'pr-url', 'message id')
      t.assert.strictEqual(filtered[1].string, '#5546', 'message string')
      t.assert.strictEqual(filtered[2].id, 'subsystem', 'message id')
      t.assert.strictEqual(filtered[2].line, 0, 'line')
      t.assert.strictEqual(filtered[2].column, 0, 'column')
    })
  })

  test('invalid pr-url, missing subsystem no meta', async (t) => {
    const v = new Validator({
      'validate-metadata': false
    })
    v.lint(str5)
    await waitForCommits(v, 1, (data) => {
      const c = data.commit.toJSON()
      t.assert.strictEqual(c.sha, '7d3a7ea0d7df9b6f11df723dec370f49f4f87e99', 'sha')
      t.assert.strictEqual(c.date, 'Thu Mar 3 10:10:46 2016 -0600', 'date')
      t.assert.deepStrictEqual(c.subsystems, ['test'], 'subsystems')
      t.assert.strictEqual(c.prUrl, null, 'pr')
      t.assert.strictEqual(c.revert, false, 'revert')
      const msgs = data.messages
      const filtered = msgs.filter((item) => {
        return item.level === 'fail'
      })
      t.assert.strictEqual(filtered.length, 0, 'messages.length')
    })
  })

  test('trailing punctuation in title line', async (t) => {
    const v = new Validator({
      'validate-metadata': false
    })
    v.lint(str7)
    await waitForCommits(v, 1, (data) => {
      const msgs = data.messages
      const filtered = msgs.filter((item) => {
        return item.level === 'fail'
      })
      t.assert.strictEqual(filtered.length, 1, 'messages.length')
      t.assert.strictEqual(filtered[0].message,
        'Do not use punctuation at end of title.',
        'message')
    })
  })

  test('first word is lowercase in title line', async (t) => {
    const v = new Validator({
      'validate-metadata': false
    })
    v.lint(str8)
    await waitForCommits(v, 1, (data) => {
      const msgs = data.messages
      const filtered = msgs.filter((item) => {
        return item.level === 'fail'
      })
      t.assert.strictEqual(filtered.length, 1, 'messages.length')
      t.assert.strictEqual(filtered[0].message,
        'First word after subsystem(s) in title should be lowercase.',
        'message')
      t.assert.strictEqual(filtered[0].column, 7, 'column')
    })
  })

  test('more than one formatting error in title line', async (t) => {
    const v = new Validator({
      'validate-metadata': false
    })
    v.lint(str9)
    await waitForCommits(v, 1, (data) => {
      const msgs = data.messages
      const filtered = msgs.filter((item) => {
        return item.level === 'fail'
      })
      t.assert.strictEqual(filtered.length, 2, 'messages.length')
    })
  })
})