mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Extract stats sharing logic to its own controller
This commit is contained in:
parent
34e71b5d17
commit
57ecda2b1b
26 changed files with 508 additions and 333 deletions
|
|
@ -15,7 +15,7 @@ class Api::V1::AreasController < ApiController
|
|||
if @area.save
|
||||
render json: @area, status: :created
|
||||
else
|
||||
render json: { errors: @area.errors.full_messages }, status: :unprocessable_entity
|
||||
render json: { errors: @area.errors.full_messages }, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ class Api::V1::AreasController < ApiController
|
|||
if @area.update(area_params)
|
||||
render json: @area, status: :ok
|
||||
else
|
||||
render json: { errors: @area.errors.full_messages }, status: :unprocessable_entity
|
||||
render json: { errors: @area.errors.full_messages }, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class Api::V1::SettingsController < ApiController
|
|||
status: :ok
|
||||
else
|
||||
render json: { message: 'Something went wrong', errors: current_api_user.errors.full_messages },
|
||||
status: :unprocessable_entity
|
||||
status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,6 @@ class Api::V1::SubscriptionsController < ApiController
|
|||
render json: { message: 'Failed to verify subscription update.' }, status: :unauthorized
|
||||
rescue ArgumentError => e
|
||||
ExceptionReporter.call(e)
|
||||
render json: { message: 'Invalid subscription data received.' }, status: :unprocessable_entity
|
||||
render json: { message: 'Invalid subscription data received.' }, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class Api::V1::VisitsController < ApiController
|
|||
render json: Api::VisitSerializer.new(service.visit).call
|
||||
else
|
||||
error_message = service.errors || 'Failed to create visit'
|
||||
render json: { error: error_message }, status: :unprocessable_entity
|
||||
render json: { error: error_message }, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ class Api::V1::VisitsController < ApiController
|
|||
# Validate that we have at least 2 visit IDs
|
||||
visit_ids = params[:visit_ids]
|
||||
if visit_ids.blank? || visit_ids.length < 2
|
||||
return render json: { error: 'At least 2 visits must be selected for merging' }, status: :unprocessable_entity
|
||||
return render json: { error: 'At least 2 visits must be selected for merging' }, status: :unprocessable_content
|
||||
end
|
||||
|
||||
# Find all visits that belong to the current user
|
||||
|
|
@ -52,7 +52,7 @@ class Api::V1::VisitsController < ApiController
|
|||
if merged_visit&.persisted?
|
||||
render json: Api::VisitSerializer.new(merged_visit).call, status: :ok
|
||||
else
|
||||
render json: { error: service.errors.join(', ') }, status: :unprocessable_entity
|
||||
render json: { error: service.errors.join(', ') }, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ class Api::V1::VisitsController < ApiController
|
|||
updated_count: result[:count]
|
||||
}, status: :ok
|
||||
else
|
||||
render json: { error: service.errors.join(', ') }, status: :unprocessable_entity
|
||||
render json: { error: service.errors.join(', ') }, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ class Api::V1::VisitsController < ApiController
|
|||
render json: {
|
||||
error: 'Failed to delete visit',
|
||||
errors: visit.errors.full_messages
|
||||
}, status: :unprocessable_entity
|
||||
}, status: :unprocessable_content
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render json: { error: 'Visit not found' }, status: :not_found
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class ExportsController < ApplicationController
|
|||
|
||||
ExceptionReporter.call(e)
|
||||
|
||||
redirect_to exports_url, alert: "Export failed to initiate: #{e.message}", status: :unprocessable_entity
|
||||
redirect_to exports_url, alert: "Export failed to initiate: #{e.message}", status: :unprocessable_content
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ class ImportsController < ApplicationController
|
|||
|
||||
def index
|
||||
@imports = policy_scope(Import)
|
||||
.select(:id, :name, :source, :created_at, :processed, :status)
|
||||
.order(created_at: :desc)
|
||||
.page(params[:page])
|
||||
.select(:id, :name, :source, :created_at, :processed, :status)
|
||||
.order(created_at: :desc)
|
||||
.page(params[:page])
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
|
@ -43,7 +43,7 @@ class ImportsController < ApplicationController
|
|||
raw_files = Array(files_params).reject(&:blank?)
|
||||
|
||||
if raw_files.empty?
|
||||
redirect_to new_import_path, alert: 'No files were selected for upload', status: :unprocessable_entity and return
|
||||
redirect_to new_import_path, alert: 'No files were selected for upload', status: :unprocessable_content and return
|
||||
end
|
||||
|
||||
created_imports = []
|
||||
|
|
@ -62,7 +62,7 @@ class ImportsController < ApplicationController
|
|||
else
|
||||
redirect_to new_import_path,
|
||||
alert: 'No valid file references were found. Please upload files using the file selector.',
|
||||
status: :unprocessable_entity and return
|
||||
status: :unprocessable_content and return
|
||||
end
|
||||
rescue StandardError => e
|
||||
if created_imports.present?
|
||||
|
|
@ -74,7 +74,7 @@ class ImportsController < ApplicationController
|
|||
Rails.logger.error e.backtrace.join("\n")
|
||||
ExceptionReporter.call(e)
|
||||
|
||||
redirect_to new_import_path, alert: e.message, status: :unprocessable_entity
|
||||
redirect_to new_import_path, alert: e.message, status: :unprocessable_content
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
|
@ -117,7 +117,7 @@ class ImportsController < ApplicationController
|
|||
# Extract filename and extension
|
||||
basename = File.basename(original_name, File.extname(original_name))
|
||||
extension = File.extname(original_name)
|
||||
|
||||
|
||||
# Add current datetime
|
||||
timestamp = Time.current.strftime('%Y%m%d_%H%M%S')
|
||||
"#{basename}_#{timestamp}#{extension}"
|
||||
|
|
@ -126,6 +126,6 @@ class ImportsController < ApplicationController
|
|||
def validate_points_limit
|
||||
limit_exceeded = PointsLimitExceeded.new(current_user).call
|
||||
|
||||
redirect_to imports_path, alert: 'Points limit exceeded', status: :unprocessable_entity if limit_exceeded
|
||||
redirect_to imports_path, alert: 'Points limit exceeded', status: :unprocessable_content if limit_exceeded
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::UsersController < ApplicationController
|
||||
before_action :authenticate_self_hosted!, except: [:export, :import]
|
||||
before_action :authenticate_admin!, except: [:export, :import]
|
||||
before_action :authenticate_self_hosted!, except: %i[export import]
|
||||
before_action :authenticate_admin!, except: %i[export import]
|
||||
before_action :authenticate_user!
|
||||
|
||||
def index
|
||||
|
|
@ -19,7 +19,7 @@ class Settings::UsersController < ApplicationController
|
|||
if @user.update(user_params)
|
||||
redirect_to settings_users_url, notice: 'User was successfully updated.'
|
||||
else
|
||||
redirect_to settings_users_url, notice: 'User could not be updated.', status: :unprocessable_entity
|
||||
redirect_to settings_users_url, notice: 'User could not be updated.', status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ class Settings::UsersController < ApplicationController
|
|||
if @user.save
|
||||
redirect_to settings_users_url, notice: 'User was successfully created'
|
||||
else
|
||||
redirect_to settings_users_url, notice: 'User could not be created.', status: :unprocessable_entity
|
||||
redirect_to settings_users_url, notice: 'User could not be created.', status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ class Settings::UsersController < ApplicationController
|
|||
if @user.destroy
|
||||
redirect_to settings_url, notice: 'User was successfully deleted.'
|
||||
else
|
||||
redirect_to settings_url, notice: 'User could not be deleted.', status: :unprocessable_entity
|
||||
redirect_to settings_url, notice: 'User could not be deleted.', status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -90,8 +90,7 @@ class Settings::UsersController < ApplicationController
|
|||
end
|
||||
|
||||
def validate_archive_file(archive_file)
|
||||
unless archive_file.content_type == 'application/zip' ||
|
||||
archive_file.content_type == 'application/x-zip-compressed' ||
|
||||
unless ['application/zip', 'application/x-zip-compressed'].include?(archive_file.content_type) ||
|
||||
File.extname(archive_file.original_filename).downcase == '.zip'
|
||||
|
||||
redirect_to edit_user_registration_path, alert: 'Please upload a valid ZIP file.' and return
|
||||
|
|
|
|||
54
app/controllers/shared/stats_controller.rb
Normal file
54
app/controllers/shared/stats_controller.rb
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Shared::StatsController < ApplicationController
|
||||
before_action :authenticate_user!, except: [:show]
|
||||
before_action :authenticate_active_user!, only: [:update]
|
||||
|
||||
def show
|
||||
@stat = Stat.find_by(sharing_uuid: params[:uuid])
|
||||
|
||||
unless @stat&.public_accessible?
|
||||
return redirect_to root_path,
|
||||
alert: 'Shared stats not found or no longer available'
|
||||
end
|
||||
|
||||
@year = @stat.year
|
||||
@month = @stat.month
|
||||
@user = @stat.user
|
||||
@is_public_view = true
|
||||
@data_bounds = @stat.calculate_data_bounds
|
||||
|
||||
render 'stats/public_month'
|
||||
end
|
||||
|
||||
def update
|
||||
@year = params[:year].to_i
|
||||
@month = params[:month].to_i
|
||||
@stat = current_user.stats.find_by(year: @year, month: @month)
|
||||
|
||||
return head :not_found unless @stat
|
||||
|
||||
if params[:enabled] == '1'
|
||||
@stat.enable_sharing!(expiration: params[:expiration] || 'permanent')
|
||||
sharing_url = shared_stat_url(@stat.sharing_uuid)
|
||||
|
||||
render json: {
|
||||
success: true,
|
||||
sharing_url: sharing_url,
|
||||
message: 'Sharing enabled successfully'
|
||||
}
|
||||
else
|
||||
@stat.disable_sharing!
|
||||
|
||||
render json: {
|
||||
success: true,
|
||||
message: 'Sharing disabled successfully'
|
||||
}
|
||||
end
|
||||
rescue StandardError
|
||||
render json: {
|
||||
success: false,
|
||||
message: 'Failed to update sharing settings'
|
||||
}, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StatsController < ApplicationController
|
||||
before_action :authenticate_user!, except: [:public_show]
|
||||
before_action :authenticate_active_user!, only: %i[update update_all update_sharing]
|
||||
before_action :authenticate_user!
|
||||
before_action :authenticate_active_user!, only: %i[update update_all]
|
||||
|
||||
def index
|
||||
@stats = build_stats
|
||||
|
|
@ -52,54 +52,6 @@ class StatsController < ApplicationController
|
|||
redirect_to stats_path, notice: 'Stats are being updated', status: :see_other
|
||||
end
|
||||
|
||||
def update_sharing
|
||||
@year = params[:year].to_i
|
||||
@month = params[:month].to_i
|
||||
@stat = current_user.stats.find_by(year: @year, month: @month)
|
||||
|
||||
return head :not_found unless @stat
|
||||
|
||||
if params[:enabled] == '1'
|
||||
@stat.enable_sharing!(expiration: params[:expiration] || 'permanent')
|
||||
sharing_url = public_stat_url(@stat.sharing_uuid)
|
||||
|
||||
render json: {
|
||||
success: true,
|
||||
sharing_url: sharing_url,
|
||||
message: 'Sharing enabled successfully'
|
||||
}
|
||||
else
|
||||
@stat.disable_sharing!
|
||||
|
||||
render json: {
|
||||
success: true,
|
||||
message: 'Sharing disabled successfully'
|
||||
}
|
||||
end
|
||||
rescue StandardError
|
||||
render json: {
|
||||
success: false,
|
||||
message: 'Failed to update sharing settings'
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def public_show
|
||||
@stat = Stat.find_by(sharing_uuid: params[:uuid])
|
||||
|
||||
unless @stat&.public_accessible?
|
||||
return redirect_to root_path,
|
||||
alert: 'Shared stats not found or no longer available'
|
||||
end
|
||||
|
||||
@year = @stat.year
|
||||
@month = @stat.month
|
||||
@user = @stat.user
|
||||
@is_public_view = true
|
||||
@data_bounds = calculate_data_bounds(@stat)
|
||||
|
||||
render 'public_month'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign_points_statistics
|
||||
|
|
@ -132,32 +84,4 @@ class StatsController < ApplicationController
|
|||
stats.sort_by(&:updated_at).reverse
|
||||
end.sort.reverse
|
||||
end
|
||||
|
||||
def calculate_data_bounds(stat)
|
||||
start_date = Date.new(stat.year, stat.month, 1).beginning_of_day
|
||||
end_date = start_date.end_of_month.end_of_day
|
||||
|
||||
points_relation = stat.user.points.where(timestamp: start_date.to_i..end_date.to_i)
|
||||
point_count = points_relation.count
|
||||
|
||||
return nil if point_count.zero?
|
||||
|
||||
bounds_result = ActiveRecord::Base.connection.exec_query(
|
||||
"SELECT MIN(latitude) as min_lat, MAX(latitude) as max_lat,
|
||||
MIN(longitude) as min_lng, MAX(longitude) as max_lng
|
||||
FROM points
|
||||
WHERE user_id = $1
|
||||
AND timestamp BETWEEN $2 AND $3",
|
||||
'data_bounds_query',
|
||||
[stat.user.id, start_date.to_i, end_date.to_i]
|
||||
).first
|
||||
|
||||
{
|
||||
min_lat: bounds_result['min_lat'].to_f,
|
||||
max_lat: bounds_result['max_lat'].to_f,
|
||||
min_lng: bounds_result['min_lng'].to_f,
|
||||
max_lng: bounds_result['max_lng'].to_f,
|
||||
point_count: point_count
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ class TripsController < ApplicationController
|
|||
end
|
||||
@photo_sources = @trip.photo_sources
|
||||
|
||||
if @trip.path.blank? || @trip.distance.blank? || @trip.visited_countries.blank?
|
||||
Trips::CalculateAllJob.perform_later(@trip.id, current_user.safe_settings.distance_unit)
|
||||
end
|
||||
return unless @trip.path.blank? || @trip.distance.blank? || @trip.visited_countries.blank?
|
||||
|
||||
Trips::CalculateAllJob.perform_later(@trip.id, current_user.safe_settings.distance_unit)
|
||||
end
|
||||
|
||||
def new
|
||||
|
|
@ -34,7 +34,7 @@ class TripsController < ApplicationController
|
|||
if @trip.save
|
||||
redirect_to @trip, notice: 'Trip was successfully created. Data is being calculated in the background.'
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
render :new, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ class TripsController < ApplicationController
|
|||
if @trip.update(trip_params)
|
||||
redirect_to @trip, notice: 'Trip was successfully updated.', status: :see_other
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
render :edit, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class VisitsController < ApplicationController
|
|||
if @visit.update(visit_params)
|
||||
redirect_back(fallback_location: visits_path(status: :suggested))
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
render :edit, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,34 @@ class Stat < ApplicationRecord
|
|||
)
|
||||
end
|
||||
|
||||
def calculate_data_bounds
|
||||
start_date = Date.new(year, month, 1).beginning_of_day
|
||||
end_date = start_date.end_of_month.end_of_day
|
||||
|
||||
points_relation = user.points.where(timestamp: start_date.to_i..end_date.to_i)
|
||||
point_count = points_relation.count
|
||||
|
||||
return nil if point_count.zero?
|
||||
|
||||
bounds_result = ActiveRecord::Base.connection.exec_query(
|
||||
"SELECT MIN(latitude) as min_lat, MAX(latitude) as max_lat,
|
||||
MIN(longitude) as min_lng, MAX(longitude) as max_lng
|
||||
FROM points
|
||||
WHERE user_id = $1
|
||||
AND timestamp BETWEEN $2 AND $3",
|
||||
'data_bounds_query',
|
||||
[user.id, start_date.to_i, end_date.to_i]
|
||||
).first
|
||||
|
||||
{
|
||||
min_lat: bounds_result['min_lat'].to_f,
|
||||
max_lat: bounds_result['max_lat'].to_f,
|
||||
min_lng: bounds_result['min_lng'].to_f,
|
||||
max_lng: bounds_result['max_lng'].to_f,
|
||||
point_count: point_count
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_sharing_uuid
|
||||
|
|
|
|||
|
|
@ -90,7 +90,9 @@
|
|||
<!-- Daily Activity Chart -->
|
||||
<div class="card bg-base-100 shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">📈 Daily Activity</h2>
|
||||
<h2 class="card-title">
|
||||
<%= icon 'trending-up' %> Daily Activity
|
||||
</h2>
|
||||
<div class="w-full h-48 bg-base-200 rounded-lg p-4 relative">
|
||||
<%= column_chart(
|
||||
@stat.daily_distance.map { |day, distance_meters|
|
||||
|
|
@ -132,7 +134,9 @@
|
|||
<!-- Countries & Cities - General Info Only -->
|
||||
<div class="card bg-base-100 shadow-xl mb-8">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">🌍 Countries & Cities</h2>
|
||||
<h2 class="card-title">
|
||||
<%= icon 'earth' %> Countries & Cities
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
<% @stat.toponyms.each_with_index do |country, index| %>
|
||||
<div class="space-y-2">
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ Devise.setup do |config|
|
|||
# When set to false, does not sign a user in automatically after their password is
|
||||
# changed. Defaults to true, so a user is signed in automatically after changing a password.
|
||||
# config.sign_in_after_change_password = true
|
||||
config.responder.error_status = :unprocessable_entity
|
||||
config.responder.error_status = :unprocessable_content
|
||||
config.responder.redirect_status = :see_other
|
||||
|
||||
if Rails.env.production? && !DawarichSettings.self_hosted?
|
||||
|
|
|
|||
|
|
@ -75,14 +75,17 @@ Rails.application.routes.draw do
|
|||
to: 'stats#update',
|
||||
as: :update_year_month_stats,
|
||||
constraints: { year: /\d{4}/, month: /\d{1,2}|all/ }
|
||||
# Public shared stats routes
|
||||
get 'shared/stats/:uuid', to: 'shared/stats#show', as: :shared_stat
|
||||
# Backward compatibility
|
||||
get 'shared/stats/:uuid', to: 'shared/stats#show', as: :public_stat
|
||||
|
||||
# Sharing management endpoint (requires auth)
|
||||
patch 'stats/:year/:month/sharing',
|
||||
to: 'stats#update_sharing',
|
||||
to: 'shared/stats#update',
|
||||
as: :sharing_stats,
|
||||
constraints: { year: /\d{4}/, month: /\d{1,2}/ }
|
||||
|
||||
# Public shared stats page
|
||||
get 'shared/stats/:uuid', to: 'stats#public_show', as: :public_stat
|
||||
|
||||
root to: 'home#index'
|
||||
|
||||
if SELF_HOSTED
|
||||
|
|
|
|||
|
|
@ -102,5 +102,165 @@ RSpec.describe Stat, type: :model do
|
|||
expect(subject).to eq(points)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#calculate_data_bounds' do
|
||||
let(:stat) { create(:stat, year: 2024, month: 6, user:) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context 'when stat has points' do
|
||||
before do
|
||||
# Create test points within the month (June 2024)
|
||||
create(:point,
|
||||
user:,
|
||||
latitude: 40.6,
|
||||
longitude: -74.1,
|
||||
timestamp: Time.new(2024, 6, 1, 12, 0).to_i)
|
||||
create(:point,
|
||||
user:,
|
||||
latitude: 40.8,
|
||||
longitude: -73.9,
|
||||
timestamp: Time.new(2024, 6, 15, 15, 0).to_i)
|
||||
create(:point,
|
||||
user:,
|
||||
latitude: 40.7,
|
||||
longitude: -74.0,
|
||||
timestamp: Time.new(2024, 6, 30, 18, 0).to_i)
|
||||
|
||||
# Points outside the month (should be ignored)
|
||||
create(:point,
|
||||
user:,
|
||||
latitude: 41.0,
|
||||
longitude: -75.0,
|
||||
timestamp: Time.new(2024, 5, 31, 23, 59).to_i) # May
|
||||
create(:point,
|
||||
user:,
|
||||
latitude: 39.0,
|
||||
longitude: -72.0,
|
||||
timestamp: Time.new(2024, 7, 1, 0, 1).to_i) # July
|
||||
end
|
||||
|
||||
it 'returns correct bounding box for points within the month' do
|
||||
result = stat.calculate_data_bounds
|
||||
|
||||
expect(result).to be_a(Hash)
|
||||
expect(result[:min_lat]).to eq(40.6)
|
||||
expect(result[:max_lat]).to eq(40.8)
|
||||
expect(result[:min_lng]).to eq(-74.1)
|
||||
expect(result[:max_lng]).to eq(-73.9)
|
||||
expect(result[:point_count]).to eq(3)
|
||||
end
|
||||
|
||||
context 'with points from different users' do
|
||||
let(:other_user) { create(:user) }
|
||||
|
||||
before do
|
||||
# Add points from a different user (should be ignored)
|
||||
create(:point,
|
||||
user: other_user,
|
||||
latitude: 50.0,
|
||||
longitude: -80.0,
|
||||
timestamp: Time.new(2024, 6, 15, 12, 0).to_i)
|
||||
end
|
||||
|
||||
it 'only includes points from the stat user' do
|
||||
result = stat.calculate_data_bounds
|
||||
|
||||
expect(result[:min_lat]).to eq(40.6)
|
||||
expect(result[:max_lat]).to eq(40.8)
|
||||
expect(result[:min_lng]).to eq(-74.1)
|
||||
expect(result[:max_lng]).to eq(-73.9)
|
||||
expect(result[:point_count]).to eq(3) # Still only 3 points from the stat user
|
||||
end
|
||||
end
|
||||
|
||||
context 'with single point' do
|
||||
let(:single_point_user) { create(:user) }
|
||||
let(:single_point_stat) { create(:stat, year: 2024, month: 7, user: single_point_user) }
|
||||
|
||||
before do
|
||||
create(:point,
|
||||
user: single_point_user,
|
||||
latitude: 45.5,
|
||||
longitude: -122.65,
|
||||
timestamp: Time.new(2024, 7, 15, 14, 30).to_i)
|
||||
end
|
||||
|
||||
it 'returns bounds with same min and max values' do
|
||||
result = single_point_stat.calculate_data_bounds
|
||||
|
||||
expect(result[:min_lat]).to eq(45.5)
|
||||
expect(result[:max_lat]).to eq(45.5)
|
||||
expect(result[:min_lng]).to eq(-122.65)
|
||||
expect(result[:max_lng]).to eq(-122.65)
|
||||
expect(result[:point_count]).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with edge case coordinates' do
|
||||
let(:edge_user) { create(:user) }
|
||||
let(:edge_stat) { create(:stat, year: 2024, month: 8, user: edge_user) }
|
||||
|
||||
before do
|
||||
# Test with extreme coordinate values
|
||||
create(:point,
|
||||
user: edge_user,
|
||||
latitude: -90.0, # South Pole
|
||||
longitude: -180.0, # Date Line West
|
||||
timestamp: Time.new(2024, 8, 1, 0, 0).to_i)
|
||||
create(:point,
|
||||
user: edge_user,
|
||||
latitude: 90.0, # North Pole
|
||||
longitude: 180.0, # Date Line East
|
||||
timestamp: Time.new(2024, 8, 31, 23, 59).to_i)
|
||||
end
|
||||
|
||||
it 'handles extreme coordinate values correctly' do
|
||||
result = edge_stat.calculate_data_bounds
|
||||
|
||||
expect(result[:min_lat]).to eq(-90.0)
|
||||
expect(result[:max_lat]).to eq(90.0)
|
||||
expect(result[:min_lng]).to eq(-180.0)
|
||||
expect(result[:max_lng]).to eq(180.0)
|
||||
expect(result[:point_count]).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when stat has no points' do
|
||||
let(:empty_user) { create(:user) }
|
||||
let(:empty_stat) { create(:stat, year: 2024, month: 10, user: empty_user) }
|
||||
|
||||
it 'returns nil' do
|
||||
result = empty_stat.calculate_data_bounds
|
||||
|
||||
expect(result).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when stat has points but none within the month timeframe' do
|
||||
let(:empty_month_user) { create(:user) }
|
||||
let(:empty_month_stat) { create(:stat, year: 2024, month: 9, user: empty_month_user) }
|
||||
|
||||
before do
|
||||
# Create points outside the target month
|
||||
create(:point,
|
||||
user: empty_month_user,
|
||||
latitude: 40.7,
|
||||
longitude: -74.0,
|
||||
timestamp: Time.new(2024, 8, 31, 23, 59).to_i) # August
|
||||
create(:point,
|
||||
user: empty_month_user,
|
||||
latitude: 40.8,
|
||||
longitude: -73.9,
|
||||
timestamp: Time.new(2024, 10, 1, 0, 1).to_i) # October
|
||||
end
|
||||
|
||||
it 'returns nil when no points exist in the month' do
|
||||
result = empty_month_stat.calculate_data_bounds
|
||||
|
||||
expect(result).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ RSpec.describe '/api/v1/areas', type: :request do
|
|||
post api_v1_areas_url, headers: { 'Authorization' => "Bearer #{user.api_key}" },
|
||||
params: { area: invalid_attributes }
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -85,7 +85,7 @@ RSpec.describe '/api/v1/areas', type: :request do
|
|||
patch api_v1_area_url(area), headers: { 'Authorization' => "Bearer #{user.api_key}" },
|
||||
params: { area: invalid_attributes }
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ RSpec.describe 'Api::V1::Settings', type: :request do
|
|||
it 'returns http unprocessable entity' do
|
||||
patch "/api/v1/settings?api_key=#{api_key}", params: { settings: { route_opacity: 'invalid' } }
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
end
|
||||
|
||||
it 'returns an error message' do
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ RSpec.describe 'Api::V1::Subscriptions', type: :request do
|
|||
|
||||
post '/api/v1/subscriptions/callback', params: { token: token }
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
expect(JSON.parse(response.body)['message']).to eq('Invalid subscription data received.')
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -81,9 +81,9 @@ RSpec.describe 'Api::V1::Visits', type: :request do
|
|||
let(:existing_place) { create(:place, latitude: 52.52, longitude: 13.405) }
|
||||
|
||||
it 'creates a new visit' do
|
||||
expect {
|
||||
expect do
|
||||
post '/api/v1/visits', params: valid_create_params, headers: auth_headers
|
||||
}.to change { user.visits.count }.by(1)
|
||||
end.to change { user.visits.count }.by(1)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
|
@ -100,9 +100,9 @@ RSpec.describe 'Api::V1::Visits', type: :request do
|
|||
end
|
||||
|
||||
it 'creates a place for the visit' do
|
||||
expect {
|
||||
expect do
|
||||
post '/api/v1/visits', params: valid_create_params, headers: auth_headers
|
||||
}.to change { Place.count }.by(1)
|
||||
end.to change { Place.count }.by(1)
|
||||
|
||||
created_place = Place.last
|
||||
expect(created_place.name).to eq('Test Visit')
|
||||
|
|
@ -114,9 +114,9 @@ RSpec.describe 'Api::V1::Visits', type: :request do
|
|||
it 'reuses existing place when coordinates are exactly the same' do
|
||||
create(:visit, user: user, place: existing_place)
|
||||
|
||||
expect {
|
||||
expect do
|
||||
post '/api/v1/visits', params: valid_create_params, headers: auth_headers
|
||||
}.not_to change { Place.count }
|
||||
end.not_to(change { Place.count })
|
||||
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['place']['id']).to eq(existing_place.id)
|
||||
|
|
@ -132,7 +132,7 @@ RSpec.describe 'Api::V1::Visits', type: :request do
|
|||
it 'returns unprocessable entity status' do
|
||||
post '/api/v1/visits', params: missing_name_params, headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
end
|
||||
|
||||
it 'returns error message' do
|
||||
|
|
@ -144,9 +144,9 @@ RSpec.describe 'Api::V1::Visits', type: :request do
|
|||
end
|
||||
|
||||
it 'does not create a visit' do
|
||||
expect {
|
||||
expect do
|
||||
post '/api/v1/visits', params: missing_name_params, headers: auth_headers
|
||||
}.not_to change { Visit.count }
|
||||
end.not_to(change { Visit.count })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -199,7 +199,7 @@ RSpec.describe 'Api::V1::Visits', type: :request do
|
|||
it 'renders a JSON response with errors for the visit' do
|
||||
put "/api/v1/visits/#{visit.id}", params: invalid_attributes, headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -234,7 +234,7 @@ RSpec.describe 'Api::V1::Visits', type: :request do
|
|||
it 'returns an error when fewer than 2 visits are specified' do
|
||||
post '/api/v1/visits/merge', params: { visit_ids: [visit1.id] }, headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['error']).to include('At least 2 visits must be selected')
|
||||
end
|
||||
|
|
@ -264,7 +264,7 @@ RSpec.describe 'Api::V1::Visits', type: :request do
|
|||
|
||||
post '/api/v1/visits/merge', params: { visit_ids: [visit1.id, visit2.id] }, headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['error']).to include('Failed to merge visits')
|
||||
end
|
||||
|
|
@ -316,7 +316,7 @@ RSpec.describe 'Api::V1::Visits', type: :request do
|
|||
|
||||
post '/api/v1/visits/bulk_update', params: invalid_update_params, headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['error']).to include('Invalid status')
|
||||
end
|
||||
|
|
@ -329,9 +329,9 @@ RSpec.describe 'Api::V1::Visits', type: :request do
|
|||
|
||||
context 'when visit exists and belongs to current user' do
|
||||
it 'deletes the visit' do
|
||||
expect {
|
||||
expect do
|
||||
delete "/api/v1/visits/#{visit.id}", headers: auth_headers
|
||||
}.to change { user.visits.count }.by(-1)
|
||||
end.to change { user.visits.count }.by(-1)
|
||||
|
||||
expect(response).to have_http_status(:no_content)
|
||||
end
|
||||
|
|
@ -363,9 +363,9 @@ RSpec.describe 'Api::V1::Visits', type: :request do
|
|||
end
|
||||
|
||||
it 'does not delete the visit' do
|
||||
expect {
|
||||
expect do
|
||||
delete "/api/v1/visits/#{other_user_visit.id}", headers: auth_headers
|
||||
}.not_to change { Visit.count }
|
||||
end.not_to(change { Visit.count })
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ RSpec.describe '/exports', type: :request do
|
|||
it 'renders a response with 422 status (i.e. to display the "new" template)' do
|
||||
post(exports_url, params:)
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ RSpec.describe '/settings/users', type: :request do
|
|||
it 'renders a response with 422 status (i.e. to display the "new" template)' do
|
||||
post settings_users_url, params: { user: invalid_attributes }
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
157
spec/requests/shared/stats_spec.rb
Normal file
157
spec/requests/shared/stats_spec.rb
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Shared::Stats', type: :request do
|
||||
before do
|
||||
stub_request(:any, 'https://api.github.com/repos/Freika/dawarich/tags')
|
||||
.to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {})
|
||||
end
|
||||
|
||||
context 'public sharing' do
|
||||
let(:user) { create(:user) }
|
||||
let(:stat) { create(:stat, :with_sharing_enabled, user:, year: 2024, month: 6) }
|
||||
|
||||
describe 'GET /shared/stats/:uuid' do
|
||||
context 'with valid sharing UUID' do
|
||||
before do
|
||||
# Create some test points for data bounds calculation
|
||||
create_list(:point, 5, user:, timestamp: Time.new(2024, 6, 15).to_i)
|
||||
end
|
||||
|
||||
it 'renders the public month view' do
|
||||
get shared_stat_url(stat.sharing_uuid)
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.body).to include('Monthly Digest')
|
||||
expect(response.body).to include('June 2024')
|
||||
end
|
||||
|
||||
it 'includes required content in response' do
|
||||
get shared_stat_url(stat.sharing_uuid)
|
||||
|
||||
expect(response.body).to include('June 2024')
|
||||
expect(response.body).to include('Monthly Digest')
|
||||
expect(response.body).to include('data-public-stat-map-uuid-value')
|
||||
expect(response.body).to include(stat.sharing_uuid)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid sharing UUID' do
|
||||
it 'redirects to root with alert' do
|
||||
get shared_stat_url('invalid-uuid')
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(flash[:alert]).to eq('Shared stats not found or no longer available')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with expired sharing' do
|
||||
let(:stat) { create(:stat, :with_sharing_expired, user:, year: 2024, month: 6) }
|
||||
|
||||
it 'redirects to root with alert' do
|
||||
get shared_stat_url(stat.sharing_uuid)
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(flash[:alert]).to eq('Shared stats not found or no longer available')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with disabled sharing' do
|
||||
let(:stat) { create(:stat, :with_sharing_disabled, user:, year: 2024, month: 6) }
|
||||
|
||||
it 'redirects to root with alert' do
|
||||
get shared_stat_url(stat.sharing_uuid)
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(flash[:alert]).to eq('Shared stats not found or no longer available')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when stat has no points' do
|
||||
it 'renders successfully' do
|
||||
get shared_stat_url(stat.sharing_uuid)
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.body).to include('Monthly Digest')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH /stats/:year/:month/sharing' do
|
||||
context 'when user is signed in' do
|
||||
let!(:stat_to_share) { create(:stat, user:, year: 2024, month: 6) }
|
||||
|
||||
before { sign_in user }
|
||||
|
||||
context 'enabling sharing' do
|
||||
it 'enables sharing and returns success' do
|
||||
patch sharing_stats_path(year: 2024, month: 6),
|
||||
params: { enabled: '1' },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['success']).to be(true)
|
||||
expect(json_response['sharing_url']).to be_present
|
||||
expect(json_response['message']).to eq('Sharing enabled successfully')
|
||||
|
||||
stat_to_share.reload
|
||||
expect(stat_to_share.sharing_enabled?).to be(true)
|
||||
expect(stat_to_share.sharing_uuid).to be_present
|
||||
end
|
||||
|
||||
it 'sets custom expiration when provided' do
|
||||
patch sharing_stats_path(year: 2024, month: 6),
|
||||
params: { enabled: '1', expiration: '1_week' },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
stat_to_share.reload
|
||||
expect(stat_to_share.sharing_enabled?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'disabling sharing' do
|
||||
let!(:enabled_stat) { create(:stat, :with_sharing_enabled, user:, year: 2024, month: 7) }
|
||||
|
||||
it 'disables sharing and returns success' do
|
||||
patch sharing_stats_path(year: 2024, month: 7),
|
||||
params: { enabled: '0' },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['success']).to be(true)
|
||||
expect(json_response['message']).to eq('Sharing disabled successfully')
|
||||
|
||||
enabled_stat.reload
|
||||
expect(enabled_stat.sharing_enabled?).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when stat does not exist' do
|
||||
it 'returns not found' do
|
||||
patch sharing_stats_path(year: 2024, month: 12),
|
||||
params: { enabled: '1' },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not signed in' do
|
||||
it 'returns unauthorized' do
|
||||
patch sharing_stats_path(year: 2024, month: 6),
|
||||
params: { enabled: '1' },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -112,150 +112,4 @@ RSpec.describe '/stats', type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
context 'public sharing' do
|
||||
let(:user) { create(:user) }
|
||||
let(:stat) { create(:stat, :with_sharing_enabled, user:, year: 2024, month: 6) }
|
||||
|
||||
describe 'GET /public/:uuid' do
|
||||
context 'with valid sharing UUID' do
|
||||
before do
|
||||
# Create some test points for data bounds calculation
|
||||
create_list(:point, 5, user:, timestamp: Time.new(2024, 6, 15).to_i)
|
||||
end
|
||||
|
||||
it 'renders the public month view' do
|
||||
get public_stat_url(stat.sharing_uuid)
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.body).to include('Monthly Digest')
|
||||
expect(response.body).to include('June 2024')
|
||||
end
|
||||
|
||||
it 'includes required content in response' do
|
||||
get public_stat_url(stat.sharing_uuid)
|
||||
|
||||
expect(response.body).to include('June 2024')
|
||||
expect(response.body).to include('Monthly Digest')
|
||||
expect(response.body).to include('data-public-stat-map-uuid-value')
|
||||
expect(response.body).to include(stat.sharing_uuid)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid sharing UUID' do
|
||||
it 'redirects to root with alert' do
|
||||
get public_stat_url('invalid-uuid')
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(flash[:alert]).to eq('Shared stats not found or no longer available')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with expired sharing' do
|
||||
let(:stat) { create(:stat, :with_sharing_expired, user:, year: 2024, month: 6) }
|
||||
|
||||
it 'redirects to root with alert' do
|
||||
get public_stat_url(stat.sharing_uuid)
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(flash[:alert]).to eq('Shared stats not found or no longer available')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with disabled sharing' do
|
||||
let(:stat) { create(:stat, :with_sharing_disabled, user:, year: 2024, month: 6) }
|
||||
|
||||
it 'redirects to root with alert' do
|
||||
get public_stat_url(stat.sharing_uuid)
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(flash[:alert]).to eq('Shared stats not found or no longer available')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when stat has no points' do
|
||||
it 'renders successfully' do
|
||||
get public_stat_url(stat.sharing_uuid)
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.body).to include('Monthly Digest')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH /update_sharing' do
|
||||
context 'when user is signed in' do
|
||||
let!(:stat_to_share) { create(:stat, user:, year: 2024, month: 6) }
|
||||
|
||||
before { sign_in user }
|
||||
|
||||
context 'enabling sharing' do
|
||||
it 'enables sharing and returns success' do
|
||||
patch sharing_stats_path(year: 2024, month: 6),
|
||||
params: { enabled: '1' },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['success']).to be(true)
|
||||
expect(json_response['sharing_url']).to be_present
|
||||
expect(json_response['message']).to eq('Sharing enabled successfully')
|
||||
|
||||
stat_to_share.reload
|
||||
expect(stat_to_share.sharing_enabled?).to be(true)
|
||||
expect(stat_to_share.sharing_uuid).to be_present
|
||||
end
|
||||
|
||||
it 'sets custom expiration when provided' do
|
||||
patch sharing_stats_path(year: 2024, month: 6),
|
||||
params: { enabled: '1', expiration: '1_week' },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
stat_to_share.reload
|
||||
expect(stat_to_share.sharing_enabled?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'disabling sharing' do
|
||||
let!(:enabled_stat) { create(:stat, :with_sharing_enabled, user:, year: 2024, month: 7) }
|
||||
|
||||
it 'disables sharing and returns success' do
|
||||
patch sharing_stats_path(year: 2024, month: 7),
|
||||
params: { enabled: '0' },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['success']).to be(true)
|
||||
expect(json_response['message']).to eq('Sharing disabled successfully')
|
||||
|
||||
enabled_stat.reload
|
||||
expect(enabled_stat.sharing_enabled?).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when stat does not exist' do
|
||||
it 'returns not found' do
|
||||
patch sharing_stats_path(year: 2024, month: 12),
|
||||
params: { enabled: '1' },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not signed in' do
|
||||
it 'returns unauthorized' do
|
||||
patch sharing_stats_path(year: 2024, month: 6),
|
||||
params: { enabled: '1' },
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ RSpec.describe '/trips', type: :request do
|
|||
|
||||
it "renders a response with 422 status (i.e. to display the 'new' template)" do
|
||||
post trips_url, params: { trip: invalid_attributes }
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -151,7 +151,7 @@ RSpec.describe '/trips', type: :request do
|
|||
|
||||
it 'renders a response with 422 status' do
|
||||
patch trip_url(trip), params: { trip: invalid_attributes }
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ require 'rails_helper'
|
|||
RSpec.describe 'Users Export-Import Integration', type: :service do
|
||||
let(:original_user) { create(:user, email: 'original@example.com') }
|
||||
let(:target_user) { create(:user, email: 'target@example.com') }
|
||||
let(:temp_archive_path) { Rails.root.join('tmp', 'test_export.zip') }
|
||||
let(:temp_archive_path) { Rails.root.join('tmp/test_export.zip') }
|
||||
|
||||
after do
|
||||
File.delete(temp_archive_path) if File.exist?(temp_archive_path)
|
||||
|
|
@ -40,17 +40,12 @@ RSpec.describe 'Users Export-Import Integration', type: :service do
|
|||
Rails.logger.level = original_log_level
|
||||
end
|
||||
|
||||
puts "Import stats: #{import_stats.inspect}"
|
||||
|
||||
user_notifications_count = original_user.notifications.where.not(
|
||||
title: ['Data import completed', 'Data import failed', 'Export completed', 'Export failed']
|
||||
).count
|
||||
|
||||
target_counts = calculate_user_entity_counts(target_user)
|
||||
|
||||
puts "Original counts: #{original_counts.inspect}"
|
||||
puts "Target counts: #{target_counts.inspect}"
|
||||
|
||||
expect(target_counts[:areas]).to eq(original_counts[:areas])
|
||||
expect(target_counts[:imports]).to eq(original_counts[:imports])
|
||||
expect(target_counts[:exports]).to eq(original_counts[:exports])
|
||||
|
|
@ -185,18 +180,18 @@ RSpec.describe 'Users Export-Import Integration', type: :service do
|
|||
|
||||
# Verify all entities were imported correctly
|
||||
expect(import_stats[:places_created]).to eq(original_places_count),
|
||||
"Expected #{original_places_count} places to be created, got #{import_stats[:places_created]}"
|
||||
"Expected #{original_places_count} places to be created, got #{import_stats[:places_created]}"
|
||||
expect(import_stats[:visits_created]).to eq(original_visits_count),
|
||||
"Expected #{original_visits_count} visits to be created, got #{import_stats[:visits_created]}"
|
||||
"Expected #{original_visits_count} visits to be created, got #{import_stats[:visits_created]}"
|
||||
|
||||
# Verify the imported user has access to all their data
|
||||
imported_places_count = import_user.places.distinct.count
|
||||
imported_visits_count = import_user.visits.count
|
||||
|
||||
expect(imported_places_count).to eq(original_places_count),
|
||||
"Expected user to have access to #{original_places_count} places, got #{imported_places_count}"
|
||||
"Expected user to have access to #{original_places_count} places, got #{imported_places_count}"
|
||||
expect(imported_visits_count).to eq(original_visits_count),
|
||||
"Expected user to have #{original_visits_count} visits, got #{imported_visits_count}"
|
||||
"Expected user to have #{original_visits_count} visits, got #{imported_visits_count}"
|
||||
|
||||
# Verify specific visits have their place associations
|
||||
imported_visits = import_user.visits.includes(:place)
|
||||
|
|
@ -205,7 +200,7 @@ RSpec.describe 'Users Export-Import Integration', type: :service do
|
|||
|
||||
# Verify place names are preserved
|
||||
place_names = visits_with_places.map { |v| v.place.name }.sort
|
||||
expect(place_names).to eq(['Gym', 'Home', 'Office'])
|
||||
expect(place_names).to eq(%w[Gym Home Office])
|
||||
|
||||
# Cleanup
|
||||
temp_export_file.unlink
|
||||
|
|
@ -217,11 +212,11 @@ RSpec.describe 'Users Export-Import Integration', type: :service do
|
|||
|
||||
def create_full_user_dataset(user)
|
||||
user.update!(settings: {
|
||||
'distance_unit' => 'km',
|
||||
'distance_unit' => 'km',
|
||||
'timezone' => 'America/New_York',
|
||||
'immich_url' => 'https://immich.example.com',
|
||||
'immich_api_key' => 'test-api-key'
|
||||
})
|
||||
})
|
||||
|
||||
usa = create(:country, name: 'United States', iso_a2: 'US', iso_a3: 'USA')
|
||||
canada = create(:country, name: 'Canada', iso_a2: 'CA', iso_a3: 'CAN')
|
||||
|
|
@ -271,37 +266,32 @@ RSpec.describe 'Users Export-Import Integration', type: :service do
|
|||
visit3 = create(:visit, user: user, place: nil, name: 'Unknown Location')
|
||||
|
||||
create_list(:point, 5,
|
||||
user: user,
|
||||
import: import1,
|
||||
country: usa,
|
||||
visit: visit1,
|
||||
latitude: 40.7589,
|
||||
longitude: -73.9851
|
||||
)
|
||||
user: user,
|
||||
import: import1,
|
||||
country: usa,
|
||||
visit: visit1,
|
||||
latitude: 40.7589,
|
||||
longitude: -73.9851)
|
||||
|
||||
create_list(:point, 3,
|
||||
user: user,
|
||||
import: import2,
|
||||
country: canada,
|
||||
visit: visit2,
|
||||
latitude: 40.7128,
|
||||
longitude: -74.0060
|
||||
)
|
||||
user: user,
|
||||
import: import2,
|
||||
country: canada,
|
||||
visit: visit2,
|
||||
latitude: 40.7128,
|
||||
longitude: -74.0060)
|
||||
|
||||
create_list(:point, 2,
|
||||
user: user,
|
||||
import: nil,
|
||||
country: nil,
|
||||
visit: nil
|
||||
)
|
||||
user: user,
|
||||
import: nil,
|
||||
country: nil,
|
||||
visit: nil)
|
||||
|
||||
create_list(:point, 2,
|
||||
user: user,
|
||||
import: import1,
|
||||
country: usa,
|
||||
visit: visit3
|
||||
)
|
||||
|
||||
user: user,
|
||||
import: import1,
|
||||
country: usa,
|
||||
visit: visit3)
|
||||
end
|
||||
|
||||
def calculate_user_entity_counts(user)
|
||||
|
|
@ -342,11 +332,13 @@ RSpec.describe 'Users Export-Import Integration', type: :service do
|
|||
latitude: 40.7589, longitude: -73.9851
|
||||
).first
|
||||
|
||||
if original_office_points && target_office_points
|
||||
expect(target_office_points.import.name).to eq(original_office_points.import.name) if original_office_points.import
|
||||
expect(target_office_points.country.name).to eq(original_office_points.country.name) if original_office_points.country
|
||||
expect(target_office_points.visit.name).to eq(original_office_points.visit.name) if original_office_points.visit
|
||||
return unless original_office_points && target_office_points
|
||||
|
||||
expect(target_office_points.import.name).to eq(original_office_points.import.name) if original_office_points.import
|
||||
if original_office_points.country
|
||||
expect(target_office_points.country.name).to eq(original_office_points.country.name)
|
||||
end
|
||||
expect(target_office_points.visit.name).to eq(original_office_points.visit.name) if original_office_points.visit
|
||||
end
|
||||
|
||||
def verify_settings_preserved(original_user, target_user)
|
||||
|
|
@ -375,9 +367,9 @@ RSpec.describe 'Users Export-Import Integration', type: :service do
|
|||
original_export = original_user.exports.find_by(name: 'Q1 2024 Export')
|
||||
target_export = target_user.exports.find_by(name: 'Q1 2024 Export')
|
||||
|
||||
if original_export&.file&.attached?
|
||||
expect(target_export).to be_present
|
||||
expect(target_export.file).to be_attached
|
||||
end
|
||||
return unless original_export&.file&.attached?
|
||||
|
||||
expect(target_export).to be_present
|
||||
expect(target_export.file).to be_attached
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue