import { scrypt, randomBytes } from "crypto"; import { promisify } from "util"; const BASE_URL = "http://localhost:5001"; let cookie = ""; async function request(method: string, path: string, body?: any) { const headers: any = { "Content-Type": "application/json" }; if (cookie) headers["Cookie"] = cookie; const res = await fetch(`${BASE_URL}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined, }); // Capture cookie const setCookie = res.headers.get("set-cookie"); if (setCookie) { cookie = setCookie.split(";")[0]; } const text = await res.text(); // console.log("RAW RESPONSE:", text); // Uncomment if needed try { const data = JSON.parse(text); if (res.status >= 400 && data.error === "Failed to update task") { console.log("DEBUG ERROR RESPONSE:", text); } return { status: res.status, data }; } catch { return { status: res.status, data: text }; } } async function requestWithRetry(method: string, path: string, body?: any, retries = 5, delay = 2000) { for (let i = 0; i < retries; i++) { try { return await request(method, path, body); } catch (err: any) { if (i === retries - 1) throw err; if (err.cause && (err.cause.code === 'ECONNRESET' || err.cause.code === 'ECONNREFUSED')) { console.log(`Connection failed (${err.cause.code}). Retrying in ${delay}ms...`); await new Promise(r => setTimeout(r, delay)); } else { throw err; } } } throw new Error("Request failed after retries"); } async function run() { console.log("šŸš€ Starting Gamification E2E Test"); // 1. Enable Registration (Just in case) // We can't easily do this via API without admin. // Let's assume registration is open or we use the Setup flow if needed. // Actually, let's try to register. If 403, we try to login as admin? const username = `test_gamer_${Date.now()}`; const password = "password123"; const email = `${username}@example.com`; console.log(`\nšŸ‘¤ Registering User: ${username}`); let res = await requestWithRetry("POST", "/api/register", { username, password, email, showOnLeaderboard: true }); if (res.status === 403) { console.log("Registration disabled. Trying Setup..."); // Try setup? Or maybe just login as admin? // Let's assume we can create a user via raw storage import if API fails, // but mixing contexts is messy. console.error("āŒ Registration disabled and no admin handling in script. Aborting."); process.exit(1); } if (res.status !== 200 && res.status !== 201) { // Maybe already logged in or error console.error("āŒ Registration failed:", res.data); // Try login res = await requestWithRetry("POST", "/api/login", { username, password }); if (res.status !== 200) { console.error("āŒ Login failed:", res.data); process.exit(1); } } const userId = res.data.id; console.log("āœ… User Logged In. ID:", userId); // 2. Create a Task & Complete it for XP console.log("\nšŸ“ Creating Task..."); res = await request("POST", "/api/tasks", { title: "XP Grind Task", dueDate: null }); if (res.status !== 201) { console.error("āŒ Task Creation Failed:", res.status, JSON.stringify(res.data, null, 2)); } const taskId = res.data?.id; console.log("Task Created:", taskId); if (taskId) { console.log("āœ… Completing Task..."); res = await request("PATCH", `/api/tasks/${taskId}`, { status: "done" }); if (res.status !== 200) { console.error("āŒ Task Completion Failed:", res.status, JSON.stringify(res.data, null, 2)); } console.log("Task Update Result:", res.data.status); } // 3. Verify XP (via Leaderboard or Profile?) // We don't have a direct 'get me' endpoint that shows XP easily, // but /api/user/history should show the event! console.log("\nšŸ“œ Checking History for XP Gain..."); res = await request("GET", "/api/user/history"); if (res.status !== 200) { console.error("āŒ History Fetch Failed:", res.status, JSON.stringify(res.data, null, 2)); } const history = Array.isArray(res.data) ? res.data : []; console.log("History Events:", history.length); const taskEvent = history.find((e: any) => e.source === 'task_completion'); if (taskEvent) { console.log(`āœ… Found Task Completion Event: +${taskEvent.amount} XP`); } else { console.error("āŒ No Task Completion Event found!"); } const clearEvent = history.find((e: any) => e.source === 'daily_clear_bonus'); if (clearEvent) { console.log(`āœ… Found Daily Clear Bonus: +${clearEvent.amount} XP`); } else { console.log("ā„¹ļø No Daily Clear Bonus (Normal if other tasks exist)"); } // 4. Create a Reward (System allows user creation for now?) console.log("\nšŸŽ Creating Custom Reward..."); res = await request("POST", "/api/rewards", { title: "Test Reward", description: "E2E Test Reward", cost: 10, type: "virtual", icon: "gift" }); const rewardId = res.data.id; console.log("Reward Created:", rewardId); // 5. Verify Leaderboard console.log("\nšŸ† Verifying Leaderboard..."); res = await request("GET", "/api/leaderboard"); const leaderboard = Array.isArray(res.data) ? res.data : []; const me = leaderboard.find((u: any) => u.username === username); if (me) { if (me.xp > 0) { console.log(`āœ… Leaderboard verified: User ${username} has ${me.xp} XP`); } else { console.error(`āŒ Leaderboard Error: User has ${me.xp} XP (expected > 0)`); } } else { console.error("āŒ Leaderboard Error: User not found in leaderboard"); // Check privacy settings? Default is showOnLeaderboard=false // Oops, default is false in schema? Let's check schema/storage. } // 6. Purchase Reward console.log("\nšŸ’° Purchasing Reward..."); res = await request("POST", "/api/rewards/purchase", { rewardId: rewardId }); if (res.status === 200) { console.log("āœ… Purchase Successful"); } else { console.error("āŒ Purchase Failed:", res.data); } // 6. Verify Inventory console.log("\nšŸŽ’ Checking Inventory..."); res = await request("GET", "/api/user/inventory"); const inventory = Array.isArray(res.data) ? res.data : []; const item = inventory.find((i: any) => i.rewardId === rewardId); if (item) { console.log("āœ… Reward found in Inventory!"); console.log("Inventory Item:", item); } else { console.error("āŒ Reward NOT found in Inventory."); console.log("Full Inventory Response:", JSON.stringify(inventory, null, 2)); } // 7. Verify Privacy: User 2 should NOT see User 1's custom reward console.log("\nšŸ•µļø Checking Privacy..."); const user2 = `test_gamer_2_${Date.now()}`; await request("POST", "/api/logout"); // Logout User 1 console.log(`Registering User 2: ${user2}`); res = await requestWithRetry("POST", "/api/register", { username: user2, password: "password123", email: `${user2}@example.com` }); if (res.status === 201) { console.log("User 2 Logged In"); res = await request("GET", "/api/rewards"); const rewards = Array.isArray(res.data) ? res.data : []; const privateReward = rewards.find((r: any) => r.id === rewardId); if (privateReward) { console.error("āŒ PRIVACY FAIL: User 2 can see User 1's custom reward!"); console.error("Reward:", privateReward); } else { console.log("āœ… PRIVACY SUCCESS: User 2 cannot see private reward."); } } else { console.warn("āš ļø Could not register User 2, skipping privacy check."); } console.log("\nāœ… Test Complete."); } run().catch(console.error);