Skip to content

Commit

Permalink
Merge branch 'main' into setup-husky-pre-commit
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholas-codecov authored Dec 5, 2023
2 parents 0503acf + cc47465 commit 979b572
Show file tree
Hide file tree
Showing 13 changed files with 239 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { bundleAnalysisPluginFactory } from "../bundleAnalysisPluginFactory";

describe("bundleAnalysisPluginFactory", () => {
it("returns a build start function", () => {
const plugin = bundleAnalysisPluginFactory({
userOptions: {},
bundleAnalysisUploadPlugin: () => ({
version: "1",
name: "plugin-name",
pluginVersion: "1.0.0",
}),
});

expect(plugin.buildStart).toEqual(expect.any(Function));
});

it("returns a build end function", () => {
const plugin = bundleAnalysisPluginFactory({
userOptions: {},
bundleAnalysisUploadPlugin: () => ({
version: "1",
name: "plugin-name",
pluginVersion: "1.0.0",
}),
});

expect(plugin.buildEnd).toEqual(expect.any(Function));
});

it("returns a write bundle function", () => {
const plugin = bundleAnalysisPluginFactory({
userOptions: {},
bundleAnalysisUploadPlugin: () => ({
version: "1",
name: "plugin-name",
pluginVersion: "1.0.0",
}),
});

expect(plugin.writeBundle).toEqual(expect.any(Function));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -52,25 +52,25 @@ export const bundleAnalysisPluginFactory = ({
const inputs: ProviderUtilInputs = { envs, args };
const provider = await detectProvider(inputs);

let sendStats = true;
let url = "";
try {
url = await getPreSignedURL({
apiURL: "http://localhost:3000",
globalUploadToken: "123",
apiURL: userOptions?.apiUrl ?? "https://api.codecov.io",
globalUploadToken: userOptions?.globalUploadToken,
repoToken: userOptions?.repoToken,
serviceParams: provider,
retryCount: userOptions?.retryCount,
});
} catch (error) {
sendStats = false;
return;
}

try {
if (sendStats) {
await uploadStats({
preSignedUrl: url,
message: JSON.stringify(output),
});
}
await uploadStats({
preSignedUrl: url,
message: JSON.stringify(output),
retryCount: userOptions?.retryCount,
});
} catch {}
},
};
Expand Down
5 changes: 5 additions & 0 deletions packages/bundler-plugin-core/src/errors/InvalidSlugError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class InvalidSlugError extends Error {
constructor(msg: string) {
super(msg);
}
}
5 changes: 5 additions & 0 deletions packages/bundler-plugin-core/src/errors/NoCommitShaError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class NoCommitShaError extends Error {
constructor(msg: string) {
super(msg);
}
}
15 changes: 0 additions & 15 deletions packages/bundler-plugin-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,28 +57,13 @@ export interface Options {
*/
globalUploadToken?: string;

/**
* The name of the repository to upload the bundle analysis information to.
*
* `globalUploadToken` and `repoName` must be set if this is not set.
*/
repoName?: string;

/**
* The upload token to use for uploading the bundle analysis information.
*
* Mutually exclusive to using `globalUploadToken` and `repoName`.
*/
repoToken?: string;

/**
* The commit hash to use for uploading the bundle analysis information.
*
* Defaults package.json name field.
*/
namespace?: string;

// TODO: Update the default value here
/**
* The api url used to fetch the upload url.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getPreSignedURL } from "../getPreSignedURL.ts";
import { FailedFetchError } from "../../errors/FailedFetchError.ts";
import { NoUploadTokenError } from "../../errors/NoUploadTokenError.ts";
import { UploadLimitReachedError } from "../../errors/UploadLimitReachedError.ts";
import { NoCommitShaError } from "../../errors/NoCommitShaError.ts";

const server = setupServer();

Expand Down Expand Up @@ -56,6 +57,25 @@ describe("getPreSignedURL", () => {

describe("successful request", () => {
describe("when the initial response is successful", () => {
describe('"globalUploadToken" is provided and "repoToken" is', () => {
it("returns the pre-signed URL", async () => {
setup({
data: { url: "http://example.com" },
});

const url = await getPreSignedURL({
apiURL: "http://localhost",
globalUploadToken: "super-cool-token",
repoToken: "super-repo-token",
serviceParams: {
commit: "123",
},
});

expect(url).toEqual("http://example.com");
});
});

describe('"globalUploadToken" is provided and "repoToken" is not', () => {
it("returns the pre-signed URL", async () => {
setup({
Expand Down Expand Up @@ -95,17 +115,16 @@ describe("getPreSignedURL", () => {
});

describe("unsuccessful request", () => {
describe("return body does not match schema", () => {
describe("no upload token found", () => {
it("throws an error", async () => {
const { consoleSpy } = setup({
data: { randomValue: "random" },
data: { url: "http://example.com" },
});

let error;
try {
await getPreSignedURL({
apiURL: "http://localhost",
globalUploadToken: "cool-upload-token",
serviceParams: {
commit: "123",
},
Expand All @@ -115,11 +134,16 @@ describe("getPreSignedURL", () => {
}

expect(consoleSpy).toHaveBeenCalled();
expect(error).toBeInstanceOf(FailedFetchError);
// for some reason, this test fails even tho it's the same values
// Expected: "No upload token found"
// Received: "No upload token found"
// Number of calls: 1
// expect(consoleSpy).toHaveBeenCalledWith("No upload token found");
expect(error).toBeInstanceOf(NoUploadTokenError);
});
});

describe("no upload token found", () => {
describe("no commit sha found", () => {
it("throws an error", async () => {
const { consoleSpy } = setup({
data: { url: "http://example.com" },
Expand All @@ -129,6 +153,29 @@ describe("getPreSignedURL", () => {
try {
await getPreSignedURL({
apiURL: "http://localhost",
globalUploadToken: "global-upload-token",
serviceParams: {},
});
} catch (e) {
error = e;
}

expect(consoleSpy).toHaveBeenCalled();
expect(error).toBeInstanceOf(NoCommitShaError);
});
});

describe("return body does not match schema", () => {
it("throws an error", async () => {
const { consoleSpy } = setup({
data: { randomValue: "random" },
});

let error;
try {
await getPreSignedURL({
apiURL: "http://localhost",
globalUploadToken: "cool-upload-token",
serviceParams: {
commit: "123",
},
Expand All @@ -138,12 +185,7 @@ describe("getPreSignedURL", () => {
}

expect(consoleSpy).toHaveBeenCalled();
// for some reason, this test fails even tho it's the same values
// Expected: "No upload token found"
// Received: "No upload token found"
// Number of calls: 1
// expect(consoleSpy).toHaveBeenCalledWith("No upload token found");
expect(error).toBeInstanceOf(NoUploadTokenError);
expect(error).toBeInstanceOf(FailedFetchError);
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { InvalidSlugError } from "../../errors/InvalidSlugError";
import { preProcessBody } from "../preProcessBody";

describe("preProcessBody", () => {
let consoleSpy: jest.SpyInstance;

beforeEach(() => {
consoleSpy = jest.spyOn(console, "log").mockImplementation(() => null);
});

afterEach(() => {
consoleSpy.mockReset();
});

describe("there is a valid value", () => {
it("does not change the `string`", () => {
const body = {
Expand All @@ -11,6 +22,39 @@ describe("preProcessBody", () => {

expect(result).toEqual({ key: "value" });
});

describe('the key is "slug"', () => {
describe("value is not an empty string", () => {
it('encodes the "slug" value', () => {
const body = {
slug: "codecov/engineering/applications-team/gazebo",
};

const result = preProcessBody(body);

expect(result).toEqual({
slug: "codecov:::engineering:::applications-team::::gazebo",
});
});
});

describe("value is an empty string", () => {
it('throws an "InvalidSlugError"', () => {
const body = {
slug: "",
};

let error;
try {
preProcessBody(body);
} catch (e) {
error = e;
}

expect(error).toBeInstanceOf(InvalidSlugError);
});
});
});
});

describe("there is an empty string", () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/bundler-plugin-core/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ export const SPAWN_PROCESS_BUFFER_SIZE = 1_048_576 * 100; // 100 MiB

export const DEFAULT_RETRY_COUNT = 3;
export const DEFAULT_RETRY_DELAY = 1000;

export const OWNER_SLUG_JOIN = ":::";
export const REPO_SLUG_JOIN = "::::";
13 changes: 9 additions & 4 deletions packages/bundler-plugin-core/src/utils/getPreSignedURL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DEFAULT_RETRY_COUNT } from "./constants.ts";
import { fetchWithRetry } from "./fetchWithRetry.ts";
import { green, red, yellow } from "./logging.ts";
import { preProcessBody } from "./preProcessBody.ts";
import { NoCommitShaError } from "../errors/NoCommitShaError.ts";

interface GetPreSignedURLArgs {
apiURL: string;
Expand All @@ -34,10 +35,14 @@ export const getPreSignedURL = async ({
throw new NoUploadTokenError("No upload token found");
}

const commitSha = serviceParams?.commit ?? "";
const url = `${apiURL}/upload/service/commits/${commitSha}/bundle_analysis`;
const commitSha = serviceParams?.commit;

if (!commitSha) {
red("No commit found");
throw new NoCommitShaError("No commit found");
}

const sentServiceParams = preProcessBody({ token, ...serviceParams });
const url = `${apiURL}/upload/service/commits/${commitSha}/bundle_analysis`;

let response: Response;
try {
Expand All @@ -51,7 +56,7 @@ export const getPreSignedURL = async ({
"Content-Type": "application/json",
Authorization: `token ${token}`,
},
body: JSON.stringify(sentServiceParams),
body: JSON.stringify(preProcessBody(serviceParams)),
},
});
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import childprocess from "child_process";

export function isProgramInstalled(programName: string): boolean {
return !childprocess.spawnSync(programName).error;
return !childprocess?.spawnSync(programName)?.error;
}
24 changes: 24 additions & 0 deletions packages/bundler-plugin-core/src/utils/preProcessBody.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
import { InvalidSlugError } from "../errors/InvalidSlugError";
import { OWNER_SLUG_JOIN, REPO_SLUG_JOIN } from "./constants";
import { red } from "./logging";

export const preProcessBody = (
body: Record<string, string | null | undefined>,
) => {
for (const [key, value] of Object.entries(body)) {
if (key === "slug" && typeof value === "string") {
body[key] = encodeSlug(value);
}

if (!value || value === "") {
body[key] = null;
}
}

return body;
};

export const encodeSlug = (slug: string): string => {
const repoIndex = slug.lastIndexOf("/") + 1;
const owner = slug.substring(0, repoIndex).trimEnd();
const repo = slug.substring(repoIndex, slug.length);

if (owner === "" || repo === "") {
red("Invalid owner and/or repo");
throw new InvalidSlugError("Invalid owner and/or repo");
}

const encodedOwner = owner?.split("/").join(OWNER_SLUG_JOIN).slice(0, -3);
const encodedSlug = [encodedOwner, repo].join(REPO_SLUG_JOIN);

return encodedSlug;
};
Loading

0 comments on commit 979b572

Please sign in to comment.