Skip to content

Commit 21ddc1d

Browse files
capJavertclaude
andcommitted
fix: guard checkQuestProgress against non-existent users
Adds a User.exists() check before attempting quest progress operations, preventing FK_user_quest_user_id violations when the user doesn't exist. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ba010fc commit 21ddc1d

2 files changed

Lines changed: 66 additions & 2 deletions

File tree

__tests__/questProgress.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -617,12 +617,63 @@ describe('checkQuestProgress', () => {
617617
},
618618
);
619619

620+
it('should return false when user does not exist', async () => {
621+
const now = new Date();
622+
const logger = createMockLogger();
623+
const nonExistentUserId = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa';
624+
const periodStart = new Date(now.getTime() - 60 * 60 * 1000);
625+
const periodEnd = new Date(now.getTime() + 60 * 60 * 1000);
626+
627+
await saveFixtures(con, Quest, [
628+
{
629+
id: questIds[0],
630+
name: 'Daily upvotes',
631+
description: 'Upvote 2 posts',
632+
type: QuestType.Daily,
633+
eventType: QuestEventType.PostUpvote,
634+
criteria: { targetCount: 2 },
635+
active: true,
636+
},
637+
]);
638+
639+
await saveFixtures(con, QuestRotation, [
640+
{
641+
id: rotationIds[0],
642+
questId: questIds[0],
643+
type: QuestType.Daily,
644+
plusOnly: false,
645+
slot: 1,
646+
periodStart,
647+
periodEnd,
648+
},
649+
]);
650+
651+
const didUpdate = await checkQuestProgress({
652+
con,
653+
logger,
654+
userId: nonExistentUserId,
655+
eventType: QuestEventType.PostUpvote,
656+
incrementBy: 1,
657+
now,
658+
});
659+
660+
expect(didUpdate).toBe(false);
661+
662+
const userQuests = await con.getRepository(UserQuest).find({
663+
where: { userId: nonExistentUserId },
664+
});
665+
expect(userQuests).toHaveLength(0);
666+
});
667+
620668
it('should rethrow quest progress errors for caller retries', async () => {
621669
const logger = createMockLogger();
622670
const expectedError = new Error('query failed');
623671
const failingConnection = {
624-
getRepository: jest.fn().mockReturnValue({
625-
find: jest.fn().mockRejectedValue(expectedError),
672+
getRepository: jest.fn().mockImplementation((entity) => {
673+
if (entity === User) {
674+
return { exists: jest.fn().mockResolvedValue(true) };
675+
}
676+
return { find: jest.fn().mockRejectedValue(expectedError) };
626677
}),
627678
} as unknown as DataSource;
628679

src/common/quest/progress.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { redisPubSub } from '../../redis';
1010
import { Quest, QuestEventType, QuestType } from '../../entity/Quest';
1111
import { QuestRotation } from '../../entity/QuestRotation';
12+
import { User } from '../../entity/user/User';
1213
import { UserQuest, UserQuestStatus } from '../../entity/user/UserQuest';
1314
import { syncMilestoneQuestProgress } from './milestone';
1415

@@ -281,6 +282,18 @@ export const checkQuestProgress = async ({
281282
return false;
282283
}
283284

285+
if (!userId) {
286+
return false;
287+
}
288+
289+
const userExists = await con.getRepository(User).exists({
290+
where: { id: userId },
291+
});
292+
293+
if (!userExists) {
294+
return false;
295+
}
296+
284297
try {
285298
const targets = await getQuestTargetsByEventType({
286299
con,

0 commit comments

Comments
 (0)