const request = require("supertest"); const express = require("express"); // Mock CouchDB service before importing routes jest.mock("../../services/couchdbService", () => ({ initialize: jest.fn().mockResolvedValue(true), create: jest.fn(), getById: jest.fn(), find: jest.fn(), createDocument: jest.fn(), updateDocument: jest.fn(), deleteDocument: jest.fn(), getDocument: jest.fn(), })); // Mock logger to avoid console output during tests jest.mock("../../utils/logger", () => ({ info: jest.fn(), error: jest.fn(), warn: jest.fn(), debug: jest.fn(), })); // Mock auth middleware jest.mock("../../middleware/auth", () => (req, res, next) => { req.user = { id: "test-user-id" }; next(); }); const aiRoute = require("../../routes/ai"); const aiService = require("../../services/aiService"); const Street = require("../../models/Street"); const Task = require("../../models/Task"); const couchdbService = require("../../services/couchdbService"); // Setup Express app for testing const app = express(); app.use(express.json()); app.use("/api/ai", aiRoute); describe("AI Routes", () => { beforeEach(() => { jest.clearAllMocks(); }); describe("GET /api/ai/status", () => { it("should return AI service status", async () => { const response = await request(app) .get("/api/ai/status") .expect(200); expect(response.body.success).toBe(true); expect(response.body.data).toHaveProperty("configured"); expect(response.body.data).toHaveProperty("status"); }); }); describe("GET /api/ai/task-suggestions", () => { it("should return task suggestions without streetId", async () => { const response = await request(app) .get("/api/ai/task-suggestions") .expect(200); expect(response.body.success).toBe(true); expect(response.body.data).toBeInstanceOf(Array); expect(response.body.data.length).toBeGreaterThan(0); expect(response.body.meta).toHaveProperty("source"); expect(response.body.meta).toHaveProperty("aiConfigured"); // Check suggestion structure const suggestion = response.body.data[0]; expect(suggestion).toHaveProperty("description"); expect(suggestion).toHaveProperty("details"); expect(suggestion).toHaveProperty("estimatedTime"); expect(suggestion).toHaveProperty("difficulty"); expect(suggestion).toHaveProperty("priority"); expect(suggestion).toHaveProperty("source"); }); it("should return task suggestions with custom count", async () => { const response = await request(app) .get("/api/ai/task-suggestions?count=3") .expect(200); expect(response.body.success).toBe(true); expect(response.body.data).toBeInstanceOf(Array); expect(response.body.data.length).toBe(3); }); it("should validate count parameter maximum", async () => { const response = await request(app) .get("/api/ai/task-suggestions?count=15") .expect(400); expect(response.body.success).toBe(false); expect(response.body.errors).toBeInstanceOf(Array); }); it("should validate count is a positive number", async () => { const response = await request(app) .get("/api/ai/task-suggestions?count=0") .expect(400); expect(response.body.success).toBe(false); expect(response.body.errors).toBeInstanceOf(Array); }); it("should return task suggestions for specific street", async () => { const streetId = "test-street-123"; const streetData = { _id: streetId, _rev: "1-abc", type: "street", name: "Test Avenue", location: { type: "Point", coordinates: [-122.4194, 37.7749] }, status: "available", stats: { completedTasksCount: 0, reportsCount: 0, openReportsCount: 0 } }; // Mock street lookup couchdbService.getDocument.mockResolvedValueOnce(streetData); // Mock task lookup couchdbService.find.mockResolvedValueOnce([ { _id: "task-1", type: "task", street: { streetId, name: "Test Avenue" }, description: "Remove graffiti from wall", status: "completed", completedAt: new Date().toISOString() }, { _id: "task-2", type: "task", street: { streetId, name: "Test Avenue" }, description: "Clear storm drains", status: "completed", completedAt: new Date().toISOString() } ]); const response = await request(app) .get(`/api/ai/task-suggestions?streetId=${streetId}`) .expect(200); expect(response.body.success).toBe(true); expect(response.body.data).toBeInstanceOf(Array); expect(response.body.data.length).toBeGreaterThan(0); // Check that street name is included const suggestion = response.body.data[0]; expect(suggestion.street).toBe("Test Avenue"); }); it("should return 404 for non-existent street", async () => { // Mock street not found couchdbService.getDocument.mockResolvedValueOnce(null); const response = await request(app) .get("/api/ai/task-suggestions?streetId=nonexistent-id") .expect(404); expect(response.body.success).toBe(false); expect(response.body.msg).toBe("Street not found"); }); it("should include warning when OpenAI is not configured", async () => { const response = await request(app) .get("/api/ai/task-suggestions") .expect(200); expect(response.body.success).toBe(true); if (!aiService.isAvailable()) { expect(response.body.warning).toBeDefined(); expect(response.body.warning).toContain("OpenAI API not configured"); } }); }); describe("AI Service Integration", () => { it("should generate suggestions with proper structure", async () => { const suggestions = await aiService.generateTaskSuggestions({ streetName: "Main Street", numberOfSuggestions: 5 }); expect(suggestions).toBeInstanceOf(Array); expect(suggestions.length).toBe(5); suggestions.forEach(suggestion => { expect(suggestion).toHaveProperty("street"); expect(suggestion).toHaveProperty("description"); expect(suggestion).toHaveProperty("details"); expect(suggestion).toHaveProperty("estimatedTime"); expect(suggestion).toHaveProperty("difficulty"); expect(suggestion).toHaveProperty("priority"); expect(suggestion).toHaveProperty("source"); expect(["easy", "moderate", "hard"]).toContain(suggestion.difficulty); expect(["low", "medium", "high"]).toContain(suggestion.priority); }); }); it("should respect numberOfSuggestions parameter", async () => { const suggestions1 = await aiService.generateTaskSuggestions({ streetName: "Oak Avenue", numberOfSuggestions: 3 }); const suggestions2 = await aiService.generateTaskSuggestions({ streetName: "Elm Street", numberOfSuggestions: 7 }); expect(suggestions1.length).toBe(3); expect(suggestions2.length).toBe(7); }); it("should include street name in suggestions", async () => { const streetName = "Custom Street Name"; const suggestions = await aiService.generateTaskSuggestions({ streetName, numberOfSuggestions: 3 }); suggestions.forEach(suggestion => { expect(suggestion.street).toBe(streetName); }); }); it("should return mock data when OpenAI is not configured", async () => { const suggestions = await aiService.generateTaskSuggestions({ streetName: "Test Street", numberOfSuggestions: 5 }); expect(suggestions).toBeInstanceOf(Array); expect(suggestions.length).toBe(5); // Mock suggestions should have source: "mock" if (!aiService.isAvailable()) { expect(suggestions[0].source).toBe("mock"); } }); }); });