-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
3,858 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.