Community 93 Premium feature complete

This commit is contained in:
Eric Gullickson
2025-12-21 11:31:10 -06:00
parent 1bde31247f
commit 95f5e89e48
60 changed files with 8061 additions and 350 deletions

View File

@@ -0,0 +1,359 @@
/**
* @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 = 'auth0|test-user-123';
const testAdminId = 'auth0|test-admin-123';
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
});
});