diff --git a/src/handlers/shared/check-assignments.ts b/src/handlers/shared/check-assignments.ts index e9704bb..6a2eef3 100644 --- a/src/handlers/shared/check-assignments.ts +++ b/src/handlers/shared/check-assignments.ts @@ -1,26 +1,5 @@ -import { Type } from "@sinclair/typebox"; -import { Context, IssueEvent, envSchema } from "../../types"; -import { Value } from "@sinclair/typebox/value"; - -function getAppId(context: Context): number { - const { env: { APP_ID } } = context; - const APP_ID_TYPE = Type.Union([Type.String(), Type.Number()], { default: APP_ID }); - - const val = Type.Transform(APP_ID_TYPE) - .Decode((val) => { - if (isNaN(Number(val))) { - throw new Error("Invalid APP_ID"); - } - return Number(val); - }) - .Encode(encoded => encoded.toString()) - - try { - return Value.Decode(val, APP_ID); - } catch (e) { - throw new Error("Invalid APP_ID"); - } -} +import { Context, IssueEvent } from "../../types"; +import { getAppId } from "../../utils/shared"; export async function hasUserBeenUnassigned(context: Context): Promise { const APP_ID = getAppId(context); diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index 25925e6..994a8c1 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -46,14 +46,15 @@ export async function start(context: Context, issue: Context["payload"]["issue"] // check max assigned issues const openedPullRequests = await getAvailableOpenedPullRequests(context, sender.login); - logger.info(`Opened Pull Requests with approved reviews or with no reviews but over 24 hours have passed: `, { openedPullRequests: openedPullRequests.map((p) => p.html_url) }); + logger.info(`Opened Pull Requests with approved reviews or with no reviews but over 24 hours have passed: `, { + openedPullRequests: openedPullRequests.map((p) => p.html_url), + }); const assignedIssues = await getAssignedIssues(context, sender.login); logger.info("Max issue allowed is", { maxConcurrentTasks, assignedIssues: assignedIssues.map((i) => i.html_url) }); // check for max and enforce max - if (Math.abs(assignedIssues.length - openedPullRequests.length) >= maxConcurrentTasks) { const log = logger.error("Too many assigned issues, you have reached your max limit", { assignedIssues: assignedIssues.length, diff --git a/src/utils/issue.ts b/src/utils/issue.ts index 5d1e96f..f0bfe04 100644 --- a/src/utils/issue.ts +++ b/src/utils/issue.ts @@ -1,5 +1,5 @@ import { Context } from "../types/context"; -import { GitHubIssueSearch, Issue, ISSUE_TYPE, PullRequest, Review } from "../types/payload"; +import { GitHubIssueSearch, Review } from "../types/payload"; import { getLinkedPullRequests, GetLinkedResults } from "./get-linked-prs"; export function isParentIssue(body: string) { diff --git a/src/utils/shared.ts b/src/utils/shared.ts index 4b8a415..d6f9a20 100644 --- a/src/utils/shared.ts +++ b/src/utils/shared.ts @@ -1,5 +1,7 @@ import ms from "ms"; -import { Label } from "../types"; +import { Context, Label } from "../types"; +import { Type } from "@sinclair/typebox"; +import { Value } from "@sinclair/typebox/value"; export function calculateDurations(labels: Label[]): number[] { // from shortest to longest @@ -17,3 +19,25 @@ export function calculateDurations(labels: Label[]): number[] { return durations.sort((a, b) => a - b); } + +export function getAppId(context: Context): number { + const { + env: { APP_ID }, + } = context; + const APP_ID_TYPE = Type.Union([Type.String(), Type.Number()], { default: APP_ID }); + + const val = Type.Transform(APP_ID_TYPE) + .Decode((val) => { + if (isNaN(Number(val))) { + throw new Error("Invalid APP_ID"); + } + return Number(val); + }) + .Encode((encoded) => encoded.toString()); + + try { + return Value.Decode(val, APP_ID); + } catch (e) { + throw new Error("Invalid APP_ID"); + } +} diff --git a/tests/__mocks__/db.ts b/tests/__mocks__/db.ts index 2033156..2b3681d 100644 --- a/tests/__mocks__/db.ts +++ b/tests/__mocks__/db.ts @@ -77,6 +77,7 @@ export const db = factory({ body: nullable(String), repo: String, owner: String, + pull_request: Object, author: nullable({ avatar_url: String, email: nullable(String), diff --git a/tests/__mocks__/handlers.ts b/tests/__mocks__/handlers.ts index baf8f19..c8d695e 100644 --- a/tests/__mocks__/handlers.ts +++ b/tests/__mocks__/handlers.ts @@ -47,14 +47,6 @@ export const handlers = [ http.get("https://api.github.com/repos/:owner/:repo/pulls", ({ params: { owner, repo } }: { params: { owner: string; repo: string } }) => HttpResponse.json(db.pull.findMany({ where: { owner: { equals: owner }, repo: { equals: repo } } })) ), - // list reviews for a pull request - http.get("https://api.github.com/repos/:owner/:repo/pulls/:pull_number/reviews", ({ params: { owner, repo, pull_number: pullNumber } }) => - HttpResponse.json( - db.review.findMany({ - where: { owner: { equals: owner as string }, repo: { equals: repo as string }, pull_number: { equals: Number(pullNumber) } }, - }) - ) - ), // list events for an issue timeline http.get("https://api.github.com/repos/:owner/:repo/issues/:issue_number/timeline", ({ params: { owner, repo, issue_number: issueNumber } }) => { return HttpResponse.json( @@ -102,15 +94,19 @@ export const handlers = [ // get commit hash http.get("https://api.github.com/repos/:owner/:repo/commits", () => HttpResponse.json({ sha: "commitHash" })), // list all pull request reviews - http.get("https://api.github.com/repos/:owner/:repo/pulls/:pull_number/reviews", ({ params: { owner, repo, pull_number: pullNumber } }) => - HttpResponse.json( - db.review.findMany({ - where: { owner: { equals: owner as string }, repo: { equals: repo as string }, pull_number: { equals: Number(pullNumber) } }, - }) - ) - ), + http.get("https://api.github.com/repos/:owner/:repo/pulls/:pull_number/reviews", () => HttpResponse.json(db.review.getAll())), // remove assignee from an issue http.delete("https://api.github.com/repos/:owner/:repo/issues/:issue_number/assignees", ({ params: { owner, repo, issue_number: issueNumber } }) => HttpResponse.json({ owner, repo, issueNumber }) ), + http.get("https://api.github.com/search/issues", ({ request }) => { + const params = new URL(request.url).searchParams; + const query = params.get("q"); + const hasAssignee = query?.includes("assignee"); + if (hasAssignee) { + return HttpResponse.json(db.issue.getAll()); + } else { + return HttpResponse.json(db.pull.getAll()); + } + }), ]; diff --git a/tests/main.test.ts b/tests/main.test.ts index 9ad4e44..5dc468d 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -29,6 +29,8 @@ afterAll(() => server.close()); describe("User start/stop", () => { beforeEach(async () => { + jest.clearAllMocks(); + jest.resetModules(); await setupTests(); }); @@ -147,54 +149,26 @@ describe("User start/stop", () => { }); test("User can't start an issue that's a parent issue", async () => { - const issue = db.issue.findFirst({ where: { id: { equals: 1 } } }) as unknown as Issue; + const issue = db.issue.findFirst({ where: { id: { equals: 5 } } }) as unknown as Issue; const sender = db.users.findFirst({ where: { id: { equals: 1 } } }) as unknown as PayloadSender; const context = createContext(issue, sender, "/start"); context.adapters = createAdapters(getSupabase(), context); - await userStartStop(context); + await expect(userStartStop(context)).rejects.toThrow("Issue is a parent issue"); }); test("User can't start another issue if they have reached the max limit", async () => { - jest.mock("../src/utils/issue", () => ({ - getAvailableOpenedPullRequests: jest.fn().mockResolvedValue([ - { - number: 1, - reviews: [ - { - state: "APPROVED", - }, - ], - }, - { - number: 2, - reviews: [ - { - state: "APPROVED", - }, - ], - }, - { - number: 3, - reviews: [ - { - state: "APPROVED", - }, - ], - }, - ]), - })); - const issue = db.issue.findFirst({ where: { id: { equals: 1 } } }) as unknown as Issue; - const sender = db.users.findFirst({ where: { id: { equals: 1 } } }) as unknown as PayloadSender; + const sender = db.users.findFirst({ where: { id: { equals: 2 } } }) as unknown as PayloadSender; const context = createContext(issue, sender); + context.config.miscellaneous.maxConcurrentTasks = 2; context.adapters = createAdapters(getSupabase(), context); - await userStartStop(context); + await expect(userStartStop(context)).rejects.toThrow("Too many assigned issues, you have reached your max limit of 2 issues."); }); test("User can't start an issue if they have previously been unassigned by an admin", async () => { @@ -315,7 +289,7 @@ async function setupTests() { db.pull.create({ id: 1, - html_url: "", + html_url: "https://github.com/ubiquity/test-repo/pull/1", number: 1, author: { id: 2, @@ -334,7 +308,7 @@ async function setupTests() { db.pull.create({ id: 2, - html_url: "", + html_url: "https://github.com/ubiquity/test-repo/pull/2", number: 2, author: { id: 2, @@ -353,7 +327,7 @@ async function setupTests() { db.pull.create({ id: 3, - html_url: "", + html_url: "https://github.com/ubiquity/test-repo/pull/3", number: 3, author: { id: 1, @@ -365,7 +339,6 @@ async function setupTests() { }, body: "Pull request body", owner: "ubiquity", - repo: "test-repo", state: "open", closed_at: null, @@ -536,7 +509,7 @@ async function setupTests() { }); } -function createContext(issue: Record, sender: Record, body = "/start", appId: string | null = "1"): Context { +function createContext(issue: Record, sender: Record, body = "/start", appId: string | number | null = "1"): Context { return { adapters: {} as ReturnType, payload: { @@ -555,7 +528,7 @@ function createContext(issue: Record, sender: Record