@@ -9,19 +9,28 @@ import {
99 User ,
1010} from '../src/entity' ;
1111import { UserQuest , UserQuestStatus } from '../src/entity/user' ;
12- import { checkQuestProgress } from '../src/common/quest' ;
12+ import {
13+ checkQuestProgress ,
14+ syncMilestoneQuestProgress ,
15+ } from '../src/common/quest' ;
1316import { createMockLogger , saveFixtures } from './helpers' ;
1417
1518const userId = '11111111-1111-4111-8111-111111111111' ;
1619const questIds = [
1720 '22222222-2222-4222-8222-222222222222' ,
1821 '33333333-3333-4333-8333-333333333333' ,
1922 '44444444-4444-4444-8444-444444444444' ,
23+ '88888888-8888-4888-8888-888888888881' ,
24+ '88888888-8888-4888-8888-888888888882' ,
25+ '88888888-8888-4888-8888-888888888883' ,
2026] ;
2127const rotationIds = [
2228 '55555555-5555-4555-8555-555555555555' ,
2329 '66666666-6666-4666-8666-666666666666' ,
2430 '77777777-7777-4777-8777-777777777777' ,
31+ '99999999-9999-4999-8999-999999999991' ,
32+ '99999999-9999-4999-8999-999999999992' ,
33+ '99999999-9999-4999-8999-999999999993' ,
2534] ;
2635
2736let con : DataSource ;
@@ -196,6 +205,230 @@ describe('checkQuestProgress', () => {
196205 expect ( userQuest . status ) . toBe ( UserQuestStatus . InProgress ) ;
197206 } ) ;
198207
208+ it ( 'should not advance quest completion milestone on completion alone, only on claim' , async ( ) => {
209+ const now = new Date ( ) ;
210+ const logger = createMockLogger ( ) ;
211+ const periodStart = new Date ( now . getTime ( ) - 60 * 60 * 1000 ) ;
212+ const periodEnd = new Date ( now . getTime ( ) + 60 * 60 * 1000 ) ;
213+ const milestonePeriodStart = new Date ( '2026-03-25T00:00:00.000Z' ) ;
214+ const milestonePeriodEnd = new Date ( '9999-12-31T23:59:59.000Z' ) ;
215+
216+ await saveFixtures ( con , Quest , [
217+ {
218+ id : questIds [ 3 ] ,
219+ name : 'Up and comer' ,
220+ description : 'Claim 2 quests' ,
221+ type : QuestType . Milestone ,
222+ eventType : QuestEventType . QuestComplete ,
223+ criteria : { targetCount : 2 } ,
224+ active : true ,
225+ } ,
226+ {
227+ id : questIds [ 4 ] ,
228+ name : 'Daily upvotes 1' ,
229+ description : 'Upvote 1 post' ,
230+ type : QuestType . Daily ,
231+ eventType : QuestEventType . PostUpvote ,
232+ criteria : { targetCount : 1 } ,
233+ active : true ,
234+ } ,
235+ {
236+ id : questIds [ 5 ] ,
237+ name : 'Daily upvotes 2' ,
238+ description : 'Upvote 1 more post' ,
239+ type : QuestType . Daily ,
240+ eventType : QuestEventType . PostUpvote ,
241+ criteria : { targetCount : 1 } ,
242+ active : true ,
243+ } ,
244+ ] ) ;
245+
246+ await saveFixtures ( con , QuestRotation , [
247+ {
248+ id : rotationIds [ 3 ] ,
249+ questId : questIds [ 3 ] ,
250+ type : QuestType . Milestone ,
251+ plusOnly : false ,
252+ slot : 1 ,
253+ periodStart : milestonePeriodStart ,
254+ periodEnd : milestonePeriodEnd ,
255+ } ,
256+ {
257+ id : rotationIds [ 4 ] ,
258+ questId : questIds [ 4 ] ,
259+ type : QuestType . Daily ,
260+ plusOnly : false ,
261+ slot : 1 ,
262+ periodStart,
263+ periodEnd,
264+ } ,
265+ {
266+ id : rotationIds [ 5 ] ,
267+ questId : questIds [ 5 ] ,
268+ type : QuestType . Daily ,
269+ plusOnly : false ,
270+ slot : 2 ,
271+ periodStart,
272+ periodEnd,
273+ } ,
274+ ] ) ;
275+
276+ const didUpdate = await checkQuestProgress ( {
277+ con,
278+ logger,
279+ userId,
280+ eventType : QuestEventType . PostUpvote ,
281+ incrementBy : 1 ,
282+ now,
283+ } ) ;
284+
285+ expect ( didUpdate ) . toBe ( true ) ;
286+
287+ const userQuests = await con . getRepository ( UserQuest ) . find ( {
288+ where : { userId } ,
289+ order : { rotationId : 'ASC' } ,
290+ } ) ;
291+
292+ expect ( userQuests ) . toHaveLength ( 3 ) ;
293+ expect (
294+ userQuests
295+ . filter ( ( { rotationId } ) =>
296+ [ rotationIds [ 4 ] , rotationIds [ 5 ] ] . includes ( rotationId ) ,
297+ )
298+ . map ( ( { progress, status } ) => ( { progress, status } ) ) ,
299+ ) . toEqual ( [
300+ {
301+ progress : 1 ,
302+ status : UserQuestStatus . Completed ,
303+ } ,
304+ {
305+ progress : 1 ,
306+ status : UserQuestStatus . Completed ,
307+ } ,
308+ ] ) ;
309+
310+ const milestoneQuest = userQuests . find (
311+ ( { rotationId } ) => rotationId === rotationIds [ 3 ] ,
312+ ) ;
313+ expect ( milestoneQuest ) . toMatchObject ( {
314+ progress : 0 ,
315+ status : UserQuestStatus . InProgress ,
316+ } ) ;
317+ } ) ;
318+
319+ it ( 'should advance quest completion milestone when quests are claimed' , async ( ) => {
320+ const now = new Date ( ) ;
321+ const logger = createMockLogger ( ) ;
322+ const periodStart = new Date ( now . getTime ( ) - 60 * 60 * 1000 ) ;
323+ const periodEnd = new Date ( now . getTime ( ) + 60 * 60 * 1000 ) ;
324+ const milestonePeriodStart = new Date ( '2026-03-25T00:00:00.000Z' ) ;
325+ const milestonePeriodEnd = new Date ( '9999-12-31T23:59:59.000Z' ) ;
326+
327+ await saveFixtures ( con , Quest , [
328+ {
329+ id : questIds [ 3 ] ,
330+ name : 'Up and comer' ,
331+ description : 'Claim 2 quests' ,
332+ type : QuestType . Milestone ,
333+ eventType : QuestEventType . QuestComplete ,
334+ criteria : { targetCount : 2 } ,
335+ active : true ,
336+ } ,
337+ {
338+ id : questIds [ 4 ] ,
339+ name : 'Daily upvotes 1' ,
340+ description : 'Upvote 1 post' ,
341+ type : QuestType . Daily ,
342+ eventType : QuestEventType . PostUpvote ,
343+ criteria : { targetCount : 1 } ,
344+ active : true ,
345+ } ,
346+ {
347+ id : questIds [ 5 ] ,
348+ name : 'Daily upvotes 2' ,
349+ description : 'Upvote 1 more post' ,
350+ type : QuestType . Daily ,
351+ eventType : QuestEventType . PostUpvote ,
352+ criteria : { targetCount : 1 } ,
353+ active : true ,
354+ } ,
355+ ] ) ;
356+
357+ await saveFixtures ( con , QuestRotation , [
358+ {
359+ id : rotationIds [ 3 ] ,
360+ questId : questIds [ 3 ] ,
361+ type : QuestType . Milestone ,
362+ plusOnly : false ,
363+ slot : 1 ,
364+ periodStart : milestonePeriodStart ,
365+ periodEnd : milestonePeriodEnd ,
366+ } ,
367+ {
368+ id : rotationIds [ 4 ] ,
369+ questId : questIds [ 4 ] ,
370+ type : QuestType . Daily ,
371+ plusOnly : false ,
372+ slot : 1 ,
373+ periodStart,
374+ periodEnd,
375+ } ,
376+ {
377+ id : rotationIds [ 5 ] ,
378+ questId : questIds [ 5 ] ,
379+ type : QuestType . Daily ,
380+ plusOnly : false ,
381+ slot : 2 ,
382+ periodStart,
383+ periodEnd,
384+ } ,
385+ ] ) ;
386+
387+ // Complete the daily quests
388+ await checkQuestProgress ( {
389+ con,
390+ logger,
391+ userId,
392+ eventType : QuestEventType . PostUpvote ,
393+ incrementBy : 1 ,
394+ now,
395+ } ) ;
396+
397+ // Simulate claiming both daily quests
398+ const dailyUserQuests = await con . getRepository ( UserQuest ) . find ( {
399+ where : {
400+ userId,
401+ rotationId : In ( [ rotationIds [ 4 ] , rotationIds [ 5 ] ] ) ,
402+ } ,
403+ } ) ;
404+
405+ for ( const uq of dailyUserQuests ) {
406+ await con
407+ . getRepository ( UserQuest )
408+ . update (
409+ { id : uq . id } ,
410+ { status : UserQuestStatus . Claimed , claimedAt : now } ,
411+ ) ;
412+ }
413+
414+ // Sync milestones after claims
415+ await syncMilestoneQuestProgress ( {
416+ con,
417+ userId,
418+ eventType : QuestEventType . QuestComplete ,
419+ now,
420+ } ) ;
421+
422+ const milestoneQuest = await con . getRepository ( UserQuest ) . findOneOrFail ( {
423+ where : { userId, rotationId : rotationIds [ 3 ] } ,
424+ } ) ;
425+
426+ expect ( milestoneQuest ) . toMatchObject ( {
427+ progress : 2 ,
428+ status : UserQuestStatus . Completed ,
429+ } ) ;
430+ } ) ;
431+
199432 it ( 'should not update claimed quests' , async ( ) => {
200433 const now = new Date ( ) ;
201434 const logger = createMockLogger ( ) ;
0 commit comments