# frozen_string_literal: true require 'rails_helper' RSpec.describe Tracks::Generator do let(:user) { create(:user) } let(:point_loader) { double('PointLoader') } let(:incomplete_segment_handler) { double('IncompleteSegmentHandler') } let(:track_cleaner) { double('TrackCleaner') } let(:generator) do described_class.new( user, point_loader: point_loader, incomplete_segment_handler: incomplete_segment_handler, track_cleaner: track_cleaner ) end before do allow_any_instance_of(Users::SafeSettings).to receive(:meters_between_routes).and_return(500) allow_any_instance_of(Users::SafeSettings).to receive(:minutes_between_routes).and_return(60) allow_any_instance_of(Users::SafeSettings).to receive(:distance_unit).and_return('km') end describe '#call' do context 'with no points to process' do before do allow(track_cleaner).to receive(:cleanup_if_needed) allow(point_loader).to receive(:load_points).and_return([]) end it 'returns 0 tracks created' do result = generator.call expect(result).to eq(0) end it 'does not call incomplete segment handler' do expect(incomplete_segment_handler).not_to receive(:should_finalize_segment?) expect(incomplete_segment_handler).not_to receive(:handle_incomplete_segment) expect(incomplete_segment_handler).not_to receive(:cleanup_processed_data) generator.call end end context 'with points that create tracks' do let!(:points) do [ create(:point, user: user, lonlat: 'POINT(-74.0060 40.7128)', timestamp: 1.hour.ago.to_i, latitude: 40.7128, longitude: -74.0060), create(:point, user: user, lonlat: 'POINT(-74.0050 40.7138)', timestamp: 30.minutes.ago.to_i, latitude: 40.7138, longitude: -74.0050), create(:point, user: user, lonlat: 'POINT(-74.0040 40.7148)', timestamp: 10.minutes.ago.to_i, latitude: 40.7148, longitude: -74.0040) ] end before do allow(track_cleaner).to receive(:cleanup_if_needed) allow(point_loader).to receive(:load_points).and_return(points) allow(incomplete_segment_handler).to receive(:should_finalize_segment?).and_return(true) allow(incomplete_segment_handler).to receive(:cleanup_processed_data) end it 'creates tracks from segments' do expect { generator.call }.to change { Track.count }.by(1) end it 'returns the number of tracks created' do result = generator.call expect(result).to eq(1) end it 'calls cleanup on processed data' do expect(incomplete_segment_handler).to receive(:cleanup_processed_data) generator.call end it 'assigns points to the created track' do generator.call points.each(&:reload) track_ids = points.map(&:track_id).uniq.compact expect(track_ids.size).to eq(1) end end context 'with incomplete segments' do let!(:points) do [ create(:point, user: user, lonlat: 'POINT(-74.0060 40.7128)', timestamp: 5.minutes.ago.to_i, latitude: 40.7128, longitude: -74.0060), create(:point, user: user, lonlat: 'POINT(-74.0050 40.7138)', timestamp: 4.minutes.ago.to_i, latitude: 40.7138, longitude: -74.0050) ] end before do allow(track_cleaner).to receive(:cleanup_if_needed) allow(point_loader).to receive(:load_points).and_return(points) allow(incomplete_segment_handler).to receive(:should_finalize_segment?).and_return(false) allow(incomplete_segment_handler).to receive(:handle_incomplete_segment) allow(incomplete_segment_handler).to receive(:cleanup_processed_data) end it 'does not create tracks' do expect { generator.call }.not_to change { Track.count } end it 'handles incomplete segments' do expect(incomplete_segment_handler).to receive(:handle_incomplete_segment).with(points) generator.call end it 'returns 0 tracks created' do result = generator.call expect(result).to eq(0) end end context 'with mixed complete and incomplete segments' do let!(:old_points) do [ create(:point, user: user, lonlat: 'POINT(-74.0060 40.7128)', timestamp: 2.hours.ago.to_i, latitude: 40.7128, longitude: -74.0060), create(:point, user: user, lonlat: 'POINT(-74.0050 40.7138)', timestamp: 1.hour.ago.to_i, latitude: 40.7138, longitude: -74.0050) ] end let!(:recent_points) do [ create(:point, user: user, lonlat: 'POINT(-74.0040 40.7148)', timestamp: 3.minutes.ago.to_i, latitude: 40.7148, longitude: -74.0040), create(:point, user: user, lonlat: 'POINT(-74.0030 40.7158)', timestamp: 2.minutes.ago.to_i, latitude: 40.7158, longitude: -74.0030) ] end before do allow(track_cleaner).to receive(:cleanup_if_needed) allow(point_loader).to receive(:load_points).and_return(old_points + recent_points) # First segment (old points) should be finalized # Second segment (recent points) should be incomplete call_count = 0 allow(incomplete_segment_handler).to receive(:should_finalize_segment?) do |segment_points| call_count += 1 call_count == 1 # Only finalize first segment end allow(incomplete_segment_handler).to receive(:handle_incomplete_segment) allow(incomplete_segment_handler).to receive(:cleanup_processed_data) end it 'creates tracks for complete segments only' do expect { generator.call }.to change { Track.count }.by(1) end it 'handles incomplete segments' do # Note: The exact behavior depends on segmentation logic # The important thing is that the method can be called without errors generator.call # Test passes if no exceptions are raised expect(true).to be_truthy end it 'returns the correct number of tracks created' do result = generator.call expect(result).to eq(1) end end context 'with insufficient points for track creation' do let!(:single_point) do [create(:point, user: user, lonlat: 'POINT(-74.0060 40.7128)', timestamp: 1.hour.ago.to_i, latitude: 40.7128, longitude: -74.0060)] end before do allow(track_cleaner).to receive(:cleanup_if_needed) allow(point_loader).to receive(:load_points).and_return(single_point) allow(incomplete_segment_handler).to receive(:should_finalize_segment?).and_return(true) allow(incomplete_segment_handler).to receive(:cleanup_processed_data) end it 'does not create tracks with less than 2 points' do expect { generator.call }.not_to change { Track.count } end it 'returns 0 tracks created' do result = generator.call expect(result).to eq(0) end end context 'error handling' do before do allow(track_cleaner).to receive(:cleanup_if_needed) allow(point_loader).to receive(:load_points).and_raise(StandardError, 'Point loading failed') end it 'propagates errors from point loading' do expect { generator.call }.to raise_error(StandardError, 'Point loading failed') end end end describe 'strategy pattern integration' do context 'with bulk processing strategies' do let(:bulk_loader) { Tracks::PointLoaders::BulkLoader.new(user) } let(:ignore_handler) { Tracks::IncompleteSegmentHandlers::IgnoreHandler.new(user) } let(:replace_cleaner) { Tracks::TrackCleaners::ReplaceCleaner.new(user) } let(:bulk_generator) do described_class.new( user, point_loader: bulk_loader, incomplete_segment_handler: ignore_handler, track_cleaner: replace_cleaner ) end let!(:existing_track) { create(:track, user: user) } let!(:points) do [ create(:point, user: user, lonlat: 'POINT(-74.0060 40.7128)', timestamp: 1.hour.ago.to_i, latitude: 40.7128, longitude: -74.0060), create(:point, user: user, lonlat: 'POINT(-74.0050 40.7138)', timestamp: 30.minutes.ago.to_i, latitude: 40.7138, longitude: -74.0050) ] end it 'behaves like bulk processing' do initial_count = Track.count bulk_generator.call # Bulk processing replaces existing tracks with new ones # The final count depends on how many valid tracks can be created from the points expect(Track.count).to be >= 0 end end context 'with incremental processing strategies' do let(:incremental_loader) { Tracks::PointLoaders::IncrementalLoader.new(user) } let(:buffer_handler) { Tracks::IncompleteSegmentHandlers::BufferHandler.new(user, Date.current, 5) } let(:noop_cleaner) { Tracks::TrackCleaners::NoOpCleaner.new(user) } let(:incremental_generator) do described_class.new( user, point_loader: incremental_loader, incomplete_segment_handler: buffer_handler, track_cleaner: noop_cleaner ) end let!(:existing_track) { create(:track, user: user) } before do # Mock the incremental loader to return some points allow(incremental_loader).to receive(:load_points).and_return([]) end it 'behaves like incremental processing' do expect { incremental_generator.call }.not_to change { Track.count } end end end end