Skip to content

Commit

Permalink
Improvement: Add tests for execute function
Browse files Browse the repository at this point in the history
  • Loading branch information
manuelmhtr committed Dec 12, 2024
1 parent 05db917 commit d72da8d
Show file tree
Hide file tree
Showing 9 changed files with 541 additions and 116 deletions.
190 changes: 190 additions & 0 deletions src/__tests__/execute.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
const core = require('@actions/core');
const github = require('@actions/github');
const { t } = require('../i18n');
const { Telemetry } = require('../services');
const { getGithubApiUrl } = require('../config');
const { fetchPullRequestById } = require('../fetchers');
const {
alreadyPublished,
checkSponsorship,
getPulls,
getEntries,
publish,
} = require('../interactors');
const execute = require('../execute');

jest.mock('@actions/core', () => ({
info: jest.fn(),
debug: jest.fn(),
}));

jest.mock('@actions/github', () => ({
getOctokit: jest.fn(),
}));

jest.mock('../services', () => ({
Telemetry: jest.fn(),
}));

jest.mock('../config', () => ({
getGithubApiUrl: jest.fn(),
}));

jest.mock('../fetchers', () => ({
fetchPullRequestById: jest.fn(),
}));

jest.mock('../interactors', () => ({
alreadyPublished: jest.fn(),
checkSponsorship: jest.fn(),
getPulls: jest.fn(),
getEntries: jest.fn(),
publish: jest.fn(),
}));

describe('execute', () => {
const githubUrl = 'https://github.com';
const entries = ['ENTRY1', 'ENTRY2', 'ENTRY3'];
const pulls = ['PULL1', 'PULL2', 'PULL3'];
const pullRequest = 'PULL_REQUEST';
const isSponsor = 'isSponsor';
const telemetry = {
start: jest.fn(),
success: jest.fn(),
error: jest.fn(),
};
const inputs = {
org: 'org',
repos: 'repos',
mainStats: 'mainStats',
limit: 'limit',
sortBy: 'sortBy',
periodLength: 'periodLength',
disableLinks: 'disableLinks',
displayCharts: 'displayCharts',
publishAs: 'publishAs',
pullRequestId: 'pullRequestId',
slack: 'slack',
teams: 'teams',
webhook: 'webhook',
telemetry: 'telemetry',
githubToken: 'githubToken',
personalToken: 'personalToken',
};
const globalOctokit = `OCTOKIT_${inputs.githubToken}`;
const personalOctokit = `OCTOKIT_${inputs.personalToken}`;

Telemetry.mockReturnValue(telemetry);
getGithubApiUrl.mockReturnValue(githubUrl);
checkSponsorship.mockResolvedValue(isSponsor);
github.getOctokit.mockImplementation((token) => `OCTOKIT_${token}`);
fetchPullRequestById.mockResolvedValue(pullRequest);
alreadyPublished.mockReturnValue(false);
getPulls.mockResolvedValue(pulls);
getEntries.mockResolvedValue(entries);

beforeEach(jest.clearAllMocks);

it('executes the action successfully', async () => {
const results = await execute(inputs);
expect(results).toEqual({ entries, pullRequest });
expect(fetchPullRequestById).toBeCalledWith(globalOctokit, inputs.pullRequestId);
expect(alreadyPublished).toBeCalledWith(pullRequest);
expect(getPulls).toBeCalledWith({
org: inputs.org,
repos: inputs.repos,
octokit: personalOctokit,
startDate: expect.any(Date),
});
expect(getEntries).toBeCalledWith({
core,
pulls,
excludeStr: inputs.excludeStr,
periodLength: inputs.periodLength,
});
expect(publish).toBeCalledWith({
core,
octokit: globalOctokit,
entries,
pullRequest,
inputs: { ...inputs, isSponsor },
});
});

it('does not request the pull request when not sending an id', async () => {
const results = await execute({ ...inputs, pullRequestId: null });
expect(results).toEqual({ entries, pullRequest: null });
expect(fetchPullRequestById).not.toBeCalled();
expect(alreadyPublished).toBeCalledWith(null);
expect(getPulls).toBeCalled();
expect(getEntries).toBeCalled();
expect(publish).toBeCalled();
});

it('aborts the execution when stats are already published', async () => {
alreadyPublished.mockReturnValueOnce(true);
const results = await execute(inputs);
expect(results).toBeNull();
expect(fetchPullRequestById).toBeCalledWith(globalOctokit, inputs.pullRequestId);
expect(alreadyPublished).toBeCalledWith(pullRequest);
expect(getPulls).not.toBeCalled();
expect(getEntries).not.toBeCalled();
expect(publish).not.toBeCalled();
});

it('configures octokit correctly', async () => {
await execute(inputs);
expect(github.getOctokit).toBeCalledWith(inputs.githubToken, { baseUrl: githubUrl });
expect(github.getOctokit).toBeCalledWith(inputs.personalToken, { baseUrl: githubUrl });
});

describe('Sponsorship', () => {
it('checks if the user is a sponsor', async () => {
checkSponsorship.mockResolvedValueOnce(true);
await execute(inputs);
expect(checkSponsorship).toBeCalledWith({
octokit: globalOctokit,
org: inputs.org,
repos: inputs.repos,
});
expect(core.info).toBeCalledWith(t('execution.logs.sponsors'));
expect(Telemetry).toBeCalledWith(expect.objectContaining({ isSponsor: true }));
});

it('disables feature when the user is not a sponsor', async () => {
checkSponsorship.mockResolvedValueOnce(false);
await execute(inputs);
expect(checkSponsorship).toBeCalled();
expect(core.info).not.toBeCalledWith(t('execution.logs.sponsors'));
expect(Telemetry).toBeCalledWith(expect.objectContaining({ isSponsor: false }));
});
});

describe('Telemetry', () => {
it('tracks the start and end of a successful execution', async () => {
const results = await execute(inputs);
expect(Telemetry).toBeCalledWith({
core,
isSponsor,
telemetry: inputs.telemetry,
});
expect(telemetry.start).toBeCalledWith(inputs);
expect(telemetry.success).toBeCalledWith(results);
expect(telemetry.error).not.toBeCalled();
});

it('tracks the start and end of a failed execution', async () => {
const error = new Error('Failed execution');
publish.mockRejectedValueOnce(error);
await expect(execute(inputs)).rejects.toThrow(error);
expect(Telemetry).toBeCalledWith({
core,
isSponsor,
telemetry: inputs.telemetry,
});
expect(telemetry.start).toBeCalledWith(inputs);
expect(telemetry.success).not.toBeCalled();
expect(telemetry.error).toBeCalledWith(error);
});
});
});
136 changes: 32 additions & 104 deletions src/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,16 @@ const { Telemetry } = require('./services');
const { fetchPullRequestById } = require('./fetchers');
const { getGithubApiUrl } = require('./config');
const {
getPulls,
buildTable,
postComment,
fulfillEntries,
getPullRequestStats,
getReviewStats,
getUsers,
mergeStats,
buildComment,
buildJsonOutput,
buildMarkdown,
checkSponsorship,
alreadyPublished,
postSlackMessage,
postSummary,
postTeamsMessage,
postWebhook,
checkSponsorship,
getPulls,
getEntries,
publish,
} = require('./interactors');

const run = async (params) => {
const {
org,
repos,
limit,
sortBy,
octokit,
mainStats,
publishAs,
periodLength,
disableLinks,
personalToken,
displayCharts,
pullRequestId,
} = params;

const pullRequest = pullRequestId
? await fetchPullRequestById(octokit, pullRequestId)
const run = async ({ inputs, octokit }) => {
const pullRequest = inputs.pullRequestId
? await fetchPullRequestById(octokit, inputs.pullRequestId)
: null;

if (alreadyPublished(pullRequest)) {
Expand All @@ -51,97 +24,52 @@ const run = async (params) => {
}

const pulls = await getPulls({
org,
repos,
octokit: github.getOctokit(personalToken, { baseUrl: getGithubApiUrl() }),
startDate: subtractDaysToDate(new Date(), periodLength),
org: inputs.org,
repos: inputs.repos,
octokit: github.getOctokit(inputs.personalToken, { baseUrl: getGithubApiUrl() }),
startDate: subtractDaysToDate(new Date(), inputs.periodLength),
});
core.info(`Found ${pulls.length} pull requests to analyze`);

const users = await getUsers(pulls, { excludeStr: params.excludeStr });
core.info(`Found ${users.length} collaborators to analyze`);

const pullRequestStats = getPullRequestStats(pulls);
core.info(`Analyzed stats for ${pullRequestStats.length} authors`);

const reviewStats = getReviewStats(pulls);
core.info(`Analyzed stats for ${reviewStats.length} reviewers`);

const entries = fulfillEntries(
mergeStats({ users, pullRequestStats, reviewStats }),
{ periodLength },
);
core.debug(`Analyzed users: ${entries.length}`);

const table = buildTable({
limit,
sortBy,
entries,
mainStats,
disableLinks,
displayCharts,
});
core.debug('Table content built successfully');

const markdownTable = buildMarkdown({ table });
core.debug('Markdown table built successfully');

const content = buildComment({
org,
repos,
periodLength,
markdownTable,
isSponsor: params.isSponsor,
const entries = await getEntries({
core,
pulls,
excludeStr: inputs.excludeStr,
periodLength: inputs.periodLength,
});
core.debug(`Commit content built successfully: ${content}`);
core.debug(`Analyzed entries: ${entries.length}`);

const whParams = {
await publish({
core,
org,
repos,
table,
periodLength,
octokit,
entries,
pullRequest,
isSponsor: params.isSponsor,
};
const jsonOutput = buildJsonOutput({ params, entries, pullRequest });
await postWebhook({ core, payload: jsonOutput, webhook: params.webhook });
await postSlackMessage({ ...whParams, slack: params.slack });
await postTeamsMessage({ ...whParams, teams: params.teams });
await postSummary({ core, content });
await core.setOutput('resultsMd', markdownTable);
await core.setOutput('resultsJson', jsonOutput);

if (pullRequestId) {
await postComment({
octokit,
content,
publishAs,
pullRequestId,
currentBody: pullRequest.body,
});
core.debug('Posted comment successfully');
}
inputs,
});

return {
entries,
pullRequest,
};
};

module.exports = async (params) => {
core.debug(`Params: ${JSON.stringify(params, null, 2)}`);
module.exports = async (inputs) => {
core.debug(`Inputs: ${JSON.stringify(inputs, null, 2)}`);

const { githubToken, org, repos } = params;
const { githubToken, org, repos } = inputs;
const octokit = github.getOctokit(githubToken, { baseUrl: getGithubApiUrl() });
const isSponsor = await checkSponsorship({ octokit, org, repos });
const telemetry = new Telemetry({ core, isSponsor, telemetry: params.telemetry });
const telemetry = new Telemetry({ core, isSponsor, telemetry: inputs.telemetry });
if (isSponsor) core.info(t('execution.logs.sponsors'));

try {
telemetry.start(params);
const results = await run({ ...params, isSponsor, octokit });
telemetry.start(inputs);
const results = await run({
octokit,
inputs: { ...inputs, isSponsor },
});
telemetry.success(results);
return results;
} catch (error) {
telemetry.error(error);
throw error;
Expand Down
Loading

0 comments on commit d72da8d

Please sign in to comment.