/** * @ai-summary Integration tests for community stations API * @ai-context Tests full API workflow with database */ import { FastifyInstance } from 'fastify'; import { Pool } from 'pg'; import { buildApp } from '../../../../app'; import { pool as pgPool } from '../../../../core/config/database'; describe('Community Stations API Integration Tests', () => { let app: FastifyInstance; let pool: Pool; const testUserId = '550e8400-e29b-41d4-a716-446655440000'; const testAdminId = '7c9e6679-7425-40de-944b-e07fc1f90ae7'; const mockStationData = { name: 'Test Gas Station', address: '123 Main St', city: 'Springfield', state: 'IL', zipCode: '62701', latitude: 39.7817, longitude: -89.6501, brand: 'Shell', has93Octane: true, has93OctaneEthanolFree: false, price93: 3.50, notes: 'Great service' }; beforeAll(async () => { pool = pgPool; app = await buildApp(); // Clean up test data await pool.query('DELETE FROM community_stations WHERE submitted_by IN ($1, $2)', [testUserId, testAdminId]); }); afterAll(async () => { // Clean up test data await pool.query('DELETE FROM community_stations WHERE submitted_by IN ($1, $2)', [testUserId, testAdminId]); await app.close(); }); describe('POST /api/stations/community - Submit station', () => { it('should submit a new community station', async () => { const response = await app.inject({ method: 'POST', url: '/api/stations/community', headers: { authorization: `Bearer ${testUserId}` }, payload: mockStationData }); expect(response.statusCode).toBe(201); const body = JSON.parse(response.body); expect(body.id).toBeDefined(); expect(body.status).toBe('pending'); expect(body.submittedBy).toBe(testUserId); expect(body.name).toBe(mockStationData.name); }); it('should validate required fields', async () => { const incompleteData = { name: 'Test Station', address: '123 Main St' // Missing latitude and longitude }; const response = await app.inject({ method: 'POST', url: '/api/stations/community', headers: { authorization: `Bearer ${testUserId}` }, payload: incompleteData }); expect(response.statusCode).toBe(400); const body = JSON.parse(response.body); expect(body.error).toBe('Validation error'); }); it('should validate latitude bounds', async () => { const invalidData = { ...mockStationData, latitude: 91 // Invalid: must be between -90 and 90 }; const response = await app.inject({ method: 'POST', url: '/api/stations/community', headers: { authorization: `Bearer ${testUserId}` }, payload: invalidData }); expect(response.statusCode).toBe(400); const body = JSON.parse(response.body); expect(body.error).toBe('Validation error'); }); it('should require authentication', async () => { const response = await app.inject({ method: 'POST', url: '/api/stations/community', payload: mockStationData }); expect(response.statusCode).toBe(401); }); }); describe('GET /api/stations/community/mine - Get user submissions', () => { let submittedStationId: string; beforeAll(async () => { // Submit a test station const response = await app.inject({ method: 'POST', url: '/api/stations/community', headers: { authorization: `Bearer ${testUserId}` }, payload: mockStationData }); const body = JSON.parse(response.body); submittedStationId = body.id; }); it('should retrieve user submissions', async () => { const response = await app.inject({ method: 'GET', url: '/api/stations/community/mine', headers: { authorization: `Bearer ${testUserId}` } }); expect(response.statusCode).toBe(200); const body = JSON.parse(response.body); expect(body.total).toBeGreaterThan(0); expect(Array.isArray(body.stations)).toBe(true); expect(body.stations.some((s: any) => s.id === submittedStationId)).toBe(true); }); it('should support pagination', async () => { const response = await app.inject({ method: 'GET', url: '/api/stations/community/mine?limit=10&offset=0', headers: { authorization: `Bearer ${testUserId}` } }); expect(response.statusCode).toBe(200); const body = JSON.parse(response.body); expect(body.stations).toBeDefined(); expect(body.total).toBeDefined(); }); it('should require authentication', async () => { const response = await app.inject({ method: 'GET', url: '/api/stations/community/mine' }); expect(response.statusCode).toBe(401); }); }); describe('DELETE /api/stations/community/:id - Withdraw submission', () => { let pendingStationId: string; beforeAll(async () => { // Submit a test station const response = await app.inject({ method: 'POST', url: '/api/stations/community', headers: { authorization: `Bearer ${testUserId}` }, payload: { ...mockStationData, name: 'Pending Station to Withdraw' } }); const body = JSON.parse(response.body); pendingStationId = body.id; }); it('should allow user to withdraw own pending submission', async () => { const response = await app.inject({ method: 'DELETE', url: `/api/stations/community/${pendingStationId}`, headers: { authorization: `Bearer ${testUserId}` } }); expect(response.statusCode).toBe(204); // Verify it's deleted const checkResponse = await app.inject({ method: 'GET', url: '/api/stations/community/mine', headers: { authorization: `Bearer ${testUserId}` } }); const body = JSON.parse(checkResponse.body); expect(body.stations.some((s: any) => s.id === pendingStationId)).toBe(false); }); it('should reject withdrawal of non-existent station', async () => { const response = await app.inject({ method: 'DELETE', url: '/api/stations/community/invalid-id', headers: { authorization: `Bearer ${testUserId}` } }); expect(response.statusCode).toBe(400); }); it('should require authentication', async () => { const response = await app.inject({ method: 'DELETE', url: `/api/stations/community/${pendingStationId}` }); expect(response.statusCode).toBe(401); }); }); describe('GET /api/stations/community/approved - Get approved stations', () => { it('should retrieve approved stations', async () => { const response = await app.inject({ method: 'GET', url: '/api/stations/community/approved', headers: { authorization: `Bearer ${testUserId}` } }); expect(response.statusCode).toBe(200); const body = JSON.parse(response.body); expect(body.total).toBeDefined(); expect(Array.isArray(body.stations)).toBe(true); }); it('should return only approved stations', async () => { const response = await app.inject({ method: 'GET', url: '/api/stations/community/approved', headers: { authorization: `Bearer ${testUserId}` } }); const body = JSON.parse(response.body); body.stations.forEach((station: any) => { expect(station.status).toBe('approved'); }); }); it('should support pagination', async () => { const response = await app.inject({ method: 'GET', url: '/api/stations/community/approved?limit=10&offset=0', headers: { authorization: `Bearer ${testUserId}` } }); expect(response.statusCode).toBe(200); }); }); describe('POST /api/stations/community/nearby - Find nearby stations', () => { it('should find nearby approved stations', async () => { const response = await app.inject({ method: 'POST', url: '/api/stations/community/nearby', headers: { authorization: `Bearer ${testUserId}` }, payload: { latitude: 39.7817, longitude: -89.6501, radiusKm: 50 } }); expect(response.statusCode).toBe(200); const body = JSON.parse(response.body); expect(Array.isArray(body.stations)).toBe(true); }); it('should validate location coordinates', async () => { const response = await app.inject({ method: 'POST', url: '/api/stations/community/nearby', headers: { authorization: `Bearer ${testUserId}` }, payload: { latitude: 91, // Invalid longitude: -89.6501, radiusKm: 50 } }); expect(response.statusCode).toBe(400); }); it('should validate radius', async () => { const response = await app.inject({ method: 'POST', url: '/api/stations/community/nearby', headers: { authorization: `Bearer ${testUserId}` }, payload: { latitude: 39.7817, longitude: -89.6501, radiusKm: 1000 // Too large } }); expect(response.statusCode).toBe(400); }); }); describe('Admin endpoints', () => { it('should require admin role for admin endpoints', async () => { const response = await app.inject({ method: 'GET', url: '/api/admin/community-stations', headers: { authorization: `Bearer ${testUserId}` } }); expect(response.statusCode).toBe(403); }); // Note: Full admin endpoint testing would require proper admin role setup // These tests verify the routes are protected }); });