Skip to content

Commit

Permalink
Merge pull request #69 from HubSpot/br-unit-tests-13
Browse files Browse the repository at this point in the history
More unit tests for github utils (and some tweaks to the utils themselves)
  • Loading branch information
brandenrodgers authored Dec 8, 2023
2 parents 3bb2307 + a92fef0 commit 963799c
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 38 deletions.
4 changes: 0 additions & 4 deletions constants/github.ts

This file was deleted.

2 changes: 1 addition & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
}
},
"downloadGithubRepoZip": {
"fetching": "Fetching {{ releaseType }} with name {{ repoName }}...",
"fetching": "Fetching repository with name {{ repoPath }}...",
"fetchingName": "Fetching {{ name }}...",
"completed": "Completed project fetch.",
"errors": {
Expand Down
133 changes: 131 additions & 2 deletions lib/__tests__/github.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
import { fetchFileFromRepository } from '../github';
import { fetchRepoFile as __fetchRepoFile } from '../../api/github';
import {
fetchReleaseData,
cloneGithubRepo,
fetchFileFromRepository,
} from '../github';
import {
fetchRepoFile as __fetchRepoFile,
fetchRepoReleaseData as __fetchRepoReleaseData,
fetchRepoAsZip as __fetchRepoAsZip,
} from '../../api/github';
import { extractZipArchive as __extractZipArchive } from '../archive';

jest.mock('../../api/github');
jest.mock('../archive');

const fetchRepoFile = __fetchRepoFile as jest.MockedFunction<
typeof __fetchRepoFile
>;
const fetchRepoReleaseData = __fetchRepoReleaseData as jest.MockedFunction<
typeof __fetchRepoReleaseData
>;
const fetchRepoAsZip = __fetchRepoAsZip as jest.MockedFunction<
typeof __fetchRepoAsZip
>;
const extractZipArchive = __extractZipArchive as jest.MockedFunction<
typeof __extractZipArchive
>;

describe('lib/github', () => {
describe('fetchFileFromRepository()', () => {
Expand All @@ -23,4 +42,114 @@ describe('lib/github', () => {
expect(fetchRepoFile).toHaveBeenCalledWith('owner/repo', 'file', 'ref');
});
});

describe('fetchReleaseData()', () => {
beforeAll(() => {
fetchRepoReleaseData.mockResolvedValue({
data: { zipball_url: null },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
});

afterAll(() => {
fetchRepoReleaseData.mockReset();
});

it('Fetches the release data for a repository', async () => {
await fetchReleaseData('owner/repo');
expect(fetchRepoReleaseData).toHaveBeenCalledWith(
'owner/repo',
undefined
);
});

it('Fetches the release data for a specific tag for a repository', async () => {
await fetchReleaseData('owner/repo', 'v1.0.0');
expect(fetchRepoReleaseData).toHaveBeenCalledWith('owner/repo', 'v1.0.0');
});

it('Inserts v for the tag when not included', async () => {
await fetchReleaseData('owner/repo', '1.0.0');
expect(fetchRepoReleaseData).toHaveBeenCalledWith('owner/repo', 'v1.0.0');
});
});

describe('cloneGithubRepo()', () => {
beforeAll(() => {
fetchRepoReleaseData.mockResolvedValue({
data: { zipball_url: null },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fetchRepoAsZip.mockResolvedValue({ data: 'zip' } as any);
extractZipArchive.mockResolvedValue(true);
});

afterAll(() => {
fetchRepoReleaseData.mockReset();
fetchRepoAsZip.mockReset();
extractZipArchive.mockReset();
});

it('Clones the default branch from a github repo', async () => {
await cloneGithubRepo('owner/repo', './dest/path');
expect(fetchRepoReleaseData).not.toHaveBeenCalled();
expect(fetchRepoAsZip).toHaveBeenCalled();
expect(extractZipArchive).toHaveBeenCalledWith(
'zip',
'repo',
'./dest/path',
{ sourceDir: undefined }
);
});

it('Clones a specified branch from a github repo', async () => {
await cloneGithubRepo('owner/repo', './dest/path', {
branch: 'my-branch',
});
expect(fetchRepoReleaseData).not.toHaveBeenCalled();
expect(fetchRepoAsZip).toHaveBeenCalled();
// Make sure the branch ref is added to the end of the zip download url
const lastCall = fetchRepoAsZip.mock.lastCall;
if (lastCall) {
expect(lastCall[0].includes('my-branch')).toBeTruthy();
}
expect(extractZipArchive).toHaveBeenCalledWith(
'zip',
'repo',
'./dest/path',
{ sourceDir: undefined }
);
});

it('Clones the latest release from a github repo', async () => {
await cloneGithubRepo('owner/repo', './dest/path', { isRelease: true });
expect(fetchRepoReleaseData).toHaveBeenCalledWith(
'owner/repo',
undefined
);
expect(fetchRepoAsZip).toHaveBeenCalled();
expect(extractZipArchive).toHaveBeenCalledWith(
'zip',
'repo',
'./dest/path',
{ sourceDir: undefined }
);
});

it('Clones the a specified release from a github repo', async () => {
await cloneGithubRepo('owner/repo', './dest/path', {
isRelease: true,
tag: 'v1.0.0',
});
expect(fetchRepoReleaseData).toHaveBeenCalledWith('owner/repo', 'v1.0.0');
expect(fetchRepoAsZip).toHaveBeenCalled();
expect(extractZipArchive).toHaveBeenCalledWith(
'zip',
'repo',
'./dest/path',
{ sourceDir: undefined }
);
});
});
});
72 changes: 41 additions & 31 deletions lib/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import { debug, makeTypedLogger } from '../utils/logger';
import { throwError, throwErrorWithMessage } from '../errors/standardErrors';
import { extractZipArchive } from './archive';

import { GITHUB_RELEASE_TYPES } from '../constants/github';
import { BaseError } from '../types/Error';
import { GithubReleaseData, GithubRepoFile } from '../types/Github';
import { ValueOf } from '../types/Utils';
import { LogCallbacksArg } from '../types/LogCallbacks';
import {
fetchRepoFile,
Expand Down Expand Up @@ -43,14 +41,18 @@ export async function fetchFileFromRepository(
}
}

// Fetches information about a specific release (Defaults to latest)
export async function fetchReleaseData(
repoPath: RepoPath,
tag = ''
tag?: string
): Promise<GithubReleaseData> {
tag = tag.trim().toLowerCase();
if (tag.length && tag[0] !== 'v') {
tag = `v${tag}`;
if (tag) {
tag = tag.trim().toLowerCase();
if (tag.length && tag[0] !== 'v') {
tag = `v${tag}`;
}
}

try {
const { data } = await fetchRepoReleaseData(repoPath, tag);
return data;
Expand All @@ -64,29 +66,33 @@ export async function fetchReleaseData(
}
}

type DownloadGithubRepoZipOptions = {
branch?: string;
tag?: string;
};

async function downloadGithubRepoZip(
repoPath: RepoPath,
tag = '',
releaseType: ValueOf<
typeof GITHUB_RELEASE_TYPES
> = GITHUB_RELEASE_TYPES.RELEASE,
ref?: string
isRelease = false,
options: DownloadGithubRepoZipOptions = {}
): Promise<Buffer> {
const { branch, tag } = options;
try {
let zipUrl: string;
if (releaseType === GITHUB_RELEASE_TYPES.REPOSITORY) {
debug(`${i18nKey}.downloadGithubRepoZip.fetching`, {
releaseType,
repoPath,
});
zipUrl = `https://api.github.com/repos/${repoPath}/zipball${
ref ? `/${ref}` : ''
}`;
} else {
if (isRelease) {
// If downloading a release, first get the release info using fetchReleaseData().
// Supports a custom tag, but will default to the latest release
const releaseData = await fetchReleaseData(repoPath, tag);
zipUrl = releaseData.zipball_url;
const { name } = releaseData;
debug(`${i18nKey}.downloadGithubRepoZip.fetchingName`, { name });
} else {
// If downloading a repository, manually construct the zip url. This url supports both branches and tags as refs
debug(`${i18nKey}.downloadGithubRepoZip.fetching`, { repoPath });
const ref = branch || tag;
zipUrl = `https://api.github.com/repos/${repoPath}/zipball${
ref ? `/${ref}` : ''
}`;
}
const { data } = await fetchRepoAsZip(zipUrl);
debug(`${i18nKey}.downloadGithubRepoZip.completed`);
Expand All @@ -101,32 +107,36 @@ async function downloadGithubRepoZip(
}

type CloneGithubRepoOptions = {
themeVersion?: string;
projectVersion?: string;
releaseType?: ValueOf<typeof GITHUB_RELEASE_TYPES>;
ref?: string;
isRelease?: boolean; // Download a repo release? (Default is to download the repo contents)
type?: string; // The type of asset being downloaded. Used for logging
branch?: string; // Repo branch
tag?: string; // Repo tag
sourceDir?: string; // The directory within the downloaded repo to write after extraction
};

const cloneGithubRepoCallbackKeys = ['success'];

export async function cloneGithubRepo(
dest: string,
type: string,
repoPath: RepoPath,
sourceDir: string,
dest: string,
options: CloneGithubRepoOptions = {},
logCallbacks?: LogCallbacksArg<typeof cloneGithubRepoCallbackKeys>
): Promise<boolean> {
const logger =
makeTypedLogger<typeof cloneGithubRepoCallbackKeys>(logCallbacks);
const { themeVersion, projectVersion, releaseType, ref } = options;
const tag = projectVersion || themeVersion;
const zip = await downloadGithubRepoZip(repoPath, tag, releaseType, ref);
const { tag, isRelease, branch, sourceDir, type } = options;
const zip = await downloadGithubRepoZip(repoPath, isRelease, {
tag,
branch,
});
const repoName = repoPath.split('/')[1];
const success = await extractZipArchive(zip, repoName, dest, { sourceDir });

if (success) {
logger('success', `${i18nKey}.cloneGithubRepo.success`, { type, dest });
logger('success', `${i18nKey}.cloneGithubRepo.success`, {
type: type || '',
dest,
});
}
return success;
}
Expand Down

0 comments on commit 963799c

Please sign in to comment.