Add a class to verify dependabot prs/commits

- Add ci config
This commit is contained in:
Barry Gordon
2021-05-26 16:44:36 +01:00
parent 5463e0e4c6
commit 0d51a971f0
3 changed files with 253 additions and 0 deletions

30
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
name: CI
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Setup nodejs
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test

View File

@@ -0,0 +1,160 @@
import * as github from '@actions/github'
import * as core from '@actions/core'
import nock from 'nock'
import { Context } from '@actions/github/lib/context'
import { getMessage } from './verified_commits'
beforeAll(() => {
nock.disableNetConnect()
})
beforeEach(() => {
jest.restoreAllMocks()
jest.spyOn(core, 'debug').mockImplementation(jest.fn())
jest.spyOn(core, 'warning').mockImplementation(jest.fn())
process.env.GITHUB_REPOSITORY = 'dependabot/dependabot'
})
test('it returns false if the action is not invoked on a PullRequest', async () => {
expect(await getMessage(mockGitHubClient, mockGitHubOtherContext())).toBe(false)
expect(core.warning).toHaveBeenCalledWith(
expect.stringContaining('Event payload missing `pull_request` key.')
)
})
test('it returns false for an event triggered by someone other than Dependabot', async () => {
expect(await getMessage(mockGitHubClient, mockGitHubPullContext('jane-doe'))).toBe(false)
expect(core.debug).toHaveBeenCalledWith(
expect.stringContaining("Event actor 'jane-doe' is not Dependabot.")
)
})
test('it returns false if there is more than 1 commit', async () => {
nock('https://api.github.com').get('/repos/dependabot/dependabot/pulls/101/commits')
.reply(200, [
{
commit: {
message: 'Bump lodash from 1.0.0 to 2.0.0'
}
},
{
commit: {
message: 'Add some more things.'
}
}
])
expect(await getMessage(mockGitHubClient, mockGitHubPullContext())).toBe(false)
expect(core.warning).toHaveBeenCalledWith(
expect.stringContaining("It looks like this PR has contains commits that aren't part of a Dependabot update.")
)
})
test('it returns false if the commit was authored by someone other than Dependabot', async () => {
nock('https://api.github.com').get('/repos/dependabot/dependabot/pulls/101/commits')
.reply(200, [
{
author: {
login: 'dependanot'
},
commit: {
message: 'Bump lodash from 1.0.0 to 2.0.0'
}
}
])
expect(await getMessage(mockGitHubClient, mockGitHubPullContext())).toBe(false)
expect(core.warning).toHaveBeenCalledWith(
expect.stringContaining("It looks like this PR has contains commits that aren't part of a Dependabot update.")
)
})
test('it returns false if the commit is has no verification payload', async () => {
nock('https://api.github.com').get('/repos/dependabot/dependabot/pulls/101/commits')
.reply(200, [
{
author: {
login: 'dependabot[bot]'
},
commit: {
message: 'Bump lodash from 1.0.0 to 2.0.0',
verification: null
}
}
])
expect(await getMessage(mockGitHubClient, mockGitHubPullContext())).toBe(false)
})
test('it returns false if the commit is not verified', async () => {
nock('https://api.github.com').get('/repos/dependabot/dependabot/pulls/101/commits')
.reply(200, [
{
author: {
login: 'dependabot[bot]'
},
commit: {
message: 'Bump lodash from 1.0.0 to 2.0.0',
verification: {
verified: false
}
}
}
])
expect(await getMessage(mockGitHubClient, mockGitHubPullContext())).toBe(false)
})
test('it returns the commit message for a PR authored exclusively by Dependabot with verified commits', async () => {
nock('https://api.github.com').get('/repos/dependabot/dependabot/pulls/101/commits')
.reply(200, [
{
author: {
login: 'dependabot[bot]'
},
commit: {
message: 'Bump lodash from 1.0.0 to 2.0.0',
verification: {
verified: true
}
}
}
])
expect(await getMessage(mockGitHubClient, mockGitHubPullContext())).toEqual('Bump lodash from 1.0.0 to 2.0.0')
})
const mockGitHubClient = github.getOctokit('mock-token')
function mockGitHubOtherContext (): Context {
const ctx = new Context()
ctx.payload = {
issue: {
number: 100
}
}
return ctx
}
function mockGitHubPullContext (actor = 'dependabot[bot]'): Context {
const ctx = new Context()
ctx.payload = {
pull_request: {
number: 101
},
repository: {
name: 'dependabot',
owner: {
login: 'dependabot'
}
}
}
ctx.actor = actor
return ctx
}

View File

@@ -0,0 +1,63 @@
import * as core from '@actions/core'
import { GitHub } from '@actions/github/lib/utils'
import { Context } from '@actions/github/lib/context'
const DEPENDABOT_LOGIN = 'dependabot[bot]'
export async function getMessage (client: InstanceType<typeof GitHub>, context: Context): Promise<string | false> {
core.debug('Verifying the job is for an authentic Dependabot Pull Request')
const { pull_request: pr } = context.payload
if (!pr) {
core.warning(
"Event payload missing `pull_request` key. Make sure you're " +
'triggering this action on the `pull_request` or `pull_request_target` events.'
)
return false
}
// Don't bother hitting the API if the event actor isn't Dependabot
if (context.actor !== DEPENDABOT_LOGIN) {
core.debug(`Event actor '${context.actor}' is not Dependabot.`)
return false
}
core.debug('Verifying the Pull Request contents are from Dependabot')
const { data: commits } = await client.rest.pulls.listCommits({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number
})
if (commits.length > 1) {
warnOtherCommits()
return false
}
const { commit, author } = commits[0]
if (author?.login !== DEPENDABOT_LOGIN) {
warnOtherCommits()
return false
}
if (!commit.verification?.verified) {
// TODO: Promote to setFailed
core.warning(
"Dependabot's commit signature is not verified, refusing to proceed."
)
return false
}
return commit.message
}
function warnOtherCommits (): void {
core.warning(
"It looks like this PR has contains commits that aren't part of a Dependabot update. " +
"Try using '@dependabot rebase' to remove merge commits or '@dependabot recreate' to remove " +
'any non-Dependabot changes.'
)
}