2025-06-26 13:24:40 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
|
|
require 'rails_helper'
|
|
|
|
|
|
|
|
|
|
RSpec.describe Users::ExportData, type: :service do
|
|
|
|
|
let(:user) { create(:user) }
|
|
|
|
|
let(:service) { described_class.new(user) }
|
|
|
|
|
let(:timestamp) { '20241201_123000' }
|
|
|
|
|
let(:export_directory) { Rails.root.join('tmp', "#{user.email.gsub(/[^0-9A-Za-z._-]/, '_')}_#{timestamp}") }
|
|
|
|
|
let(:files_directory) { export_directory.join('files') }
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
allow(Time).to receive(:current).and_return(Time.new(2024, 12, 1, 12, 30, 0))
|
|
|
|
|
allow(FileUtils).to receive(:mkdir_p)
|
|
|
|
|
allow(FileUtils).to receive(:rm_rf)
|
|
|
|
|
allow(File).to receive(:open).and_call_original
|
|
|
|
|
allow(File).to receive(:directory?).and_return(true)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe '#export' do
|
|
|
|
|
context 'when export is successful' do
|
|
|
|
|
let(:zip_file_path) { export_directory.join('export.zip') }
|
|
|
|
|
let(:zip_file_double) { double('ZipFile') }
|
|
|
|
|
let(:export_record) { double('Export', id: 1, name: 'test.zip', update!: true, file: double('File', attach: true)) }
|
|
|
|
|
let(:notification_service_double) { double('Notifications::Create', call: true) }
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
# Mock all the export data services
|
|
|
|
|
allow(Users::ExportData::Areas).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Imports).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Exports).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Trips).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Stats).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Notifications).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Points).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Visits).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Places).to receive(:new).and_return(double(call: []))
|
|
|
|
|
|
|
|
|
|
# Mock user settings
|
|
|
|
|
allow(user).to receive(:safe_settings).and_return(double(settings: { theme: 'dark' }))
|
|
|
|
|
|
2025-06-28 06:22:56 -04:00
|
|
|
# Mock user associations for counting (needed before error occurs)
|
|
|
|
|
allow(user).to receive(:areas).and_return(double(count: 5))
|
|
|
|
|
allow(user).to receive(:imports).and_return(double(count: 12))
|
|
|
|
|
allow(user).to receive(:trips).and_return(double(count: 8))
|
|
|
|
|
allow(user).to receive(:stats).and_return(double(count: 24))
|
|
|
|
|
allow(user).to receive(:notifications).and_return(double(count: 10))
|
2025-08-22 15:27:50 -04:00
|
|
|
allow(user).to receive(:points_count).and_return(15000)
|
2025-06-28 06:22:56 -04:00
|
|
|
allow(user).to receive(:visits).and_return(double(count: 45))
|
|
|
|
|
allow(user).to receive(:places).and_return(double(count: 20))
|
|
|
|
|
|
2025-06-26 13:24:40 -04:00
|
|
|
# Mock Export creation and file attachment
|
2025-06-28 06:22:56 -04:00
|
|
|
exports_double = double('Exports', count: 3)
|
2025-06-26 13:24:40 -04:00
|
|
|
allow(user).to receive(:exports).and_return(exports_double)
|
|
|
|
|
allow(exports_double).to receive(:create!).and_return(export_record)
|
|
|
|
|
allow(export_record).to receive(:update!)
|
|
|
|
|
allow(export_record).to receive_message_chain(:file, :attach)
|
|
|
|
|
|
|
|
|
|
# Mock Zip file creation
|
2025-09-26 17:31:45 -04:00
|
|
|
allow(Zip::File).to receive(:open).with(zip_file_path, create: true).and_yield(zip_file_double)
|
2025-06-26 13:24:40 -04:00
|
|
|
allow(zip_file_double).to receive(:default_compression=)
|
|
|
|
|
allow(zip_file_double).to receive(:default_compression_level=)
|
|
|
|
|
allow(zip_file_double).to receive(:add)
|
|
|
|
|
allow(Dir).to receive(:glob).and_return([export_directory.join('data.json').to_s])
|
|
|
|
|
|
|
|
|
|
# Mock file operations - return a File instance for the zip file
|
|
|
|
|
allow(File).to receive(:open).with(export_directory.join('data.json'), 'w').and_yield(StringIO.new)
|
|
|
|
|
zip_file_io = File.new(__FILE__) # Use current file as a placeholder
|
|
|
|
|
allow(File).to receive(:open).with(zip_file_path).and_return(zip_file_io)
|
|
|
|
|
|
|
|
|
|
# Mock notifications service - prevent actual notification creation
|
|
|
|
|
allow(service).to receive(:create_success_notification)
|
|
|
|
|
|
|
|
|
|
# Mock cleanup to verify it's called
|
|
|
|
|
allow(service).to receive(:cleanup_temporary_files)
|
|
|
|
|
allow_any_instance_of(Pathname).to receive(:exist?).and_return(true)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'creates an Export record with correct attributes' do
|
|
|
|
|
expect(user.exports).to receive(:create!).with(
|
|
|
|
|
name: "user_data_export_#{timestamp}.zip",
|
|
|
|
|
file_format: :archive,
|
|
|
|
|
file_type: :user_data,
|
|
|
|
|
status: :processing
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
service.export
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'creates the export directory structure' do
|
|
|
|
|
expect(FileUtils).to receive(:mkdir_p).with(files_directory)
|
|
|
|
|
|
|
|
|
|
service.export
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'calls all export data services with correct parameters' do
|
|
|
|
|
expect(Users::ExportData::Areas).to receive(:new).with(user)
|
|
|
|
|
expect(Users::ExportData::Imports).to receive(:new).with(user, files_directory)
|
|
|
|
|
expect(Users::ExportData::Exports).to receive(:new).with(user, files_directory)
|
|
|
|
|
expect(Users::ExportData::Trips).to receive(:new).with(user)
|
|
|
|
|
expect(Users::ExportData::Stats).to receive(:new).with(user)
|
|
|
|
|
expect(Users::ExportData::Notifications).to receive(:new).with(user)
|
|
|
|
|
expect(Users::ExportData::Points).to receive(:new).with(user)
|
|
|
|
|
expect(Users::ExportData::Visits).to receive(:new).with(user)
|
|
|
|
|
expect(Users::ExportData::Places).to receive(:new).with(user)
|
|
|
|
|
|
|
|
|
|
service.export
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'creates a zip file with proper compression settings' do
|
2025-09-26 17:31:45 -04:00
|
|
|
expect(Zip::File).to receive(:open).with(zip_file_path, create: true)
|
2025-06-30 14:29:47 -04:00
|
|
|
expect(Zip).to receive(:default_compression).and_return(-1) # Mock original compression
|
|
|
|
|
expect(Zip).to receive(:default_compression=).with(Zip::Entry::DEFLATED)
|
|
|
|
|
expect(Zip).to receive(:default_compression=).with(-1) # Restoration
|
2025-06-26 13:24:40 -04:00
|
|
|
|
|
|
|
|
service.export
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'attaches the zip file to the export record' do
|
|
|
|
|
expect(export_record.file).to receive(:attach).with(
|
|
|
|
|
io: an_instance_of(File),
|
|
|
|
|
filename: export_record.name,
|
|
|
|
|
content_type: 'application/zip'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
service.export
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'marks the export as completed' do
|
|
|
|
|
expect(export_record).to receive(:update!).with(status: :completed)
|
|
|
|
|
|
|
|
|
|
service.export
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'creates a success notification' do
|
|
|
|
|
expect(service).to receive(:create_success_notification)
|
|
|
|
|
|
|
|
|
|
service.export
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'cleans up temporary files' do
|
|
|
|
|
expect(service).to receive(:cleanup_temporary_files).with(export_directory)
|
|
|
|
|
|
|
|
|
|
service.export
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'returns the export record' do
|
|
|
|
|
result = service.export
|
|
|
|
|
expect(result).to eq(export_record)
|
|
|
|
|
end
|
2025-06-28 06:22:56 -04:00
|
|
|
|
|
|
|
|
it 'calculates entity counts correctly' do
|
|
|
|
|
counts = service.send(:calculate_entity_counts)
|
|
|
|
|
|
|
|
|
|
expect(counts).to eq({
|
|
|
|
|
areas: 5,
|
|
|
|
|
imports: 12,
|
|
|
|
|
exports: 3,
|
|
|
|
|
trips: 8,
|
|
|
|
|
stats: 24,
|
|
|
|
|
notifications: 10,
|
|
|
|
|
points: 15000,
|
|
|
|
|
visits: 45,
|
|
|
|
|
places: 20
|
|
|
|
|
})
|
|
|
|
|
end
|
2025-06-26 13:24:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context 'when an error occurs during export' do
|
|
|
|
|
let(:export_record) { double('Export', id: 1, name: 'test.zip', update!: true) }
|
|
|
|
|
let(:error_message) { 'Something went wrong' }
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
# Mock Export creation first
|
2025-06-28 06:22:56 -04:00
|
|
|
exports_double = double('Exports', count: 3)
|
2025-06-26 13:24:40 -04:00
|
|
|
allow(user).to receive(:exports).and_return(exports_double)
|
|
|
|
|
allow(exports_double).to receive(:create!).and_return(export_record)
|
|
|
|
|
allow(export_record).to receive(:update!)
|
|
|
|
|
|
|
|
|
|
# Mock user settings and other dependencies that are needed before the error
|
|
|
|
|
allow(user).to receive(:safe_settings).and_return(double(settings: { theme: 'dark' }))
|
|
|
|
|
|
2025-06-28 06:22:56 -04:00
|
|
|
# Mock user associations for counting
|
|
|
|
|
allow(user).to receive(:areas).and_return(double(count: 5))
|
|
|
|
|
allow(user).to receive(:imports).and_return(double(count: 12))
|
|
|
|
|
# exports already mocked above
|
|
|
|
|
allow(user).to receive(:trips).and_return(double(count: 8))
|
|
|
|
|
allow(user).to receive(:stats).and_return(double(count: 24))
|
|
|
|
|
allow(user).to receive(:notifications).and_return(double(count: 10))
|
2025-08-22 15:27:50 -04:00
|
|
|
allow(user).to receive(:points_count).and_return(15000)
|
2025-06-28 06:22:56 -04:00
|
|
|
allow(user).to receive(:visits).and_return(double(count: 45))
|
|
|
|
|
allow(user).to receive(:places).and_return(double(count: 20))
|
|
|
|
|
|
2025-06-26 13:24:40 -04:00
|
|
|
# Then set up the error condition - make it happen during the JSON writing step
|
|
|
|
|
allow(File).to receive(:open).with(export_directory.join('data.json'), 'w').and_raise(StandardError, error_message)
|
|
|
|
|
|
2025-06-28 06:22:56 -04:00
|
|
|
allow(ExceptionReporter).to receive(:call)
|
2025-06-26 13:24:40 -04:00
|
|
|
|
|
|
|
|
# Mock cleanup method and pathname existence
|
|
|
|
|
allow(service).to receive(:cleanup_temporary_files)
|
|
|
|
|
allow_any_instance_of(Pathname).to receive(:exist?).and_return(true)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'marks the export as failed' do
|
|
|
|
|
expect(export_record).to receive(:update!).with(status: :failed)
|
|
|
|
|
|
|
|
|
|
expect { service.export }.to raise_error(StandardError, error_message)
|
|
|
|
|
end
|
|
|
|
|
|
2025-06-28 06:22:56 -04:00
|
|
|
it 'reports the error via ExceptionReporter' do
|
|
|
|
|
expect(ExceptionReporter).to receive(:call).with(an_instance_of(StandardError), 'Export failed')
|
2025-06-26 13:24:40 -04:00
|
|
|
|
|
|
|
|
expect { service.export }.to raise_error(StandardError, error_message)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'still cleans up temporary files' do
|
|
|
|
|
expect(service).to receive(:cleanup_temporary_files)
|
|
|
|
|
|
|
|
|
|
expect { service.export }.to raise_error(StandardError, error_message)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 're-raises the error' do
|
|
|
|
|
expect { service.export }.to raise_error(StandardError, error_message)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context 'when export record creation fails' do
|
|
|
|
|
before do
|
2025-06-28 06:22:56 -04:00
|
|
|
exports_double = double('Exports', count: 3)
|
2025-06-26 13:24:40 -04:00
|
|
|
allow(user).to receive(:exports).and_return(exports_double)
|
|
|
|
|
allow(exports_double).to receive(:create!).and_raise(ActiveRecord::RecordInvalid)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'does not try to mark export as failed when export_record is nil' do
|
|
|
|
|
expect { service.export }.to raise_error(ActiveRecord::RecordInvalid)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context 'with file compression scenarios' do
|
|
|
|
|
let(:export_record) { double('Export', id: 1, name: 'test.zip', update!: true, file: double('File', attach: true)) }
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
# Mock Export creation
|
2025-06-28 06:22:56 -04:00
|
|
|
exports_double = double('Exports', count: 3)
|
2025-06-26 13:24:40 -04:00
|
|
|
allow(user).to receive(:exports).and_return(exports_double)
|
|
|
|
|
allow(exports_double).to receive(:create!).and_return(export_record)
|
|
|
|
|
allow(export_record).to receive(:update!)
|
|
|
|
|
allow(export_record).to receive_message_chain(:file, :attach)
|
|
|
|
|
|
|
|
|
|
# Mock all export services to prevent actual calls
|
|
|
|
|
allow(Users::ExportData::Areas).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Imports).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Exports).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Trips).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Stats).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Notifications).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Points).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Visits).to receive(:new).and_return(double(call: []))
|
|
|
|
|
allow(Users::ExportData::Places).to receive(:new).and_return(double(call: []))
|
|
|
|
|
|
|
|
|
|
allow(user).to receive(:safe_settings).and_return(double(settings: {}))
|
2025-06-28 06:22:56 -04:00
|
|
|
|
|
|
|
|
# Mock user associations for counting
|
|
|
|
|
allow(user).to receive(:areas).and_return(double(count: 5))
|
|
|
|
|
allow(user).to receive(:imports).and_return(double(count: 12))
|
|
|
|
|
# exports already mocked above
|
|
|
|
|
allow(user).to receive(:trips).and_return(double(count: 8))
|
|
|
|
|
allow(user).to receive(:stats).and_return(double(count: 24))
|
|
|
|
|
allow(user).to receive(:notifications).and_return(double(count: 10))
|
2025-08-22 15:27:50 -04:00
|
|
|
allow(user).to receive(:points_count).and_return(15000)
|
2025-06-28 06:22:56 -04:00
|
|
|
allow(user).to receive(:visits).and_return(double(count: 45))
|
|
|
|
|
allow(user).to receive(:places).and_return(double(count: 20))
|
|
|
|
|
|
2025-06-26 13:24:40 -04:00
|
|
|
allow(File).to receive(:open).and_call_original
|
|
|
|
|
allow(File).to receive(:open).with(export_directory.join('data.json'), 'w').and_yield(StringIO.new)
|
|
|
|
|
|
|
|
|
|
# Use current file as placeholder for zip file
|
|
|
|
|
zip_file_io = File.new(__FILE__)
|
|
|
|
|
allow(File).to receive(:open).with(export_directory.join('export.zip')).and_return(zip_file_io)
|
|
|
|
|
|
|
|
|
|
# Mock notifications service
|
|
|
|
|
allow(service).to receive(:create_success_notification)
|
|
|
|
|
|
|
|
|
|
# Mock cleanup
|
|
|
|
|
allow(service).to receive(:cleanup_temporary_files)
|
|
|
|
|
allow_any_instance_of(Pathname).to receive(:exist?).and_return(true)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'calls create_zip_archive with correct parameters' do
|
|
|
|
|
expect(service).to receive(:create_zip_archive).with(export_directory, export_directory.join('export.zip'))
|
|
|
|
|
|
|
|
|
|
service.export
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe 'private methods' do
|
|
|
|
|
describe '#export_directory' do
|
|
|
|
|
it 'generates correct directory path' do
|
|
|
|
|
allow(Time).to receive_message_chain(:current, :strftime).with('%Y%m%d_%H%M%S').and_return(timestamp)
|
|
|
|
|
|
|
|
|
|
# Call export to initialize the directory paths
|
|
|
|
|
service.instance_variable_set(:@export_directory, Rails.root.join('tmp', "#{user.email.gsub(/[^0-9A-Za-z._-]/, '_')}_#{timestamp}"))
|
|
|
|
|
|
|
|
|
|
expect(service.send(:export_directory).to_s).to include(user.email.gsub(/[^0-9A-Za-z._-]/, '_'))
|
|
|
|
|
expect(service.send(:export_directory).to_s).to include(timestamp)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe '#files_directory' do
|
|
|
|
|
it 'returns files subdirectory of export directory' do
|
|
|
|
|
# Initialize the export directory first
|
|
|
|
|
service.instance_variable_set(:@export_directory, Rails.root.join('tmp', "test_export"))
|
|
|
|
|
service.instance_variable_set(:@files_directory, service.instance_variable_get(:@export_directory).join('files'))
|
|
|
|
|
|
|
|
|
|
files_dir = service.send(:files_directory)
|
|
|
|
|
expect(files_dir.to_s).to end_with('files')
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe '#cleanup_temporary_files' do
|
|
|
|
|
context 'when directory exists' do
|
|
|
|
|
before do
|
|
|
|
|
allow(File).to receive(:directory?).and_return(true)
|
|
|
|
|
allow(Rails.logger).to receive(:info)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'removes the directory' do
|
|
|
|
|
expect(FileUtils).to receive(:rm_rf).with(export_directory)
|
|
|
|
|
|
|
|
|
|
service.send(:cleanup_temporary_files, export_directory)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'logs the cleanup' do
|
|
|
|
|
expect(Rails.logger).to receive(:info).with("Cleaning up temporary export directory: #{export_directory}")
|
|
|
|
|
|
|
|
|
|
service.send(:cleanup_temporary_files, export_directory)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context 'when cleanup fails' do
|
|
|
|
|
before do
|
|
|
|
|
allow(File).to receive(:directory?).and_return(true)
|
|
|
|
|
allow(FileUtils).to receive(:rm_rf).and_raise(StandardError, 'Permission denied')
|
2025-06-28 06:22:56 -04:00
|
|
|
allow(ExceptionReporter).to receive(:call)
|
2025-06-26 13:24:40 -04:00
|
|
|
end
|
|
|
|
|
|
2025-06-28 06:22:56 -04:00
|
|
|
it 'reports the error via ExceptionReporter but does not re-raise' do
|
|
|
|
|
expect(ExceptionReporter).to receive(:call).with(an_instance_of(StandardError), 'Failed to cleanup temporary files')
|
2025-06-26 13:24:40 -04:00
|
|
|
|
|
|
|
|
expect { service.send(:cleanup_temporary_files, export_directory) }.not_to raise_error
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context 'when directory does not exist' do
|
|
|
|
|
before do
|
|
|
|
|
allow(File).to receive(:directory?).and_return(false)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'does not attempt cleanup' do
|
|
|
|
|
expect(FileUtils).not_to receive(:rm_rf)
|
|
|
|
|
|
|
|
|
|
service.send(:cleanup_temporary_files, export_directory)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2025-06-28 06:22:56 -04:00
|
|
|
|
|
|
|
|
describe '#calculate_entity_counts' do
|
|
|
|
|
before do
|
|
|
|
|
# Mock user associations for counting
|
|
|
|
|
allow(user).to receive(:areas).and_return(double(count: 5))
|
|
|
|
|
allow(user).to receive(:imports).and_return(double(count: 12))
|
|
|
|
|
allow(user).to receive(:exports).and_return(double(count: 3))
|
|
|
|
|
allow(user).to receive(:trips).and_return(double(count: 8))
|
|
|
|
|
allow(user).to receive(:stats).and_return(double(count: 24))
|
|
|
|
|
allow(user).to receive(:notifications).and_return(double(count: 10))
|
2025-08-22 15:27:50 -04:00
|
|
|
allow(user).to receive(:points_count).and_return(15000)
|
2025-06-28 06:22:56 -04:00
|
|
|
allow(user).to receive(:visits).and_return(double(count: 45))
|
|
|
|
|
allow(user).to receive(:places).and_return(double(count: 20))
|
|
|
|
|
allow(Rails.logger).to receive(:info)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'returns correct counts for all entity types' do
|
|
|
|
|
counts = service.send(:calculate_entity_counts)
|
|
|
|
|
|
|
|
|
|
expect(counts).to eq({
|
|
|
|
|
areas: 5,
|
|
|
|
|
imports: 12,
|
|
|
|
|
exports: 3,
|
|
|
|
|
trips: 8,
|
|
|
|
|
stats: 24,
|
|
|
|
|
notifications: 10,
|
|
|
|
|
points: 15000,
|
|
|
|
|
visits: 45,
|
|
|
|
|
places: 20
|
|
|
|
|
})
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'logs the calculation process' do
|
|
|
|
|
expect(Rails.logger).to receive(:info).with("Calculating entity counts for export")
|
|
|
|
|
expect(Rails.logger).to receive(:info).with(/Entity counts:/)
|
|
|
|
|
|
|
|
|
|
service.send(:calculate_entity_counts)
|
|
|
|
|
end
|
|
|
|
|
end
|
2025-06-26 13:24:40 -04:00
|
|
|
end
|
|
|
|
|
end
|