From f5bc1d5adb3ee811f0b984750f938e8e1608ccbd Mon Sep 17 00:00:00 2001 From: Keyrxng <106303466+Keyrxng@users.noreply.github.com> Date: Tue, 27 Aug 2024 20:00:09 +0100 Subject: [PATCH] fix: add env typebox validator to worker, fix tests --- src/handlers/shared/check-assignments.ts | 4 +++- src/handlers/shared/start.ts | 2 -- src/types/env.ts | 26 ++++++++++++++++----- src/utils/shared.ts | 6 ++--- src/worker.ts | 13 ++++++++++- tests/main.test.ts | 29 +++++++++++++++++------- 6 files changed, 58 insertions(+), 22 deletions(-) diff --git a/src/handlers/shared/check-assignments.ts b/src/handlers/shared/check-assignments.ts index 002feda..c969231 100644 --- a/src/handlers/shared/check-assignments.ts +++ b/src/handlers/shared/check-assignments.ts @@ -20,7 +20,9 @@ async function getUserStopComments(context: Context, username: string): Promise< } export async function hasUserBeenUnassigned(context: Context, username: string): Promise { - const { env: { APP_ID } } = context + const { + env: { APP_ID }, + } = context; const events = await getAssignmentEvents(context); const userAssignments = events.filter((event) => event.assignee === username); diff --git a/src/handlers/shared/start.ts b/src/handlers/shared/start.ts index d301fd5..13537b4 100644 --- a/src/handlers/shared/start.ts +++ b/src/handlers/shared/start.ts @@ -93,8 +93,6 @@ export async function start(context: Context, issue: Context["payload"]["issue"] return userId; }); - - const assignmentComment = await generateAssignmentComment(context, issue.created_at, issue.number, sender.id, duration); const logMessage = logger.info("Task assigned successfully", { taskDeadline: assignmentComment.deadline, diff --git a/src/types/env.ts b/src/types/env.ts index 60e0626..3c3722a 100644 --- a/src/types/env.ts +++ b/src/types/env.ts @@ -1,17 +1,31 @@ import { Type as T } from "@sinclair/typebox"; import { StaticDecode } from "@sinclair/typebox"; +import { StandardValidator } from "typebox-validators"; +const ERROR_MSG = "Invalid APP_ID"; export const envSchema = T.Object({ SUPABASE_URL: T.String(), SUPABASE_KEY: T.String(), - APP_ID: T.Transform(T.Union([T.String(), T.Number()])) - .Decode((val) => { - if (isNaN(Number(val))) { - throw new Error("Invalid APP_ID"); + APP_ID: T.Transform(T.Union([T.String(), T.Number()], { examples: 123456 })) + .Decode((value) => { + if (typeof value === "string" && !isNaN(Number(value))) { + return Number(value); } - return Number(val); + if (typeof value === "number") { + return value; + } + throw new Error(ERROR_MSG); }) - .Encode((encoded) => encoded.toString()) + .Encode((value) => { + if (typeof value === "number") { + return value.toString(); + } + if (typeof value === "string") { + return value; + } + throw new Error(ERROR_MSG); + }), }); export type Env = StaticDecode; +export const envConfigValidator = new StandardValidator(envSchema); diff --git a/src/utils/shared.ts b/src/utils/shared.ts index e33459f..4b8a415 100644 --- a/src/utils/shared.ts +++ b/src/utils/shared.ts @@ -1,7 +1,5 @@ import ms from "ms"; -import { Context, Label } from "../types"; -import { Type } from "@sinclair/typebox"; -import { Value } from "@sinclair/typebox/value"; +import { Label } from "../types"; export function calculateDurations(labels: Label[]): number[] { // from shortest to longest @@ -18,4 +16,4 @@ export function calculateDurations(labels: Label[]): number[] { }); return durations.sort((a, b) => a - b); -} \ No newline at end of file +} diff --git a/src/worker.ts b/src/worker.ts index b572ebc..215ef79 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -1,6 +1,6 @@ import { Value } from "@sinclair/typebox/value"; import { startStopTask } from "./plugin"; -import { Env, startStopSchema, startStopSettingsValidator } from "./types"; +import { Env, envConfigValidator, startStopSchema, startStopSettingsValidator } from "./types"; import manifest from "../manifest.json"; export default { @@ -36,6 +36,17 @@ export default { throw new Error("Invalid settings provided"); } + if (!envConfigValidator.test(env)) { + const errorDetails: string[] = []; + for (const error of envConfigValidator.errors(env)) { + errorDetails.push(`${error.path}: ${error.message}`); + } + return new Response(JSON.stringify({ error: `Bad Request: the environment is invalid. ${errorDetails.join("; ")}` }), { + status: 400, + headers: { "content-type": "application/json" }, + }); + } + webhookPayload.settings = settings; await startStopTask(webhookPayload, env); return new Response(JSON.stringify("OK"), { status: 200, headers: { "content-type": "application/json" } }); diff --git a/tests/main.test.ts b/tests/main.test.ts index 0cde968..38ee127 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -1,5 +1,5 @@ import { drop } from "@mswjs/data"; -import { Context, Sender, SupportedEventsU } from "../src/types"; +import { Context, envConfigValidator, Sender, SupportedEventsU } from "../src/types"; import { db } from "./__mocks__/db"; import { server } from "./__mocks__/node"; import usersGet from "./__mocks__/users-get.json"; @@ -198,12 +198,18 @@ describe("User start/stop", () => { 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 context = createContext(issue, sender, "/start", null); + const context = createContext(issue, sender, "/start", undefined); - context.env.APP_ID = null as unknown as string; - context.adapters = createAdapters(getSupabase(), context); + const env = { ...context.env }; + Reflect.deleteProperty(env, "APP_ID"); + if (!envConfigValidator.test(env)) { + const errorDetails: string[] = []; + for (const error of envConfigValidator.errors(env)) { + errorDetails.push(`${error.path}: ${error.message}`); + } - await expect(userStartStop(context)).rejects.toThrow("Invalid APP_ID"); + expect(errorDetails).toContain("/APP_ID: Required property"); + } }); test("Should throw if APP_ID is not a number", async () => { @@ -211,9 +217,16 @@ describe("User start/stop", () => { const sender = db.users.findFirst({ where: { id: { equals: 1 } } }) as unknown as PayloadSender; const context = createContext(issue, sender, "/start", "testing-one"); - context.adapters = createAdapters(getSupabase(), context); + const env = { ...context.env }; + + if (!envConfigValidator.test(env)) { + const errorDetails: string[] = []; + for (const error of envConfigValidator.errors(env)) { + errorDetails.push(`${error.path}: ${error.message}`); + } - await expect(userStartStop(context)).rejects.toThrow("Invalid APP_ID"); + expect(errorDetails).toContain("Invalid APP_ID"); + } }); }); @@ -559,7 +572,7 @@ function createContext( env: { SUPABASE_KEY: "key", SUPABASE_URL: "url", - APP_ID: appId as string, + APP_ID: appId as unknown as number, }, }; }