Skip to content

Commit

Permalink
Merge pull request ubiquity-os#156 from gentlementlegen/fix/execution
Browse files Browse the repository at this point in the history
fix: execution break
  • Loading branch information
gentlementlegen authored Oct 19, 2024
2 parents 7db4ed9 + 75ce51a commit e62393b
Show file tree
Hide file tree
Showing 7 changed files with 362 additions and 56 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"@octokit/plugin-rest-endpoint-methods": "13.2.4",
"@octokit/plugin-retry": "7.1.1",
"@octokit/plugin-throttling": "9.3.1",
"@octokit/rest": "^21.0.2",
"@octokit/types": "13.5.0",
"@octokit/webhooks": "13.2.8",
"@octokit/webhooks-types": "7.5.1",
Expand Down
25 changes: 15 additions & 10 deletions src/github/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,21 @@ async function handleEvent(event: EmitterWebhookEvent, eventHandler: InstanceTyp
state.inputs[0] = inputs;
await eventHandler.pluginChainState.put(stateId, state);

if (!isGithubPluginObject) {
await dispatchWorker(plugin, await inputs.getWorkerInputs());
} else {
await dispatchWorkflow(context, {
owner: plugin.owner,
repository: plugin.repo,
workflowId: plugin.workflowId,
ref: plugin.ref,
inputs: await inputs.getWorkflowInputs(),
});
// We wrap the dispatch so a failing plugin doesn't break the whole execution
try {
if (!isGithubPluginObject) {
await dispatchWorker(plugin, await inputs.getWorkerInputs());
} else {
await dispatchWorkflow(context, {
owner: plugin.owner,
repository: plugin.repo,
workflowId: plugin.workflowId,
ref: plugin.ref,
inputs: await inputs.getWorkflowInputs(),
});
}
} catch (e) {
console.error(`An error occurred while processing the plugin chain, will skip plugin ${JSON.stringify(plugin)}`, e);
}
}
}
16 changes: 3 additions & 13 deletions src/github/handlers/push-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,18 +125,8 @@ async function checkPluginConfigurations(context: GitHubContext<"push">, config:
export default async function handlePushEvent(context: GitHubContext<"push">) {
const { payload } = context;
const { repository, commits, after } = payload;
const configPaths = [CONFIG_FULL_PATH, DEV_CONFIG_FULL_PATH];
const didConfigurationFileChange = commits.some((commit) =>
configPaths.some((path) => {
if (commit.modified?.includes(path) || commit.added?.includes(path)) {
// Keeps only the config that matched the modified elements
configPaths.length = 0;
configPaths.push(path);
return true;
}
return false;
})
);
const configPath = context.eventHandler.environment === "production" ? CONFIG_FULL_PATH : DEV_CONFIG_FULL_PATH;
const didConfigurationFileChange = commits.some((commit) => commit.modified?.includes(configPath) || commit.added?.includes(configPath));

if (!didConfigurationFileChange || !repository.owner) {
return;
Expand All @@ -154,7 +144,7 @@ export default async function handlePushEvent(context: GitHubContext<"push">) {
try {
if (errors.length) {
const body = [];
body.push(...constructErrorBody(errors, rawData, repository, after, configPaths[0]));
body.push(...constructErrorBody(errors, rawData, repository, after, configPath));
await createCommitComment(
context,
{
Expand Down
165 changes: 165 additions & 0 deletions tests/dispatch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, jest } from "@jest/globals";
import crypto from "crypto";
import { server } from "./__mocks__/node";
import { http, HttpResponse } from "msw";
import { Octokit } from "@octokit/rest";

jest.mock("@octokit/plugin-paginate-rest", () => ({}));
jest.mock("@octokit/plugin-rest-endpoint-methods", () => ({}));
jest.mock("@octokit/plugin-retry", () => ({}));
jest.mock("@octokit/plugin-throttling", () => ({}));
jest.mock("@octokit/auth-app", () => ({
createAppAuth: jest.fn(() => () => jest.fn(() => "1234")),
}));

jest.mock("../src/github/utils/cloudflare-kv", () => ({
CloudflareKv: jest.fn().mockImplementation(() => ({
get: jest.fn(),
put: jest.fn(),
})),
}));

jest.mock("../src/github/types/plugin", () => {
const originalModule: typeof import("../src/github/types/plugin") = jest.requireActual("../src/github/types/plugin");

return {
...originalModule,
PluginInput: class extends originalModule.PluginInput {
async getWorkerInputs() {
return {
stateId: this.stateId,
eventName: this.eventName,
eventPayload: this.eventPayload,
settings: this.settings,
authToken: this.authToken,
ref: this.ref,
signature: "",
};
}
},
};
});

function calculateSignature(payload: string, secret: string) {
return `sha256=${crypto.createHmac("sha256", secret).update(payload).digest("hex")}`;
}

beforeAll(() => {
server.listen();
});
afterEach(() => {
server.resetHandlers();
});
afterAll(() => {
server.close();
});

describe("handleEvent", () => {
beforeEach(() => {
server.use(
http.get("https://plugin-a.internal/manifest.json", () =>
HttpResponse.json({
name: "plugin",
"ubiquity:listeners": ["issue_comment.created"],
commands: {
foo: {
description: "foo command",
"ubiquity:example": "/foo bar",
},
bar: {
description: "bar command",
"ubiquity:example": "/bar foo",
},
},
})
),
http.get("https://api.github.com/repos/test-user/.ubiquity-os/contents/.github%2F.ubiquity-os.config.yml", (req) => {
const acceptHeader = req.request.headers.get("accept");
const yamlContent = `plugins:\n - uses:\n - plugin: "https://plugin-a.internal"\n - uses:\n - plugin: "https://plugin-a.internal"`;
if (acceptHeader === "application/vnd.github.v3.raw") {
return HttpResponse.text(yamlContent);
} else {
return HttpResponse.json({
type: "file",
encoding: "base64",
size: 62,
name: ".ubiquity-os.config.yml",
path: ".github/.ubiquity-os.config.yml",
content: Buffer.from(yamlContent).toString("base64"),
sha: "3ffce0fe837a21b1237acd38f7b1c3d2f7d73656",
url: "https://api.github.com/repos/test-user/.ubiquity-os/contents/.github%2F.ubiquity-os.config.yml",
git_url: "https://api.github.com/repos/test-user/.ubiquity-os/git/blobs/3ffce0fe837a21b1237acd38f7b1c3d2f7d73656",
html_url: "https://github.com/test-user/.ubiquity-os/blob/main/.github/.ubiquity-os.config.yml",
download_url: "https://raw.githubusercontent.com/test-user/.ubiquity-os/main/.github/.ubiquity-os.config.yml",
});
}
})
);
});

it("should not stop the plugin chain if dispatch throws an error", async () => {
jest.mock("../src/github/github-client", () => {
return {
customOctokit: jest.fn().mockReturnValue(new Octokit()),
};
});
const dispatchWorker = jest
.fn()
.mockImplementationOnce(() => {
throw new Error("Test induced first call failure");
})
.mockImplementationOnce(() => Promise.resolve("success"));
jest.mock("../src/github/utils/workflow-dispatch", () => ({
...(jest.requireActual("../src/github/utils/workflow-dispatch") as object),
dispatchWorker: dispatchWorker,
}));
const payload = {
installation: {
id: 1,
},
sender: {
type: "User",
},
comment: {
body: "/foo",
},
repository: {
id: 123456,
name: ".ubiquity-os",
full_name: "test-user/.ubiquity-os",
owner: {
login: "test-user",
id: 654321,
},
},
};
const secret = "1234";
const payloadString = JSON.stringify(payload);
const signature = calculateSignature(payloadString, secret);

const req = new Request("http://localhost:8080", {
method: "POST",
headers: {
"x-github-event": "issue_comment.created",
"x-hub-signature-256": signature,
"x-github-delivery": "mocked_delivery_id",
"content-type": "application/json",
},
body: payloadString,
});

const worker = (await import("../src/worker")).default;
const res = await worker.fetch(req, {
ENVIRONMENT: "production",
APP_WEBHOOK_SECRET: secret,
APP_ID: "1",
APP_PRIVATE_KEY: "1234",
PLUGIN_CHAIN_STATE: {} as KVNamespace,
});

expect(res).toBeTruthy();
// 2 calls means the execution didn't break
expect(dispatchWorker).toHaveBeenCalledTimes(2);
dispatchWorker.mockReset();
});
});
71 changes: 38 additions & 33 deletions tests/events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ jest.mock("@octokit/auth-app", () => ({}));

config({ path: ".dev.vars" });

const name = "ubiquity-os-kernel";

beforeAll(() => {
server.listen();
});
afterEach(() => {
server.resetHandlers();
jest.clearAllMocks();
jest.resetAllMocks();
});
afterAll(() => {
server.close();
Expand Down Expand Up @@ -58,17 +62,10 @@ describe("Event related tests", () => {
},
};
const spy = jest.spyOn(issues, "createComment");
await issueCommentCreated({
id: "",
key: "issue_comment.created",
octokit: {
rest: {
issues,
repos: {
getContent(params?: RestEndpointMethodTypes["repos"]["getContent"]["parameters"]) {
if (params?.path === CONFIG_FULL_PATH) {
return {
data: `
const getContent = jest.fn((params?: RestEndpointMethodTypes["repos"]["getContent"]["parameters"]) => {
if (params?.path === CONFIG_FULL_PATH) {
return {
data: `
plugins:
- name: "Run on comment created"
uses:
Expand All @@ -79,35 +76,43 @@ describe("Event related tests", () => {
- id: plugin-B
plugin: ubiquity-os/plugin-b
`,
};
} else if (params?.path === "manifest.json") {
return {
data: {
content: btoa(
JSON.stringify({
name: "plugin",
commands: {
action: {
description: "action",
"ubiquity:example": "/action",
},
},
})
),
};
} else if (params?.path === "manifest.json") {
return {
data: {
content: btoa(
JSON.stringify({
name: "plugin",
commands: {
action: {
description: "action",
"ubiquity:example": "/action",
},
};
} else {
throw new Error("Not found");
}
},
},
})
),
},
};
} else {
throw new Error("Not found");
}
});
await issueCommentCreated({
id: "",
key: "issue_comment.created",
octokit: {
rest: {
issues,
repos: {
getContent: getContent,
},
},
},
eventHandler: eventHandler,
payload: {
repository: {
owner: { login: "ubiquity" },
name: "ubiquity-os-kernel",
name,
},
issue: { number: 1 },
comment: {
Expand All @@ -124,7 +129,7 @@ describe("Event related tests", () => {
" all available commands. | `/help` |\n| `/action` | action | `/action` |\n| `/bar` | bar command | `/bar foo` |\n| `/foo` | foo command | `/foo bar` |",
issue_number: 1,
owner: "ubiquity",
repo: "ubiquity-os-kernel",
repo: name,
},
],
]);
Expand Down
Loading

0 comments on commit e62393b

Please sign in to comment.