mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 01:31:39 -05:00
297 lines
11 KiB
Ruby
297 lines
11 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rails_helper'
|
|
|
|
RSpec.describe Users::ImportData, type: :service do
|
|
let(:user) { create(:user) }
|
|
let(:archive_path) { Rails.root.join('tmp', 'test_export.zip') }
|
|
let(:service) { described_class.new(user, archive_path) }
|
|
let(:import_directory) { Rails.root.join('tmp', "import_#{user.email.gsub(/[^0-9A-Za-z._-]/, '_')}_1234567890") }
|
|
|
|
before do
|
|
allow(Time).to receive(:current).and_return(Time.zone.at(1234567890))
|
|
allow(FileUtils).to receive(:mkdir_p)
|
|
allow(FileUtils).to receive(:rm_rf)
|
|
allow(File).to receive(:directory?).and_return(true)
|
|
end
|
|
|
|
describe '#import' do
|
|
let(:sample_data) do
|
|
{
|
|
'counts' => {
|
|
'areas' => 2,
|
|
'places' => 3,
|
|
'imports' => 1,
|
|
'exports' => 1,
|
|
'trips' => 2,
|
|
'stats' => 1,
|
|
'notifications' => 2,
|
|
'visits' => 4,
|
|
'points' => 1000
|
|
},
|
|
'settings' => { 'theme' => 'dark' },
|
|
'areas' => [{ 'name' => 'Home', 'latitude' => '40.7128', 'longitude' => '-74.0060' }],
|
|
'places' => [{ 'name' => 'Office', 'latitude' => '40.7589', 'longitude' => '-73.9851' }],
|
|
'imports' => [{ 'name' => 'test.json', 'source' => 'owntracks' }],
|
|
'exports' => [{ 'name' => 'export.json', 'status' => 'completed' }],
|
|
'trips' => [{ 'name' => 'Trip to NYC', 'distance' => 100.5 }],
|
|
'stats' => [{ 'year' => 2024, 'month' => 1, 'distance' => 456.78 }],
|
|
'notifications' => [{ 'title' => 'Test', 'content' => 'Test notification' }],
|
|
'visits' => [{ 'name' => 'Work Visit', 'duration' => 3600 }],
|
|
'points' => [{ 'latitude' => 40.7128, 'longitude' => -74.0060, 'timestamp' => 1234567890 }]
|
|
}
|
|
end
|
|
|
|
before do
|
|
# Mock ZIP file extraction
|
|
zipfile_mock = double('ZipFile')
|
|
allow(zipfile_mock).to receive(:each)
|
|
allow(Zip::File).to receive(:open).with(archive_path).and_yield(zipfile_mock)
|
|
|
|
# Mock JSON loading and File operations
|
|
allow(File).to receive(:exist?).and_return(false)
|
|
allow(File).to receive(:exist?).with(import_directory.join('data.json')).and_return(true)
|
|
allow(File).to receive(:read).with(import_directory.join('data.json')).and_return(sample_data.to_json)
|
|
|
|
# Mock all import services
|
|
allow(Users::ImportData::Settings).to receive(:new).and_return(double(call: true))
|
|
allow(Users::ImportData::Areas).to receive(:new).and_return(double(call: 2))
|
|
allow(Users::ImportData::Places).to receive(:new).and_return(double(call: 3))
|
|
allow(Users::ImportData::Imports).to receive(:new).and_return(double(call: [1, 5]))
|
|
allow(Users::ImportData::Exports).to receive(:new).and_return(double(call: [1, 2]))
|
|
allow(Users::ImportData::Trips).to receive(:new).and_return(double(call: 2))
|
|
allow(Users::ImportData::Stats).to receive(:new).and_return(double(call: 1))
|
|
allow(Users::ImportData::Notifications).to receive(:new).and_return(double(call: 2))
|
|
allow(Users::ImportData::Visits).to receive(:new).and_return(double(call: 4))
|
|
allow(Users::ImportData::Points).to receive(:new).and_return(double(call: 1000))
|
|
|
|
# Mock notifications
|
|
allow(::Notifications::Create).to receive(:new).and_return(double(call: true))
|
|
|
|
# Mock cleanup
|
|
allow(service).to receive(:cleanup_temporary_files)
|
|
allow_any_instance_of(Pathname).to receive(:exist?).and_return(true)
|
|
end
|
|
|
|
context 'when import is successful' do
|
|
it 'creates import directory' do
|
|
expect(FileUtils).to receive(:mkdir_p).with(import_directory)
|
|
|
|
service.import
|
|
end
|
|
|
|
it 'extracts the archive' do
|
|
expect(Zip::File).to receive(:open).with(archive_path)
|
|
|
|
service.import
|
|
end
|
|
|
|
it 'loads JSON data from extracted files' do
|
|
expect(File).to receive(:exist?).with(import_directory.join('data.json'))
|
|
expect(File).to receive(:read).with(import_directory.join('data.json'))
|
|
|
|
service.import
|
|
end
|
|
|
|
it 'calls all import services in correct order' do
|
|
expect(Users::ImportData::Settings).to receive(:new).with(user, sample_data['settings']).ordered
|
|
expect(Users::ImportData::Areas).to receive(:new).with(user, sample_data['areas']).ordered
|
|
expect(Users::ImportData::Places).to receive(:new).with(user, sample_data['places']).ordered
|
|
expect(Users::ImportData::Imports).to receive(:new).with(user, sample_data['imports'], import_directory.join('files')).ordered
|
|
expect(Users::ImportData::Exports).to receive(:new).with(user, sample_data['exports'], import_directory.join('files')).ordered
|
|
expect(Users::ImportData::Trips).to receive(:new).with(user, sample_data['trips']).ordered
|
|
expect(Users::ImportData::Stats).to receive(:new).with(user, sample_data['stats']).ordered
|
|
expect(Users::ImportData::Notifications).to receive(:new).with(user, sample_data['notifications']).ordered
|
|
expect(Users::ImportData::Visits).to receive(:new).with(user, sample_data['visits']).ordered
|
|
expect(Users::ImportData::Points).to receive(:new).with(user, sample_data['points']).ordered
|
|
|
|
service.import
|
|
end
|
|
|
|
it 'creates success notification with import stats' do
|
|
expect(::Notifications::Create).to receive(:new).with(
|
|
user: user,
|
|
title: 'Data import completed',
|
|
content: match(/1000 points.*4 visits.*3 places.*2 trips/),
|
|
kind: :info
|
|
)
|
|
|
|
service.import
|
|
end
|
|
|
|
it 'cleans up temporary files' do
|
|
expect(service).to receive(:cleanup_temporary_files).with(import_directory)
|
|
|
|
service.import
|
|
end
|
|
|
|
it 'returns import statistics' do
|
|
result = service.import
|
|
|
|
expect(result).to include(
|
|
settings_updated: true,
|
|
areas_created: 2,
|
|
places_created: 3,
|
|
imports_created: 1,
|
|
exports_created: 1,
|
|
trips_created: 2,
|
|
stats_created: 1,
|
|
notifications_created: 2,
|
|
visits_created: 4,
|
|
points_created: 1000,
|
|
files_restored: 7
|
|
)
|
|
end
|
|
|
|
it 'logs expected counts if available' do
|
|
allow(Rails.logger).to receive(:info) # Allow other log messages
|
|
expect(Rails.logger).to receive(:info).with(/Expected entity counts from export:/)
|
|
|
|
service.import
|
|
end
|
|
end
|
|
|
|
context 'when JSON file is missing' do
|
|
before do
|
|
allow(File).to receive(:exist?).and_return(false)
|
|
allow(File).to receive(:exist?).with(import_directory.join('data.json')).and_return(false)
|
|
allow(ExceptionReporter).to receive(:call)
|
|
end
|
|
|
|
it 'raises an error' do
|
|
expect { service.import }.to raise_error(StandardError, 'Data file not found in archive: data.json')
|
|
end
|
|
end
|
|
|
|
context 'when JSON is invalid' do
|
|
before do
|
|
allow(File).to receive(:exist?).and_return(false)
|
|
allow(File).to receive(:exist?).with(import_directory.join('data.json')).and_return(true)
|
|
allow(File).to receive(:read).with(import_directory.join('data.json')).and_return('invalid json')
|
|
allow(ExceptionReporter).to receive(:call)
|
|
end
|
|
|
|
it 'raises a JSON parse error' do
|
|
expect { service.import }.to raise_error(StandardError, /Invalid JSON format in data file/)
|
|
end
|
|
end
|
|
|
|
context 'when an error occurs during import' do
|
|
let(:error_message) { 'Something went wrong' }
|
|
|
|
before do
|
|
allow(File).to receive(:exist?).and_return(false)
|
|
allow(File).to receive(:exist?).with(import_directory.join('data.json')).and_return(true)
|
|
allow(File).to receive(:read).with(import_directory.join('data.json')).and_return(sample_data.to_json)
|
|
allow(Users::ImportData::Settings).to receive(:new).and_raise(StandardError, error_message)
|
|
allow(ExceptionReporter).to receive(:call)
|
|
allow(::Notifications::Create).to receive(:new).and_return(double(call: true))
|
|
end
|
|
|
|
it 'creates failure notification' do
|
|
expect(::Notifications::Create).to receive(:new).with(
|
|
user: user,
|
|
title: 'Data import failed',
|
|
content: "Your data import failed with error: #{error_message}. Please check the archive format and try again.",
|
|
kind: :error
|
|
)
|
|
|
|
expect { service.import }.to raise_error(StandardError, error_message)
|
|
end
|
|
|
|
it 'reports error via ExceptionReporter' do
|
|
expect(ExceptionReporter).to receive(:call).with(
|
|
an_instance_of(StandardError),
|
|
'Data import failed'
|
|
)
|
|
|
|
expect { service.import }.to raise_error(StandardError, error_message)
|
|
end
|
|
|
|
it 'still cleans up temporary files' do
|
|
expect(service).to receive(:cleanup_temporary_files)
|
|
|
|
expect { service.import }.to raise_error(StandardError, error_message)
|
|
end
|
|
|
|
it 're-raises the error' do
|
|
expect { service.import }.to raise_error(StandardError, error_message)
|
|
end
|
|
end
|
|
|
|
context 'when data sections are missing' do
|
|
let(:minimal_data) { { 'settings' => { 'theme' => 'dark' } } }
|
|
|
|
before do
|
|
# Reset JSON file mocking
|
|
allow(File).to receive(:exist?).and_return(false)
|
|
allow(File).to receive(:exist?).with(import_directory.join('data.json')).and_return(true)
|
|
allow(File).to receive(:read).with(import_directory.join('data.json')).and_return(minimal_data.to_json)
|
|
|
|
# Only expect Settings to be called
|
|
allow(Users::ImportData::Settings).to receive(:new).and_return(double(call: true))
|
|
allow(::Notifications::Create).to receive(:new).and_return(double(call: true))
|
|
end
|
|
|
|
it 'only imports available sections' do
|
|
expect(Users::ImportData::Settings).to receive(:new).with(user, minimal_data['settings'])
|
|
expect(Users::ImportData::Areas).not_to receive(:new)
|
|
expect(Users::ImportData::Places).not_to receive(:new)
|
|
|
|
service.import
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'private methods' do
|
|
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(import_directory)
|
|
|
|
service.send(:cleanup_temporary_files, import_directory)
|
|
end
|
|
|
|
it 'logs the cleanup' do
|
|
expect(Rails.logger).to receive(:info).with("Cleaning up temporary import directory: #{import_directory}")
|
|
|
|
service.send(:cleanup_temporary_files, import_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')
|
|
allow(ExceptionReporter).to receive(:call)
|
|
end
|
|
|
|
it 'reports error via ExceptionReporter but does not re-raise' do
|
|
expect(ExceptionReporter).to receive(:call).with(
|
|
an_instance_of(StandardError),
|
|
'Failed to cleanup temporary files'
|
|
)
|
|
|
|
expect { service.send(:cleanup_temporary_files, import_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, import_directory)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|