222 lines
8.0 KiB
TypeScript
222 lines
8.0 KiB
TypeScript
|
||
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);
|