Skip to content

Commit a8c7bcc

Browse files
committed
refactor: reduce type assertions and add test fixtures
- Remove 36 of 37 'as unknown as' type assertions across test files - Simplify JSON fixture casts to direct type assertions - Use proper BotEvent/EventType constants instead of string literal casts - Replace global.fetch mock pattern with jest.mocked() - Create test/fixtures.ts with reusable test utilities: - Quick event builders (quickMintEvent, quickBurnEvent, etc.) - Mock factories (createDiscordMock, createTwitterMock, createOpenSeaMock) - Environment setup helpers (setupDiscordEnv, setupTwitterEnv) - Twitter mock helpers (getTweetCalls, getTweetTexts) - Simplify platform test files using new fixtures: - discord.test.ts: 178 → 75 lines (~58% reduction) - platform-event-selection.test.ts: 197 → 84 lines (~57% reduction) - twitter.test.ts and twitter-text.test.ts updated to use fixtures - Update helpers.ts to document relationship with fixtures.ts
1 parent 6603984 commit a8c7bcc

18 files changed

+794
-729
lines changed

src/platforms/discord/discord.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,11 @@ export const channelsWithEvents = (): ChannelEvents => {
4040
for (const channel of DISCORD_EVENTS.split("&")) {
4141
const channelWithEvents = channel.split("=");
4242
const channelId = channelWithEvents[0];
43-
const eventTypes = channelWithEvents[1].split(",");
44-
list.push([channelId, eventTypes as unknown as (EventType | BotEvent)[]]);
43+
const eventTypes = channelWithEvents[1].split(",") as (
44+
| EventType
45+
| BotEvent
46+
)[];
47+
list.push([channelId, eventTypes]);
4548
}
4649

4750
return list;
@@ -176,9 +179,7 @@ const processIndividualMessages = async (
176179
);
177180
}
178181

179-
const messages = await messagesForEvents(
180-
processableEvents as AggregatorEvent[]
181-
);
182+
const messages = await messagesForEvents(processableEvents);
182183

183184
for (const [index, message] of messages.entries()) {
184185
const event = processableEvents[index];

src/platforms/discord/utils.ts

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ import {
55
} from "discord.js";
66
import { format } from "timeago.js";
77
import { EventType, getCollectionSlug, opensea, username } from "../../opensea";
8-
import { BotEvent, type OpenSeaAssetEvent } from "../../types";
8+
import {
9+
BotEvent,
10+
type OpenSeaAssetEvent,
11+
type OpenSeaEventType,
12+
type OpenSeaOrderType,
13+
} from "../../types";
914
import type { AggregatorEvent } from "../../utils/aggregator";
1015
import { MS_PER_SECOND } from "../../utils/constants";
1116
import {
@@ -42,8 +47,10 @@ export const escapeMarkdown = (text: string): string =>
4247

4348
export type Field = { name: string; value: string; inline?: true };
4449

45-
const colorFor = (eventType: EventType | BotEvent, orderType: string) =>
46-
colorForEvent(eventType, orderType);
50+
const colorFor = (
51+
eventType: EventType | BotEvent,
52+
orderType: OpenSeaOrderType | undefined
53+
) => colorForEvent(eventType, orderType);
4754

4855
// ---- Order/Sale/Transfer embed builders ----
4956

@@ -52,7 +59,7 @@ export const buildOrderEmbed = async (
5259
): Promise<{ title: string; fields: Field[] }> => {
5360
const { payment, order_type, expiration_date, maker, criteria } = event as {
5461
payment: { quantity: string; decimals: number; symbol: string };
55-
order_type: string;
62+
order_type: OpenSeaOrderType | string;
5663
expiration_date: number;
5764
maker: string;
5865
criteria: {
@@ -70,7 +77,7 @@ export const buildOrderEmbed = async (
7077
const inTime = expiration_date
7178
? format(new Date(expiration_date * MS_PER_SECOND))
7279
: "Unknown";
73-
if (order_type === "trait_offer") {
80+
if (order_type === ("trait_offer" satisfies OpenSeaOrderType)) {
7481
// Get trait info from criteria - can be in trait or traits array
7582
const traitInfo = criteria?.trait ?? criteria?.traits?.[0];
7683
const traitType = traitInfo?.type ?? "Unknown";
@@ -80,15 +87,15 @@ export const buildOrderEmbed = async (
8087
fields.push({ name: "Price", value: price });
8188
fields.push({ name: "Expires", value: inTime });
8289
} else if (
83-
order_type === "item_offer" ||
90+
order_type === ("item_offer" satisfies OpenSeaOrderType) ||
8491
order_type === "offer" ||
8592
order_type === "criteria_offer"
8693
) {
8794
title += "Item offer:";
8895
const price = formatAmount(quantity, decimals, symbol);
8996
fields.push({ name: "Price", value: price });
9097
fields.push({ name: "Expires", value: inTime });
91-
} else if (order_type === "collection_offer") {
98+
} else if (order_type === ("collection_offer" satisfies OpenSeaOrderType)) {
9299
title += "Collection offer";
93100
const price = formatAmount(quantity, decimals, symbol);
94101
fields.push({ name: "Price", value: price });
@@ -132,15 +139,12 @@ export const buildTransferEmbed = async (
132139
const fields: Field[] = [];
133140
if (kind === "mint") {
134141
// Include editions for ERC1155 mints if quantity > 1
135-
const quantity = (event as unknown as { quantity?: number })?.quantity;
142+
const openSeaEvent = event as OpenSeaAssetEvent;
143+
const quantity = openSeaEvent.quantity;
136144
const tokenStandard =
137-
(
138-
event as unknown as {
139-
nft?: { token_standard?: string };
140-
asset?: { token_standard?: string };
141-
}
142-
)?.nft?.token_standard ??
143-
(event as unknown as { asset?: { token_standard?: string } })?.asset
145+
(openSeaEvent.nft as { token_standard?: string } | undefined)
146+
?.token_standard ??
147+
(openSeaEvent.asset as { token_standard?: string } | undefined)
144148
?.token_standard;
145149

146150
const toName = escapeMarkdown(await username(to_address));
@@ -168,23 +172,26 @@ export const buildTransferEmbed = async (
168172

169173
// ---- Event type classification helpers ----
170174

171-
export const isOrderLikeType = (t: unknown, orderType?: string): boolean => {
175+
export const isOrderLikeType = (
176+
t: unknown,
177+
orderType?: OpenSeaOrderType | string
178+
): boolean => {
172179
const s = String(t);
173180
// Check order_type for "order" events
174181
if (s === "order" && orderType) {
175182
return (
176-
orderType === "listing" ||
177-
orderType === "item_offer" ||
178-
orderType === "trait_offer" ||
179-
orderType === "collection_offer"
183+
orderType === ("listing" satisfies OpenSeaOrderType) ||
184+
orderType === ("item_offer" satisfies OpenSeaOrderType) ||
185+
orderType === ("trait_offer" satisfies OpenSeaOrderType) ||
186+
orderType === ("collection_offer" satisfies OpenSeaOrderType)
180187
);
181188
}
182189
// Legacy event_type handling
183190
return (
184191
s === BotEvent.listing ||
185192
s === BotEvent.offer ||
186-
s === "trait_offer" ||
187-
s === "collection_offer" ||
193+
s === ("trait_offer" satisfies OpenSeaEventType) ||
194+
s === ("collection_offer" satisfies OpenSeaEventType) ||
188195
s === "listing"
189196
);
190197
};
@@ -269,11 +276,7 @@ export type EmbedResult = {
269276
export const buildEmbed = async (
270277
event: AggregatorEvent
271278
): Promise<EmbedResult> => {
272-
const { event_type, asset, order_type } = event as unknown as {
273-
event_type?: EventType | string;
274-
asset?: { opensea_url?: string; name?: string };
275-
order_type?: string;
276-
};
279+
const { event_type, asset, order_type } = event;
277280

278281
const nft = event.nft ?? asset;
279282
// Use effective event type to correctly identify burns, mints, etc.
@@ -287,7 +290,10 @@ export const buildEmbed = async (
287290

288291
const built = new EmbedBuilder()
289292
.setColor(
290-
colorFor(effectiveType, order_type ?? "") as unknown as ColorResolvable
293+
colorFor(
294+
effectiveType,
295+
(order_type as OpenSeaOrderType | undefined) ?? undefined
296+
) as ColorResolvable
291297
)
292298
.setTitle(title)
293299
.setFields(

src/platforms/twitter/twitter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ const ensureTwitterClient = () => {
340340
appSecret: String(process.env.TWITTER_CONSUMER_SECRET),
341341
accessToken: String(process.env.TWITTER_ACCESS_TOKEN),
342342
accessSecret: String(process.env.TWITTER_ACCESS_TOKEN_SECRET),
343-
}).readWrite as unknown as MinimalTwitterClient;
343+
}).readWrite as MinimalTwitterClient;
344344
}
345345
};
346346

src/platforms/twitter/utils.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { username } from "../../opensea";
2-
import type { OpenSeaAssetEvent, OpenSeaPayment } from "../../types";
2+
import type {
3+
OpenSeaAssetEvent,
4+
OpenSeaEventType,
5+
OpenSeaOrderType,
6+
OpenSeaPayment,
7+
} from "../../types";
38
import {
49
classifyTransfer,
510
formatAmount,
@@ -40,28 +45,28 @@ export const formatNftName = (
4045
export const formatOrderText = async (
4146
payment: OpenSeaPayment,
4247
maker: string,
43-
order_type: string
48+
order_type: OpenSeaOrderType | string
4449
) => {
4550
const name = await username(maker);
4651
const price = formatAmount(
4752
payment.quantity,
4853
payment.decimals,
4954
payment.symbol
5055
);
51-
if (order_type === "listing") {
56+
if (order_type === ("listing" satisfies OpenSeaOrderType)) {
5257
return `listed on sale for ${price} by ${name}`;
5358
}
5459
if (
55-
order_type === "item_offer" ||
60+
order_type === ("item_offer" satisfies OpenSeaOrderType) ||
5661
order_type === "offer" ||
5762
order_type === "criteria_offer"
5863
) {
5964
return `has a new offer for ${price} by ${name}`;
6065
}
61-
if (order_type === "collection_offer") {
66+
if (order_type === ("collection_offer" satisfies OpenSeaOrderType)) {
6267
return `has a new collection offer for ${price} by ${name}`;
6368
}
64-
if (order_type === "trait_offer") {
69+
if (order_type === ("trait_offer" satisfies OpenSeaOrderType)) {
6570
return `has a new trait offer for ${price} by ${name}`;
6671
}
6772
return "";
@@ -101,7 +106,7 @@ export const textForOrder = async (params: {
101106
nft: { name?: string; identifier?: string | number } | undefined;
102107
payment: OpenSeaPayment;
103108
maker: string;
104-
order_type: string;
109+
order_type: OpenSeaOrderType | string;
105110
}): Promise<string> => {
106111
const { nft, payment, maker, order_type } = params;
107112
let text = "";
@@ -153,7 +158,7 @@ export const textForTransfer = async (
153158
};
154159

155160
// Helper sets for event type classification
156-
const ORDER_EVENT_TYPES = new Set([
161+
const ORDER_EVENT_TYPES = new Set<OpenSeaEventType | "order">([
157162
"order",
158163
"listing",
159164
"offer",

src/types.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,24 +75,32 @@ export type OpenSeaNFT = {
7575
// OpenSea API event_type values
7676
// Note: The API returns "order" for listings and all offer types
7777
// The actual distinction is made via the order_type field
78-
export type OpenSeaEventType =
79-
| "sale"
80-
| "transfer"
81-
| "mint"
82-
| "order" // Used for listings and offers - check order_type for specifics
78+
export const OpenSeaEventType = {
79+
sale: "sale",
80+
transfer: "transfer",
81+
mint: "mint",
82+
order: "order", // Used for listings and offers - check order_type for specifics
8383
// Legacy values (for backwards compatibility with existing code/tests)
84-
| "listing"
85-
| "offer"
86-
| "trait_offer"
87-
| "collection_offer";
84+
listing: "listing",
85+
offer: "offer",
86+
trait_offer: "trait_offer",
87+
collection_offer: "collection_offer",
88+
} as const;
89+
90+
export type OpenSeaEventType =
91+
(typeof OpenSeaEventType)[keyof typeof OpenSeaEventType];
8892

8993
// OpenSea API order_type values (when event_type is "order")
94+
export const OpenSeaOrderType = {
95+
listing: "listing",
96+
item_offer: "item_offer",
97+
trait_offer: "trait_offer",
98+
collection_offer: "collection_offer",
99+
criteria_offer: "criteria_offer", // Legacy value
100+
} as const;
101+
90102
export type OpenSeaOrderType =
91-
| "listing"
92-
| "item_offer"
93-
| "trait_offer"
94-
| "collection_offer"
95-
| "criteria_offer"; // Legacy value
103+
(typeof OpenSeaOrderType)[keyof typeof OpenSeaOrderType];
96104

97105
export type OpenSeaAssetEvent = {
98106
event_type: OpenSeaEventType;

src/utils/aggregator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export type AggregatorEvent = {
2121
export const txHashFor = (event: AggregatorEvent): string | undefined =>
2222
(typeof event?.transaction === "string" ? event.transaction : undefined) ||
2323
(typeof event?.transaction === "object"
24-
? (event.transaction?.hash as string | undefined)
24+
? event.transaction?.hash
2525
: undefined) ||
2626
event?.transaction_hash ||
2727
event?.tx_hash ||

0 commit comments

Comments
 (0)