Files
task-manager/scripts/test-gamification.ts
2025-12-12 08:35:48 +01:00

222 lines
8.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);