diff --git a/open-api/openapi.json b/open-api/openapi.json index a6a8811..07b8763 100644 --- a/open-api/openapi.json +++ b/open-api/openapi.json @@ -1089,7 +1089,7 @@ "examples": [ { "id": 1, - "name": "Normal", + "name": "Standard", "path": "normal", "associatedActivityId": null, "isChallengeMode": false diff --git a/src/RaidHubRoute.ts b/src/RaidHubRoute.ts index f9316c1..1de9c0b 100644 --- a/src/RaidHubRoute.ts +++ b/src/RaidHubRoute.ts @@ -220,7 +220,15 @@ export class RaidHubRoute< private controller: RequestHandler, any, z.infer, z.infer> = async (req, res, next) => { try { - const result = await this.handler(req) + const result = await this.handler(req, callback => { + res.on("finish", () => { + try { + callback() + } catch (err) { + process.env.NODE_ENV !== "test" && console.error(err) + } + }) + }) const response = this.buildResponse(result) if (result.success) { res.status(this.successCode).json(response) @@ -378,12 +386,18 @@ export class RaidHubRoute< body?: unknown headers?: IncomingHttpHeaders }) { - const res = await this.handler({ - params: this.paramsSchema?.parse(req.params) ?? {}, - query: this.querySchema?.parse(req.query) ?? {}, - body: this.bodySchema?.parse(req.body) ?? {}, - headers: req.headers ?? {} - }).then(this.buildResponse) + let after: () => Promise = () => Promise.resolve() + const res = await this.handler( + { + params: this.paramsSchema?.parse(req.params) ?? {}, + query: this.querySchema?.parse(req.query) ?? {}, + body: this.bodySchema?.parse(req.body) ?? {}, + headers: req.headers ?? {} + }, + afterCallback => (after = afterCallback) + ).then(this.buildResponse) + + await after() // We essentially can use this type to narrow down the type of res in our unit tests // This will guarantee that we are testing the correct type of response and that @@ -391,7 +405,7 @@ export class RaidHubRoute< if (res.success) { return { type: "ok", - parsed: this.responseSchema.parse(res.response) + parsed: this.responseSchema.parse(res.response) as z.infer } as const } else { const schema = diff --git a/src/RaidHubRouterTypes.ts b/src/RaidHubRouterTypes.ts index e61ac04..b763379 100644 --- a/src/RaidHubRouterTypes.ts +++ b/src/RaidHubRouterTypes.ts @@ -27,12 +27,15 @@ export type RaidHubHandler< Body extends ZodType, T, ErrorResponse extends ErrorData -> = (req: { - params: z.infer - query: z.infer - body: z.infer - headers: IncomingHttpHeaders -}) => Promise> +> = ( + req: { + params: z.infer + query: z.infer + body: z.infer + headers: IncomingHttpHeaders + }, + after: (callback: () => Promise) => void +) => Promise> export type RaidHubHandlerReturn = | { success: true; response: T } diff --git a/src/middlewares/async.test.ts b/src/middlewares/async.test.ts index a780cc1..2779f87 100644 --- a/src/middlewares/async.test.ts +++ b/src/middlewares/async.test.ts @@ -1,9 +1,7 @@ import { beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test" import express from "express" import request from "supertest" -import { clanQueue } from "../services/rabbitmq/queues/clan" import { playersQueue } from "../services/rabbitmq/queues/player" -import { processClanAsync } from "./processClanAsync" import { processPlayerAsync } from "./processPlayerAsync" const app = express() @@ -43,38 +41,3 @@ describe("player", () => { expect(spyPlayersQueueSend).toHaveBeenCalledWith({ membershipId: 4611686018488107374n }) }) }) - -describe("clan", () => { - beforeAll(() => { - app.use("/clan/200/:groupId", processClanAsync, (req, res) => { - req.params.groupId = BigInt(req.params.groupId) - res.sendStatus(200) - }) - - app.use("/clan/404/:groupId", processClanAsync, (req, res) => { - req.params.groupId = BigInt(req.params.groupId) - res.sendStatus(404) - }) - }) - - const spyClanQueueSend = spyOn(clanQueue, "send") - beforeEach(() => { - spyClanQueueSend.mockReset() - spyClanQueueSend.mockResolvedValueOnce(true) - }) - - test("sends on 200", async () => { - const res = await request(app).get("/clan/200/3148408") - expect(res.status).toBe(200) - - expect(spyClanQueueSend).toHaveBeenCalledTimes(1) - expect(spyClanQueueSend).toHaveBeenCalledWith({ groupId: 3148408n }) - }) - - test("does not send on 404", async () => { - const res = await request(app).get("/clan/404/3148408") - expect(res.status).toBe(404) - - expect(spyClanQueueSend).not.toHaveBeenCalled() - }) -}) diff --git a/src/middlewares/processClanAsync.ts b/src/middlewares/processClanAsync.ts deleted file mode 100644 index 1f99e22..0000000 --- a/src/middlewares/processClanAsync.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RequestHandler } from "express" -import { clanQueue } from "../services/rabbitmq/queues/clan" - -export const processClanAsync: RequestHandler<{ groupId: bigint }> = async (req, res, next) => { - res.on("finish", () => { - if (res.statusCode == 200) { - clanQueue.send({ groupId: req.params.groupId }) - } - }) - next() -} diff --git a/src/routes/clan.test.ts b/src/routes/clanStats.test.ts similarity index 67% rename from src/routes/clan.test.ts rename to src/routes/clanStats.test.ts index 0f2d134..26ae329 100644 --- a/src/routes/clan.test.ts +++ b/src/routes/clanStats.test.ts @@ -1,15 +1,32 @@ -import { describe, expect, spyOn, test } from "bun:test" +import { beforeEach, describe, expect, spyOn, test } from "bun:test" import { PlatformErrorCodes } from "bungie-net-core/enums" import { ErrorCode } from "../schema/errors/ErrorCode" import { bungiePlatformHttp } from "../services/bungie/client" import { BungieApiError } from "../services/bungie/error" +import { clanQueue } from "../services/rabbitmq/queues/clan" +import { playersQueue } from "../services/rabbitmq/queues/player" import { expectErr, expectOk } from "../util.test" -import { clanStatsRoute } from "./clan" +import { clanStatsRoute } from "./clanStats" describe("clan 200", () => { + const spyClanQueueSend = spyOn(clanQueue, "send") + const spyPlayersQueueSend = spyOn(playersQueue, "send") + + beforeEach(() => { + spyClanQueueSend.mockReset() + spyClanQueueSend.mockResolvedValueOnce(true) + spyPlayersQueueSend.mockReset() + spyPlayersQueueSend.mockResolvedValue(true) + }) + const t = async (groupId: string) => { const result = await clanStatsRoute.$mock({ params: { groupId } }) expectOk(result) + expect(spyClanQueueSend).toHaveBeenCalledTimes(1) + if (result.type === "ok") { + console.log("test", result.parsed.members.length) + expect(spyPlayersQueueSend).toHaveBeenCalledTimes(result.parsed.members.length) + } } test("Elysium", () => t("3148408")) diff --git a/src/routes/clan.ts b/src/routes/clanStats.ts similarity index 83% rename from src/routes/clan.ts rename to src/routes/clanStats.ts index 86a9ffb..5daf339 100644 --- a/src/routes/clan.ts +++ b/src/routes/clanStats.ts @@ -4,13 +4,14 @@ import { z } from "zod" import { RaidHubRoute } from "../RaidHubRoute" import { getClanStats } from "../data/clan" import { cacheControl } from "../middlewares/cache-control" -import { processClanAsync } from "../middlewares/processClanAsync" import { zClanStats } from "../schema/components/Clan" import { ErrorCode } from "../schema/errors/ErrorCode" import { zBigIntString } from "../schema/util" import { BungieApiError } from "../services/bungie/error" import { getClan } from "../services/bungie/getClan" import { getClanMembers } from "../services/bungie/getClanMembers" +import { clanQueue } from "../services/rabbitmq/queues/clan" +import { playersQueue } from "../services/rabbitmq/queues/player" export const clanStatsRoute = new RaidHubRoute({ method: "get", @@ -18,7 +19,7 @@ export const clanStatsRoute = new RaidHubRoute({ params: z.object({ groupId: zBigIntString() }), - middleware: [cacheControl(30), processClanAsync], + middleware: [cacheControl(30)], response: { success: { statusCode: 200, @@ -42,7 +43,7 @@ export const clanStatsRoute = new RaidHubRoute({ } ] as const }, - async handler({ params }) { + async handler({ params }, after) { const groupId = params.groupId try { @@ -65,6 +66,17 @@ export const clanStatsRoute = new RaidHubRoute({ const stats = await getClanStats(members.map(m => m.destinyUserInfo.membershipId)) + after(async () => { + await clanQueue.send({ groupId }) + await Promise.all( + members.map(member => + playersQueue.send({ + membershipId: BigInt(member.destinyUserInfo.membershipId) + }) + ) + ) + }) + return RaidHubRoute.ok(stats) } }) diff --git a/src/routes/index.ts b/src/routes/index.ts index a87d09e..bf55458 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -3,7 +3,7 @@ import { activityRoute } from "./activity" import { adminRouter } from "./admin" import { adminAuthorizationRoute } from "./authorize/admin" import { userAuthorizationRoute } from "./authorize/user" -import { clanStatsRoute } from "./clan" +import { clanStatsRoute } from "./clanStats" import { leaderboardRouter } from "./leaderboard" import { manifestRoute } from "./manifest" import { pgcrRoute } from "./pgcr" diff --git a/src/schema/components/VersionDefinition.ts b/src/schema/components/VersionDefinition.ts index c30b842..f5aef07 100644 --- a/src/schema/components/VersionDefinition.ts +++ b/src/schema/components/VersionDefinition.ts @@ -19,7 +19,7 @@ export const zVersionDefinition = registry.register( examples: [ { id: 1, - name: "Normal", + name: "Standard", path: "normal", associatedActivityId: null, isChallengeMode: false