mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
Clean up some mess
This commit is contained in:
parent
708bca26eb
commit
f969d5d3e6
22 changed files with 325 additions and 260 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
22
app/serializers/tracks_serializer.rb
Normal file
22
app/serializers/tracks_serializer.rb
Normal 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
|
||||
|
|
@ -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'])
|
||||
update_visits_name(properties) if properties['name'].present?
|
||||
|
||||
place
|
||||
end
|
||||
end
|
||||
|
||||
@place
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,6 @@ class Users::SafeSettings
|
|||
end
|
||||
|
||||
def distance_unit
|
||||
# km or mi
|
||||
settings.dig('maps', 'distance_unit')
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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|
|
||||
|
|
|
|||
|
|
@ -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 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(
|
||||
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 'serializes the track ID correctly' do
|
||||
expect(serialized_track[:id]).to eq(track.id)
|
||||
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)
|
||||
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 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 '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 '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 '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
|
||||
|
||||
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
|
||||
context 'with decimal avg_speed values' do
|
||||
let(:track) { create(:track, user: user, avg_speed: 25.75) }
|
||||
|
||||
it 'converts avg_speed to float' do
|
||||
expect(serialized_track[:avg_speed]).to eq(25.75)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when track IDs belong to different users' do
|
||||
subject(:serializer) { described_class.new(user, track_ids).call }
|
||||
context 'with different original_path formats' do
|
||||
let(:track) { create(:track, user: user, original_path: 'LINESTRING(0 0, 1 1, 2 2)') }
|
||||
|
||||
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 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 array is empty' do
|
||||
subject(:serializer) { described_class.new(user, []).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
|
||||
|
||||
it 'returns an empty array' do
|
||||
expect(serializer).to eq([])
|
||||
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
|
||||
|
||||
context 'when track IDs contain non-existent IDs' do
|
||||
subject(:serializer) { described_class.new(user, track_ids).call }
|
||||
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
|
||||
|
||||
let!(:existing_track) { create(:track, user: user) }
|
||||
let(:track_ids) { [existing_track.id, 999999] }
|
||||
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
|
||||
|
||||
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 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
|
||||
|
|
|
|||
99
spec/serializers/tracks_serializer_spec.rb
Normal file
99
spec/serializers/tracks_serializer_spec.rb
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
RSpec.configure do |config|
|
||||
config.before(:each) do
|
||||
# Clear the cache before each test
|
||||
Rails.cache.clear
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue