dawarich/spec/controllers/api/v1/maps/hexagons_controller_spec.rb
2025-08-24 11:08:56 +02:00

320 lines
No EOL
10 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Api::V1::Maps::HexagonsController, type: :request do
let(:valid_params) do
{
min_lon: -74.0,
min_lat: 40.7,
max_lon: -73.9,
max_lat: 40.8
}
end
let(:mock_geojson_response) do
{
type: 'FeatureCollection',
features: [
{
type: 'Feature',
id: '1',
geometry: {
type: 'Polygon',
coordinates: [[[-74.0, 40.7], [-73.99, 40.7], [-73.99, 40.71], [-74.0, 40.71], [-74.0, 40.7]]]
},
properties: {
hex_id: '1',
hex_i: '0',
hex_j: '0',
hex_size: 500
}
}
],
metadata: {
bbox: [-74.0, 40.7, -73.9, 40.8],
area_km2: 111.0,
hex_size_m: 500,
count: 1,
estimated_count: 170
}
}
end
describe 'GET #index' do
context 'with valid parameters' do
before do
allow_any_instance_of(Maps::HexagonGrid).to receive(:call).and_return(mock_geojson_response)
end
it 'returns successful response' do
get '/api/v1/maps/hexagons', params: valid_params
expect(response).to have_http_status(:success)
expect(response.content_type).to eq('application/json; charset=utf-8')
end
it 'returns GeoJSON FeatureCollection' do
get '/api/v1/maps/hexagons', params: valid_params
json_response = JSON.parse(response.body, symbolize_names: true)
expect(json_response[:type]).to eq('FeatureCollection')
expect(json_response[:features]).to be_an(Array)
expect(json_response[:metadata]).to be_a(Hash)
end
it 'includes proper feature structure' do
get '/api/v1/maps/hexagons', params: valid_params
json_response = JSON.parse(response.body, symbolize_names: true)
feature = json_response[:features].first
expect(feature[:type]).to eq('Feature')
expect(feature[:id]).to eq('1')
expect(feature[:geometry]).to include(type: 'Polygon')
expect(feature[:properties]).to include(
hex_id: '1',
hex_i: '0',
hex_j: '0',
hex_size: 500
)
end
it 'includes metadata about the generation' do
get '/api/v1/maps/hexagons', params: valid_params
json_response = JSON.parse(response.body, symbolize_names: true)
metadata = json_response[:metadata]
expect(metadata).to include(
bbox: [-74.0, 40.7, -73.9, 40.8],
area_km2: 111.0,
hex_size_m: 500,
count: 1,
estimated_count: 170
)
end
it 'accepts custom hex_size parameter' do
custom_params = valid_params.merge(hex_size: 1000)
allow_any_instance_of(Maps::HexagonGrid).to receive(:call).and_return(mock_geojson_response)
get '/api/v1/maps/hexagons', params: custom_params
expect(response).to have_http_status(:success)
end
end
context 'with missing required parameters' do
it 'returns bad request when min_lon is missing' do
invalid_params = valid_params.except(:min_lon)
get '/api/v1/maps/hexagons', params: invalid_params
expect(response).to have_http_status(:bad_request)
json_response = JSON.parse(response.body, symbolize_names: true)
expect(json_response[:error]).to include('Missing required parameters: min_lon')
end
it 'returns bad request when multiple parameters are missing' do
invalid_params = valid_params.except(:min_lon, :max_lat)
get '/api/v1/maps/hexagons', params: invalid_params
expect(response).to have_http_status(:bad_request)
json_response = JSON.parse(response.body, symbolize_names: true)
expect(json_response[:error]).to include('min_lon')
expect(json_response[:error]).to include('max_lat')
end
it 'returns bad request when all parameters are missing' do
get '/api/v1/maps/hexagons', params: {}
expect(response).to have_http_status(:bad_request)
json_response = JSON.parse(response.body, symbolize_names: true)
expect(json_response[:error]).to include('Missing required parameters')
end
end
context 'with invalid coordinates' do
before do
allow_any_instance_of(Maps::HexagonGrid).to receive(:call)
.and_raise(Maps::HexagonGrid::InvalidCoordinatesError, 'Invalid coordinates provided')
end
it 'returns bad request with error message' do
invalid_params = valid_params.merge(min_lon: 200)
get '/api/v1/maps/hexagons', params: invalid_params
expect(response).to have_http_status(:bad_request)
json_response = JSON.parse(response.body, symbolize_names: true)
expect(json_response[:error]).to eq('Invalid coordinates provided')
end
end
context 'with bounding box too large' do
before do
allow_any_instance_of(Maps::HexagonGrid).to receive(:call)
.and_raise(Maps::HexagonGrid::BoundingBoxTooLargeError, 'Area too large (1000000 km²). Maximum allowed: 250000 km²')
end
it 'returns bad request with descriptive error' do
large_area_params = {
min_lon: -180,
min_lat: -89,
max_lon: 180,
max_lat: 89
}
get '/api/v1/maps/hexagons', params: large_area_params
expect(response).to have_http_status(:bad_request)
json_response = JSON.parse(response.body, symbolize_names: true)
expect(json_response[:error]).to include('Area too large')
expect(json_response[:error]).to include('Maximum allowed: 250000 km²')
end
end
context 'with PostGIS errors' do
before do
allow_any_instance_of(Maps::HexagonGrid).to receive(:call)
.and_raise(Maps::HexagonGrid::PostGISError, 'PostGIS function ST_HexagonGrid not available')
end
it 'returns internal server error' do
get '/api/v1/maps/hexagons', params: valid_params
expect(response).to have_http_status(:internal_server_error)
json_response = JSON.parse(response.body, symbolize_names: true)
expect(json_response[:error]).to eq('PostGIS function ST_HexagonGrid not available')
end
end
context 'with unexpected errors' do
before do
allow_any_instance_of(Maps::HexagonGrid).to receive(:call)
.and_raise(StandardError, 'Unexpected database error')
allow(Rails.logger).to receive(:error)
end
it 'returns generic internal server error' do
get '/api/v1/maps/hexagons', params: valid_params
expect(response).to have_http_status(:internal_server_error)
json_response = JSON.parse(response.body, symbolize_names: true)
expect(json_response[:error]).to eq('Failed to generate hexagon grid')
end
it 'logs the full error details' do
get '/api/v1/maps/hexagons', params: valid_params
expect(Rails.logger).to have_received(:error).with(/Hexagon generation error: Unexpected database error/)
end
end
end
describe 'parameter filtering' do
it 'permits required bounding box parameters' do
# Controller method testing removed for request specs.and_return(valid_params)
allow_any_instance_of(Maps::HexagonGrid).to receive(:call).and_return(mock_geojson_response)
get '/api/v1/maps/hexagons', params: valid_params
end
it 'permits optional hex_size parameter' do
params_with_hex_size = valid_params.merge(hex_size: 750)
# Controller method testing removed for request specs.and_return(params_with_hex_size)
allow_any_instance_of(Maps::HexagonGrid).to receive(:call).and_return(mock_geojson_response)
get '/api/v1/maps/hexagons', params: params_with_hex_size
end
it 'filters out unauthorized parameters' do
params_with_extra = valid_params.merge(
unauthorized_param: 'should_be_filtered',
another_bad_param: 'also_filtered'
)
allow_any_instance_of(Maps::HexagonGrid).to receive(:call).and_return(mock_geojson_response)
get '/api/v1/maps/hexagons', params: params_with_extra
expect(response).to have_http_status(:success)
end
end
describe 'authentication' do
it 'skips API key authentication' do
# This test verifies the skip_before_action :authenticate_api_key is working
get '/api/v1/maps/hexagons', params: valid_params
# Should not return unauthorized status
expect(response).not_to have_http_status(:unauthorized)
end
end
describe 'edge case parameters' do
context 'with boundary longitude values' do
let(:boundary_params) do
{
min_lon: -180,
min_lat: 40,
max_lon: 180,
max_lat: 41
}
end
before do
allow_any_instance_of(Maps::HexagonGrid).to receive(:call).and_return(mock_geojson_response)
end
it 'handles boundary longitude values' do
get '/api/v1/maps/hexagons', params: boundary_params
expect(response).to have_http_status(:success)
end
end
context 'with boundary latitude values' do
let(:boundary_params) do
{
min_lon: 0,
min_lat: -90,
max_lon: 1,
max_lat: 90
}
end
before do
allow_any_instance_of(Maps::HexagonGrid).to receive(:call).and_return(mock_geojson_response)
end
it 'handles boundary latitude values' do
get '/api/v1/maps/hexagons', params: boundary_params
expect(response).to have_http_status(:success)
end
end
context 'with very small areas' do
let(:small_area_params) do
{
min_lon: -74.0000,
min_lat: 40.7000,
max_lon: -73.9999,
max_lat: 40.7001
}
end
before do
allow_any_instance_of(Maps::HexagonGrid).to receive(:call).and_return(mock_geojson_response)
end
it 'handles very small bounding boxes' do
get '/api/v1/maps/hexagons', params: small_area_params
expect(response).to have_http_status(:success)
end
end
end
end