Clean up some mess

This commit is contained in:
Eugene Burmakin 2025-07-20 18:57:53 +02:00
parent 708bca26eb
commit f969d5d3e6
22 changed files with 325 additions and 260 deletions

View file

@ -31,7 +31,8 @@ class MapController < ApplicationController
def build_tracks
track_ids = extract_track_ids
TrackSerializer.new(current_user, track_ids).call
TracksSerializer.new(current_user, track_ids).call
end
def calculate_distance

View file

@ -6,20 +6,7 @@ class Tracks::CreateJob < ApplicationJob
def perform(user_id, start_at: nil, end_at: nil, mode: :daily)
user = User.find(user_id)
# Translate mode parameter to Generator mode
generator_mode = case mode
when :daily then :daily
when :none then :incremental
else :bulk
end
# Generate tracks and get the count of tracks created
tracks_created = Tracks::Generator.new(
user,
start_at: start_at,
end_at: end_at,
mode: generator_mode
).call
tracks_created = Tracks::Generator.new(user, start_at:, end_at:, mode:).call
create_success_notification(user, tracks_created)
rescue StandardError => e

View file

@ -19,7 +19,6 @@
# track.distance # => 5000 (meters stored in DB)
# track.distance_in_unit('km') # => 5.0 (converted to km)
# track.distance_in_unit('mi') # => 3.11 (converted to miles)
# track.formatted_distance('km') # => "5.0 km"
#
module DistanceConvertible
extend ActiveSupport::Concern
@ -38,21 +37,11 @@ module DistanceConvertible
distance.to_f / conversion_factor
end
def formatted_distance(unit, precision: 2)
converted_distance = distance_in_unit(unit)
"#{converted_distance.round(precision)} #{unit}"
end
def distance_for_user(user)
user_unit = user.safe_settings.distance_unit
distance_in_unit(user_unit)
end
def formatted_distance_for_user(user, precision: 2)
user_unit = user.safe_settings.distance_unit
formatted_distance(user_unit, precision: precision)
end
module ClassMethods
def convert_distance(distance_meters, unit)
return 0.0 unless distance_meters.present?
@ -66,10 +55,5 @@ module DistanceConvertible
distance_meters.to_f / conversion_factor
end
def format_distance(distance_meters, unit, precision: 2)
converted = convert_distance(distance_meters, unit)
"#{converted.round(precision)} #{unit}"
end
end
end

View file

@ -34,7 +34,7 @@ class Point < ApplicationRecord
after_create :set_country
after_create_commit :broadcast_coordinates
after_create_commit :trigger_incremental_track_generation, if: -> { import_id.nil? }
after_commit :recalculate_track, on: :update
after_commit :recalculate_track, on: :update, if: -> { track.present? }
def self.without_raw_data
select(column_names - ['raw_data'])
@ -99,8 +99,6 @@ class Point < ApplicationRecord
end
def recalculate_track
return unless track.present?
track.recalculate_path_and_distance!
end

View file

@ -1,38 +1,23 @@
# frozen_string_literal: true
class TrackSerializer
def initialize(user, track_ids)
@user = user
@track_ids = track_ids
def initialize(track)
@track = track
end
def call
return [] if track_ids.empty?
tracks = user.tracks
.where(id: track_ids)
.order(start_at: :asc)
tracks.map { |track| serialize_track_data(track) }
end
private
attr_reader :user, :track_ids
def serialize_track_data(track)
{
id: track.id,
start_at: track.start_at.iso8601,
end_at: track.end_at.iso8601,
distance: track.distance.to_i,
avg_speed: track.avg_speed.to_f,
duration: track.duration,
elevation_gain: track.elevation_gain,
elevation_loss: track.elevation_loss,
elevation_max: track.elevation_max,
elevation_min: track.elevation_min,
original_path: track.original_path.to_s
id: @track.id,
start_at: @track.start_at.iso8601,
end_at: @track.end_at.iso8601,
distance: @track.distance.to_i,
avg_speed: @track.avg_speed.to_f,
duration: @track.duration,
elevation_gain: @track.elevation_gain,
elevation_loss: @track.elevation_loss,
elevation_max: @track.elevation_max,
elevation_min: @track.elevation_min,
original_path: @track.original_path.to_s
}
end
end

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
class TracksSerializer
def initialize(user, track_ids)
@user = user
@track_ids = track_ids
end
def call
return [] if track_ids.empty?
tracks = user.tracks
.where(id: track_ids)
.order(start_at: :asc)
tracks.map { |track| TrackSerializer.new(track).call }
end
private
attr_reader :user, :track_ids
end

View file

@ -7,7 +7,7 @@ module Places
end
def call
geodata = Geocoder.search([@place.lat, @place.lon], units: :km, limit: 1, distance_sort: true).first
geodata = Geocoder.search([place.lat, place.lon], units: :km, limit: 1, distance_sort: true).first
return if geodata.blank?
@ -15,21 +15,29 @@ module Places
return if properties.blank?
ActiveRecord::Base.transaction do
@place.name = properties['name'] if properties['name'].present?
@place.city = properties['city'] if properties['city'].present?
@place.country = properties['country'] if properties['country'].present?
@place.geodata = geodata.data if DawarichSettings.store_geodata?
@place.save!
update_place_name(properties, geodata)
if properties['name'].present?
@place
.visits
.where(name: Place::DEFAULT_NAME)
.update_all(name: properties['name'])
end
update_visits_name(properties) if properties['name'].present?
@place
place
end
end
private
attr_reader :place
def update_place_name(properties, geodata)
place.name = properties['name'] if properties['name'].present?
place.city = properties['city'] if properties['city'].present?
place.country = properties['country'] if properties['country'].present?
place.geodata = geodata.data if DawarichSettings.store_geodata?
place.save!
end
def update_visits_name(properties)
place.visits.where(name: Place::DEFAULT_NAME).update_all(name: properties['name'])
end
end
end

View file

@ -48,6 +48,7 @@ class Tracks::Generator
Rails.logger.debug "Generator: created #{segments.size} segments"
tracks_created = 0
segments.each do |segment|
track = create_track_from_segment(segment)
tracks_created += 1 if track
@ -146,10 +147,6 @@ class Tracks::Generator
day.beginning_of_day.to_i..day.end_of_day.to_i
end
def incremental_mode?
mode == :incremental
end
def clean_existing_tracks
case mode
when :bulk then clean_bulk_tracks

View file

@ -36,12 +36,7 @@ class Tracks::IncrementalProcessor
start_at = find_start_time
end_at = find_end_time
Tracks::CreateJob.perform_later(
user.id,
start_at: start_at,
end_at: end_at,
mode: :none
)
Tracks::CreateJob.perform_later(user.id, start_at:, end_at:, mode: :incremental)
end
private

View file

@ -77,7 +77,7 @@ module Tracks::Segmentation
return true if time_diff_seconds > time_threshold_seconds
# Check distance threshold - convert km to meters to match frontend logic
distance_km = calculate_distance_kilometers_between_points(previous_point, current_point)
distance_km = calculate_km_distance_between_points(previous_point, current_point)
distance_meters = distance_km * 1000 # Convert km to meters
return true if distance_meters > distance_threshold_meters
@ -85,7 +85,7 @@ module Tracks::Segmentation
false
end
def calculate_distance_kilometers_between_points(point1, point2)
def calculate_km_distance_between_points(point1, point2)
lat1, lon1 = point_coordinates(point1)
lat2, lon2 = point_coordinates(point2)

View file

@ -113,7 +113,6 @@ class Users::SafeSettings
end
def distance_unit
# km or mi
settings.dig('maps', 'distance_unit')
end

View file

@ -50,7 +50,7 @@
</div>
</div>
<div class="card bg-base-300 w-96 shadow-xl m-5">
<div class="card bg-base-300 w-96 shadow-xl m-5">
<div class="card-body">
<h2 class="card-title">Visits suggestions</h2>
<p>Enable or disable visits suggestions. It's a background task that runs every day at midnight. Disabling it might be useful if you don't want to receive visits suggestions or if you're using the Dawarich iOS app, which has its own visits suggestions.</p>

View file

@ -5,11 +5,11 @@ SELF_HOSTED = ENV.fetch('SELF_HOSTED', 'true') == 'true'
MIN_MINUTES_SPENT_IN_CITY = ENV.fetch('MIN_MINUTES_SPENT_IN_CITY', 60).to_i
DISTANCE_UNITS = {
km: 1000, # to meters
km: 1000, # to meters
mi: 1609.34, # to meters
m: 1, # already in meters
ft: 0.3048, # to meters
yd: 0.9144 # to meters
m: 1, # already in meters
ft: 0.3048, # to meters
yd: 0.9144 # to meters
}.freeze
APP_VERSION = File.read('.app_version').strip

View file

@ -4,13 +4,13 @@ require 'rails_helper'
RSpec.describe AreaVisitsCalculationSchedulingJob, type: :job do
describe '#perform' do
let!(:user) { create(:user) }
let!(:area) { create(:area, user: user) }
let(:user) { create(:user) }
let(:area) { create(:area, user: user) }
it 'calls the AreaVisitsCalculationService' do
expect(AreaVisitsCalculatingJob).to receive(:perform_later).with(user.id)
expect(AreaVisitsCalculatingJob).to receive(:perform_later).with(user.id).and_call_original
described_class.new.perform_now
described_class.new.perform
end
end
end

View file

@ -14,12 +14,10 @@ RSpec.describe Tracks::CreateJob, type: :job do
allow(generator_instance).to receive(:call)
allow(Notifications::Create).to receive(:new).and_return(notification_service)
allow(notification_service).to receive(:call)
allow(generator_instance).to receive(:call).and_return(2)
end
it 'calls the generator and creates a notification' do
# Mock the generator to return the count of tracks created
allow(generator_instance).to receive(:call).and_return(2)
described_class.new.perform(user.id)
expect(Tracks::Generator).to have_received(:new).with(
@ -48,12 +46,10 @@ RSpec.describe Tracks::CreateJob, type: :job do
allow(generator_instance).to receive(:call)
allow(Notifications::Create).to receive(:new).and_return(notification_service)
allow(notification_service).to receive(:call)
allow(generator_instance).to receive(:call).and_return(1)
end
it 'passes custom parameters to the generator' do
# Mock generator to return the count of tracks created
allow(generator_instance).to receive(:call).and_return(1)
described_class.new.perform(user.id, start_at: start_at, end_at: end_at, mode: mode)
expect(Tracks::Generator).to have_received(:new).with(
@ -73,72 +69,6 @@ RSpec.describe Tracks::CreateJob, type: :job do
end
end
context 'with mode translation' do
before do
allow(Tracks::Generator).to receive(:new).and_return(generator_instance)
allow(generator_instance).to receive(:call) # No tracks created for mode tests
allow(Notifications::Create).to receive(:new).and_return(notification_service)
allow(notification_service).to receive(:call)
end
it 'translates :none to :incremental' do
allow(generator_instance).to receive(:call).and_return(0)
described_class.new.perform(user.id, mode: :none)
expect(Tracks::Generator).to have_received(:new).with(
user,
start_at: nil,
end_at: nil,
mode: :incremental
)
expect(Notifications::Create).to have_received(:new).with(
user: user,
kind: :info,
title: 'Tracks Generated',
content: 'Created 0 tracks from your location data. Check your tracks section to view them.'
)
end
it 'translates :daily to :daily' do
allow(generator_instance).to receive(:call).and_return(0)
described_class.new.perform(user.id, mode: :daily)
expect(Tracks::Generator).to have_received(:new).with(
user,
start_at: nil,
end_at: nil,
mode: :daily
)
expect(Notifications::Create).to have_received(:new).with(
user: user,
kind: :info,
title: 'Tracks Generated',
content: 'Created 0 tracks from your location data. Check your tracks section to view them.'
)
end
it 'translates other modes to :bulk' do
allow(generator_instance).to receive(:call).and_return(0)
described_class.new.perform(user.id, mode: :replace)
expect(Tracks::Generator).to have_received(:new).with(
user,
start_at: nil,
end_at: nil,
mode: :bulk
)
expect(Notifications::Create).to have_received(:new).with(
user: user,
kind: :info,
title: 'Tracks Generated',
content: 'Created 0 tracks from your location data. Check your tracks section to view them.'
)
end
end
context 'when generator raises an error' do
let(:error_message) { 'Something went wrong' }
let(:notification_service) { instance_double(Notifications::Create) }
@ -175,12 +105,13 @@ RSpec.describe Tracks::CreateJob, type: :job do
end
context 'when user does not exist' do
it 'handles the error gracefully and creates error notification' do
before do
allow(User).to receive(:find).with(999).and_raise(ActiveRecord::RecordNotFound)
allow(ExceptionReporter).to receive(:call)
allow(Notifications::Create).to receive(:new).and_return(instance_double(Notifications::Create, call: nil))
end
# Should not raise an error because it's caught by the rescue block
it 'handles the error gracefully and creates error notification' do
expect { described_class.new.perform(999) }.not_to raise_error
expect(ExceptionReporter).to have_received(:call)
@ -188,15 +119,14 @@ RSpec.describe Tracks::CreateJob, type: :job do
end
context 'when tracks are deleted and recreated' do
it 'returns the correct count of newly created tracks' do
# Create some existing tracks first
create_list(:track, 3, user: user)
let(:existing_tracks) { create_list(:track, 3, user: user) }
# Mock the generator to simulate deleting existing tracks and creating new ones
# This should return the count of newly created tracks, not the difference
before do
allow(generator_instance).to receive(:call).and_return(2)
end
described_class.new.perform(user.id, mode: :bulk)
it 'returns the correct count of newly created tracks' do
described_class.new.perform(user.id, mode: :incremental)
expect(Tracks::Generator).to have_received(:new).with(
user,

View file

@ -160,7 +160,7 @@ RSpec.describe Trip, type: :model do
end
end
describe '#recalculate_distance!' do
describe '#recalculate_distance!' do
it 'recalculates and saves the distance' do
original_distance = trip.distance

View file

@ -41,9 +41,6 @@ RSpec.configure do |config|
config.before(:suite) do
Rails.application.reload_routes!
# DatabaseCleaner.strategy = :transaction
# DatabaseCleaner.clean_with(:truncation)
end
config.before do
@ -92,12 +89,6 @@ RSpec.configure do |config|
config.after(:suite) do
Rake::Task['rswag:generate'].invoke
end
# config.around(:each) do |example|
# DatabaseCleaner.cleaning do
# example.run
# end
# end
end
Shoulda::Matchers.configure do |config|

View file

@ -5,95 +5,166 @@ require 'rails_helper'
RSpec.describe TrackSerializer do
describe '#call' do
let(:user) { create(:user) }
let(:track) { create(:track, user: user) }
let(:serializer) { described_class.new(track) }
context 'when serializing user tracks with track IDs' do
subject(:serializer) { described_class.new(user, track_ids).call }
subject(:serialized_track) { serializer.call }
let!(:track1) { create(:track, user: user, start_at: 2.hours.ago, end_at: 1.hour.ago) }
let!(:track2) { create(:track, user: user, start_at: 4.hours.ago, end_at: 3.hours.ago) }
let!(:track3) { create(:track, user: user, start_at: 6.hours.ago, end_at: 5.hours.ago) }
let(:track_ids) { [track1.id, track2.id] }
it 'returns a hash with all required attributes' do
expect(serialized_track).to be_a(Hash)
expect(serialized_track.keys).to contain_exactly(
:id, :start_at, :end_at, :distance, :avg_speed, :duration,
:elevation_gain, :elevation_loss, :elevation_max, :elevation_min, :original_path
)
end
it 'returns an array of serialized tracks' do
expect(serializer).to be_an(Array)
expect(serializer.length).to eq(2)
end
it 'serializes the track ID correctly' do
expect(serialized_track[:id]).to eq(track.id)
end
it 'serializes each track correctly' do
serialized_ids = serializer.map { |track| track[:id] }
expect(serialized_ids).to contain_exactly(track1.id, track2.id)
expect(serialized_ids).not_to include(track3.id)
end
it 'formats start_at as ISO8601 timestamp' do
expect(serialized_track[:start_at]).to eq(track.start_at.iso8601)
expect(serialized_track[:start_at]).to match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
end
it 'formats timestamps as ISO8601 for all tracks' do
serializer.each do |track|
expect(track[:start_at]).to match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
expect(track[:end_at]).to match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
end
end
it 'formats end_at as ISO8601 timestamp' do
expect(serialized_track[:end_at]).to eq(track.end_at.iso8601)
expect(serialized_track[:end_at]).to match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
end
it 'includes all required fields for each track' do
serializer.each do |track|
expect(track.keys).to contain_exactly(
:id, :start_at, :end_at, :distance, :avg_speed, :duration,
:elevation_gain, :elevation_loss, :elevation_max, :elevation_min, :original_path
)
end
end
it 'converts distance to integer' do
expect(serialized_track[:distance]).to eq(track.distance.to_i)
expect(serialized_track[:distance]).to be_a(Integer)
end
it 'handles numeric values correctly' do
serializer.each do |track|
expect(track[:distance]).to be_a(Numeric)
expect(track[:avg_speed]).to be_a(Numeric)
expect(track[:duration]).to be_a(Numeric)
expect(track[:elevation_gain]).to be_a(Numeric)
expect(track[:elevation_loss]).to be_a(Numeric)
expect(track[:elevation_max]).to be_a(Numeric)
expect(track[:elevation_min]).to be_a(Numeric)
end
end
it 'converts avg_speed to float' do
expect(serialized_track[:avg_speed]).to eq(track.avg_speed.to_f)
expect(serialized_track[:avg_speed]).to be_a(Float)
end
it 'orders tracks by start_at in ascending order' do
serialized_tracks = serializer
expect(serialized_tracks.first[:id]).to eq(track2.id) # Started 4 hours ago
expect(serialized_tracks.second[:id]).to eq(track1.id) # Started 2 hours ago
it 'serializes duration as numeric value' do
expect(serialized_track[:duration]).to eq(track.duration)
expect(serialized_track[:duration]).to be_a(Numeric)
end
it 'serializes elevation_gain as numeric value' do
expect(serialized_track[:elevation_gain]).to eq(track.elevation_gain)
expect(serialized_track[:elevation_gain]).to be_a(Numeric)
end
it 'serializes elevation_loss as numeric value' do
expect(serialized_track[:elevation_loss]).to eq(track.elevation_loss)
expect(serialized_track[:elevation_loss]).to be_a(Numeric)
end
it 'serializes elevation_max as numeric value' do
expect(serialized_track[:elevation_max]).to eq(track.elevation_max)
expect(serialized_track[:elevation_max]).to be_a(Numeric)
end
it 'serializes elevation_min as numeric value' do
expect(serialized_track[:elevation_min]).to eq(track.elevation_min)
expect(serialized_track[:elevation_min]).to be_a(Numeric)
end
it 'converts original_path to string' do
expect(serialized_track[:original_path]).to eq(track.original_path.to_s)
expect(serialized_track[:original_path]).to be_a(String)
end
context 'with decimal distance values' do
let(:track) { create(:track, user: user, distance: 1234.56) }
it 'truncates distance to integer' do
expect(serialized_track[:distance]).to eq(1234)
end
end
context 'when track IDs belong to different users' do
subject(:serializer) { described_class.new(user, track_ids).call }
context 'with decimal avg_speed values' do
let(:track) { create(:track, user: user, avg_speed: 25.75) }
let(:other_user) { create(:user) }
let!(:user_track) { create(:track, user: user) }
let!(:other_user_track) { create(:track, user: other_user) }
let(:track_ids) { [user_track.id, other_user_track.id] }
it 'only returns tracks belonging to the specified user' do
serialized_ids = serializer.map { |track| track[:id] }
expect(serialized_ids).to contain_exactly(user_track.id)
expect(serialized_ids).not_to include(other_user_track.id)
it 'converts avg_speed to float' do
expect(serialized_track[:avg_speed]).to eq(25.75)
end
end
context 'when track IDs array is empty' do
subject(:serializer) { described_class.new(user, []).call }
context 'with different original_path formats' do
let(:track) { create(:track, user: user, original_path: 'LINESTRING(0 0, 1 1, 2 2)') }
it 'returns an empty array' do
expect(serializer).to eq([])
it 'converts geometry to WKT string format' do
expect(serialized_track[:original_path]).to eq('LINESTRING (0 0, 1 1, 2 2)')
expect(serialized_track[:original_path]).to be_a(String)
end
end
context 'when track IDs contain non-existent IDs' do
subject(:serializer) { described_class.new(user, track_ids).call }
context 'with zero values' do
let(:track) do
create(:track, user: user,
distance: 0,
avg_speed: 0.0,
duration: 0,
elevation_gain: 0,
elevation_loss: 0,
elevation_max: 0,
elevation_min: 0)
end
let!(:existing_track) { create(:track, user: user) }
let(:track_ids) { [existing_track.id, 999999] }
it 'handles zero values correctly' do
expect(serialized_track[:distance]).to eq(0)
expect(serialized_track[:avg_speed]).to eq(0.0)
expect(serialized_track[:duration]).to eq(0)
expect(serialized_track[:elevation_gain]).to eq(0)
expect(serialized_track[:elevation_loss]).to eq(0)
expect(serialized_track[:elevation_max]).to eq(0)
expect(serialized_track[:elevation_min]).to eq(0)
end
end
it 'only returns existing tracks' do
serialized_ids = serializer.map { |track| track[:id] }
expect(serialized_ids).to contain_exactly(existing_track.id)
expect(serializer.length).to eq(1)
context 'with very large values' do
let(:track) do
create(:track, user: user,
distance: 1_000_000.0,
avg_speed: 999.99,
duration: 86_400, # 24 hours in seconds
elevation_gain: 10_000,
elevation_loss: 8_000,
elevation_max: 5_000,
elevation_min: 0)
end
it 'handles large values correctly' do
expect(serialized_track[:distance]).to eq(1_000_000)
expect(serialized_track[:avg_speed]).to eq(999.99)
expect(serialized_track[:duration]).to eq(86_400)
expect(serialized_track[:elevation_gain]).to eq(10_000)
expect(serialized_track[:elevation_loss]).to eq(8_000)
expect(serialized_track[:elevation_max]).to eq(5_000)
expect(serialized_track[:elevation_min]).to eq(0)
end
end
context 'with different timestamp formats' do
let(:start_time) { Time.current }
let(:end_time) { start_time + 1.hour }
let(:track) { create(:track, user: user, start_at: start_time, end_at: end_time) }
it 'formats timestamps consistently' do
expect(serialized_track[:start_at]).to eq(start_time.iso8601)
expect(serialized_track[:end_at]).to eq(end_time.iso8601)
end
end
end
describe '#initialize' do
let(:track) { create(:track) }
it 'accepts a track parameter' do
expect { described_class.new(track) }.not_to raise_error
end
it 'stores the track instance' do
serializer = described_class.new(track)
expect(serializer.instance_variable_get(:@track)).to eq(track)
end
end
end

View file

@ -0,0 +1,99 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe TracksSerializer do
describe '#call' do
let(:user) { create(:user) }
context 'when serializing user tracks with track IDs' do
subject(:serializer) { described_class.new(user, track_ids).call }
let!(:track1) { create(:track, user: user, start_at: 2.hours.ago, end_at: 1.hour.ago) }
let!(:track2) { create(:track, user: user, start_at: 4.hours.ago, end_at: 3.hours.ago) }
let!(:track3) { create(:track, user: user, start_at: 6.hours.ago, end_at: 5.hours.ago) }
let(:track_ids) { [track1.id, track2.id] }
it 'returns an array of serialized tracks' do
expect(serializer).to be_an(Array)
expect(serializer.length).to eq(2)
end
it 'serializes each track correctly' do
serialized_ids = serializer.map { |track| track[:id] }
expect(serialized_ids).to contain_exactly(track1.id, track2.id)
expect(serialized_ids).not_to include(track3.id)
end
it 'formats timestamps as ISO8601 for all tracks' do
serializer.each do |track|
expect(track[:start_at]).to match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
expect(track[:end_at]).to match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
end
end
it 'includes all required fields for each track' do
serializer.each do |track|
expect(track.keys).to contain_exactly(
:id, :start_at, :end_at, :distance, :avg_speed, :duration,
:elevation_gain, :elevation_loss, :elevation_max, :elevation_min, :original_path
)
end
end
it 'handles numeric values correctly' do
serializer.each do |track|
expect(track[:distance]).to be_a(Numeric)
expect(track[:avg_speed]).to be_a(Numeric)
expect(track[:duration]).to be_a(Numeric)
expect(track[:elevation_gain]).to be_a(Numeric)
expect(track[:elevation_loss]).to be_a(Numeric)
expect(track[:elevation_max]).to be_a(Numeric)
expect(track[:elevation_min]).to be_a(Numeric)
end
end
it 'orders tracks by start_at in ascending order' do
serialized_tracks = serializer
expect(serialized_tracks.first[:id]).to eq(track2.id) # Started 4 hours ago
expect(serialized_tracks.second[:id]).to eq(track1.id) # Started 2 hours ago
end
end
context 'when track IDs belong to different users' do
subject(:serializer) { described_class.new(user, track_ids).call }
let(:other_user) { create(:user) }
let!(:user_track) { create(:track, user: user) }
let!(:other_user_track) { create(:track, user: other_user) }
let(:track_ids) { [user_track.id, other_user_track.id] }
it 'only returns tracks belonging to the specified user' do
serialized_ids = serializer.map { |track| track[:id] }
expect(serialized_ids).to contain_exactly(user_track.id)
expect(serialized_ids).not_to include(other_user_track.id)
end
end
context 'when track IDs array is empty' do
subject(:serializer) { described_class.new(user, []).call }
it 'returns an empty array' do
expect(serializer).to eq([])
end
end
context 'when track IDs contain non-existent IDs' do
subject(:serializer) { described_class.new(user, track_ids).call }
let!(:existing_track) { create(:track, user: user) }
let(:track_ids) { [existing_track.id, 999999] }
it 'only returns existing tracks' do
serialized_ids = serializer.map { |track| track[:id] }
expect(serialized_ids).to contain_exactly(existing_track.id)
expect(serializer.length).to eq(1)
end
end
end
end

View file

@ -30,7 +30,7 @@ RSpec.describe Tracks::IncrementalProcessor do
it 'processes first point' do
expect(Tracks::CreateJob).to receive(:perform_later)
.with(user.id, start_at: nil, end_at: nil, mode: :none)
.with(user.id, start_at: nil, end_at: nil, mode: :incremental)
processor.call
end
end
@ -47,7 +47,7 @@ RSpec.describe Tracks::IncrementalProcessor do
it 'processes when time threshold exceeded' do
expect(Tracks::CreateJob).to receive(:perform_later)
.with(user.id, start_at: nil, end_at: Time.zone.at(previous_point.timestamp), mode: :none)
.with(user.id, start_at: nil, end_at: Time.zone.at(previous_point.timestamp), mode: :incremental)
processor.call
end
end
@ -65,7 +65,7 @@ RSpec.describe Tracks::IncrementalProcessor do
it 'uses existing track end time as start_at' do
expect(Tracks::CreateJob).to receive(:perform_later)
.with(user.id, start_at: existing_track.end_at, end_at: Time.zone.at(previous_point.timestamp), mode: :none)
.with(user.id, start_at: existing_track.end_at, end_at: Time.zone.at(previous_point.timestamp), mode: :incremental)
processor.call
end
end
@ -88,7 +88,7 @@ RSpec.describe Tracks::IncrementalProcessor do
it 'processes when distance threshold exceeded' do
expect(Tracks::CreateJob).to receive(:perform_later)
.with(user.id, start_at: nil, end_at: Time.zone.at(previous_point.timestamp), mode: :none)
.with(user.id, start_at: nil, end_at: Time.zone.at(previous_point.timestamp), mode: :incremental)
processor.call
end
end

View file

@ -75,7 +75,6 @@ RSpec.describe Visits::Suggest do
end
context 'when reverse geocoding is enabled' do
# Use a different time range to avoid interference with main tests
let(:reverse_geocoding_start_at) { Time.zone.local(2020, 6, 1, 0, 0, 0) }
let(:reverse_geocoding_end_at) { Time.zone.local(2020, 6, 1, 2, 0, 0) }

View file

@ -2,7 +2,6 @@
RSpec.configure do |config|
config.before(:each) do
# Clear the cache before each test
Rails.cache.clear
end
end