Skip to content

Commit f9637c6

Browse files
vcarlclaude
andcommitted
Refactor buildVoteMessageContent to use escalation object
Simplify function signatures by passing the escalation object instead of individual fields. This makes the code cleaner and allows direct use of scheduled_for for Discord timestamps instead of recalculating. - buildVoteMessageContent now takes (modRoleId, escalation, tally, votingStrategy) - buildConfirmedMessageContent now takes (escalation, resolution, tally) - Update all call sites in handlers.ts - Update tests with mock escalation helper 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 6a83b67 commit f9637c6

File tree

4 files changed

+137
-157
lines changed

4 files changed

+137
-157
lines changed

app/commands/escalate/handlers.ts

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
resolveEscalation,
2929
updateEscalationStrategy,
3030
updateScheduledFor,
31+
type Escalation,
3132
} from "#~/models/escalationVotes.server";
3233
import {
3334
DEFAULT_QUORUM,
@@ -342,11 +343,6 @@ ${buildVotesListContent(tally)}`,
342343
const quorum = flags.quorum;
343344
const votingStrategy =
344345
escalation.voting_strategy as VotingStrategy | null;
345-
const earlyResolution = shouldTriggerEarlyResolution(
346-
tally,
347-
quorum,
348-
votingStrategy,
349-
);
350346

351347
// Update scheduled_for based on new vote count
352348
const newScheduledFor = calculateScheduledFor(
@@ -355,14 +351,25 @@ ${buildVotesListContent(tally)}`,
355351
);
356352
await updateScheduledFor(escalationId, newScheduledFor);
357353

354+
// Create updated escalation object with new scheduled_for
355+
const updatedEscalation: Escalation = {
356+
...escalation,
357+
scheduled_for: newScheduledFor,
358+
};
359+
360+
const earlyResolution = shouldTriggerEarlyResolution(
361+
tally,
362+
quorum,
363+
votingStrategy,
364+
);
365+
358366
// Check if early resolution triggered with clear winner - show confirmed state
359367
if (earlyResolution && !tally.isTied && tally.leader) {
360368
await interaction.update({
361369
content: buildConfirmedMessageContent(
362-
escalation.reported_user_id,
370+
updatedEscalation,
363371
tally.leader,
364372
tally,
365-
escalation.created_at,
366373
),
367374
components: [
368375
new ActionRowBuilder<ButtonBuilder>().addComponents(
@@ -380,11 +387,8 @@ ${buildVotesListContent(tally)}`,
380387
await interaction.update({
381388
content: buildVoteMessageContent(
382389
modRoleId,
383-
escalation.initiator_id,
384-
escalation.reported_user_id,
390+
updatedEscalation,
385391
tally,
386-
quorum,
387-
escalation.created_at,
388392
votingStrategy,
389393
),
390394
components: buildVoteButtons(
@@ -440,14 +444,29 @@ ${buildVotesListContent(tally)}`,
440444
};
441445

442446
const createdAt = new Date().toISOString();
447+
const scheduledFor = calculateScheduledFor(createdAt, 0);
448+
449+
// Create a temporary escalation-like object for initial message
450+
const tempEscalation: Escalation = {
451+
id: escalationId,
452+
guild_id: guildId,
453+
thread_id: threadId,
454+
vote_message_id: "", // Will be set after message is sent
455+
reported_user_id: reportedUserId,
456+
initiator_id: interaction.user.id,
457+
flags: JSON.stringify({ quorum }),
458+
created_at: createdAt,
459+
resolved_at: null,
460+
resolution: null,
461+
voting_strategy: votingStrategy,
462+
scheduled_for: scheduledFor,
463+
};
464+
443465
const content = {
444466
content: buildVoteMessageContent(
445467
modRoleId,
446-
interaction.user.id,
447-
reportedUserId,
468+
tempEscalation,
448469
emptyTally,
449-
quorum,
450-
createdAt,
451470
votingStrategy,
452471
),
453472
components: buildVoteButtons(
@@ -472,16 +491,7 @@ ${buildVotesListContent(tally)}`,
472491
}
473492
voteMessage = await channel.send(content);
474493
// Now create escalation record with the correct message ID
475-
await createEscalation({
476-
id: escalationId as `${string}-${string}-${string}-${string}-${string}`,
477-
guildId,
478-
threadId,
479-
voteMessageId: voteMessage.id,
480-
reportedUserId,
481-
initiatorId: interaction.user.id,
482-
quorum,
483-
votingStrategy,
484-
});
494+
await createEscalation(tempEscalation);
485495

486496
// Send notification
487497
await interaction.editReply("Escalation started");
@@ -508,15 +518,25 @@ ${buildVotesListContent(tally)}`,
508518
const votes = await getVotesForEscalation(escalationId);
509519
const tally = tallyVotes(votes);
510520

521+
// Recalculate scheduled_for based on current vote count
522+
const newScheduledFor = calculateScheduledFor(
523+
escalation.created_at,
524+
tally.totalVotes,
525+
);
526+
527+
// Create updated escalation object
528+
const updatedEscalation: Escalation = {
529+
...escalation,
530+
voting_strategy: votingStrategy,
531+
scheduled_for: newScheduledFor,
532+
};
533+
511534
// Update content with current votes and new strategy
512535
const updatedContent = {
513536
content: buildVoteMessageContent(
514537
modRoleId,
515-
escalation.initiator_id,
516-
reportedUserId,
538+
updatedEscalation,
517539
tally,
518-
quorum,
519-
escalation.created_at,
520540
votingStrategy,
521541
),
522542
components: buildVoteButtons(
@@ -536,11 +556,6 @@ ${buildVotesListContent(tally)}`,
536556
await updateEscalationStrategy(escalationId, votingStrategy);
537557
}
538558

539-
// Recalculate scheduled_for based on current vote count
540-
const newScheduledFor = calculateScheduledFor(
541-
escalation.created_at,
542-
tally.totalVotes,
543-
);
544559
await updateScheduledFor(escalationId, newScheduledFor);
545560

546561
// Send notification

app/commands/escalate/strings.test.ts

Lines changed: 42 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { tallyVotes, type VoteTally } from "#~/commands/escalate/voting";
22
import { resolutions } from "#~/helpers/modResponse";
3+
import type { Escalation } from "#~/models/escalationVotes.server";
34

45
import {
56
buildConfirmedMessageContent,
@@ -9,6 +10,27 @@ import {
910

1011
const emptyTally: VoteTally = tallyVotes([]);
1112

13+
// Helper to create mock escalation objects for testing
14+
function createMockEscalation(overrides: Partial<Escalation> = {}): Escalation {
15+
const createdAt = new Date("2024-01-01T12:00:00Z").toISOString();
16+
const scheduledFor = new Date("2024-01-02T12:00:00Z").toISOString(); // 24h later
17+
return {
18+
id: "test-escalation-id",
19+
guild_id: "test-guild",
20+
thread_id: "test-thread",
21+
vote_message_id: "test-message",
22+
reported_user_id: "123456789",
23+
initiator_id: "987654321",
24+
flags: JSON.stringify({ quorum: 3 }),
25+
created_at: createdAt,
26+
resolved_at: null,
27+
resolution: null,
28+
voting_strategy: null,
29+
scheduled_for: scheduledFor,
30+
...overrides,
31+
};
32+
}
33+
1234
describe("buildVotesListContent", () => {
1335
it("returns empty string for no votes", () => {
1436
const result = buildVotesListContent(emptyTally);
@@ -47,60 +69,38 @@ describe("buildVotesListContent", () => {
4769
});
4870

4971
describe("buildVoteMessageContent", () => {
50-
const reportedUserId = "123456789";
51-
const initiatorId = "987654321";
5272
const modRoleId = "564738291";
53-
const createdAt = new Date("2024-01-01T12:00:00Z").toISOString();
5473

5574
it("shows vote count toward quorum", () => {
56-
const result = buildVoteMessageContent(
57-
modRoleId,
58-
59-
initiatorId,
60-
reportedUserId,
61-
emptyTally,
62-
3,
63-
createdAt,
64-
);
75+
const escalation = createMockEscalation();
76+
const result = buildVoteMessageContent(modRoleId, escalation, emptyTally);
6577

6678
expect(result).toMatch(/0 vote.*quorum at 3/);
6779
expect(result).not.toMatch("null");
6880
});
6981

7082
it("mentions the reported user", () => {
71-
const result = buildVoteMessageContent(
72-
modRoleId,
73-
74-
initiatorId,
75-
reportedUserId,
76-
emptyTally,
77-
3,
78-
createdAt,
79-
);
83+
const escalation = createMockEscalation();
84+
const result = buildVoteMessageContent(modRoleId, escalation, emptyTally);
8085

81-
expect(result).toContain(`<@${reportedUserId}>`);
86+
expect(result).toContain(`<@${escalation.reported_user_id}>`);
8287
});
8388

8489
it("shows quorum reached status when votes >= quorum", () => {
90+
const escalation = createMockEscalation();
8591
const tally = tallyVotes([
8692
{ vote: resolutions.ban, voter_id: "u1" },
8793
{ vote: resolutions.ban, voter_id: "u2" },
8894
{ vote: resolutions.ban, voter_id: "u3" },
8995
]);
90-
const result = buildVoteMessageContent(
91-
modRoleId,
92-
initiatorId,
93-
reportedUserId,
94-
tally,
95-
3,
96-
createdAt,
97-
);
96+
const result = buildVoteMessageContent(modRoleId, escalation, tally);
9897

9998
expect(result).toContain("Quorum reached");
10099
expect(result).toContain("Ban");
101100
});
102101

103102
it("shows tied status when quorum reached but tied", () => {
103+
const escalation = createMockEscalation();
104104
// Need 3+ votes for each option to reach quorum while tied
105105
const tally = tallyVotes([
106106
{ vote: resolutions.ban, voter_id: "u1" },
@@ -110,99 +110,82 @@ describe("buildVoteMessageContent", () => {
110110
{ vote: resolutions.kick, voter_id: "u5" },
111111
{ vote: resolutions.kick, voter_id: "u6" },
112112
]);
113-
const result = buildVoteMessageContent(
114-
modRoleId,
115-
initiatorId,
116-
reportedUserId,
117-
tally,
118-
3,
119-
createdAt,
120-
);
113+
const result = buildVoteMessageContent(modRoleId, escalation, tally);
121114

122115
expect(result).toContain("Tied between");
123116
expect(result).toContain("tiebreaker");
124117
});
125118

126119
it("includes Discord timestamp", () => {
127-
const result = buildVoteMessageContent(
128-
modRoleId,
129-
130-
initiatorId,
131-
reportedUserId,
132-
emptyTally,
133-
3,
134-
createdAt,
135-
);
120+
const escalation = createMockEscalation();
121+
const result = buildVoteMessageContent(modRoleId, escalation, emptyTally);
136122

137123
expect(result).toMatch(/<t:\d+:R>/);
138124
});
139125
});
140126

141127
describe("buildConfirmedMessageContent", () => {
142-
const reportedUserId = "123456789";
143-
const createdAt = new Date("2024-01-01T12:00:00Z").toISOString();
144-
145128
it("shows the confirmed resolution", () => {
129+
const escalation = createMockEscalation();
146130
const tally = tallyVotes([
147131
{ vote: resolutions.ban, voter_id: "u1" },
148132
{ vote: resolutions.ban, voter_id: "u2" },
149133
{ vote: resolutions.ban, voter_id: "u3" },
150134
]);
151135
const result = buildConfirmedMessageContent(
152-
reportedUserId,
136+
escalation,
153137
resolutions.ban,
154138
tally,
155-
createdAt,
156139
);
157140

158141
expect(result).toContain("Ban");
159142
expect(result).toContain("✅");
160143
});
161144

162145
it("mentions the reported user", () => {
146+
const escalation = createMockEscalation();
163147
const tally = tallyVotes([
164148
{ vote: resolutions.kick, voter_id: "u1" },
165149
{ vote: resolutions.kick, voter_id: "u2" },
166150
{ vote: resolutions.kick, voter_id: "u3" },
167151
]);
168152
const result = buildConfirmedMessageContent(
169-
reportedUserId,
153+
escalation,
170154
resolutions.kick,
171155
tally,
172-
createdAt,
173156
);
174157

175-
expect(result).toContain(`<@${reportedUserId}>`);
158+
expect(result).toContain(`<@${escalation.reported_user_id}>`);
176159
});
177160

178161
it("shows execution timestamp", () => {
162+
const escalation = createMockEscalation();
179163
const tally = tallyVotes([
180164
{ vote: resolutions.track, voter_id: "u1" },
181165
{ vote: resolutions.track, voter_id: "u2" },
182166
{ vote: resolutions.track, voter_id: "u3" },
183167
]);
184168
const result = buildConfirmedMessageContent(
185-
reportedUserId,
169+
escalation,
186170
resolutions.track,
187171
tally,
188-
createdAt,
189172
);
190173

191174
expect(result).toContain("Executes");
192175
expect(result).toMatch(/<t:\d+:R>/);
193176
});
194177

195178
it("includes vote record", () => {
179+
const escalation = createMockEscalation();
196180
const tally = tallyVotes([
197181
{ vote: resolutions.restrict, voter_id: "mod1" },
198182
{ vote: resolutions.restrict, voter_id: "mod2" },
199183
{ vote: resolutions.kick, voter_id: "mod3" },
200184
]);
201185
const result = buildConfirmedMessageContent(
202-
reportedUserId,
186+
escalation,
203187
resolutions.restrict,
204188
tally,
205-
createdAt,
206189
);
207190

208191
expect(result).toContain("<@mod1>");

0 commit comments

Comments
 (0)