mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
Remove tracks api endpoint
This commit is contained in:
parent
565f92c463
commit
1468f1f9dc
14 changed files with 120 additions and 370 deletions
|
|
@ -1,39 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::TracksController < ApiController
|
||||
def index
|
||||
start_time = parse_timestamp(params[:start_at])
|
||||
end_time = parse_timestamp(params[:end_at])
|
||||
|
||||
# Find tracks that overlap with the date range
|
||||
@tracks = current_api_user.tracks
|
||||
.where('start_at <= ? AND end_at >= ?', end_time, start_time)
|
||||
.order(:start_at)
|
||||
|
||||
render json: { tracks: @tracks }
|
||||
end
|
||||
|
||||
def create
|
||||
tracks_created = Tracks::CreateFromPoints.new(current_api_user).call
|
||||
|
||||
render json: {
|
||||
message: "#{tracks_created} tracks created successfully",
|
||||
tracks_created: tracks_created
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_timestamp(timestamp_param)
|
||||
return Time.current if timestamp_param.blank?
|
||||
|
||||
# Handle both Unix timestamps and ISO date strings
|
||||
if timestamp_param.to_s.match?(/^\d+$/)
|
||||
Time.zone.at(timestamp_param.to_i)
|
||||
else
|
||||
Time.zone.parse(timestamp_param)
|
||||
end
|
||||
rescue ArgumentError
|
||||
Time.current
|
||||
end
|
||||
end
|
||||
|
|
@ -4,21 +4,65 @@ class MapController < ApplicationController
|
|||
before_action :authenticate_user!
|
||||
|
||||
def index
|
||||
@points = points.where('timestamp >= ? AND timestamp <= ?', start_at, end_at)
|
||||
|
||||
@coordinates =
|
||||
@points.pluck(:lonlat, :battery, :altitude, :timestamp, :velocity, :id, :country, :track_id)
|
||||
.map { |lonlat, *rest| [lonlat.y, lonlat.x, *rest.map(&:to_s)] }
|
||||
@tracks = TrackSerializer.new(current_user, @coordinates).call
|
||||
@distance = distance
|
||||
@start_at = Time.zone.at(start_at)
|
||||
@end_at = Time.zone.at(end_at)
|
||||
@years = (@start_at.year..@end_at.year).to_a
|
||||
@points_number = @coordinates.count
|
||||
@points = filtered_points
|
||||
@coordinates = build_coordinates
|
||||
@tracks = build_tracks
|
||||
@distance = calculate_distance
|
||||
@start_at = parsed_start_at
|
||||
@end_at = parsed_end_at
|
||||
@years = years_range
|
||||
@points_number = points_count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filtered_points
|
||||
points.where('timestamp >= ? AND timestamp <= ?', start_at, end_at)
|
||||
end
|
||||
|
||||
def build_coordinates
|
||||
@points.pluck(:lonlat, :battery, :altitude, :timestamp, :velocity, :id, :country, :track_id)
|
||||
.map { |lonlat, *rest| [lonlat.y, lonlat.x, *rest.map(&:to_s)] }
|
||||
end
|
||||
|
||||
def extract_track_ids
|
||||
# Extract track IDs from coordinates (index 8: [lat, lng, battery, altitude, timestamp, velocity, id, country, track_id])
|
||||
@coordinates.map { |coord| coord[8]&.to_i }.compact.uniq.reject(&:zero?)
|
||||
end
|
||||
|
||||
def build_tracks
|
||||
track_ids = extract_track_ids
|
||||
TrackSerializer.new(current_user, track_ids).call
|
||||
end
|
||||
|
||||
def calculate_distance
|
||||
distance = 0
|
||||
|
||||
@coordinates.each_cons(2) do
|
||||
distance += Geocoder::Calculations.distance_between(
|
||||
[_1[0], _1[1]], [_2[0], _2[1]], units: current_user.safe_settings.distance_unit.to_sym
|
||||
)
|
||||
end
|
||||
|
||||
distance.round(1)
|
||||
end
|
||||
|
||||
def parsed_start_at
|
||||
Time.zone.at(start_at)
|
||||
end
|
||||
|
||||
def parsed_end_at
|
||||
Time.zone.at(end_at)
|
||||
end
|
||||
|
||||
def years_range
|
||||
(parsed_start_at.year..parsed_end_at.year).to_a
|
||||
end
|
||||
|
||||
def points_count
|
||||
@coordinates.count
|
||||
end
|
||||
|
||||
def start_at
|
||||
return Time.zone.parse(params[:start_at]).to_i if params[:start_at].present?
|
||||
return Time.zone.at(points.last.timestamp).beginning_of_day.to_i if points.any?
|
||||
|
|
@ -33,18 +77,6 @@ class MapController < ApplicationController
|
|||
Time.zone.today.end_of_day.to_i
|
||||
end
|
||||
|
||||
def distance
|
||||
@distance ||= 0
|
||||
|
||||
@coordinates.each_cons(2) do
|
||||
@distance += Geocoder::Calculations.distance_between(
|
||||
[_1[0], _1[1]], [_2[0], _2[1]], units: current_user.safe_settings.distance_unit.to_sym
|
||||
)
|
||||
end
|
||||
|
||||
@distance.round(1)
|
||||
end
|
||||
|
||||
def points
|
||||
params[:import_id] ? points_from_import : points_from_user
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,119 +0,0 @@
|
|||
# Tracks Map Layer
|
||||
|
||||
This module provides functionality for rendering tracks as a separate layer on Leaflet maps in Dawarich.
|
||||
|
||||
## Features
|
||||
|
||||
- **Distinct visual styling** - Tracks use brown color to differentiate from blue polylines
|
||||
- **Interactive hover/click** - Rich popups with track details including distance, duration, elevation
|
||||
- **Consistent styling** - All tracks use the same brown color for easy identification
|
||||
- **Layer management** - Integrates with Leaflet layer control
|
||||
- **Performance optimized** - Uses canvas rendering and efficient event handling
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Integration
|
||||
|
||||
The tracks layer is automatically integrated into the main maps controller:
|
||||
|
||||
```javascript
|
||||
// Import the tracks module
|
||||
import { createTracksLayer, updateTracksColors } from "../maps/tracks";
|
||||
|
||||
// Create tracks layer
|
||||
const tracksLayer = createTracksLayer(tracksData, map, userSettings, distanceUnit);
|
||||
|
||||
// Add to map
|
||||
tracksLayer.addTo(map);
|
||||
```
|
||||
|
||||
### Styling
|
||||
|
||||
All tracks use a consistent brown color (#8B4513) to ensure they are easily distinguishable from the blue polylines used for regular routes.
|
||||
|
||||
### Track Data Format
|
||||
|
||||
Tracks expect data in this format:
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: 123,
|
||||
start_at: "2025-01-15T10:00:00Z",
|
||||
end_at: "2025-01-15T11:30:00Z",
|
||||
distance: 15000, // meters
|
||||
duration: 5400, // seconds
|
||||
avg_speed: 25.5, // km/h
|
||||
elevation_gain: 200, // meters
|
||||
elevation_loss: 150, // meters
|
||||
elevation_max: 500, // meters
|
||||
elevation_min: 300, // meters
|
||||
original_path: "LINESTRING(-74.0060 40.7128, -74.0070 40.7130)", // PostGIS format
|
||||
// OR
|
||||
coordinates: [[40.7128, -74.0060], [40.7130, -74.0070]], // [lat, lng] array
|
||||
// OR
|
||||
path: [[40.7128, -74.0060], [40.7130, -74.0070]] // alternative coordinate format
|
||||
}
|
||||
```
|
||||
|
||||
### Coordinate Parsing
|
||||
|
||||
The module automatically handles different coordinate formats:
|
||||
|
||||
1. **Array format**: `track.coordinates` or `track.path` as `[[lat, lng], ...]`
|
||||
2. **PostGIS LineString**: Parses `"LINESTRING(lng lat, lng lat, ...)"` format
|
||||
3. **Fallback**: Creates simple line from start/end points if available
|
||||
|
||||
### API Integration
|
||||
|
||||
The tracks layer integrates with these API endpoints:
|
||||
|
||||
- **GET `/api/v1/tracks`** - Fetch existing tracks
|
||||
- **POST `/api/v1/tracks`** - Trigger track generation from points
|
||||
|
||||
### Settings Integration
|
||||
|
||||
Track settings are integrated into the main map settings panel:
|
||||
|
||||
- **Show Tracks** - Toggle track layer visibility
|
||||
- **Refresh Tracks** - Regenerate tracks from current points
|
||||
|
||||
### Layer Control
|
||||
|
||||
Tracks appear as "Tracks" in the Leaflet layer control, positioned above regular polylines with z-index 460.
|
||||
|
||||
## Visual Features
|
||||
|
||||
### Markers
|
||||
|
||||
- **Start marker**: 🚀 (rocket emoji)
|
||||
- **End marker**: 🎯 (target emoji)
|
||||
|
||||
### Popup Content
|
||||
|
||||
Track popups display:
|
||||
- Track ID
|
||||
- Start/end timestamps
|
||||
- Duration (formatted as days/hours/minutes)
|
||||
- Total distance
|
||||
- Average speed
|
||||
- Elevation statistics (gain/loss/max/min)
|
||||
|
||||
### Interaction States
|
||||
|
||||
- **Default**: Brown polylines (weight: 4)
|
||||
- **Hover**: Orange polylines (weight: 6)
|
||||
- **Clicked**: Red polylines (weight: 8, persistent until clicked elsewhere)
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Uses Leaflet canvas renderer for efficient rendering
|
||||
- Custom pane (`tracksPane`) with z-index 460
|
||||
- Efficient coordinate parsing with error handling
|
||||
- Minimal DOM manipulation during interactions
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Graceful handling of missing coordinate data
|
||||
- Console warnings for unparseable track data
|
||||
- Fallback to empty layer if tracks API unavailable
|
||||
- Error messages for failed track generation
|
||||
|
|
@ -68,7 +68,7 @@ module Calculateable
|
|||
def convert_distance_to_meters(calculated_distance)
|
||||
# For Track model - convert to meters for storage (Track expects distance in meters)
|
||||
case user_distance_unit.to_s
|
||||
when 'miles', 'mi'
|
||||
when 'mi'
|
||||
(calculated_distance * 1609.344).round(2) # miles to meters
|
||||
else
|
||||
(calculated_distance * 1000).round(2) # km to meters
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TrackSerializer
|
||||
def initialize(user, coordinates)
|
||||
def initialize(user, track_ids)
|
||||
@user = user
|
||||
@coordinates = coordinates
|
||||
@track_ids = track_ids
|
||||
end
|
||||
|
||||
def call
|
||||
# Extract track IDs from the coordinates that are already filtered by timeframe
|
||||
track_ids = extract_track_ids_from_coordinates
|
||||
return [] if track_ids.empty?
|
||||
|
||||
# Show only tracks that have points in the selected timeframe
|
||||
|
|
@ -29,15 +27,7 @@ class TrackSerializer
|
|||
|
||||
private
|
||||
|
||||
attr_reader :user, :coordinates
|
||||
|
||||
def extract_track_ids_from_coordinates
|
||||
# Extract track_id from coordinates (index 8: [lat, lng, battery, altitude, timestamp, velocity, id, country, track_id])
|
||||
track_ids = coordinates.map { |coord| coord[8]&.to_i }.compact.uniq
|
||||
track_ids.reject(&:zero?) # Remove any nil/zero track IDs
|
||||
end
|
||||
|
||||
|
||||
attr_reader :user, :track_ids
|
||||
|
||||
def serialize_track_data(
|
||||
id, start_at, end_at, distance, avg_speed, duration, elevation_gain,
|
||||
|
|
|
|||
|
|
@ -1,130 +0,0 @@
|
|||
# Tracks Services
|
||||
|
||||
This directory contains services for working with tracks generated from user points.
|
||||
|
||||
## Tracks::CreateFromPoints
|
||||
|
||||
This service takes all points for a user and creates tracks by splitting them based on the user's configured settings for distance and time thresholds.
|
||||
|
||||
### Usage
|
||||
|
||||
```ruby
|
||||
# Basic usage
|
||||
user = User.find(123)
|
||||
service = Tracks::CreateFromPoints.new(user)
|
||||
tracks_created = service.call
|
||||
|
||||
puts "Created #{tracks_created} tracks for user #{user.email}"
|
||||
```
|
||||
|
||||
### How it works
|
||||
|
||||
The service:
|
||||
|
||||
1. **Fetches all user points** ordered by timestamp
|
||||
2. **Splits points into track segments** based on two thresholds:
|
||||
- **Distance threshold**: `user.safe_settings.meters_between_routes` (default: 500 meters)
|
||||
- **Time threshold**: `user.safe_settings.minutes_between_routes` (default: 30 minutes)
|
||||
3. **Creates Track records** with calculated statistics:
|
||||
- Distance (in meters)
|
||||
- Duration (in seconds)
|
||||
- Average speed (in km/h)
|
||||
- Elevation statistics (gain, loss, min, max)
|
||||
- PostGIS LineString path
|
||||
4. **Associates points with tracks** by updating the `track_id` field
|
||||
|
||||
### Track Splitting Logic
|
||||
|
||||
A new track is created when either condition is met:
|
||||
- **Time gap**: Time between consecutive points > time threshold
|
||||
- **Distance gap**: Distance between consecutive points > distance threshold
|
||||
|
||||
### Example with custom settings
|
||||
|
||||
```ruby
|
||||
# User with custom settings
|
||||
user.update!(settings: {
|
||||
'meters_between_routes' => 1000, # 1km distance threshold
|
||||
'minutes_between_routes' => 60 # 1 hour time threshold
|
||||
})
|
||||
|
||||
service = Tracks::CreateFromPoints.new(user)
|
||||
service.call
|
||||
```
|
||||
|
||||
### Background Job Usage
|
||||
|
||||
For large datasets, consider running in a background job:
|
||||
|
||||
```ruby
|
||||
class Tracks::CreateJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(user_id)
|
||||
user = User.find(user_id)
|
||||
tracks_created = Tracks::CreateFromPoints.new(user).call
|
||||
|
||||
# Create notification for user
|
||||
Notification.create!(
|
||||
user: user,
|
||||
title: 'Tracks Generated',
|
||||
content: "Created #{tracks_created} tracks from your location data",
|
||||
kind: :info
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Enqueue the job
|
||||
Tracks::CreateJob.perform_later(user.id)
|
||||
```
|
||||
|
||||
### Console Usage
|
||||
|
||||
```ruby
|
||||
# In Rails console
|
||||
rails console
|
||||
|
||||
# Generate tracks for a specific user
|
||||
user = User.find_by(email: 'user@example.com')
|
||||
Tracks::CreateFromPoints.new(user).call
|
||||
|
||||
# Generate tracks for all users
|
||||
User.find_each do |user|
|
||||
tracks_created = Tracks::CreateFromPoints.new(user).call
|
||||
puts "User #{user.id}: #{tracks_created} tracks created"
|
||||
end
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The service respects user settings:
|
||||
|
||||
- `meters_between_routes`: Maximum distance between points in the same track (meters)
|
||||
- `minutes_between_routes`: Maximum time between points in the same track (minutes)
|
||||
- `distance_unit`: Used for internal calculations (km/miles)
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- Uses database transactions for consistency
|
||||
- Processes points with `find_each` to avoid loading all points into memory
|
||||
- Destroys existing tracks before regenerating (use with caution)
|
||||
- For users with many points, consider running as background job
|
||||
|
||||
### Track Statistics
|
||||
|
||||
Each track includes:
|
||||
|
||||
- **start_at/end_at**: First and last point timestamps
|
||||
- **distance**: Total distance in meters (converted from user's preferred unit)
|
||||
- **duration**: Total time in seconds
|
||||
- **avg_speed**: Average speed in km/h
|
||||
- **elevation_gain/loss**: Cumulative elevation changes
|
||||
- **elevation_min/max**: Altitude range
|
||||
- **original_path**: PostGIS LineString geometry
|
||||
|
||||
### Dependencies
|
||||
|
||||
- PostGIS for distance calculations and path geometry
|
||||
- Existing `Tracks::BuildPath` service for creating LineString geometry
|
||||
- User settings via `Users::SafeSettings`
|
||||
- Point model with `Distanceable` concern
|
||||
|
|
@ -6,5 +6,6 @@
|
|||
- imports
|
||||
- exports
|
||||
- stats
|
||||
- tracks
|
||||
- reverse_geocoding
|
||||
- visit_suggesting
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ class CreateTracks < ActiveRecord::Migration[8.0]
|
|||
t.datetime :end_at, null: false
|
||||
t.references :user, null: false, foreign_key: true
|
||||
t.line_string :original_path, null: false
|
||||
t.float :distance
|
||||
t.integer :distance
|
||||
t.float :avg_speed
|
||||
t.integer :duration
|
||||
t.integer :elevation_gain
|
||||
|
|
|
|||
2
db/schema.rb
generated
2
db/schema.rb
generated
|
|
@ -223,7 +223,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_03_193657) do
|
|||
t.datetime "end_at", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.geometry "original_path", limit: {srid: 0, type: "line_string"}, null: false
|
||||
t.float "distance"
|
||||
t.integer "distance"
|
||||
t.float "avg_speed"
|
||||
t.integer "duration"
|
||||
t.integer "elevation_gain"
|
||||
|
|
|
|||
|
|
@ -6,15 +6,17 @@ RSpec.describe Tracks::CreateJob, type: :job do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
describe '#perform' do
|
||||
it 'calls the service and creates a notification' do
|
||||
service_instance = instance_double(Tracks::CreateFromPoints)
|
||||
let(:service_instance) { instance_double(Tracks::CreateFromPoints) }
|
||||
let(:notification_service) { instance_double(Notifications::Create) }
|
||||
|
||||
before do
|
||||
allow(Tracks::CreateFromPoints).to receive(:new).with(user).and_return(service_instance)
|
||||
allow(service_instance).to receive(:call).and_return(3)
|
||||
|
||||
notification_service = instance_double(Notifications::Create)
|
||||
allow(Notifications::Create).to receive(:new).and_return(notification_service)
|
||||
allow(notification_service).to receive(:call)
|
||||
end
|
||||
|
||||
it 'calls the service and creates a notification' do
|
||||
described_class.new.perform(user.id)
|
||||
|
||||
expect(Tracks::CreateFromPoints).to have_received(:new).with(user)
|
||||
|
|
@ -30,18 +32,17 @@ RSpec.describe Tracks::CreateJob, type: :job do
|
|||
|
||||
context 'when service raises an error' do
|
||||
let(:error_message) { 'Something went wrong' }
|
||||
let(:service_instance) { instance_double(Tracks::CreateFromPoints) }
|
||||
let(:notification_service) { instance_double(Notifications::Create) }
|
||||
|
||||
before do
|
||||
service_instance = instance_double(Tracks::CreateFromPoints)
|
||||
allow(Tracks::CreateFromPoints).to receive(:new).with(user).and_return(service_instance)
|
||||
allow(service_instance).to receive(:call).and_raise(StandardError, error_message)
|
||||
allow(Notifications::Create).to receive(:new).and_return(notification_service)
|
||||
allow(notification_service).to receive(:call)
|
||||
end
|
||||
|
||||
it 'creates an error notification' do
|
||||
notification_service = instance_double(Notifications::Create)
|
||||
allow(Notifications::Create).to receive(:new).and_return(notification_service)
|
||||
allow(notification_service).to receive(:call)
|
||||
|
||||
described_class.new.perform(user.id)
|
||||
|
||||
expect(Notifications::Create).to have_received(:new).with(
|
||||
|
|
@ -74,7 +75,7 @@ RSpec.describe Tracks::CreateJob, type: :job do
|
|||
|
||||
describe 'queue' do
|
||||
it 'is queued on default queue' do
|
||||
expect(described_class.new.queue_name).to eq('default')
|
||||
expect(described_class.new.queue_name).to eq('tracks')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -29,6 +29,17 @@ RSpec.describe Point, type: :model do
|
|||
expect(point.country_id).to eq(country.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#recalculate_track' do
|
||||
let(:point) { create(:point, track: track) }
|
||||
let(:track) { create(:track) }
|
||||
|
||||
it 'recalculates the track' do
|
||||
expect(track).to receive(:recalculate_path_and_distance!)
|
||||
|
||||
point.update(lonlat: 'POINT(-79.85581250721961 15.854775993302411)')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'scopes' do
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ RSpec.describe Track, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#recalculate_distance!' do
|
||||
describe '#recalculate_distance!' do
|
||||
it 'recalculates and saves the distance' do
|
||||
original_distance = track.distance
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "Api::V1::Tracks", type: :request do
|
||||
describe "GET /index" do
|
||||
pending "add some examples (or delete) #{__FILE__}"
|
||||
end
|
||||
end
|
||||
|
|
@ -6,11 +6,13 @@ RSpec.describe TrackSerializer do
|
|||
describe '#call' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context 'when serializing user tracks without date range restrictions' do
|
||||
subject(:serializer) { described_class.new(user, 1.year.ago.to_i, 1.year.from_now.to_i).call }
|
||||
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)
|
||||
|
|
@ -20,6 +22,7 @@ RSpec.describe TrackSerializer do
|
|||
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
|
||||
|
|
@ -49,41 +52,48 @@ RSpec.describe TrackSerializer do
|
|||
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 serializing user tracks with date range' do
|
||||
subject(:serializer) { described_class.new(user, start_at.to_i, end_at.to_i).call }
|
||||
context 'when track IDs belong to different users' do
|
||||
subject(:serializer) { described_class.new(user, track_ids).call }
|
||||
|
||||
let(:start_at) { 6.hours.ago }
|
||||
let(:end_at) { 30.minutes.ago }
|
||||
let!(:track_in_range) { create(:track, user: user, start_at: 2.hours.ago, end_at: 1.hour.ago) }
|
||||
let!(:track_out_of_range) { create(:track, user: user, start_at: 10.hours.ago, end_at: 9.hours.ago) }
|
||||
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 'returns an array of serialized tracks' do
|
||||
expect(serializer).to be_an(Array)
|
||||
expect(serializer.length).to eq(1)
|
||||
end
|
||||
|
||||
it 'only includes tracks within the date range' do
|
||||
it 'only returns tracks belonging to the specified user' do
|
||||
serialized_ids = serializer.map { |track| track[:id] }
|
||||
expect(serialized_ids).to contain_exactly(track_in_range.id)
|
||||
expect(serialized_ids).not_to include(track_out_of_range.id)
|
||||
end
|
||||
|
||||
it 'formats timestamps as ISO8601' 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
|
||||
expect(serialized_ids).to contain_exactly(user_track.id)
|
||||
expect(serialized_ids).not_to include(other_user_track.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has no tracks' do
|
||||
subject(:serializer) { described_class.new(user, 1.day.ago.to_i, Time.current.to_i).call }
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue