diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c9f1351..a7fdb3e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## Fixed -- Cities visited during a trip are now being calculated correctly. #547 +- Cities visited during a trip are now being calculated correctly. #547 #641 - Points on the map are now show time in user's timezone. #580 +- Date range inputs now handle pre-epoch dates gracefully by clamping to valid PostgreSQL integer range. #685 # [0.36.2] - 2025-12-06 diff --git a/app/controllers/api/v1/countries/visited_cities_controller.rb b/app/controllers/api/v1/countries/visited_cities_controller.rb index 90baf6ce..5efee0d6 100644 --- a/app/controllers/api/v1/countries/visited_cities_controller.rb +++ b/app/controllers/api/v1/countries/visited_cities_controller.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true class Api::V1::Countries::VisitedCitiesController < ApiController + include SafeTimestampParser + before_action :validate_params def index - start_at = DateTime.parse(params[:start_at]).to_i - end_at = DateTime.parse(params[:end_at]).to_i + start_at = safe_timestamp(params[:start_at]) + end_at = safe_timestamp(params[:end_at]) points = current_api_user .points diff --git a/app/controllers/api/v1/points_controller.rb b/app/controllers/api/v1/points_controller.rb index ad5dca57..1595d326 100644 --- a/app/controllers/api/v1/points_controller.rb +++ b/app/controllers/api/v1/points_controller.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true class Api::V1::PointsController < ApiController + include SafeTimestampParser + before_action :authenticate_active_api_user!, only: %i[create update destroy bulk_destroy] before_action :validate_points_limit, only: %i[create] def index - start_at = params[:start_at]&.to_datetime&.to_i - end_at = params[:end_at]&.to_datetime&.to_i || Time.zone.now.to_i + start_at = params[:start_at].present? ? safe_timestamp(params[:start_at]) : nil + end_at = params[:end_at].present? ? safe_timestamp(params[:end_at]) : Time.zone.now.to_i order = params[:order] || 'desc' points = current_api_user diff --git a/app/controllers/concerns/safe_timestamp_parser.rb b/app/controllers/concerns/safe_timestamp_parser.rb new file mode 100644 index 00000000..b2e833dc --- /dev/null +++ b/app/controllers/concerns/safe_timestamp_parser.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module SafeTimestampParser + extend ActiveSupport::Concern + + private + + def safe_timestamp(date_string) + return Time.zone.now.to_i if date_string.blank? + + parsed_time = Time.zone.parse(date_string) + + # Time.zone.parse returns epoch time (2000-01-01) for unparseable strings + # Check if it's a valid parse by seeing if year is suspiciously at epoch + return Time.zone.now.to_i if parsed_time.nil? || (parsed_time.year == 2000 && !date_string.include?('2000')) + + min_timestamp = Time.zone.parse('1970-01-01').to_i + max_timestamp = Time.zone.parse('2100-01-01').to_i + + parsed_time.to_i.clamp(min_timestamp, max_timestamp) + rescue ArgumentError, TypeError + Time.zone.now.to_i + end +end diff --git a/app/controllers/map/leaflet_controller.rb b/app/controllers/map/leaflet_controller.rb index 2c5e2672..660b9615 100644 --- a/app/controllers/map/leaflet_controller.rb +++ b/app/controllers/map/leaflet_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Map::LeafletController < ApplicationController + include SafeTimestampParser + before_action :authenticate_user! layout 'map', only: :index @@ -71,14 +73,14 @@ class Map::LeafletController < ApplicationController end def start_at - return Time.zone.parse(params[:start_at]).to_i if params[:start_at].present? + return safe_timestamp(params[:start_at]) if params[:start_at].present? return Time.zone.at(points.last.timestamp).beginning_of_day.to_i if points.any? Time.zone.today.beginning_of_day.to_i end def end_at - return Time.zone.parse(params[:end_at]).to_i if params[:end_at].present? + return safe_timestamp(params[:end_at]) if params[:end_at].present? return Time.zone.at(points.last.timestamp).end_of_day.to_i if points.any? Time.zone.today.end_of_day.to_i diff --git a/app/controllers/map/maplibre_controller.rb b/app/controllers/map/maplibre_controller.rb index 529242d5..b11bc1e8 100644 --- a/app/controllers/map/maplibre_controller.rb +++ b/app/controllers/map/maplibre_controller.rb @@ -1,5 +1,7 @@ module Map class MaplibreController < ApplicationController + include SafeTimestampParser + before_action :authenticate_user! layout 'map' @@ -11,13 +13,13 @@ module Map private def start_at - return Time.zone.parse(params[:start_at]).to_i if params[:start_at].present? + return safe_timestamp(params[:start_at]) if params[:start_at].present? Time.zone.today.beginning_of_day.to_i end def end_at - return Time.zone.parse(params[:end_at]).to_i if params[:end_at].present? + return safe_timestamp(params[:end_at]) if params[:end_at].present? Time.zone.today.end_of_day.to_i end diff --git a/app/controllers/points_controller.rb b/app/controllers/points_controller.rb index 65d99698..87cdd1a4 100644 --- a/app/controllers/points_controller.rb +++ b/app/controllers/points_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class PointsController < ApplicationController + include SafeTimestampParser + before_action :authenticate_user! def index @@ -40,13 +42,13 @@ class PointsController < ApplicationController def start_at return 1.month.ago.beginning_of_day.to_i if params[:start_at].nil? - Time.zone.parse(params[:start_at]).to_i + safe_timestamp(params[:start_at]) end def end_at return Time.zone.today.end_of_day.to_i if params[:end_at].nil? - Time.zone.parse(params[:end_at]).to_i + safe_timestamp(params[:end_at]) end def points diff --git a/lib/timestamps.rb b/lib/timestamps.rb index 2154a3ef..59273d59 100644 --- a/lib/timestamps.rb +++ b/lib/timestamps.rb @@ -2,17 +2,20 @@ module Timestamps def self.parse_timestamp(timestamp) - begin - # if the timestamp is in ISO 8601 format, try to parse it - DateTime.parse(timestamp).to_time.to_i - rescue + min_timestamp = Time.zone.parse('1970-01-01').to_i + max_timestamp = Time.zone.parse('2100-01-01').to_i + + parsed = DateTime.parse(timestamp).to_time.to_i + + parsed.clamp(min_timestamp, max_timestamp) + rescue StandardError + result = if timestamp.to_s.length > 10 - # If the timestamp is in milliseconds, convert to seconds timestamp.to_i / 1000 else - # If the timestamp is in seconds, return it without change timestamp.to_i end - end + + result.clamp(min_timestamp, max_timestamp) end end diff --git a/spec/controllers/concerns/safe_timestamp_parser_spec.rb b/spec/controllers/concerns/safe_timestamp_parser_spec.rb new file mode 100644 index 00000000..b5248468 --- /dev/null +++ b/spec/controllers/concerns/safe_timestamp_parser_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe SafeTimestampParser, type: :controller do + include ActiveSupport::Testing::TimeHelpers + + controller(ActionController::Base) do + include SafeTimestampParser + + def index + render plain: safe_timestamp(params[:date]).to_s + end + end + + before do + routes.draw { get 'index' => 'anonymous#index' } + end + + describe '#safe_timestamp' do + context 'with valid dates within range' do + it 'returns correct timestamp for 2020-01-01' do + get :index, params: { date: '2020-01-01' } + expected = Time.zone.parse('2020-01-01').to_i + expect(response.body).to eq(expected.to_s) + end + + it 'returns correct timestamp for 1980-06-15' do + get :index, params: { date: '1980-06-15' } + expected = Time.zone.parse('1980-06-15').to_i + expect(response.body).to eq(expected.to_s) + end + end + + context 'with dates before valid range' do + it 'clamps year 1000 to minimum timestamp (1970-01-01)' do + get :index, params: { date: '1000-01-30' } + min_timestamp = Time.zone.parse('1970-01-01').to_i + expect(response.body).to eq(min_timestamp.to_s) + end + + it 'clamps year 1900 to minimum timestamp (1970-01-01)' do + get :index, params: { date: '1900-12-25' } + min_timestamp = Time.zone.parse('1970-01-01').to_i + expect(response.body).to eq(min_timestamp.to_s) + end + + it 'clamps year 1969 to minimum timestamp (1970-01-01)' do + get :index, params: { date: '1969-07-20' } + min_timestamp = Time.zone.parse('1970-01-01').to_i + expect(response.body).to eq(min_timestamp.to_s) + end + end + + context 'with dates after valid range' do + it 'clamps year 2150 to maximum timestamp (2100-01-01)' do + get :index, params: { date: '2150-01-01' } + max_timestamp = Time.zone.parse('2100-01-01').to_i + expect(response.body).to eq(max_timestamp.to_s) + end + + it 'clamps year 3000 to maximum timestamp (2100-01-01)' do + get :index, params: { date: '3000-12-31' } + max_timestamp = Time.zone.parse('2100-01-01').to_i + expect(response.body).to eq(max_timestamp.to_s) + end + end + + context 'with invalid date strings' do + it 'returns current time for unparseable date' do + travel_to Time.zone.parse('2023-06-15 12:00:00') do + get :index, params: { date: 'not-a-date' } + expected = Time.zone.now.to_i + expect(response.body).to eq(expected.to_s) + end + end + + it 'returns current time for empty string' do + travel_to Time.zone.parse('2023-06-15 12:00:00') do + get :index, params: { date: '' } + expected = Time.zone.now.to_i + expect(response.body).to eq(expected.to_s) + end + end + end + + context 'edge cases' do + it 'handles Unix epoch exactly (1970-01-01)' do + get :index, params: { date: '1970-01-01' } + expected = Time.zone.parse('1970-01-01').to_i + expect(response.body).to eq(expected.to_s) + end + + it 'handles maximum date exactly (2100-01-01)' do + get :index, params: { date: '2100-01-01' } + expected = Time.zone.parse('2100-01-01').to_i + expect(response.body).to eq(expected.to_s) + end + end + end +end diff --git a/spec/fixtures/files/geojson/export_same_points.json b/spec/fixtures/files/geojson/export_same_points.json index 4afe6201..4f21d3cf 100644 --- a/spec/fixtures/files/geojson/export_same_points.json +++ b/spec/fixtures/files/geojson/export_same_points.json @@ -1 +1 @@ -{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459200,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null,"timestamp_year":null,"timestamp_month":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459201,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null,"timestamp_year":null,"timestamp_month":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459202,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null,"timestamp_year":null,"timestamp_month":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459203,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null,"timestamp_year":null,"timestamp_month":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459204,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null,"timestamp_year":null,"timestamp_month":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459205,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null,"timestamp_year":null,"timestamp_month":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459206,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null,"timestamp_year":null,"timestamp_month":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459207,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null,"timestamp_year":null,"timestamp_month":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459208,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null,"timestamp_year":null,"timestamp_month":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459209,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null,"timestamp_year":null,"timestamp_month":null}}]} \ No newline at end of file +{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459200,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459201,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459202,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459203,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459204,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459205,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459206,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459207,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459208,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.6173,55.755826]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459209,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{},"course":null,"course_accuracy":null,"external_track_id":null,"track_id":null,"country_name":null,"raw_data_archived":false,"raw_data_archive_id":null}}]} diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index c11017e6..7eac9400 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -199,8 +199,8 @@ RSpec.describe User, type: :model do describe '#total_distance' do subject { user.total_distance } - let!(:stat1) { create(:stat, user:, distance: 10_000) } - let!(:stat2) { create(:stat, user:, distance: 20_000) } + let!(:stat1) { create(:stat, user:, year: 2020, month: 10, distance: 10_000) } + let!(:stat2) { create(:stat, user:, year: 2020, month: 11, distance: 20_000) } it 'returns sum of distances' do expect(subject).to eq(30) # 30 km @@ -341,14 +341,16 @@ RSpec.describe User, type: :model do describe '.from_omniauth' do let(:auth_hash) do - OmniAuth::AuthHash.new({ - provider: 'github', - uid: '123545', - info: { - email: email, - name: 'Test User' + OmniAuth::AuthHash.new( + { + provider: 'github', + uid: '123545', + info: { + email: email, + name: 'Test User' + } } - }) + ) end context 'when user exists with the same email' do @@ -394,14 +396,16 @@ RSpec.describe User, type: :model do context 'when OAuth provider is Google' do let(:email) { 'google@example.com' } let(:auth_hash) do - OmniAuth::AuthHash.new({ - provider: 'google_oauth2', - uid: '123545', - info: { - email: email, - name: 'Google User' + OmniAuth::AuthHash.new( + { + provider: 'google_oauth2', + uid: '123545', + info: { + email: email, + name: 'Google User' + } } - }) + ) end it 'creates a user from Google OAuth data' do diff --git a/spec/requests/api/v1/stats_spec.rb b/spec/requests/api/v1/stats_spec.rb index 314c375e..a7765d85 100644 --- a/spec/requests/api/v1/stats_spec.rb +++ b/spec/requests/api/v1/stats_spec.rb @@ -5,8 +5,8 @@ require 'rails_helper' RSpec.describe 'Api::V1::Stats', type: :request do describe 'GET /index' do let(:user) { create(:user) } - let(:stats_in_2020) { create_list(:stat, 12, year: 2020, user:) } - let(:stats_in_2021) { create_list(:stat, 12, year: 2021, user:) } + let(:stats_in_2020) { (1..12).map { |month| create(:stat, year: 2020, month:, user:) } } + let(:stats_in_2021) { (1..12).map { |month| create(:stat, year: 2021, month:, user:) } } let(:points_in_2020) do (1..85).map do |i| create(:point, :with_geodata, @@ -50,17 +50,17 @@ RSpec.describe 'Api::V1::Stats', type: :request do totalCitiesVisited: 1, monthlyDistanceKm: { january: 1, - february: 0, - march: 0, - april: 0, - may: 0, - june: 0, - july: 0, - august: 0, - september: 0, - october: 0, - november: 0, - december: 0 + february: 1, + march: 1, + april: 1, + may: 1, + june: 1, + july: 1, + august: 1, + september: 1, + october: 1, + november: 1, + december: 1 } }, { @@ -70,17 +70,17 @@ RSpec.describe 'Api::V1::Stats', type: :request do totalCitiesVisited: 1, monthlyDistanceKm: { january: 1, - february: 0, - march: 0, - april: 0, - may: 0, - june: 0, - july: 0, - august: 0, - september: 0, - october: 0, - november: 0, - december: 0 + february: 1, + march: 1, + april: 1, + may: 1, + june: 1, + july: 1, + august: 1, + september: 1, + october: 1, + november: 1, + december: 1 } } ] @@ -100,4 +100,3 @@ RSpec.describe 'Api::V1::Stats', type: :request do end end end - diff --git a/spec/serializers/point_serializer_spec.rb b/spec/serializers/point_serializer_spec.rb index fa3d37d5..4042b2aa 100644 --- a/spec/serializers/point_serializer_spec.rb +++ b/spec/serializers/point_serializer_spec.rb @@ -37,9 +37,7 @@ RSpec.describe PointSerializer do 'track_id' => point.track_id, 'country_name' => point.read_attribute(:country_name), 'raw_data_archived' => point.raw_data_archived, - 'raw_data_archive_id' => point.raw_data_archive_id, - 'timestamp_year' => point.timestamp_year, - 'timestamp_month' => point.timestamp_month + 'raw_data_archive_id' => point.raw_data_archive_id } end diff --git a/spec/serializers/stats_serializer_spec.rb b/spec/serializers/stats_serializer_spec.rb index 7198f48f..af394752 100644 --- a/spec/serializers/stats_serializer_spec.rb +++ b/spec/serializers/stats_serializer_spec.rb @@ -26,8 +26,8 @@ RSpec.describe StatsSerializer do end context 'when the user has stats' do - let!(:stats_in_2020) { create_list(:stat, 12, year: 2020, user:) } - let!(:stats_in_2021) { create_list(:stat, 12, year: 2021, user:) } + let!(:stats_in_2020) { (1..12).map { |month| create(:stat, year: 2020, month:, user:) } } + let!(:stats_in_2021) { (1..12).map { |month| create(:stat, year: 2021, month:, user:) } } let!(:points_in_2020) do (1..85).map do |i| create(:point, :with_geodata, @@ -63,17 +63,17 @@ RSpec.describe StatsSerializer do "totalCitiesVisited": 1, "monthlyDistanceKm": { "january": 1, - "february": 0, - "march": 0, - "april": 0, - "may": 0, - "june": 0, - "july": 0, - "august": 0, - "september": 0, - "october": 0, - "november": 0, - "december": 0 + "february": 1, + "march": 1, + "april": 1, + "may": 1, + "june": 1, + "july": 1, + "august": 1, + "september": 1, + "october": 1, + "november": 1, + "december": 1 } }, { @@ -83,17 +83,17 @@ RSpec.describe StatsSerializer do "totalCitiesVisited": 1, "monthlyDistanceKm": { "january": 1, - "february": 0, - "march": 0, - "april": 0, - "may": 0, - "june": 0, - "july": 0, - "august": 0, - "september": 0, - "october": 0, - "november": 0, - "december": 0 + "february": 1, + "march": 1, + "april": 1, + "may": 1, + "june": 1, + "july": 1, + "august": 1, + "september": 1, + "october": 1, + "november": 1, + "december": 1 } } ] diff --git a/spec/swagger/api/v1/stats_controller_spec.rb b/spec/swagger/api/v1/stats_controller_spec.rb index b1fda703..a3ec8a2f 100644 --- a/spec/swagger/api/v1/stats_controller_spec.rb +++ b/spec/swagger/api/v1/stats_controller_spec.rb @@ -55,8 +55,8 @@ describe 'Stats API', type: :request do ] let!(:user) { create(:user) } - let!(:stats_in_2020) { create_list(:stat, 12, year: 2020, user:) } - let!(:stats_in_2021) { create_list(:stat, 12, year: 2021, user:) } + let!(:stats_in_2020) { (1..12).map { |month| create(:stat, year: 2020, month:, user:) } } + let!(:stats_in_2021) { (1..12).map { |month| create(:stat, year: 2021, month:, user:) } } let!(:points_in_2020) do (1..85).map do |i| create(:point, :with_geodata, :reverse_geocoded, timestamp: Time.zone.local(2020, 1, 1).to_i + i.hours,