mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Merge pull request #21 from Freika/fix/import-files-rework
Fix imports uploading
This commit is contained in:
commit
b447c67916
27 changed files with 191 additions and 56 deletions
|
|
@ -1 +1 @@
|
|||
0.1.8.1
|
||||
0.1.9
|
||||
|
|
|
|||
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [0.1.9] — 2024-04-25
|
||||
|
||||
### Added
|
||||
|
||||
- A test for CheckAppVersion service class
|
||||
|
||||
### Changed
|
||||
|
||||
- Replaced ActiveStorage with Shrine for file uploads
|
||||
|
||||
### Fixed
|
||||
|
||||
- `ActiveStorage::FileNotFoundError` error when uploading export files
|
||||
|
||||
|
||||
## [0.1.8.1] — 2024-04-21
|
||||
|
||||
### Changed
|
||||
|
|
|
|||
17
Gemfile
17
Gemfile
|
|
@ -5,37 +5,38 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
|||
|
||||
ruby '3.2.3'
|
||||
gem 'bootsnap', require: false
|
||||
gem 'chartkick'
|
||||
gem 'devise'
|
||||
gem 'geocoder'
|
||||
gem 'importmap-rails'
|
||||
gem 'pg'
|
||||
gem 'puma'
|
||||
gem 'pundit'
|
||||
gem 'rails'
|
||||
gem 'shrine', '~> 3.5'
|
||||
gem 'sidekiq'
|
||||
gem 'sidekiq-cron'
|
||||
gem 'sprockets-rails'
|
||||
gem 'stimulus-rails'
|
||||
gem 'tailwindcss-rails'
|
||||
gem 'turbo-rails'
|
||||
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
|
||||
gem 'importmap-rails'
|
||||
gem 'chartkick'
|
||||
gem 'geocoder'
|
||||
gem 'sidekiq'
|
||||
gem 'sidekiq-cron'
|
||||
|
||||
|
||||
group :development, :test do
|
||||
gem 'debug', platforms: %i[mri mingw x64_mingw]
|
||||
gem 'dotenv-rails'
|
||||
gem 'factory_bot_rails'
|
||||
gem 'ffaker'
|
||||
gem 'rspec-rails'
|
||||
gem 'dotenv-rails'
|
||||
gem 'pry-byebug'
|
||||
gem 'pry-rails'
|
||||
gem 'rspec-rails'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'shoulda-matchers'
|
||||
gem 'simplecov'
|
||||
gem 'super_diff'
|
||||
gem 'webmock'
|
||||
end
|
||||
|
||||
group :development do
|
||||
|
|
|
|||
19
Gemfile.lock
19
Gemfile.lock
|
|
@ -75,6 +75,8 @@ GEM
|
|||
minitest (>= 5.1)
|
||||
mutex_m
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.6)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
ast (2.4.2)
|
||||
attr_extras (7.1.0)
|
||||
base64 (0.2.0)
|
||||
|
|
@ -88,6 +90,10 @@ GEM
|
|||
coderay (1.1.3)
|
||||
concurrent-ruby (1.2.3)
|
||||
connection_pool (2.4.1)
|
||||
content_disposition (1.0.0)
|
||||
crack (1.0.0)
|
||||
bigdecimal
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
date (3.3.4)
|
||||
debug (1.9.2)
|
||||
|
|
@ -105,6 +111,8 @@ GEM
|
|||
dotenv-rails (3.1.0)
|
||||
dotenv (= 3.1.0)
|
||||
railties (>= 6.1)
|
||||
down (5.4.2)
|
||||
addressable (~> 2.8)
|
||||
drb (2.2.1)
|
||||
erubi (1.12.0)
|
||||
et-orbi (1.2.11)
|
||||
|
|
@ -122,6 +130,7 @@ GEM
|
|||
geocoder (1.8.2)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
hashdiff (1.1.0)
|
||||
i18n (1.14.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
importmap-rails (2.0.1)
|
||||
|
|
@ -189,6 +198,7 @@ GEM
|
|||
pry (>= 0.10.4)
|
||||
psych (5.1.2)
|
||||
stringio
|
||||
public_suffix (5.0.5)
|
||||
puma (6.4.2)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.3.1)
|
||||
|
|
@ -285,6 +295,9 @@ GEM
|
|||
ruby-progressbar (1.13.0)
|
||||
shoulda-matchers (6.2.0)
|
||||
activesupport (>= 5.2.0)
|
||||
shrine (3.5.0)
|
||||
content_disposition (~> 1.0)
|
||||
down (~> 5.1)
|
||||
sidekiq (7.2.2)
|
||||
concurrent-ruby (< 2)
|
||||
connection_pool (>= 2.3.0)
|
||||
|
|
@ -337,6 +350,10 @@ GEM
|
|||
unicode-display_width (2.5.0)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
webmock (3.23.0)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.8.1)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
|
|
@ -372,6 +389,7 @@ DEPENDENCIES
|
|||
rspec-rails
|
||||
rubocop-rails
|
||||
shoulda-matchers
|
||||
shrine (~> 3.5)
|
||||
sidekiq
|
||||
sidekiq-cron
|
||||
simplecov
|
||||
|
|
@ -381,6 +399,7 @@ DEPENDENCIES
|
|||
tailwindcss-rails
|
||||
turbo-rails
|
||||
tzinfo-data
|
||||
webmock
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.2.3p157
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -15,19 +15,18 @@ class ImportsController < ApplicationController
|
|||
|
||||
def create
|
||||
files = import_params[:files].reject(&:blank?)
|
||||
|
||||
import_ids = files.map do |file|
|
||||
import = current_user.imports.create(
|
||||
name: file.original_filename,
|
||||
source: params[:import][:source]
|
||||
)
|
||||
|
||||
import.file.attach(file)
|
||||
import.update(raw_data: JSON.parse(File.read(file)))
|
||||
import.id
|
||||
end
|
||||
|
||||
import_ids.each do |import_id|
|
||||
ImportJob.set(wait: 5.seconds).perform_later(current_user.id, import_id)
|
||||
end
|
||||
import_ids.each { ImportJob.perform_later(current_user.id, _1) }
|
||||
|
||||
redirect_to imports_url, notice: "#{files.size} files are queued to be imported in background", status: :see_other
|
||||
rescue StandardError => e
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ImportJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(user_id, import_id)
|
||||
user = User.find(user_id)
|
||||
import = user.imports.find(import_id)
|
||||
file = import.file
|
||||
|
||||
sleep 3 # It takes time to process uploaded file
|
||||
|
||||
result = parser(import.source).new(import).call
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Import < ApplicationRecord
|
||||
belongs_to :user
|
||||
has_many :points, dependent: :destroy
|
||||
|
||||
has_one_attached :file
|
||||
include ImportUploader::Attachment(:raw)
|
||||
|
||||
enum source: { google: 0, owntracks: 1 }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class CheckAppVersion
|
|||
def call
|
||||
begin
|
||||
latest_version = JSON.parse(Net::HTTP.get(URI.parse(@repo_url)))[0]['name']
|
||||
rescue
|
||||
rescue StandardError
|
||||
return false
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class GoogleMaps::TimelineParser
|
||||
attr_reader :import, :json
|
||||
attr_reader :import
|
||||
|
||||
def initialize(import)
|
||||
@import = import
|
||||
@json = JSON.parse(import.file.download)
|
||||
end
|
||||
|
||||
def call
|
||||
|
|
@ -38,7 +37,7 @@ class GoogleMaps::TimelineParser
|
|||
private
|
||||
|
||||
def parse_json
|
||||
json['timelineObjects'].flat_map do |timeline_object|
|
||||
import.raw_data['timelineObjects'].flat_map do |timeline_object|
|
||||
if timeline_object['activitySegment'].present?
|
||||
if timeline_object['activitySegment']['startLocation'].blank?
|
||||
next if timeline_object['activitySegment']['waypointPath'].blank?
|
||||
|
|
@ -61,7 +60,7 @@ class GoogleMaps::TimelineParser
|
|||
end
|
||||
elsif timeline_object['placeVisit'].present?
|
||||
if timeline_object['placeVisit']['location']['latitudeE7'].present? &&
|
||||
timeline_object['placeVisit']['location']['longitudeE7'].present?
|
||||
timeline_object['placeVisit']['location']['longitudeE7'].present?
|
||||
{
|
||||
latitude: timeline_object['placeVisit']['location']['latitudeE7'].to_f / 10**7,
|
||||
longitude: timeline_object['placeVisit']['location']['longitudeE7'].to_f / 10**7,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ class OwnTracks::ExportParser
|
|||
|
||||
def initialize(import)
|
||||
@import = import
|
||||
@json = JSON.parse(import.file.download)
|
||||
@json = import.raw_data
|
||||
end
|
||||
|
||||
def call
|
||||
|
|
@ -32,7 +32,7 @@ class OwnTracks::ExportParser
|
|||
doubles = points_data.size - points
|
||||
processed = points + doubles
|
||||
|
||||
{ raw_points: points_data.size, points: points, doubles: doubles, processed: processed }
|
||||
{ raw_points: points_data.size, points:, doubles:, processed: }
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -40,8 +40,8 @@ class OwnTracks::ExportParser
|
|||
def parse_json
|
||||
points = []
|
||||
|
||||
json.keys.each do |user|
|
||||
json[user].keys.each do |devise|
|
||||
json.each_key do |user|
|
||||
json[user].each_key do |devise|
|
||||
json[user][devise].each { |point| points << OwnTracks::Params.new(point).call }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
5
app/uploaders/import_uploader.rb
Normal file
5
app/uploaders/import_uploader.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ImportUploader < Shrine
|
||||
# plugins and uploading logic
|
||||
end
|
||||
13
config/shrine.rb
Normal file
13
config/shrine.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'shrine'
|
||||
require 'shrine/storage/file_system'
|
||||
|
||||
Shrine.storages = {
|
||||
cache: Shrine::Storage::FileSystem.new('public', prefix: 'uploads/cache'), # temporary
|
||||
store: Shrine::Storage::FileSystem.new('public', prefix: 'uploads') # permanent
|
||||
}
|
||||
|
||||
Shrine.plugin :activerecord # loads Active Record integration
|
||||
Shrine.plugin :cached_attachment_data # enables retaining cached file across form redisplays
|
||||
Shrine.plugin :restore_cached_data # extracts metadata for assigned cached files
|
||||
5
db/migrate/20240425200155_add_raw_data_to_imports.rb
Normal file
5
db/migrate/20240425200155_add_raw_data_to_imports.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
class AddRawDataToImports < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_column :imports, :raw_data, :jsonb
|
||||
end
|
||||
end
|
||||
3
db/schema.rb
generated
3
db/schema.rb
generated
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_04_04_154959) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_04_25_200155) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
|
|
@ -51,6 +51,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_04_04_154959) do
|
|||
t.integer "raw_points", default: 0
|
||||
t.integer "doubles", default: 0
|
||||
t.integer "processed", default: 0
|
||||
t.jsonb "raw_data"
|
||||
t.index ["source"], name: "index_imports_on_source"
|
||||
t.index ["user_id"], name: "index_imports_on_user_id"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :import do
|
||||
user
|
||||
name { 'APRIL_2013.json' }
|
||||
source { 1 }
|
||||
raw_data { JSON.parse(File.read('spec/fixtures/files/owntracks/export.json')) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@ RSpec.describe ImportJob, type: :job do
|
|||
describe '#perform' do
|
||||
subject(:perform) { described_class.new.perform(user.id, import.id) }
|
||||
|
||||
let(:file_path) { 'spec/fixtures/files/owntracks/export.json' }
|
||||
let(:file) { fixture_file_upload(file_path) }
|
||||
let(:user) { create(:user) }
|
||||
let(:import) { create(:import, user: user, file: file, name: File.basename(file.path)) }
|
||||
let(:import) { create(:import, user:, name: 'owntracks_export.json') }
|
||||
|
||||
it 'creates points' do
|
||||
expect { perform }.to change { Point.count }.by(8)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
ENV['RAILS_ENV'] ||= 'test'
|
||||
require_relative '../config/environment'
|
||||
|
|
@ -6,7 +8,7 @@ abort('The Rails environment is running in production mode!') if Rails.env.produ
|
|||
require 'rspec/rails'
|
||||
# Add additional requires below this line. Rails is not loaded until this point!
|
||||
|
||||
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
|
||||
Dir[Rails.root.join('spec/support/**/*.rb')].sort.each { |f| require f }
|
||||
|
||||
# Checks for pending migrations and applies them before tests are run.
|
||||
# If you are not using ActiveRecord, you can remove these lines.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "Exports", type: :request do
|
||||
describe "GET /create" do
|
||||
RSpec.describe 'Exports', type: :request do
|
||||
describe 'GET /create' do
|
||||
before do
|
||||
stub_request(:any, 'https://api.github.com/repos/Freika/dawarich/tags')
|
||||
.to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {})
|
||||
|
||||
sign_in create(:user)
|
||||
end
|
||||
|
||||
it "returns http success" do
|
||||
get "/export"
|
||||
it 'returns http success' do
|
||||
get '/export'
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "Homes", type: :request do
|
||||
describe "GET /" do
|
||||
it "returns http success" do
|
||||
get "/"
|
||||
RSpec.describe 'Homes', type: :request do
|
||||
describe 'GET /' 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
|
||||
|
||||
it 'returns http success' do
|
||||
get '/'
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "Imports", type: :request do
|
||||
describe "GET /imports" do
|
||||
RSpec.describe 'Imports', 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
|
||||
|
||||
describe 'GET /imports' do
|
||||
context 'when user is logged in' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
|
|
@ -9,7 +14,7 @@ RSpec.describe "Imports", type: :request do
|
|||
sign_in user
|
||||
end
|
||||
|
||||
it "returns http success" do
|
||||
it 'returns http success' do
|
||||
get imports_path
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
|
@ -27,7 +32,7 @@ RSpec.describe "Imports", type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
describe "POST /imports" do
|
||||
describe 'POST /imports' do
|
||||
context 'when user is logged in' do
|
||||
let(:user) { create(:user) }
|
||||
let(:file) { fixture_file_upload('owntracks/export.json', 'application/json') }
|
||||
|
|
|
|||
|
|
@ -1,13 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "Points", type: :request do
|
||||
describe "GET /index" do
|
||||
RSpec.describe 'Points', 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
|
||||
|
||||
describe 'GET /index' do
|
||||
context 'when user signed in' do
|
||||
before do
|
||||
sign_in create(:user)
|
||||
end
|
||||
|
||||
it "returns http success" do
|
||||
it 'returns http success' do
|
||||
get points_path
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
|
@ -15,7 +22,7 @@ RSpec.describe "Points", type: :request do
|
|||
end
|
||||
|
||||
context 'when user not signed in' do
|
||||
it "returns http success" do
|
||||
it 'returns http success' do
|
||||
get points_path
|
||||
|
||||
expect(response).to have_http_status(302)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "/stats", type: :request do
|
||||
RSpec.describe '/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
|
||||
|
||||
describe "GET /index" do
|
||||
it "renders a successful response" do
|
||||
describe 'GET /index' do
|
||||
it 'renders a successful response' do
|
||||
get stats_url
|
||||
expect(response.status).to eq(302)
|
||||
end
|
||||
|
|
|
|||
43
spec/services/check_app_version_spec.rb
Normal file
43
spec/services/check_app_version_spec.rb
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe CheckAppVersion do
|
||||
describe '#call' do
|
||||
subject(:check_app_version) { described_class.new.call }
|
||||
|
||||
let(:app_version) { File.read('.app_version').strip }
|
||||
|
||||
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 'when latest version is newer' do
|
||||
before { allow(File).to receive(:read).with('.app_version').and_return('0.9.0') }
|
||||
|
||||
it { is_expected.to be true }
|
||||
end
|
||||
|
||||
context 'when latest version is the same' do
|
||||
before { allow(File).to receive(:read).with('.app_version').and_return('1.0.0') }
|
||||
|
||||
it { is_expected.to be false }
|
||||
end
|
||||
|
||||
context 'when latest version is older' do
|
||||
before { allow(File).to receive(:read).with('.app_version').and_return('1.1.0') }
|
||||
|
||||
it { is_expected.to be true }
|
||||
end
|
||||
|
||||
context 'when request fails' do
|
||||
before do
|
||||
allow(Net::HTTP).to receive(:get).and_raise(StandardError)
|
||||
allow(File).to receive(:read).with('.app_version').and_return(app_version)
|
||||
end
|
||||
|
||||
it { is_expected.to be false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe OwnTracks::ExportParser do
|
||||
describe '#call' do
|
||||
subject(:parser) { described_class.new(import).call }
|
||||
|
||||
let(:file_path) { 'spec/fixtures/files/owntracks/export.json' }
|
||||
let(:file) { fixture_file_upload(file_path) }
|
||||
let(:user) { create(:user) }
|
||||
let(:import) { create(:import, user: user, file: file, name: File.basename(file.path)) }
|
||||
let(:import) { create(:import, user:, name: 'owntracks_export.json') }
|
||||
|
||||
context 'when file exists' do
|
||||
it 'creates points' do
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'simplecov'
|
||||
require 'webmock/rspec'
|
||||
|
||||
SimpleCov.start
|
||||
|
||||
# This file was generated by the `rails generate rspec:install` command. Conventionally, all
|
||||
|
|
|
|||
|
|
@ -8,14 +8,13 @@ module DeviseRequestSpecHelpers
|
|||
def sign_in(resource_or_scope, resource = nil)
|
||||
resource ||= resource_or_scope
|
||||
scope = Devise::Mapping.find_scope!(resource_or_scope)
|
||||
login_as(resource, scope: scope)
|
||||
login_as(resource, scope:)
|
||||
end
|
||||
|
||||
def sign_out(resource_or_scope)
|
||||
scope = Devise::Mapping.find_scope!(resource_or_scope)
|
||||
logout(scope)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
|
|
|
|||
Loading…
Reference in a new issue