const request = require("supertest"); const app = require("../../server"); const redisClient = require("../../src/utils/redis-client"); const fallbackStore = require("../../src/utils/fallback-store"); describe("API Endpoints - Integration Tests", () => { let server; let testEventId = "888"; // Use a unique event ID for testing beforeAll(async () => { // Start the server server = app.listen(0); // Use random port // Wait for server to be ready await new Promise((resolve) => setTimeout(resolve, 1000)); // Ensure Redis is connected if (!redisClient.isConnected) { await redisClient.connect(); } }); afterAll(async () => { // Clean up test data try { if (redisClient.isConnected) { const testEventKey = `event:${testEventId}:meta`; const testTicketsKey = `event:${testEventId}:tickets`; await redisClient.client.del(testEventKey); await redisClient.client.del(testTicketsKey); } } catch (error) { console.warn("Failed to cleanup test data:", error.message); } // Close server if (server) { await new Promise((resolve) => server.close(resolve)); } // Disconnect Redis if (redisClient.isConnected) { await redisClient.disconnect(); } }); beforeEach(async () => { // Reset fallback store fallbackStore.deactivate(); fallbackStore.events.clear(); fallbackStore.globalStats = { totalEvents: 0, totalTickets: 0, totalSold: 0, lastSeeded: null, }; // Create test event with 5 tickets in Redis if (redisClient.isConnected) { const testEventKey = `event:${testEventId}:meta`; const testTicketsKey = `event:${testEventId}:tickets`; // Create event metadata await redisClient.client.hSet(testEventKey, { eventId: testEventId, name: "Test Event for API Testing", description: "Test event to verify API endpoints", totalTickets: "5", soldTickets: "0", createdAt: new Date().toISOString(), lastSoldAt: "never", }); // Create 5 test tickets const testTickets = Array.from( { length: 5 }, (_, i) => `api-test-ticket-${i + 1}` ); await redisClient.client.lPush(testTicketsKey, testTickets); } }); describe("Health Check Endpoint", () => { test("GET /health should return system status", async () => { const response = await request(server).get("/health").expect(200); expect(response.body).toHaveProperty("status", "ok"); expect(response.body).toHaveProperty("timestamp"); expect(response.body).toHaveProperty("redis"); expect(response.body).toHaveProperty("uptime"); expect(response.body.redis).toHaveProperty("connected"); expect(response.body.redis).toHaveProperty("fallbackActive"); }); }); describe("Events Endpoints", () => { test("GET /events should return all events", async () => { const response = await request(server).get("/events").expect(200); expect(response.body).toHaveProperty("success", true); expect(response.body).toHaveProperty("events"); expect(response.body).toHaveProperty("usingFallback"); expect(Array.isArray(response.body.events)).toBe(true); }); test("GET /events/:eventId should return specific event", async () => { const response = await request(server) .get(`/events/${testEventId}`) .expect(200); expect(response.body).toHaveProperty("success", true); expect(response.body).toHaveProperty("event"); expect(response.body.event).toHaveProperty("eventId", testEventId); expect(response.body.event).toHaveProperty("name"); expect(response.body.event).toHaveProperty("totalTickets", 5); expect(response.body.event).toHaveProperty("remainingTickets", 5); }); test("GET /events/:eventId should return 404 for non-existent event", async () => { const response = await request(server).get("/events/99999").expect(404); expect(response.body).toHaveProperty("success", false); expect(response.body).toHaveProperty("message", "Event not found"); }); test("GET /events/:eventId should validate event ID format", async () => { const response = await request(server).get("/events/invalid").expect(400); expect(response.body).toHaveProperty("success", false); expect(response.body).toHaveProperty("message", "Invalid event ID"); }); }); describe("Ticket Purchase Endpoint", () => { test("POST /buy/:eventId should purchase ticket successfully", async () => { const response = await request(server) .post(`/buy/${testEventId}`) .set("Content-Type", "application/json") .expect(200); expect(response.body).toHaveProperty("success", true); expect(response.body).toHaveProperty("ticket"); expect(response.body).toHaveProperty("purchaseId"); expect(response.body).toHaveProperty("eventId", testEventId); expect(response.body).toHaveProperty("soldCount", 1); expect(response.body).toHaveProperty("usingFallback", false); expect(response.body).toHaveProperty("pdf"); expect(response.body.pdf).toHaveProperty("generated"); }); test("POST /buy/:eventId should fail for non-existent event", async () => { const response = await request(server) .post("/buy/99999") .set("Content-Type", "application/json") .expect(404); expect(response.body).toHaveProperty("success", false); expect(response.body).toHaveProperty("message", "Event not found"); }); test("POST /buy/:eventId should fail when no tickets available", async () => { // Purchase all available tickets first for (let i = 0; i < 5; i++) { await request(server) .post(`/buy/${testEventId}`) .set("Content-Type", "application/json") .expect(200); } // Try to purchase one more const response = await request(server) .post(`/buy/${testEventId}`) .set("Content-Type", "application/json") .expect(409); expect(response.body).toHaveProperty("success", false); expect(response.body).toHaveProperty( "message", "No tickets available for this event" ); }); test("POST /buy/:eventId should validate event ID format", async () => { const response = await request(server) .post("/buy/invalid") .set("Content-Type", "application/json") .expect(400); expect(response.body).toHaveProperty("success", false); expect(response.body).toHaveProperty("message", "Invalid event ID"); }); }); describe("Ticket Download Endpoint", () => { let purchaseId; beforeEach(async () => { // Purchase a ticket first const response = await request(server) .post(`/buy/${testEventId}`) .set("Content-Type", "application/json"); purchaseId = response.body.purchaseId; }); test("GET /tickets/:purchaseId should download ticket PDF", async () => { const response = await request(server) .get(`/tickets/${purchaseId}`) .expect(200); expect(response.headers["content-type"]).toBe("application/pdf"); expect(response.headers["content-disposition"]).toContain( `filename="ticket-${purchaseId}.pdf"` ); expect(response.body).toBeDefined(); }); test("GET /tickets/:purchaseId should return 404 for non-existent ticket", async () => { const response = await request(server) .get("/tickets/non-existent-uuid") .expect(404); expect(response.body).toHaveProperty("success", false); expect(response.body).toHaveProperty("message", "Ticket not found"); }); test("GET /tickets/:purchaseId should validate purchase ID format", async () => { const response = await request(server) .get("/tickets/invalid-uuid") .expect(400); expect(response.body).toHaveProperty("success", false); expect(response.body).toHaveProperty("message", "Invalid purchase ID"); }); }); describe("Admin Endpoints", () => { test("GET /admin/pdf-stats should return PDF statistics", async () => { const response = await request(server) .get("/admin/pdf-stats") .expect(200); expect(response.body).toHaveProperty("success", true); expect(response.body).toHaveProperty("stats"); expect(response.body.stats).toHaveProperty("totalFiles"); expect(response.body.stats).toHaveProperty("totalSize"); }); test("POST /admin/cleanup-tickets should cleanup old tickets", async () => { const response = await request(server) .post("/admin/cleanup-tickets") .set("Content-Type", "application/json") .send({ maxAgeHours: 24 }) .expect(200); expect(response.body).toHaveProperty("success", true); expect(response.body).toHaveProperty("message"); expect(response.body).toHaveProperty("deletedCount"); }); test("POST /admin/cleanup-tickets should validate maxAgeHours parameter", async () => { const response = await request(server) .post("/admin/cleanup-tickets") .set("Content-Type", "application/json") .send({ maxAgeHours: -1 }) .expect(400); expect(response.body).toHaveProperty("success", false); expect(response.body).toHaveProperty( "message", "Invalid cleanup parameters" ); }); test("POST /admin/seed-fallback should seed fallback store", async () => { const response = await request(server) .post("/admin/seed-fallback") .set("Content-Type", "application/json") .expect(200); expect(response.body).toHaveProperty("success", true); expect(response.body).toHaveProperty("message"); expect(response.body).toHaveProperty("eventsCount"); expect(response.body).toHaveProperty("totalTickets"); expect(response.body).toHaveProperty("totalSold"); }); }); describe("Metrics Endpoint", () => { test("GET /metrics should return system metrics", async () => { const response = await request(server).get("/metrics").expect(200); expect(response.body).toHaveProperty("timestamp"); expect(response.body).toHaveProperty("global"); expect(response.body).toHaveProperty("events"); expect(response.body).toHaveProperty("system"); expect(response.body).toHaveProperty("pdf"); expect(response.body.system).toHaveProperty("usingFallback"); expect(response.body.system).toHaveProperty("redisConnected"); expect(response.body.system).toHaveProperty("uptime"); expect(response.body.system).toHaveProperty("memoryUsage"); }); }); describe("Fallback Mode Operation", () => { test("should operate in fallback mode when Redis is unavailable", async () => { // Disconnect Redis to simulate failure if (redisClient.isConnected) { await redisClient.disconnect(); } // Seed fallback store const metadata = { eventId: testEventId, name: "Test Event for Fallback Testing", description: "Test event in fallback mode", totalTickets: 3, soldTickets: 0, createdAt: new Date().toISOString(), lastSoldAt: "never", }; const testTickets = [ "fallback-ticket-1", "fallback-ticket-2", "fallback-ticket-3", ]; fallbackStore.seedEvent(testEventId, testTickets, metadata); fallbackStore.activate("Test fallback mode"); // Test events endpoint in fallback mode const eventsResponse = await request(server).get("/events").expect(200); expect(eventsResponse.body.usingFallback).toBe(true); // Test ticket purchase in fallback mode const purchaseResponse = await request(server) .post(`/buy/${testEventId}`) .set("Content-Type", "application/json") .expect(200); expect(purchaseResponse.body.success).toBe(true); expect(purchaseResponse.body.usingFallback).toBe(true); expect(purchaseResponse.body.ticket).toBeDefined(); // Verify ticket was removed from fallback store const event = fallbackStore.events.get(testEventId); expect(event.tickets).toHaveLength(2); }); }); describe("Error Handling", () => { test("should handle malformed JSON requests gracefully", async () => { const response = await request(server) .post(`/buy/${testEventId}`) .set("Content-Type", "application/json") .send("invalid json") .expect(400); expect(response.body).toHaveProperty("success", false); }); test("should handle missing required parameters", async () => { const response = await request(server) .post("/admin/cleanup-tickets") .set("Content-Type", "application/json") .send({}) .expect(200); // Should use default value expect(response.body).toHaveProperty("success", true); }); }); });