Some checks failed
Deploy to Staging / Build Images (pull_request) Successful in 6m41s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 52s
Deploy to Staging / Verify Staging (pull_request) Failing after 4m7s
Deploy to Staging / Notify Staging Ready (pull_request) Has been skipped
Deploy to Staging / Notify Staging Failure (pull_request) Successful in 9s
Backend test fixtures: - Replace auth0|xxx format with UUID in all test userId values - Update admin tests for new id/userProfileId schema - Add missing deletionRequestedAt/deletionScheduledFor to auth test mocks - Fix admin integration test supertest usage (app.server) Frontend: - AdminUser type: auth0Sub -> id + userProfileId - admin.api.ts: all user management methods use userId (UUID) params - useUsers/useAdmins hooks: auth0Sub -> userId/id in mutations - AdminUsersPage + AdminUsersMobileScreen: user.auth0Sub -> user.id - Remove encodeURIComponent (UUIDs don't need encoding) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
360 lines
9.9 KiB
TypeScript
360 lines
9.9 KiB
TypeScript
/**
|
|
* @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
|
|
});
|
|
});
|