Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin' into ARC-adding-modals-for-back…
Browse files Browse the repository at this point in the history
…fill-page
  • Loading branch information
krazziekay committed Dec 7, 2023
2 parents 0b4d49f + 247f143 commit 2cebe4d
Show file tree
Hide file tree
Showing 13 changed files with 286 additions and 99 deletions.
12 changes: 11 additions & 1 deletion etc/poco/bundle/extras-prod-test.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"allowed": true
},
{
"name": "Allow Prod Basic Check Critical Pollinator Test to call Get Audit Log endpoints",
"name": "Allow Prod Basic Check Critical Pollinator Test to call Get Audit Log endpoints for cloud",
"path": "/api/audit-log/subscription/255625",
"method": "GET",
"mechanism": "asap",
Expand All @@ -50,6 +50,16 @@
],
"allowed": true
},
{
"name": "Allow Prod Basic Check Critical Pollinator Test to call Get Audit Log endpoints for ghe",
"path": "/api/audit-log/subscription/256125",
"method": "GET",
"mechanism": "asap",
"principals": [
"pollinator-check/d4f03d07-12fe-4a69-9d68-c1841066772e"
],
"allowed": true
},
{
"name": "Allow pollinator test to call Delete Installation endpoints",
"path": "/api/deleteInstallation/21266506/https%3A%2F%2Ffusion-arc-pollinator-staging-app.atlassian.net",
Expand Down
3 changes: 2 additions & 1 deletion etc/poco/bundle/extras-prod.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"issuers": [
"pollinator-check/f24ec1a9-d03d-45c7-bbd8-f2094543eaba",
"pollinator-check/8692803e-287a-48e3-bad1-49a60a7a4f9d",
"pollinator-check/d4f03d07-12fe-4a69-9d68-c1841066772e"
"pollinator-check/d4f03d07-12fe-4a69-9d68-c1841066772e",
"pollinator-check/42166522-a00b-4c93-858c-bda16bbf7aba"
]
}
}
Expand Down
6 changes: 4 additions & 2 deletions spa/src/api/subscriptions/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { axiosRest } from "../axiosInstance";
import { axiosRest, axiosRestWithGitHubToken } from "../axiosInstance";

export default {
getSubscriptions: () => axiosRest.get("/rest/subscriptions")
getSubscriptions: () => axiosRest.get("/rest/subscriptions"),
deleteSubscription: (subscriptionId: number) =>
axiosRestWithGitHubToken.delete("/rest/app/cloud/subscription/:subscriptionId", { params: { subscriptionId } })
};
17 changes: 16 additions & 1 deletion src/github/client/github-client-errors.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GithubClientBlockedIpError } from "./github-client-errors";
import { GithubClientBlockedIpError, GithubClientError } from "./github-client-errors";

describe("GitHubClientError", () => {

Expand All @@ -19,4 +19,19 @@ describe("GitHubClientError", () => {
expect(error.stack).toContain("existing stack trace line 2");
expect(error.stack).toContain("existing stack trace line 3");
});

it("extract the error response body (empty)", async () => {
const error = new GithubClientError("test", { } as any);
expect(error.resBody).toEqual(undefined);
});

it("extract the error response body (str)", async () => {
const error = new GithubClientError("test", { response: { data: "test resp body" } } as any);
expect(error.resBody).toEqual("test resp body");
});

it("extract the error response body (object)", async () => {
const error = new GithubClientError("test", { response: { data: { hello: "error" } } } as any);
expect(error.resBody).toEqual(`{"hello":"error"}`);
});
});
14 changes: 14 additions & 0 deletions src/github/client/github-client-errors.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
import { AxiosError, AxiosResponse } from "axios";
import { ErrorCode } from "rest-interfaces";
import safeJsonStringify from "safe-json-stringify";

const safeParseResponseBody = (data: unknown): string | undefined => {
if (data === undefined) return undefined;
if ((typeof data) === "string") {
return data as string;
}
if ((typeof data) === "object") {
return safeJsonStringify(data as object);
}
return String(data);
};

export class GithubClientError extends Error {
cause: AxiosError;
isRetryable = true;

status?: number;
code?: string;
resBody?: string;
uiErrorCode: ErrorCode;

constructor(message: string, cause: AxiosError) {
super(message);

this.status = cause.response?.status;
this.code = cause.code;
this.resBody = safeParseResponseBody(cause.response?.data);
this.uiErrorCode = "UNKNOWN";

this.cause = { ...cause };
Expand Down
2 changes: 2 additions & 0 deletions src/rest/rest-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ subRouter.use("/installation", GitHubAppsRoute);

subRouter.use("/jira/cloudid", JiraCloudIDRouter);

subRouter.use("/subscriptions/:subscriptionId", SubscriptionsRouter);

subRouter.use(GitHubTokenHandler);

subRouter.use("/org", GitHubOrgsRouter);
Expand Down
97 changes: 97 additions & 0 deletions src/rest/routes/subscriptions/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Installation } from "models/installation";
import { Subscription } from "models/subscription";
import { encodeSymmetric } from "atlassian-jwt";
import { getFrontendApp } from "~/src/app";
import supertest from "supertest";
import { envVars } from "config/env";

describe("Subscription", () => {
const testSharedSecret = "test-secret";
const gitHubInstallationId = 15;
const getToken = ({
secret = testSharedSecret,
iss = "jira-client-key",
exp = Date.now() / 1000 + 10000,
qsh = "context-qsh" } = {}): string => encodeSymmetric({
qsh,
iss,
exp
}, secret);
let app, subscription;
beforeEach(async () => {
app = getFrontendApp();
await Installation.install({
clientKey: "jira-client-key",
host: jiraHost,
sharedSecret: testSharedSecret
});
subscription = await Subscription.create({
gitHubInstallationId,
jiraHost
});
});

it("Should return 400 for invalid delete subscription route", async () => {
const resp = await supertest(app)
.delete("/rest/subscriptions/" + gitHubInstallationId)
.set("authorization", `${getToken()}`);

expect(resp.status).toBe(404);
});

it("Should return 401 for valid delete subscription route when missing githubToken", async () => {
const resp = await supertest(app)
.delete("/rest/app/cloud/subscriptions/" + gitHubInstallationId);

expect(resp.status).toBe(401);
expect(await Subscription.count()).toEqual(1);
});

it("Should return 404 for valid delete subscription route when no valid subscriptionId is passed", async () => {
const resp = await supertest(app)
.delete("/rest/app/cloud/subscriptions/random-installation-id")
.set("authorization", `${getToken()}`);

expect(resp.status).toBe(404);
expect(await Subscription.count()).toEqual(1);
});

it("Should return 404 for valid delete subscription route when a different subscriptionId is passed", async () => {
const resp = await supertest(app)
.delete("/rest/app/cloud/subscriptions/12")
.set("authorization", `${getToken()}`);

expect(resp.status).toBe(404);
expect(await Subscription.count()).toEqual(1);
});

it("Should return 204 for valid delete subscription route when subscription is deleted", async () => {
jiraNock
.delete("/rest/devinfo/0.10/bulkByProperties")
.query({ installationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.delete("/rest/builds/0.1/bulkByProperties")
.query({ gitHubInstallationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.delete("/rest/deployments/0.1/bulkByProperties")
.query({ gitHubInstallationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.put(`/rest/atlassian-connect/latest/addons/${envVars.APP_KEY}/properties/is-configured`, {
isConfigured: false
})
.reply(200);

const resp = await supertest(app)
.delete("/rest/app/cloud/subscriptions/" + subscription.id)
.set("authorization", `${getToken()}`);

expect(resp.status).toBe(204);
expect(await Subscription.count()).toEqual(0);
});
});
40 changes: 40 additions & 0 deletions src/rest/routes/subscriptions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Router, Request, Response } from "express";
import { errorWrapper } from "../../helper";
import { getAllSubscriptions } from "./service";
import { Installation } from "models/installation";
import { removeSubscription } from "utils/jira-utils";
import { GitHubServerApp } from "models/github-server-app";
import { InvalidArgumentError } from "config/errors";

export const SubscriptionsRouter = Router({ mergeParams: true });

SubscriptionsRouter.get("/", errorWrapper("SubscriptionsGet", async (req: Request, res: Response) => {
const { jiraHost, installation } = res.locals;
const { ghCloudSubscriptions, ghEnterpriseServers } = await getAllSubscriptions(jiraHost as string, (installation as Installation).id, req);

res.status(200).json({
ghCloudSubscriptions,
ghEnterpriseServers
});
}));

/**
* This delete endpoint only handles Github cloud subscriptions
*/
SubscriptionsRouter.delete("/", errorWrapper("SubscriptionDelete", async (req: Request, res: Response) => {
const subscriptionId: number = Number(req.params.subscriptionId);
const { installation } = res.locals as { installation: Installation; };

const cloudOrUUID = req.params.cloudOrUUID;
if (!cloudOrUUID) {
throw new InvalidArgumentError("Invalid route, couldn't determine if its cloud or enterprise!");
}

// TODO: Check and add test cases for GHE later
const gitHubAppId = cloudOrUUID === "cloud" ? undefined :
(await GitHubServerApp.getForUuidAndInstallationId(cloudOrUUID, installation.id))?.appId; //TODO: validate the uuid regex

await removeSubscription(installation, undefined, gitHubAppId, req.log, subscriptionId);

res.sendStatus(204);
}));
17 changes: 0 additions & 17 deletions src/rest/routes/subscriptions/index.tsx

This file was deleted.

20 changes: 10 additions & 10 deletions src/routes/jira/jira-delete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe("DELETE /jira/configuration", () => {

beforeEach(async () => {
subscription = {
githubInstallationId: 15,
gitHubInstallationId: 15,
jiraHost,
destroy: jest.fn().mockResolvedValue(undefined)
};
Expand All @@ -41,17 +41,17 @@ describe("DELETE /jira/configuration", () => {
it("Delete Jira Configuration", async () => {
jiraNock
.delete("/rest/devinfo/0.10/bulkByProperties")
.query({ installationId: subscription.githubInstallationId })
.query({ installationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.delete("/rest/builds/0.1/bulkByProperties")
.query({ gitHubInstallationId: subscription.githubInstallationId })
.query({ gitHubInstallationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.delete("/rest/deployments/0.1/bulkByProperties")
.query({ gitHubInstallationId: subscription.githubInstallationId })
.query({ gitHubInstallationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
Expand All @@ -67,7 +67,7 @@ describe("DELETE /jira/configuration", () => {
jiraHost: subscription.jiraHost
},
params: {
installationId: subscription.githubInstallationId
installationId: subscription.gitHubInstallationId
}
};

Expand All @@ -81,17 +81,17 @@ describe("DELETE /jira/configuration", () => {
when(booleanFlag).calledWith(BooleanFlags.ENABLE_GITHUB_SECURITY_IN_JIRA, expect.anything()).mockResolvedValue(true);
jiraNock
.delete("/rest/devinfo/0.10/bulkByProperties")
.query({ installationId: subscription.githubInstallationId })
.query({ installationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.delete("/rest/builds/0.1/bulkByProperties")
.query({ gitHubInstallationId: subscription.githubInstallationId })
.query({ gitHubInstallationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.delete("/rest/deployments/0.1/bulkByProperties")
.query({ gitHubInstallationId: subscription.githubInstallationId })
.query({ gitHubInstallationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
Expand All @@ -115,7 +115,7 @@ describe("DELETE /jira/configuration", () => {
jiraHost: subscription.jiraHost
},
params: {
installationId: subscription.githubInstallationId
installationId: subscription.gitHubInstallationId
}
};

Expand All @@ -132,7 +132,7 @@ describe("DELETE /jira/configuration", () => {
jiraHost: subscription.jiraHost
},
params: {
installationId: subscription.githubInstallationId
installationId: subscription.gitHubInstallationId
}
};

Expand Down
Loading

0 comments on commit 2cebe4d

Please sign in to comment.