// src/tests/integration/category.routes.test.ts import { describe, it, expect, beforeAll } from 'vitest'; import supertest from 'supertest'; /** * @vitest-environment node */ describe('Category API Routes (Integration)', () => { let request: ReturnType; beforeAll(async () => { const app = (await import('../../../server')).default; request = supertest(app); }); describe('GET /api/categories', () => { it('should return list of all categories', async () => { const response = await request.get('/api/categories'); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(Array.isArray(response.body.data)).toBe(true); expect(response.body.data.length).toBeGreaterThan(0); // Verify category structure const firstCategory = response.body.data[0]; expect(firstCategory).toHaveProperty('category_id'); expect(firstCategory).toHaveProperty('name'); expect(firstCategory).toHaveProperty('created_at'); expect(firstCategory).toHaveProperty('updated_at'); expect(typeof firstCategory.category_id).toBe('number'); expect(typeof firstCategory.name).toBe('string'); }); it('should return categories in alphabetical order', async () => { const response = await request.get('/api/categories'); const categories = response.body.data; // Verify alphabetical ordering for (let i = 1; i < categories.length; i++) { const prevName = categories[i - 1].name.toLowerCase(); const currName = categories[i].name.toLowerCase(); expect(currName >= prevName).toBe(true); } }); it('should include expected categories', async () => { const response = await request.get('/api/categories'); const categories = response.body.data; const categoryNames = categories.map((c: { name: string }) => c.name); // Verify some expected categories exist expect(categoryNames).toContain('Dairy & Eggs'); expect(categoryNames).toContain('Fruits & Vegetables'); expect(categoryNames).toContain('Meat & Seafood'); expect(categoryNames).toContain('Bakery & Bread'); }); }); describe('GET /api/categories/:id', () => { it('should return specific category by valid ID', async () => { // First get all categories to find a valid ID const listResponse = await request.get('/api/categories'); const firstCategory = listResponse.body.data[0]; const response = await request.get(`/api/categories/${firstCategory.category_id}`); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data.category_id).toBe(firstCategory.category_id); expect(response.body.data.name).toBe(firstCategory.name); }); it('should return 404 for non-existent category ID', async () => { const response = await request.get('/api/categories/999999'); expect(response.status).toBe(404); expect(response.body.success).toBe(false); expect(response.body.error).toContain('not found'); }); it('should return 400 for invalid category ID (not a number)', async () => { const response = await request.get('/api/categories/invalid'); expect(response.status).toBe(400); expect(response.body.success).toBe(false); expect(response.body.error).toContain('Invalid category ID'); }); it('should return 400 for negative category ID', async () => { const response = await request.get('/api/categories/-1'); expect(response.status).toBe(400); expect(response.body.success).toBe(false); expect(response.body.error).toContain('Invalid category ID'); }); it('should return 400 for zero category ID', async () => { const response = await request.get('/api/categories/0'); expect(response.status).toBe(400); expect(response.body.success).toBe(false); expect(response.body.error).toContain('Invalid category ID'); }); }); describe('GET /api/categories/lookup', () => { it('should find category by exact name', async () => { const response = await request.get('/api/categories/lookup?name=Dairy%20%26%20Eggs'); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data.name).toBe('Dairy & Eggs'); expect(response.body.data.category_id).toBeGreaterThan(0); }); it('should find category by case-insensitive name', async () => { const response = await request.get('/api/categories/lookup?name=dairy%20%26%20eggs'); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data.name).toBe('Dairy & Eggs'); }); it('should find category with mixed case', async () => { const response = await request.get('/api/categories/lookup?name=DaIrY%20%26%20eGgS'); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data.name).toBe('Dairy & Eggs'); }); it('should return 404 for non-existent category name', async () => { const response = await request.get('/api/categories/lookup?name=NonExistentCategory'); expect(response.status).toBe(404); expect(response.body.success).toBe(false); expect(response.body.error).toContain('not found'); }); it('should return 400 if name parameter is missing', async () => { const response = await request.get('/api/categories/lookup'); expect(response.status).toBe(400); expect(response.body.success).toBe(false); expect(response.body.error).toContain('required'); }); it('should return 400 for empty name parameter', async () => { const response = await request.get('/api/categories/lookup?name='); expect(response.status).toBe(400); expect(response.body.success).toBe(false); expect(response.body.error).toContain('required'); }); it('should return 400 for whitespace-only name parameter', async () => { const response = await request.get('/api/categories/lookup?name= '); expect(response.status).toBe(400); expect(response.body.success).toBe(false); expect(response.body.error).toContain('required'); }); it('should handle URL-encoded category names', async () => { const response = await request.get('/api/categories/lookup?name=Dairy%20%26%20Eggs'); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.data.name).toBe('Dairy & Eggs'); }); }); });