feat: Implement AI Chat Agent, Email Notifications, and UI enhancements
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-12-12 08:35:48 +01:00
parent ccfb674318
commit 5d8976b1cd
58 changed files with 7867 additions and 844 deletions
+221
View File
@@ -0,0 +1,221 @@
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);