Skip to content

Commit

Permalink
[chore] Add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Laura Warr authored and laurawarr committed Mar 29, 2023
1 parent 35722cf commit c57e760
Show file tree
Hide file tree
Showing 11 changed files with 3,858 additions and 117 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

name: Unit Tests

on:
pull_request

jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '16'
- run: yarn install
- run: yarn test
145 changes: 145 additions & 0 deletions __tests__/action.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import * as action from '../src/action'

jest.mock('axios')
jest.mock('@actions/core')
jest.mock('@actions/exec')
jest.mock('@actions/github')

const mockInputs: Record<string, string> = {
'github-token': 'token',
'project-key': 'my-project',
'client-id': 'id',
'client-secret': 'secret'
}

describe('run', () => {
const core = require('@actions/core')
const github = require('@actions/github')
const exec = require('@actions/exec')

beforeAll(() => {
jest.spyOn(action, 'authenticate').mockResolvedValue('generated-token')
jest.spyOn(action, 'postCodeUsages').mockResolvedValue()
})

afterAll(() => {
jest.restoreAllMocks()
})

beforeEach(() => {
jest.clearAllMocks()
github.getOctokit = jest.fn().mockReturnValue({})
core.getInput = jest.fn().mockImplementation((key) => mockInputs[key])
})

test.each([
'github-token', 'project-key', 'client-id', 'client-secret'
])('fails when missing parameter: %s', async (param) => {
const inputs = { ...mockInputs }
delete inputs[param]
core.getInput = jest.fn().mockImplementation((key) => inputs[key])
await action.run()

expect(core.setFailed).toBeCalledWith(`Missing ${param}`)
})

it('calls postCodeUsages for a valid request', async () => {
exec.getExecOutput = jest.fn().mockResolvedValue({ stdout: '[]' })
await action.run()

expect(action.authenticate).not.toBeCalled()
expect(core.setFailed).not.toBeCalled()
expect(action.postCodeUsages).toBeCalledWith([])
})
})

describe('authenticate', () => {
const axios = require('axios')

beforeEach(() => {
jest.clearAllMocks()
})

it('sends authentication request', async () => {
axios.post = jest.fn().mockResolvedValue({ data: { access_token: '123' } })

const returnedToken = await action.authenticate('mock-client-id', 'mock-client-secret')

expect(axios.post).toBeCalledWith(
'https://auth.devcycle.com/oauth/token',
expect.objectContaining({
grant_type: 'client_credentials',
client_id: 'mock-client-id',
client_secret: 'mock-client-secret',
audience: 'https://api.devcycle.com/',
})
)
expect(returnedToken).toEqual('123')
})

it('fails if an error is thrown during authentication', async () => {
axios.post = jest.fn().mockRejectedValue('Some error')

const authenticate = () => action.authenticate('mock-client-id', 'mock-client-secret')

expect(authenticate).rejects.toThrow('Failed to authenticate with the DevCycle API. Check your credentials.')
})
})

describe('postCodeUsages', () => {
const axios = require('axios')
const core = require('@actions/core')
const github = require('@actions/github')

beforeAll(() => {
jest.spyOn(action, 'authenticate').mockResolvedValue('generated-token')
})

afterAll(() => {
jest.restoreAllMocks()
})

beforeEach(() => {
jest.clearAllMocks()
core.getInput = jest.fn().mockImplementation((key) => mockInputs[key])
github.context = {
repo: {
owner: 'mock-owner',
repo: 'mock-repo'
},
ref: 'refs/heads/main'
}
})

it('sends request to API', async () => {
axios.post = jest.fn()

await action.postCodeUsages([])

expect(action.authenticate).toBeCalledWith('id', 'secret')
expect(axios.post).toBeCalledWith(
'https://api.devcycle.com/v1/projects/my-project/codeUsages',
expect.objectContaining({
source: 'github',
repo: 'mock-owner/mock-repo',
branch: github.context.ref.split('/').pop(),
variables: []
}),
expect.objectContaining({
headers: {
Authorization: 'generated-token'
}
})
)
})

it('fails if an error is thrown when sending code usages', async () => {
axios.post = jest.fn().mockRejectedValue({
response: { data: { message: 'Some error' } }
})

const postCodeUsages = () => action.postCodeUsages([])

expect(postCodeUsages).rejects.toThrow('Failed to submit Code Usages.')
})
})
114 changes: 114 additions & 0 deletions dist/action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.postCodeUsages = exports.authenticate = exports.run = void 0;
const action = __importStar(require("./action"));
const github = __importStar(require("@actions/github"));
const core = __importStar(require("@actions/core"));
const exec_1 = require("@actions/exec");
const axios_1 = __importDefault(require("axios"));
const API_URL = 'https://api.devcycle.com/';
const AUTH_URL = 'https://auth.devcycle.com/';
function run() {
return __awaiter(this, void 0, void 0, function* () {
const requiredInputs = ['github-token', 'project-key', 'client-id', 'client-secret'];
for (const inputKey of requiredInputs) {
if (!core.getInput(inputKey)) {
core.setFailed(`Missing ${inputKey}`);
return;
}
}
const token = core.getInput('github-token');
const octokit = token && github.getOctokit(token);
if (!octokit) {
core.setFailed('No octokit client');
return;
}
try {
yield (0, exec_1.exec)('npm', ['install', '-g', '@devcycle/[email protected]']);
const output = yield (0, exec_1.getExecOutput)('dvc', ['usages', '--format', 'json', '--caller', 'github']);
const variables = JSON.parse(output.stdout);
yield action.postCodeUsages(variables);
}
catch (err) {
core.setFailed(err);
}
});
}
exports.run = run;
const authenticate = (client_id, client_secret) => __awaiter(void 0, void 0, void 0, function* () {
const url = new URL('/oauth/token', AUTH_URL);
try {
const response = yield axios_1.default.post(url.href, {
grant_type: 'client_credentials',
client_id,
client_secret,
audience: 'https://api.devcycle.com/',
});
return response.data.access_token;
}
catch (e) {
core.error(e);
throw new Error('Failed to authenticate with the DevCycle API. Check your credentials.');
}
});
exports.authenticate = authenticate;
const postCodeUsages = (variables) => __awaiter(void 0, void 0, void 0, function* () {
const projectKey = core.getInput('project-key');
const clientId = core.getInput('client-id');
const clientSecret = core.getInput('client-secret');
const authToken = yield action.authenticate(clientId, clientSecret);
const url = new URL(`/v1/projects/${projectKey}/codeUsages`, API_URL);
const headers = {
Authorization: authToken,
'dvc-referrer': 'github.code_usages',
'dvc-referrer-metadata': JSON.stringify({
action: 'code_usages'
})
};
const { owner, repo } = github.context.repo;
try {
yield axios_1.default.post(url.href, {
source: 'github',
repo: `${owner}/${repo}`,
branch: github.context.ref.split('/').pop(),
variables
}, { headers });
}
catch (e) {
core.error(e);
core.error(e.response.data);
throw new Error('Failed to submit Code Usages.');
}
});
exports.postCodeUsages = postCodeUsages;
Loading

0 comments on commit c57e760

Please sign in to comment.