375 lines
13 KiB
JavaScript
375 lines
13 KiB
JavaScript
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);
|
|
});
|
|
});
|
|
});
|