From f34b8b56d7f1e842403b53350f47d6abb8c75adf Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Sat, 27 Jul 2024 10:11:21 +0100 Subject: [PATCH] chore: eslint, cspell --- .cspell.json | 19 ++++- README.md | 24 +++---- manifest.json | 2 +- src/handlers/hello-world.ts | 81 ++++++++++++---------- src/plugin.ts | 6 +- src/types/plugin-inputs.ts | 9 ++- src/types/typeguards.ts | 4 +- tests/__mocks__/db.ts | 2 +- tests/__mocks__/handlers.ts | 13 ++-- tests/__mocks__/helpers.ts | 104 ++++++++++++++-------------- tests/__mocks__/issue-template.ts | 111 +++++++++++++++--------------- tests/__mocks__/strings.ts | 26 +++---- tests/__mocks__/users-get.json | 2 +- tests/main.test.ts | 30 ++++---- 14 files changed, 231 insertions(+), 202 deletions(-) diff --git a/.cspell.json b/.cspell.json index 213394b..c2b732e 100644 --- a/.cspell.json +++ b/.cspell.json @@ -4,7 +4,24 @@ "ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log", "./src/adapters/supabase/**/**.ts"], "useGitignore": true, "language": "en", - "words": ["Nektos", "dataurl", "devpool", "outdir", "servedir", "Supabase", "SUPABASE", "typebox", "ubiquibot", "Smee"], + "words": [ + "Nektos", + "dataurl", + "devpool", + "outdir", + "servedir", + "Supabase", + "SUPABASE", + "typebox", + "ubiquibot", + "Smee", + "typeguards", + "mswjs", + "Typeguards", + "sonarjs", + "knip", + "mischeck" + ], "dictionaries": ["typescript", "node", "software-terms"], "import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"], "ignoreRegExpList": ["[0-9a-fA-F]{6}"] diff --git a/README.md b/README.md index cfc45bb..0e70409 100644 --- a/README.md +++ b/README.md @@ -26,18 +26,18 @@ ```yml plugins: "issue_comment.created": - - name: hello-world - id: hello-world - type: github - description: "A simple hello world plugin" # small description of what the plugin does - command: "\/hello" # if you are creating a plugin with a slash command - example: "/hello" # how to invoke the slash command - uses: - # - plugin: /:compute.yml@development - - plugin: http://localhost:4000 - with: - # Define configurables here and the kernel will pass these to the plugin. - configurableResponse: "Hello, is it me you are looking for?" + - name: hello-world + id: hello-world + type: github + description: "A simple hello world plugin" # small description of what the plugin does + command: "\/hello" # if you are creating a plugin with a slash command + example: "/hello" # how to invoke the slash command + uses: + # - plugin: /:compute.yml@development + - plugin: http://localhost:4000 + with: + # Define configurable items here and the kernel will pass these to the plugin. + configurableResponse: "Hello, is it me you are looking for?" ``` ###### At this stage, your plugin will fire on your defined events with the required settings passed in from the kernel. You can now start writing your plugin's logic. diff --git a/manifest.json b/manifest.json index c128a96..83aa3f2 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "ts-template", "description": "ts-template for Ubiquibot plugins.", - "ubiquity:listeners": [ "issue_comment.created" ], + "ubiquity:listeners": ["issue_comment.created"], "commands": { "command1": { "ubiquity:example": "/command1 argument", diff --git a/src/handlers/hello-world.ts b/src/handlers/hello-world.ts index 9951b84..4c58ae1 100644 --- a/src/handlers/hello-world.ts +++ b/src/handlers/hello-world.ts @@ -2,53 +2,58 @@ import { Context } from "../types"; /** * NOTICE: Remove this file or use it as a template for your own plugins. - * + * * This encapsulates the logic for a plugin if the only thing it does is say "Hello, world!". - * + * * Try it out by running your local kernel worker and running the `yarn worker` command. * Comment on an issue in a repository where your GitHub App is installed and see the magic happen! - * + * * Logger examples are provided to show how to log different types of data. */ export async function helloWorld(context: Context) { - const { logger, payload, octokit, config: { configurableResponse } } = context; + const { + logger, + payload, + octokit, + config: { configurableResponse }, + } = context; - const sender = payload.comment.user?.login; - const repo = payload.repository.name; - const issueNumber = payload.issue.number; - const owner = payload.repository.owner.login; - const body = payload.comment.body; + const sender = payload.comment.user?.login; + const repo = payload.repository.name; + const issueNumber = payload.issue.number; + const owner = payload.repository.owner.login; + const body = payload.comment.body; - if (!body.match(/hello/i)) { - logger.error(`Invalid use of slash command, use "/hello".`, { body }); - return; - } + if (!body.match(/hello/i)) { + logger.error(`Invalid use of slash command, use "/hello".`, { body }); + return; + } - logger.info("Hello, world!"); - logger.debug(`Executing helloWorld:`, { sender, repo, issueNumber, owner }); + logger.info("Hello, world!"); + logger.debug(`Executing helloWorld:`, { sender, repo, issueNumber, owner }); - try { - await octokit.issues.createComment({ - owner: payload.repository.owner.login, - repo: payload.repository.name, - issue_number: payload.issue.number, - body: configurableResponse - }); - } catch (error) { - /** - * logger.fatal should not be used in 9/10 cases. Use logger.error instead. - * - * Below are examples of passing error objects to the logger, only one is needed. - */ - if (error instanceof Error) { - logger.error(`Error creating comment:`, { error: error, stack: error.stack }); - throw error; - } else { - logger.error(`Error creating comment:`, { err: error, error: new Error() }); - throw error; - } + try { + await octokit.issues.createComment({ + owner: payload.repository.owner.login, + repo: payload.repository.name, + issue_number: payload.issue.number, + body: configurableResponse, + }); + } catch (error) { + /** + * logger.fatal should not be used in 9/10 cases. Use logger.error instead. + * + * Below are examples of passing error objects to the logger, only one is needed. + */ + if (error instanceof Error) { + logger.error(`Error creating comment:`, { error: error, stack: error.stack }); + throw error; + } else { + logger.error(`Error creating comment:`, { err: error, error: new Error() }); + throw error; } + } - logger.ok(`Successfully created comment!`); - logger.verbose(`Exiting helloWorld`); -} \ No newline at end of file + logger.ok(`Successfully created comment!`); + logger.verbose(`Exiting helloWorld`); +} diff --git a/src/plugin.ts b/src/plugin.ts index a8df261..93bf020 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,5 +1,5 @@ import { Octokit } from "@octokit/rest"; -import { createClient } from "@supabase/supabase-js"; +// import { createClient } from "@supabase/supabase-js"; import { createAdapters } from "./adapters"; import { Env, PluginInputs } from "./types"; import { Context } from "./types"; @@ -38,8 +38,8 @@ export async function plugin(inputs: PluginInputs, env: Env) { }; // consider non-database storage solutions unless necessary - // TODO: deprecate adapters/supabase from context. + // TODO: deprecate adapters/supabase from context. // context.adapters = createAdapters(supabase, context); return runPlugin(context); -} \ No newline at end of file +} diff --git a/src/types/plugin-inputs.ts b/src/types/plugin-inputs.ts index b68010d..1c8a30a 100644 --- a/src/types/plugin-inputs.ts +++ b/src/types/plugin-inputs.ts @@ -18,9 +18,12 @@ export interface PluginInputs { - return context.eventName === "issue_comment.created"; -} \ No newline at end of file + return context.eventName === "issue_comment.created"; +} diff --git a/tests/__mocks__/db.ts b/tests/__mocks__/db.ts index c823ab2..f7bd2b6 100644 --- a/tests/__mocks__/db.ts +++ b/tests/__mocks__/db.ts @@ -81,4 +81,4 @@ export const db = factory({ id: Number, }, }, -}); \ No newline at end of file +}); diff --git a/tests/__mocks__/handlers.ts b/tests/__mocks__/handlers.ts index 4101e3f..ab4c73e 100644 --- a/tests/__mocks__/handlers.ts +++ b/tests/__mocks__/handlers.ts @@ -1,7 +1,6 @@ import { http, HttpResponse } from "msw"; import { db } from "./db"; import issueTemplate from "./issue-template"; -import { get } from "http"; /** * Intercepts the routes and returns a custom payload */ @@ -15,8 +14,10 @@ export const handlers = [ HttpResponse.json(db.issue.findMany({ where: { owner: { equals: owner as string }, repo: { equals: repo as string } } })) ), // get issue - http.get("https://api.github.com/repos/:owner/:repo/issues/:issue_number", ({ params: { owner, repo, issue_number } }) => - HttpResponse.json(db.issue.findFirst({ where: { owner: { equals: owner as string }, repo: { equals: repo as string }, number: { equals: Number(issue_number) } } })) + http.get("https://api.github.com/repos/:owner/:repo/issues/:issue_number", ({ params: { owner, repo, issue_number: issueNumber } }) => + HttpResponse.json( + db.issue.findFirst({ where: { owner: { equals: owner as string }, repo: { equals: repo as string }, number: { equals: Number(issueNumber) } } }) + ) ), // get user http.get("https://api.github.com/users/:username", ({ params: { username } }) => @@ -38,10 +39,10 @@ export const handlers = [ return HttpResponse.json(newItem); }), // create comment - http.post("https://api.github.com/repos/:owner/:repo/issues/:issue_number/comments", async ({ params: { owner, repo, issue_number }, request }) => { + http.post("https://api.github.com/repos/:owner/:repo/issues/:issue_number/comments", async ({ params: { issue_number: issueNumber }, request }) => { const { body } = await getValue(request.body); const id = db.issueComments.count() + 1; - const newItem = { id, body, issue_number: Number(issue_number), user: db.users.getAll()[0] }; + const newItem = { id, body, issue_number: Number(issueNumber), user: db.users.getAll()[0] }; db.issueComments.create(newItem); return HttpResponse.json(newItem); }), @@ -60,4 +61,4 @@ async function getValue(body: ReadableStream | null) { } } } -} \ No newline at end of file +} diff --git a/tests/__mocks__/helpers.ts b/tests/__mocks__/helpers.ts index 4ee2ede..a9fc8b0 100644 --- a/tests/__mocks__/helpers.ts +++ b/tests/__mocks__/helpers.ts @@ -5,70 +5,70 @@ import usersGet from "./users-get.json"; /** * Helper function to setup tests. - * + * * This function populates the mock database with the external API * data you'd expect to find in a real-world scenario. - * + * * Here is where you create issues, commits, pull requests, etc. */ export async function setupTests() { - for (const item of usersGet) { - db.users.create(item); - } + for (const item of usersGet) { + db.users.create(item); + } - db.repo.create({ - id: 1, - name: STRINGS.TEST_REPO, - owner: { - login: STRINGS.USER_1, - id: 1, - }, - issues: [], - }); + db.repo.create({ + id: 1, + name: STRINGS.TEST_REPO, + owner: { + login: STRINGS.USER_1, + id: 1, + }, + issues: [], + }); - db.issue.create({ - ...issueTemplate, - }); + db.issue.create({ + ...issueTemplate, + }); - db.issue.create({ - ...issueTemplate, - id: 2, - number: 2, - labels: [], - }); + db.issue.create({ + ...issueTemplate, + id: 2, + number: 2, + labels: [], + }); - createComment("/Hello", 1); + createComment("/Hello", 1); } export function createComment(comment: string, commentId: number) { - const isComment = db.issueComments.findFirst({ - where: { - id: { - equals: commentId, - }, + const isComment = db.issueComments.findFirst({ + where: { + id: { + equals: commentId, + }, + }, + }); + + if (isComment) { + db.issueComments.update({ + where: { + id: { + equals: commentId, }, + }, + data: { + body: comment, + }, }); - - if (isComment) { - db.issueComments.update({ - where: { - id: { - equals: commentId, - }, - }, - data: { - body: comment, - }, - }); - } else { - db.issueComments.create({ - id: commentId, - body: comment, - issue_number: 1, - user: { - login: STRINGS.USER_1, - id: 1, - }, - }); - } + } else { + db.issueComments.create({ + id: commentId, + body: comment, + issue_number: 1, + user: { + login: STRINGS.USER_1, + id: 1, + }, + }); + } } diff --git a/tests/__mocks__/issue-template.ts b/tests/__mocks__/issue-template.ts index 4c247fa..17f8451 100644 --- a/tests/__mocks__/issue-template.ts +++ b/tests/__mocks__/issue-template.ts @@ -1,63 +1,62 @@ /** - * This is generic and not fully featured, but it is a good + * This is generic and not fully featured, but it is a good * starting point for testing your plugins. Adjust as needed. */ export default { - - author_association: "NONE", - closed_at: null, - comments: 0, - comments_url: "", - created_at: new Date().toISOString(), + author_association: "NONE", + closed_at: null, + comments: 0, + comments_url: "", + created_at: new Date().toISOString(), + events_url: "", + html_url: "https://github.com/ubiquity/test-repo/issues/1", + id: 1, + labels_url: "", + locked: false, + milestone: null, + node_id: "1", + owner: "ubiquity", + number: 1, + repository_url: "https://github.com/ubiquity/test-repo", + state: "open", + title: "issue", + updated_at: "", + url: "", + user: null, + repo: "test-repo", + labels: [ + { + name: "Price: 25 USD", + }, + { + name: "Time: <1 Hour", + }, + { + name: "Priority: 1 (Normal)", + }, + ], + body: "body", + assignee: { + login: "", + avatar_url: "", + email: "undefined", events_url: "", - html_url: "https://github.com/ubiquity/test-repo/issues/1", + followers_url: "", + following_url: "", + gists_url: "", + gravatar_id: null, + html_url: "", id: 1, - labels_url: "", - locked: false, - milestone: null, - node_id: "1", - owner: "ubiquity", - number: 1, - repository_url: "https://github.com/ubiquity/test-repo", - state: "open", - title: "issue", - updated_at: "", + name: "undefined", + node_id: "", + organizations_url: "", + received_events_url: "", + repos_url: "", + site_admin: false, + starred_at: "", + starred_url: "", + subscriptions_url: "", + type: "", url: "", - user: null, - repo: "test-repo", - labels: [ - { - name: "Price: 25 USD", - }, - { - name: "Time: <1 Hour", - }, - { - name: "Priority: 1 (Normal)", - }, - ], - body: "body", - assignee: { - login: "", - avatar_url: "", - email: "undefined", - events_url: "", - followers_url: "", - following_url: "", - gists_url: "", - gravatar_id: null, - html_url: "", - id: 1, - name: "undefined", - node_id: "", - organizations_url: "", - received_events_url: "", - repos_url: "", - site_admin: false, - starred_at: "", - starred_url: "", - subscriptions_url: "", - type: "", - url: "", - }, -}; \ No newline at end of file + }, +}; diff --git a/tests/__mocks__/strings.ts b/tests/__mocks__/strings.ts index 7d17516..fbd568b 100644 --- a/tests/__mocks__/strings.ts +++ b/tests/__mocks__/strings.ts @@ -2,16 +2,16 @@ * Strings repeated more than twice are stored here. */ export const STRINGS = { - HELLO_WORLD: "Hello, world!", - TEST_REPO: "test-repo", - SUCCESSFULLY_CREATED_COMMENT: "Successfully created comment!", - EXITING_HELLO_WORLD: "Exiting helloWorld", - ERROR_CREATING_COMMENT: "Error creating comment:", - EXECUTING_HELLO_WORLD: "Executing helloWorld:", - USER_1: "ubiquity", - USER_2: "user2", - CALLER_LOGS_ANON: "_Logs.", - INVALID_USE_OF_SLASH_COMMAND: 'Invalid use of slash command, use "/hello".', - CONFIGURABLE_RESPONSE: "Hello, Code Reviewers!", - INVALID_COMMAND: "/Goodbye", -} \ No newline at end of file + HELLO_WORLD: "Hello, world!", + TEST_REPO: "test-repo", + SUCCESSFULLY_CREATED_COMMENT: "Successfully created comment!", + EXITING_HELLO_WORLD: "Exiting helloWorld", + ERROR_CREATING_COMMENT: "Error creating comment:", + EXECUTING_HELLO_WORLD: "Executing helloWorld:", + USER_1: "ubiquity", + USER_2: "user2", + CALLER_LOGS_ANON: "_Logs.", + INVALID_USE_OF_SLASH_COMMAND: 'Invalid use of slash command, use "/hello".', + CONFIGURABLE_RESPONSE: "Hello, Code Reviewers!", + INVALID_COMMAND: "/Goodbye", +}; diff --git a/tests/__mocks__/users-get.json b/tests/__mocks__/users-get.json index 1148798..ca9acf3 100644 --- a/tests/__mocks__/users-get.json +++ b/tests/__mocks__/users-get.json @@ -9,4 +9,4 @@ "name": "user2", "login": "user1" } -] \ No newline at end of file +] diff --git a/tests/main.test.ts b/tests/main.test.ts index 1c438e2..5360347 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -45,10 +45,16 @@ describe("Plugin tests", () => { expect(context.eventName).toBe("issue_comment.created"); expect(context.payload.comment.body).toBe("/Hello"); - await runPlugin(context) + await runPlugin(context); expect(errorSpy).not.toHaveBeenCalled(); - expect(debugSpy).toHaveBeenNthCalledWith(1, STRINGS.EXECUTING_HELLO_WORLD, { caller: STRINGS.CALLER_LOGS_ANON, sender: STRINGS.USER_1, repo: STRINGS.TEST_REPO, issueNumber: 1, owner: STRINGS.USER_1 }); + expect(debugSpy).toHaveBeenNthCalledWith(1, STRINGS.EXECUTING_HELLO_WORLD, { + caller: STRINGS.CALLER_LOGS_ANON, + sender: STRINGS.USER_1, + repo: STRINGS.TEST_REPO, + issueNumber: 1, + owner: STRINGS.USER_1, + }); expect(infoSpy).toHaveBeenNthCalledWith(1, STRINGS.HELLO_WORLD); expect(okSpy).toHaveBeenNthCalledWith(1, STRINGS.SUCCESSFULLY_CREATED_COMMENT); expect(verboseSpy).toHaveBeenNthCalledWith(1, STRINGS.EXITING_HELLO_WORLD); @@ -80,22 +86,21 @@ describe("Plugin tests", () => { }); }); - /** * The heart of each test. This function creates a context object with the necessary data for the plugin to run. - * + * * So long as everything is defined correctly in the db (see `./__mocks__/helpers.ts: setupTests()`), * this function should be able to handle any event type and the conditions that come with it. - * + * * Refactor according to your needs. */ function createContext( - configurableResponse: string = "Hello, world!", // we pass the plugin configurables here + configurableResponse: string = "Hello, world!", // we pass the plugin configurable items here commentBody: string = "/Hello", repoId: number = 1, payloadSenderId: number = 1, commentId: number = 1, - issueOne: number = 1, + issueOne: number = 1 ) { const repo = db.repo.findFirst({ where: { id: { equals: repoId } } }) as unknown as Context["payload"]["repository"]; const sender = db.users.findFirst({ where: { id: { equals: payloadSenderId } } }) as unknown as Context["payload"]["sender"]; @@ -104,7 +109,7 @@ function createContext( createComment(commentBody, commentId); // create it first then pull it from the DB and feed it to _createContext const comment = db.issueComments.findFirst({ where: { id: { equals: commentId } } }) as unknown as Context["payload"]["comment"]; - const context = _createContext(repo, sender, issue1, comment, configurableResponse); + const context = createContextInner(repo, sender, issue1, comment, configurableResponse); const infoSpy = jest.spyOn(context.logger, "info"); const errorSpy = jest.spyOn(context.logger, "error"); const debugSpy = jest.spyOn(context.logger, "debug"); @@ -125,10 +130,10 @@ function createContext( /** * Creates the context object central to the plugin. - * + * * This should represent the active `SupportedEvents` payload for any given event. */ -function _createContext( +function createContextInner( repo: Context["payload"]["repository"], sender: Context["payload"]["sender"], issue: Context["payload"]["issue"], @@ -153,8 +158,7 @@ function _createContext( adapters: { supabase: {} as ReturnType["supabase"], } as ReturnType, - env: { - } as Env, + env: {} as Env, octokit: octokit, }; -} \ No newline at end of file +}