diff --git a/src/scripts/modules/ajax-handlers/golem.ts b/src/scripts/modules/ajax-handlers/golem.ts index e7d86054..b14eb7e1 100644 --- a/src/scripts/modules/ajax-handlers/golem.ts +++ b/src/scripts/modules/ajax-handlers/golem.ts @@ -1,9 +1,7 @@ import {LoggerService} from "@scripts/util/logger"; import {AjaxSuccessHandler} from "./ajaxSuccessHandler"; -import {GolemPayload, GolemResponse} from "./golem.types"; +import {GolemPayload, GolemResponse, golemResponseSchema} from "./golem.types"; import {HgResponse, JournalMarkup} from "@scripts/types/hg"; -import {EventDates} from "@scripts/util/constants"; -import {hasEventEnded} from "@scripts/util/time"; const rarities = ["area", "hat", "scarf"] as const; @@ -17,25 +15,20 @@ export class GWHGolemAjaxHandler extends AjaxSuccessHandler { } public match(url: string): boolean { + // Triggers on Golem claim, dispatch, upgrade, and on "Decorate" click (+others, perhaps). if (!url.includes("mousehuntgame.com/managers/ajax/events/winter_hunt_region.php")) { return false; } - if (hasEventEnded(EventDates.GreatWinterHuntEndDate)) { - return false; - } - return true; } public async execute(responseJSON: HgResponse): Promise { - // Triggers on Golem claim, dispatch, upgrade, and on "Decorate" click (+others, perhaps). - if (!(responseJSON && typeof responseJSON === 'object' && 'golem_rewards' in responseJSON)) { - this.logger.debug("Skipped GWH golem submission since there are no golem rewards.", responseJSON); + if (!(this.isGolemRewardResponse(responseJSON))) { return; } - const golemData: GolemResponse = responseJSON.golem_rewards as GolemResponse; + const golemData = responseJSON.golem_rewards; const uid = responseJSON.user.sn_user_id.toString(); if (!uid) { this.logger.warn("Skipped GWH golem submission due to missing user attribution.", responseJSON); @@ -131,4 +124,20 @@ export class GWHGolemAjaxHandler extends AjaxSuccessHandler { return null; } + + /** + * Validates that the given object is a JSON response from retrieving golem rewards + * @param responseJSON + * @returns + */ + private isGolemRewardResponse(responseJSON: unknown): responseJSON is GolemResponse { + const response = golemResponseSchema.safeParse(responseJSON); + + if (!response.success) { + const errorMessage = response.error.message; + this.logger.debug("Skipped GWH golem submission since there are no golem rewards.", errorMessage); + } + + return response.success; + } } diff --git a/src/scripts/modules/ajax-handlers/golem.types.ts b/src/scripts/modules/ajax-handlers/golem.types.ts index a22d52f6..40fdfce9 100644 --- a/src/scripts/modules/ajax-handlers/golem.types.ts +++ b/src/scripts/modules/ajax-handlers/golem.types.ts @@ -1,9 +1,39 @@ +import z from 'zod'; +import {hgResponseSchema} from '@scripts/types/hg'; +import {zodRecordWithEnum} from '@scripts/util/zod'; + +/** + * An item brought back back a golem + */ +const golemItemSchema = z.object({ + name: z.string(), + quantity: z.coerce.number(), +}); + +type GolemItem = z.infer; + +const raritySchema = z.enum(["area", "hat", "scarf"]); +export type Rarity = z.infer; + /** * Golem Response from HG, previewed by CBS */ +const golemRewardsSchema = z.object({ + items: zodRecordWithEnum(raritySchema, z.array(golemItemSchema)), +}); + +export type GolemRewards = z.infer; + +export const golemResponseSchema = hgResponseSchema.extend({ + golem_rewards: golemRewardsSchema, +}); + +export type GolemResponse = z.infer; + +/* export interface GolemResponse { items: Record - //bonus_items: []; + // bonus_items: []; // num_upgrade_items: number; // num_gilded_charms: number; // num_hailstones: number; @@ -21,16 +51,7 @@ export interface GolemResponse { // level: number; // }; } - -/** - * An item brought back back a golem - */ -interface GolemItem { - name: string; - quantity: number; -} - -export type Rarity = "area" | "hat" | "scarf"; +*/ /** * The data that will be recorded externally diff --git a/src/scripts/util/constants.ts b/src/scripts/util/constants.ts index 3d692f9c..5e29a7c9 100644 --- a/src/scripts/util/constants.ts +++ b/src/scripts/util/constants.ts @@ -33,7 +33,4 @@ export class CustomConvertibleIds { export class EventDates { // KGA public static readonly KingsGiveawayEndDate: Date = new Date("2024-07-09T15:00:00Z"); - - // GWH - public static readonly GreatWinterHuntEndDate: Date = new Date("2024-01-16T16:00:00Z"); } diff --git a/tests/scripts/modules/ajax-handlers/golem.spec.ts b/tests/scripts/modules/ajax-handlers/golem.spec.ts index 973855af..5a26430e 100644 --- a/tests/scripts/modules/ajax-handlers/golem.spec.ts +++ b/tests/scripts/modules/ajax-handlers/golem.spec.ts @@ -1,9 +1,10 @@ import {GWHGolemAjaxHandler} from '@scripts/modules/ajax-handlers/golem'; -import type {GolemPayload} from '@scripts/modules/ajax-handlers/golem.types'; +import type {GolemPayload, GolemResponse} from '@scripts/modules/ajax-handlers/golem.types'; import {HgResponse} from '@scripts/types/hg'; +import {ConsoleLogger} from '@scripts/util/logger'; +import {HgResponseBuilder} from '@tests/utility/builders'; jest.mock('@scripts/util/logger'); -import {ConsoleLogger} from '@scripts/util/logger'; const logger = new ConsoleLogger(); const showFlashMessage = jest.fn(); @@ -21,15 +22,7 @@ describe('GWHGolemAjaxHandler', () => { expect(handler.match('mousehuntgame.com/managers/ajax/events/kings_giveaway.php')).toBe(false); }); - it('is false when GWH is done', () => { - // return the day after our filter - Date.now = jest.fn(() => new Date('2024-01-22T05:00:00Z').getTime()); - - expect(handler.match(gwhURL)).toBe(false); - }); - - it('is true on match during event', () => { - Date.now = jest.fn(() => new Date('2023-12-07T05:00:00Z').getTime()); + it('is true when url is gwh', () => { expect(handler.match(gwhURL)).toBe(true); }); }); @@ -40,19 +33,27 @@ describe('GWHGolemAjaxHandler', () => { handler.execute({} as unknown as HgResponse); - expect(logger.debug).toHaveBeenCalledWith('Skipped GWH golem submission since there are no golem rewards.', {}); + expect(logger.debug).toHaveBeenCalledWith('Skipped GWH golem submission since there are no golem rewards.', expect.anything()); expect(handler.submitGolems).not.toHaveBeenCalled(); }); it('calls submitGolems with expected data', () => { + + const builder = new HgResponseBuilder() + .withJournalMarkup(testResponses.prologuePondResponse.journal_markup); + + const response: GolemResponse = { + ...builder.build(), + golem_rewards: testResponses.prologuePondResponse.golem_rewards, + }; Date.now = jest.fn(() => 12345); handler.submitGolems = jest.fn(); - handler.execute(testResponses.prologuePondResponse); + handler.execute(response); const expectedPayload: GolemPayload[] = [ { - uid: '987654321', + uid: '2', location: 'Prologue Pond', timestamp: 12345, loot: [ @@ -87,10 +88,6 @@ describe('GWHGolemAjaxHandler', () => { const testResponses = { // responses are the minimum that are required for the test to pass prologuePondResponse: { - user: { - user_id: 'not this', - sn_user_id: 987654321, - }, golem_rewards: { items: { area: [ @@ -119,9 +116,15 @@ const testResponses = { journal_markup: [ { render_data: { + entry_id: 1, + mouse_type: false, + css_class: '', + entry_date: '1:23 pm', + environment: 'Town of Gnawnia', + entry_timestamp: 1234567890, text: 'My golem returned from the Prologue Pond with 1', }, }, ], - } as unknown as HgResponse, + }, };