import test from "node:test"; import assert from "node:assert/strict"; import { registerHooks } from "node:module"; const configUrl = new URL("../functions/dailyTaskConfig.ts", import.meta.url); const serviceUrl = new URL("../functions/dailyTaskService.ts", import.meta.url); registerHooks({ resolve(specifier, context, nextResolve) { if (specifier === "@/dailyTaskConfig") return { url: configUrl.href, shortCircuit: true }; return nextResolve(specifier, context); }, }); const { getDayKey, getDayEndsAt, getWeekKey, getWeekEndsAt, refreshTaskData, mergeClientProgress, claimDaily, claimWeekly } = await import(serviceUrl); test("uses Shanghai natural day and Monday week keys", () => { assert.equal(getDayKey(Date.parse("2026-09-06T15:59:59Z")), "2026-09-06"); assert.equal(getDayKey(Date.parse("2026-09-06T16:00:00Z")), "2026-09-07"); assert.equal(getDayEndsAt(Date.parse("2026-09-06T16:00:00Z")), Date.parse("2026-09-08T00:00:00+08:00")); assert.equal(getWeekKey(Date.parse("2026-09-06T16:00:00Z")), "2026-09-07"); assert.equal(getWeekEndsAt(Date.parse("2026-09-06T16:00:00Z")), Date.parse("2026-09-14T00:00:00+08:00")); }); test("returns current server time and refreshes it on repeated reads", () => { const firstNow = Date.parse("2026-09-07T04:00:00Z"); const secondNow = firstNow + 5000; const first = refreshTaskData(null, "user-time", 20, firstNow).data; const second = refreshTaskData(JSON.stringify(first), "user-time", 20, secondNow); assert.equal(second.changed, false); assert.equal(second.data.serverNow, secondNow); assert.equal(second.data.dayEndsAt, Date.parse("2026-09-08T00:00:00+08:00")); assert.equal(second.data.weekEndsAt, Date.parse("2026-09-14T00:00:00+08:00")); }); test("migrates legacy tasks into five v2 stages totaling twenty stars", () => { const legacy = JSON.stringify({ levelPass: { value: 3, state: 1 } }); const result = refreshTaskData(legacy, "user-1", 20, Date.parse("2026-09-04T04:00:00Z")); assert.equal(result.changed, true); assert.equal(result.data.version, 2); assert.equal(result.data.dailyTasks.length, 5); assert.equal(result.data.dailyTasks.reduce((sum, task) => sum + task.stars, 0), 20); assert.deepEqual(result.data.dailyTasks.map(task => task.locked), [false, false, false, false, false]); }); test("keeps the same selection during a day and avoids previous slot tasks next day", () => { const first = refreshTaskData(null, "user-2", 20, Date.parse("2026-09-04T04:00:00Z")).data; const same = refreshTaskData(JSON.stringify(first), "user-2", 20, Date.parse("2026-09-04T14:00:00Z")); assert.equal(same.changed, false); assert.deepEqual(same.data.dailyTasks.map(task => task.taskId), first.dailyTasks.map(task => task.taskId)); const next = refreshTaskData(JSON.stringify(first), "user-2", 20, Date.parse("2026-09-05T04:00:00Z")).data; next.dailyTasks.forEach((task, index) => assert.notEqual(task.taskId, first.dailyTasks[index].taskId)); }); test("all daily tasks accumulate and can be claimed in any order", () => { const data = refreshTaskData(null, "user-3", 20, Date.parse("2026-09-04T04:00:00Z")).data; const submitted = structuredClone(data); submitted.dailyTasks.forEach(task => task.value = task.target); mergeClientProgress(data, submitted); assert.ok(data.dailyTasks.every(task => task.state === 1 && !task.locked)); for (const task of [...data.dailyTasks].reverse()) { assert.equal(claimDaily(data, task.taskId).ok, true); assert.equal(claimDaily(data, task.taskId).ok, false); } assert.equal(data.weeklyStars, 20); }); test("opens stored locked tasks immediately without resetting progress or rewards", () => { const now = Date.parse("2026-09-04T04:00:00Z"); const stored = refreshTaskData(null, "existing-user", 20, now).data; stored.dailyTasks.forEach((task, index) => task.locked = index > 0); stored.dailyTasks[4].value = stored.dailyTasks[4].target; stored.dailyTasks[4].state = 1; const original = structuredClone(stored); const result = refreshTaskData(JSON.stringify(stored), "existing-user", 20, now); assert.equal(result.changed, true); assert.deepEqual(result.data.dailyTasks, original.dailyTasks.map(task => ({ ...task, locked: false }))); assert.deepEqual(result.data.weeklyRewards, original.weeklyRewards); assert.equal(result.data.weeklyStars, original.weeklyStars); assert.equal(refreshTaskData(JSON.stringify(result.data), "existing-user", 20, now).changed, false); assert.equal(claimDaily(result.data, result.data.dailyTasks[4].taskId).ok, true); }); test("ignores legacy lock flags but still requires completion", () => { const data = refreshTaskData(null, "legacy-flags", 20, Date.parse("2026-09-04T04:00:00Z")).data; const task = data.dailyTasks[4]; task.locked = true; task.value = 0; task.state = 0; assert.equal(claimDaily(data, task.taskId).ok, false); task.value = task.target; task.state = 1; assert.equal(claimDaily(data, task.taskId).ok, true); assert.ok(data.dailyTasks.every(item => !item.locked)); }); test("daily claims add stars once and weekly tiers claim independently", () => { const data = refreshTaskData(null, "user-4", 20, Date.parse("2026-09-04T04:00:00Z")).data; for (const task of data.dailyTasks) { task.value = task.target; task.state = 1; task.locked = false; assert.equal(claimDaily(data, task.taskId).ok, true); } assert.equal(data.weeklyStars, 20); assert.equal(claimDaily(data, data.dailyTasks[0].taskId).ok, false); data.weeklyStars = 140; const high = claimWeekly(data, "weekly_140"); assert.equal(high.ok, true); assert.equal(data.weeklyRewards.find(item => item.rewardId === "weekly_35").state, 0); }); test("daily reset preserves stars while weekly reset clears them", () => { const friday = refreshTaskData(null, "user-5", 20, Date.parse("2026-09-04T04:00:00Z")).data; friday.weeklyStars = 70; const saturday = refreshTaskData(JSON.stringify(friday), "user-5", 20, Date.parse("2026-09-05T04:00:00Z")).data; assert.equal(saturday.weeklyStars, 70); const monday = refreshTaskData(JSON.stringify(saturday), "user-5", 20, Date.parse("2026-09-07T04:00:00Z")).data; assert.equal(monday.weeklyStars, 0); assert.ok(monday.weeklyRewards.every(reward => reward.state === 0)); });