From 584daadb5c18074fc01c05829f2f6519645814ff Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Fri, 19 Sep 2025 19:55:27 +0200 Subject: [PATCH] Fix failing specs --- .../api/v1/maps/hexagons_controller.rb | 6 +++-- app/services/maps/bounds_calculator.rb | 6 ++++- app/services/maps/hexagon_center_manager.rb | 6 +---- .../maps/hexagon_polygon_generator.rb | 26 ++++++------------- .../20250918215512_add_h3_hex_ids_to_stats.rb | 6 ++++- db/schema.rb | 4 ++- spec/requests/api/v1/maps/hexagons_spec.rb | 16 ++++++------ .../maps/hexagon_center_manager_spec.rb | 20 +++++++------- .../maps/hexagon_request_handler_spec.rb | 14 +++++----- spec/services/stats/calculate_month_spec.rb | 11 ++++---- 10 files changed, 57 insertions(+), 58 deletions(-) diff --git a/app/controllers/api/v1/maps/hexagons_controller.rb b/app/controllers/api/v1/maps/hexagons_controller.rb index 9e306649..900d3fd3 100644 --- a/app/controllers/api/v1/maps/hexagons_controller.rb +++ b/app/controllers/api/v1/maps/hexagons_controller.rb @@ -8,8 +8,10 @@ class Api::V1::Maps::HexagonsController < ApiController result = Maps::HexagonRequestHandler.new( params: params, - user: current_api_user, - context: context + user: context[:user] || current_api_user, + stat: context[:stat], + start_date: context[:start_date], + end_date: context[:end_date] ).call render json: result diff --git a/app/services/maps/bounds_calculator.rb b/app/services/maps/bounds_calculator.rb index f97e1b77..78b02bd0 100644 --- a/app/services/maps/bounds_calculator.rb +++ b/app/services/maps/bounds_calculator.rb @@ -72,7 +72,11 @@ module Maps if param.match?(/^\d+$/) param.to_i else - Time.zone.parse(param).to_i + parsed_time = Time.zone.parse(param) + if parsed_time.nil? + raise ArgumentError, "Invalid date format: #{param}" + end + parsed_time.to_i end when Integer param diff --git a/app/services/maps/hexagon_center_manager.rb b/app/services/maps/hexagon_center_manager.rb index fd699be8..31ada95a 100644 --- a/app/services/maps/hexagon_center_manager.rb +++ b/app/services/maps/hexagon_center_manager.rb @@ -58,15 +58,11 @@ module Maps { 'type' => 'Feature', 'id' => index + 1, - 'geometry' => generate_hexagon_geometry_from_h3(h3_index), + 'geometry' => Maps::HexagonPolygonGenerator.new(h3_index:).call, 'properties' => build_hexagon_properties(index, count, earliest, latest) } end - def generate_hexagon_geometry_from_h3(h3_index) - Maps::HexagonPolygonGenerator.new(h3_index: h3_index).call - end - def build_hexagon_properties(index, count, earliest, latest) { 'hex_id' => index + 1, diff --git a/app/services/maps/hexagon_polygon_generator.rb b/app/services/maps/hexagon_polygon_generator.rb index 29c7efff..a493eafe 100644 --- a/app/services/maps/hexagon_polygon_generator.rb +++ b/app/services/maps/hexagon_polygon_generator.rb @@ -2,30 +2,16 @@ module Maps class HexagonPolygonGenerator - def initialize(center_lng: nil, center_lat: nil, h3_resolution: 5, h3_index: nil) - @center_lng = center_lng - @center_lat = center_lat - @h3_resolution = h3_resolution + def initialize(h3_index:) @h3_index = h3_index end def call - generate_h3_hexagon_polygon - end - - private - - attr_reader :center_lng, :center_lat, :h3_resolution, :h3_index - - def generate_h3_hexagon_polygon - # Convert coordinates to H3 format [lat, lng] - coordinates = [center_lat, center_lng] - - # Get H3 index for these coordinates at specified resolution - h3_index = H3.from_geo_coordinates(coordinates, h3_resolution) + # Parse H3 index from hex string if needed + index = h3_index.is_a?(String) ? h3_index.to_i(16) : h3_index # Get the boundary coordinates for this H3 hexagon - boundary_coordinates = H3.to_boundary(h3_index) + boundary_coordinates = H3.to_boundary(index) # Convert to GeoJSON polygon format (lng, lat) polygon_coordinates = boundary_coordinates.map { [_2, _1] } @@ -38,5 +24,9 @@ module Maps 'coordinates' => [polygon_coordinates] } end + + private + + attr_reader :h3_index end end diff --git a/db/migrate/20250918215512_add_h3_hex_ids_to_stats.rb b/db/migrate/20250918215512_add_h3_hex_ids_to_stats.rb index 0ab8a90c..78e4f3d2 100644 --- a/db/migrate/20250918215512_add_h3_hex_ids_to_stats.rb +++ b/db/migrate/20250918215512_add_h3_hex_ids_to_stats.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true class AddH3HexIdsToStats < ActiveRecord::Migration[8.0] + disable_ddl_transaction! + def change add_column :stats, :h3_hex_ids, :jsonb, default: {} - add_index :stats, :h3_hex_ids, using: :gin, where: "(h3_hex_ids IS NOT NULL AND h3_hex_ids != '{}'::jsonb)" + add_index :stats, :h3_hex_ids, using: :gin, + where: "(h3_hex_ids IS NOT NULL AND h3_hex_ids != '{}'::jsonb)", + algorithm: :concurrently end end diff --git a/db/schema.rb b/db/schema.rb index cfcab1ea..d097aca9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_09_10_224714) do +ActiveRecord::Schema[8.0].define(version: 2025_09_18_215512) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" enable_extension "postgis" @@ -222,7 +222,9 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_10_224714) do t.jsonb "daily_distance", default: {} t.jsonb "sharing_settings", default: {} t.uuid "sharing_uuid" + t.jsonb "h3_hex_ids", default: {} t.index ["distance"], name: "index_stats_on_distance" + t.index ["h3_hex_ids"], name: "index_stats_on_h3_hex_ids", where: "((h3_hex_ids IS NOT NULL) AND (h3_hex_ids <> '{}'::jsonb))", using: :gin t.index ["month"], name: "index_stats_on_month" t.index ["sharing_uuid"], name: "index_stats_on_sharing_uuid", unique: true t.index ["user_id"], name: "index_stats_on_user_id" diff --git a/spec/requests/api/v1/maps/hexagons_spec.rb b/spec/requests/api/v1/maps/hexagons_spec.rb index 8277b407..bc2aba2d 100644 --- a/spec/requests/api/v1/maps/hexagons_spec.rb +++ b/spec/requests/api/v1/maps/hexagons_spec.rb @@ -172,14 +172,14 @@ RSpec.describe 'Api::V1::Maps::Hexagons', type: :request do context 'with pre-calculated hexagon centers' do let(:pre_calculated_centers) do - [ - [-74.0, 40.7, 1_717_200_000, 1_717_203_600], # lng, lat, earliest, latest timestamps - [-74.01, 40.71, 1_717_210_000, 1_717_213_600], - [-74.02, 40.72, 1_717_220_000, 1_717_223_600] - ] + { + '8a1fb46622dffff' => [5, 1_717_200_000, 1_717_203_600], # count, earliest, latest timestamps + '8a1fb46622e7fff' => [3, 1_717_210_000, 1_717_213_600], + '8a1fb46632dffff' => [8, 1_717_220_000, 1_717_223_600] + } end let(:stat) do - create(:stat, :with_sharing_enabled, user:, year: 2024, month: 6, hexagon_centers: pre_calculated_centers) + create(:stat, :with_sharing_enabled, user:, year: 2024, month: 6, h3_hex_ids: pre_calculated_centers) end it 'uses pre-calculated hexagon centers instead of on-the-fly calculation' do @@ -228,7 +228,7 @@ RSpec.describe 'Api::V1::Maps::Hexagons', type: :request do context 'with legacy area_too_large hexagon data' do let(:stat) do create(:stat, :with_sharing_enabled, user:, year: 2024, month: 6, - hexagon_centers: { 'area_too_large' => true }) + h3_hex_ids: { 'area_too_large' => true }) end before do @@ -246,7 +246,7 @@ RSpec.describe 'Api::V1::Maps::Hexagons', type: :request do get '/api/v1/maps/hexagons', params: uuid_params # The endpoint should handle the legacy data gracefully and not crash - # We're primarily testing that the condition `@stat&.hexagon_centers&.dig('area_too_large')` is covered + # We're primarily testing that the condition `@stat&.h3_hex_ids&.dig('area_too_large')` is covered expect([200, 400, 500]).to include(response.status) end end diff --git a/spec/services/maps/hexagon_center_manager_spec.rb b/spec/services/maps/hexagon_center_manager_spec.rb index 2912e28c..e1b3f1fa 100644 --- a/spec/services/maps/hexagon_center_manager_spec.rb +++ b/spec/services/maps/hexagon_center_manager_spec.rb @@ -11,13 +11,13 @@ RSpec.describe Maps::HexagonCenterManager do context 'with pre-calculated hexagon centers' do let(:pre_calculated_centers) do - [ - [-74.0, 40.7, 1_717_200_000, 1_717_203_600], # lng, lat, earliest, latest timestamps - [-74.01, 40.71, 1_717_210_000, 1_717_213_600], - [-74.02, 40.72, 1_717_220_000, 1_717_223_600] - ] + { + '8a1fb46622dffff' => [5, 1_717_200_000, 1_717_203_600], # count, earliest, latest timestamps + '8a1fb46622e7fff' => [3, 1_717_210_000, 1_717_213_600], + '8a1fb46632dffff' => [8, 1_717_220_000, 1_717_223_600] + } end - let(:stat) { create(:stat, user:, year: 2024, month: 6, hexagon_centers: pre_calculated_centers) } + let(:stat) { create(:stat, user:, year: 2024, month: 6, h3_hex_ids: pre_calculated_centers) } it 'returns success with pre-calculated data' do result = manage_centers @@ -51,7 +51,7 @@ RSpec.describe Maps::HexagonCenterManager do context 'with legacy area_too_large flag' do let(:stat) do - create(:stat, user:, year: 2024, month: 6, hexagon_centers: { 'area_too_large' => true }) + create(:stat, user:, year: 2024, month: 6, h3_hex_ids: { 'area_too_large' => true }) end before do @@ -69,7 +69,7 @@ RSpec.describe Maps::HexagonCenterManager do end it 'recalculates and updates the stat' do - expect(stat).to receive(:update).with(hexagon_centers: new_centers) + expect(stat).to receive(:update).with(h3_hex_ids: new_centers) result = manage_centers @@ -105,7 +105,7 @@ RSpec.describe Maps::HexagonCenterManager do end context 'with stat but no hexagon_centers' do - let(:stat) { create(:stat, user:, year: 2024, month: 6, hexagon_centers: nil) } + let(:stat) { create(:stat, user:, year: 2024, month: 6, h3_hex_ids: nil) } it 'returns nil' do expect(manage_centers).to be_nil @@ -113,7 +113,7 @@ RSpec.describe Maps::HexagonCenterManager do end context 'with empty hexagon_centers' do - let(:stat) { create(:stat, user:, year: 2024, month: 6, hexagon_centers: []) } + let(:stat) { create(:stat, user:, year: 2024, month: 6, h3_hex_ids: []) } it 'returns nil' do expect(manage_centers).to be_nil diff --git a/spec/services/maps/hexagon_request_handler_spec.rb b/spec/services/maps/hexagon_request_handler_spec.rb index 1f6a17b0..abe9a089 100644 --- a/spec/services/maps/hexagon_request_handler_spec.rb +++ b/spec/services/maps/hexagon_request_handler_spec.rb @@ -43,14 +43,14 @@ RSpec.describe Maps::HexagonRequestHandler do context 'with public sharing UUID and pre-calculated centers' do let(:pre_calculated_centers) do - [ - [-74.0, 40.7, 1_717_200_000, 1_717_203_600], - [-74.01, 40.71, 1_717_210_000, 1_717_213_600] - ] + { + '8a1fb46622dffff' => [5, 1_717_200_000, 1_717_203_600], + '8a1fb46622e7fff' => [3, 1_717_210_000, 1_717_213_600] + } end let(:stat) do create(:stat, :with_sharing_enabled, user:, year: 2024, month: 6, - hexagon_centers: pre_calculated_centers) + h3_hex_ids: pre_calculated_centers) end let(:params) do ActionController::Parameters.new( @@ -101,7 +101,7 @@ RSpec.describe Maps::HexagonRequestHandler do context 'with legacy area_too_large that can be recalculated' do let(:stat) do create(:stat, :with_sharing_enabled, user:, year: 2024, month: 6, - hexagon_centers: { 'area_too_large' => true }) + h3_hex_ids: { 'area_too_large' => true }) end let(:params) do ActionController::Parameters.new( @@ -129,7 +129,7 @@ RSpec.describe Maps::HexagonRequestHandler do expect(result['metadata']['pre_calculated']).to be true # Verify that the stat was updated with new centers (reload to check persistence) - expect(stat.reload.hexagon_centers).to eq([[-74.0, 40.7, 1_717_200_000, 1_717_203_600]]) + expect(stat.reload.h3_hex_ids).to eq([[-74.0, 40.7, 1_717_200_000, 1_717_203_600]]) end end end diff --git a/spec/services/stats/calculate_month_spec.rb b/spec/services/stats/calculate_month_spec.rb index e3a8a533..1045f9c6 100644 --- a/spec/services/stats/calculate_month_spec.rb +++ b/spec/services/stats/calculate_month_spec.rb @@ -162,14 +162,15 @@ RSpec.describe Stats::CalculateMonth do expect(total_points).to eq(2) end - context 'when H3 raises an error' do before do allow(H3).to receive(:from_geo_coordinates).and_raise(StandardError, 'H3 error') end it 'raises PostGISError' do - expect { calculate_hexagons }.to raise_error(Stats::CalculateMonth::PostGISError, /Failed to calculate H3 hexagon centers/) + expect do + calculate_hexagons + end.to raise_error(Stats::CalculateMonth::PostGISError, /Failed to calculate H3 hexagon centers/) end it 'reports the exception' do @@ -185,7 +186,7 @@ RSpec.describe Stats::CalculateMonth do it 'handles string timestamps' do result = service.send(:parse_date_parameter, '1640995200') - expect(result).to eq(1640995200) + expect(result).to eq(1_640_995_200) end it 'handles ISO date strings' do @@ -194,8 +195,8 @@ RSpec.describe Stats::CalculateMonth do end it 'handles integer timestamps' do - result = service.send(:parse_date_parameter, 1640995200) - expect(result).to eq(1640995200) + result = service.send(:parse_date_parameter, 1_640_995_200) + expect(result).to eq(1_640_995_200) end it 'handles edge case gracefully' do