diff --git a/__fixtures__/events/default.json b/__fixtures__/events/default.json new file mode 100644 index 0000000..f6be12a --- /dev/null +++ b/__fixtures__/events/default.json @@ -0,0 +1,21 @@ +{ + "inputs": { + "jobID": "1", + "jobToken": "xxx", + "credentialsToken": "yyy", + "dependabotAPIURL": "http://localhost:9000" + }, + "ref": "main", + "repository": { + "owner": { + "login": "dependabot" + }, + "name": "dependabot-core" + }, + "sender": { + "type": "User" + }, + "installation": null, + "organization": null, + "workflow": "--workflow-yaml-goes-here--" +} diff --git a/__tests__/helpers.ts b/__tests__/helpers.ts index 6cbc29e..2c9ab7b 100644 --- a/__tests__/helpers.ts +++ b/__tests__/helpers.ts @@ -25,7 +25,7 @@ export const removeDanglingUpdaterContainers = async (): Promise => { await docker.pruneContainers() } -export const runFakeDependabotApi = async (port: number): Promise => { +export const runFakeDependabotApi = async (port = 9000): Promise => { const server = spawn('node', [ `${path.join(__dirname, 'server/server.js')}`, `${port}` @@ -44,3 +44,13 @@ export const runFakeDependabotApi = async (port: number): Promise => { server.kill() } } + +export const eventFixturePath = (fixtureName: string): string => { + return path.join( + __dirname, + '..', + '__fixtures__', + 'events', + `${fixtureName}.json` + ) +} diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 3c1f2fe..998b887 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -1,2 +1,156 @@ -// shows how the runner will run a javascript action with env / stdout protocol -test('test runs', () => {}) +import * as core from '@actions/core' +import {Context} from '@actions/github/lib/context' +import {APIClient} from '../src/api-client' +import {Updater} from '../src/updater' +import * as inputs from '../src/inputs' +import {run} from '../src/main' + +import {eventFixturePath} from './helpers' + +// We do not need to build actual containers or run updates for this test. +jest.mock('../src/api-client') +jest.mock('../src/image-service') +jest.mock('../src/updater') + +describe('run', () => { + let context: Context + + beforeEach(async () => { + jest.spyOn(core, 'info').mockImplementation(jest.fn()) + jest.spyOn(core, 'setFailed').mockImplementation(jest.fn()) + }) + + afterEach(async () => { + jest.clearAllMocks() // Reset any mocked classes + }) + + describe('when the run follows the happy path', () => { + beforeAll(() => { + process.env.GITHUB_EVENT_PATH = eventFixturePath('default') + process.env.GITHUB_EVENT_NAME = 'workflow_dispatch' + context = new Context() + }) + + test('it signs off at completion without any errors', async () => { + await run(context) + + expect(core.setFailed).not.toHaveBeenCalled() + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining('🤖 ~fin~') + ) + }) + }) + + describe('when the action is triggered on an unsupported event', () => { + beforeAll(() => { + process.env.GITHUB_EVENT_PATH = eventFixturePath('default') + process.env.GITHUB_EVENT_NAME = 'issue_created' + context = new Context() + }) + + test('it explains the event is unsupported without logging to dependabot-api', async () => { + await run(context) + + expect(core.setFailed).not.toHaveBeenCalled() + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining( + "Dependabot Updater Action does not support 'issue_created' events." + ) + ) + }) + }) + + describe('when there is an error retrieving job parameters', () => { + beforeEach(() => { + jest.spyOn(inputs, 'getJobParameters').mockImplementationOnce( + jest.fn(() => { + throw new Error('unexpected error retrieving job params') + }) + ) + + process.env.GITHUB_EVENT_PATH = eventFixturePath('default') + process.env.GITHUB_EVENT_NAME = 'workflow_dispatch' + context = new Context() + }) + + test('it relays an error to dependabot-api and marks the job as processed', async () => { + await run(context) + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('unexpected error retrieving job params') + ) + }) + }) + + describe('when there is an error retrieving job details from DependabotAPI', () => { + beforeEach(() => { + jest + .spyOn(APIClient.prototype, 'getJobDetails') + .mockImplementationOnce( + jest.fn(async () => + Promise.reject(new Error('error getting job details')) + ) + ) + + process.env.GITHUB_EVENT_PATH = eventFixturePath('default') + process.env.GITHUB_EVENT_NAME = 'workflow_dispatch' + context = new Context() + }) + + test('it relays an error to dependabot-api and marks the job as processed', async () => { + await run(context) + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('error getting job details') + ) + }) + }) + + describe('when there is an error retrieving job credentials from DependabotAPI', () => { + beforeEach(() => { + jest + .spyOn(APIClient.prototype, 'getCredentials') + .mockImplementationOnce( + jest.fn(async () => + Promise.reject(new Error('error getting credentials')) + ) + ) + + process.env.GITHUB_EVENT_PATH = eventFixturePath('default') + process.env.GITHUB_EVENT_NAME = 'workflow_dispatch' + context = new Context() + }) + + test('it relays an error to dependabot-api and marks the job as processed', async () => { + await run(context) + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('error getting credentials') + ) + }) + }) + + describe('when there is an error running the update', () => { + beforeAll(() => { + jest + .spyOn(Updater.prototype, 'runUpdater') + .mockImplementationOnce( + jest.fn(async () => + Promise.reject(new Error('error running the update')) + ) + ) + + process.env.GITHUB_EVENT_PATH = eventFixturePath('default') + process.env.GITHUB_EVENT_NAME = 'workflow_dispatch' + context = new Context() + }) + + test('it relays an error to dependabot-api and marks the job as processed', async () => { + await run(context) + + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining('error running the update') + ) + }) + }) +}) diff --git a/src/main.ts b/src/main.ts index f100120..26e6ed8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,6 @@ import * as core from '@actions/core' import * as github from '@actions/github' +import {Context} from '@actions/github/lib/context' import {getJobParameters} from './inputs' import {ImageService} from './image-service' import {Updater} from './updater' @@ -11,15 +12,15 @@ export const UPDATER_IMAGE_NAME = export const PROXY_IMAGE_NAME = 'docker.pkg.github.com/github/dependabot-update-job-proxy:latest' -async function run(): Promise { +export async function run(context: Context): Promise { try { // Decode JobParameters: - const params = getJobParameters(github.context) + const params = getJobParameters(context) if (params === null) { return // No parameters, nothing to do } - core.info(JSON.stringify(params)) + core.debug(JSON.stringify(params)) core.setSecret(params.jobToken) core.setSecret(params.credentialsToken) @@ -39,9 +40,10 @@ async function run(): Promise { await ImageService.pull(PROXY_IMAGE_NAME) await updater.runUpdater() + core.info('🤖 ~fin~') } catch (error) { - core.setFailed(error.message) + core.setFailed(error) } } -run() +run(github.context)