dawarich/spec/services/maps/h3_hexagon_calculator_spec.rb
2025-09-17 01:55:42 +02:00

221 lines
No EOL
7 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Maps::H3HexagonCalculator, type: :service do
let(:user) { create(:user) }
let(:start_date) { Time.zone.parse('2024-01-01') }
let(:end_date) { Time.zone.parse('2024-01-02') }
let(:service) { described_class.new(user.id, start_date, end_date, 5) }
describe '#call' do
context 'when user has no points' do
it 'returns error response' do
result = service.call
expect(result[:success]).to be false
expect(result[:error]).to eq('No points found for the given date range')
end
end
context 'when user has points outside date range' do
before do
create(:point,
user: user,
latitude: 52.5200,
longitude: 13.4050,
lonlat: 'POINT(13.4050 52.5200)',
timestamp: end_date.to_i + 1.hour) # Outside range
end
it 'returns error response' do
result = service.call
expect(result[:success]).to be false
expect(result[:error]).to eq('No points found for the given date range')
end
end
context 'when user has valid points' do
before do
# Create points in Berlin area
create(:point,
user: user,
latitude: 52.5200,
longitude: 13.4050,
lonlat: 'POINT(13.4050 52.5200)',
timestamp: start_date.to_i + 1.hour)
create(:point,
user: user,
latitude: 52.5190,
longitude: 13.4040,
lonlat: 'POINT(13.4040 52.5190)',
timestamp: start_date.to_i + 2.hours)
# Point outside date range
create(:point,
user: user,
latitude: 52.5200,
longitude: 13.4050,
lonlat: 'POINT(13.4050 52.5200)',
timestamp: end_date.to_i + 1.hour)
end
it 'returns successful response with hexagon features' do
result = service.call
expect(result[:success]).to be true
expect(result[:data]).to have_key(:type)
expect(result[:data][:type]).to eq('FeatureCollection')
expect(result[:data]).to have_key(:features)
expect(result[:data][:features]).to be_an(Array)
expect(result[:data][:features]).not_to be_empty
end
it 'creates proper GeoJSON features' do
result = service.call
feature = result[:data][:features].first
expect(feature).to have_key(:type)
expect(feature[:type]).to eq('Feature')
expect(feature).to have_key(:geometry)
expect(feature[:geometry][:type]).to eq('Polygon')
expect(feature[:geometry][:coordinates]).to be_an(Array)
expect(feature[:geometry][:coordinates].first).to be_an(Array)
expect(feature).to have_key(:properties)
expect(feature[:properties]).to have_key(:h3_index)
expect(feature[:properties]).to have_key(:point_count)
expect(feature[:properties]).to have_key(:center)
end
it 'only includes points within date range' do
result = service.call
# Should only have features from the 2 points within range
total_points = result[:data][:features].sum { |f| f[:properties][:point_count] }
expect(total_points).to eq(2)
end
it 'creates closed polygon coordinates' do
result = service.call
feature = result[:data][:features].first
coordinates = feature[:geometry][:coordinates].first
# First and last coordinates should be the same (closed polygon)
expect(coordinates.first).to eq(coordinates.last)
# Should have 7 coordinates (6 vertices + 1 to close)
expect(coordinates.length).to eq(7)
end
it 'counts points correctly per hexagon' do
result = service.call
# Both points are very close, should likely be in same hexagon
if result[:data][:features].length == 1
expect(result[:data][:features].first[:properties][:point_count]).to eq(2)
else
# Or they might be in adjacent hexagons
total_points = result[:data][:features].sum { |f| f[:properties][:point_count] }
expect(total_points).to eq(2)
end
end
it 'includes H3 index as hex string' do
result = service.call
feature = result[:data][:features].first
h3_index = feature[:properties][:h3_index]
expect(h3_index).to be_a(String)
expect(h3_index).to match(/^[0-9a-f]+$/) # Hex string
end
it 'includes center coordinates' do
result = service.call
feature = result[:data][:features].first
center = feature[:properties][:center]
expect(center).to be_an(Array)
expect(center.length).to eq(2)
expect(center[0]).to be_between(52.0, 53.0) # Lat around Berlin
expect(center[1]).to be_between(13.0, 14.0) # Lng around Berlin
end
end
context 'with different H3 resolution' do
let(:service) { described_class.new(user.id, start_date, end_date, 7) }
before do
create(:point,
user: user,
latitude: 52.5200,
longitude: 13.4050,
lonlat: 'POINT(13.4050 52.5200)',
timestamp: start_date.to_i + 1.hour)
end
it 'uses the specified resolution' do
result = service.call
expect(result[:success]).to be true
expect(result[:data][:features]).not_to be_empty
# Higher resolution should create different sized hexagons
feature = result[:data][:features].first
expect(feature[:properties][:h3_index]).to be_present
end
end
context 'when H3 operations fail' do
before do
create(:point,
user: user,
latitude: 52.5200,
longitude: 13.4050,
lonlat: 'POINT(13.4050 52.5200)',
timestamp: start_date.to_i + 1.hour)
allow(H3).to receive(:from_geo_coordinates).and_raise(StandardError, 'H3 error')
end
it 'returns error response' do
result = service.call
expect(result[:success]).to be false
expect(result[:error]).to eq('H3 error')
end
end
context 'with points from different users' do
let(:other_user) { create(:user) }
before do
# Points for target user
create(:point,
user: user,
latitude: 52.5200,
longitude: 13.4050,
lonlat: 'POINT(13.4050 52.5200)',
timestamp: start_date.to_i + 1.hour)
# Points for other user (should be ignored)
create(:point,
user: other_user,
latitude: 52.5200,
longitude: 13.4050,
lonlat: 'POINT(13.4050 52.5200)',
timestamp: start_date.to_i + 1.hour)
end
it 'only includes points from specified user' do
result = service.call
total_points = result[:data][:features].sum { |f| f[:properties][:point_count] }
expect(total_points).to eq(1)
end
end
end
end