Carrierwave testing - clean up or separate file uploads? - ruby-on-rails

I'd like some feedback and/or help please.
I'm having this test
scenario 'can create a new post' do
attach_file('Image', 'spec/files/hello-world.png')
fill_in 'Caption', with: 'Hello World! This is the first post!'
click_button 'Create Post'
expect(page).to have_css("img[src*='hello-world.png']")
expect(page).to have_content('Post was successfully created')
end
This test uploads the image to uploads/post/image/1/hello-world.png through Carrierwave, which is probably the same path on development environment etc, so I wonder if this is going to cause any issues on between files and posts.
My question is do I need to keep separate the file uploads on each Environment, or is it something that Rails can manage internally?

The location where the files are stored is defined in a Carrierwave Uploader by the method store_dir. If you need to separate the files created in different environments, the simplest solution is to add Rails.env to the path:
def store_dir
#store_dir ||= File.join(
'public',
'uploads',
Rails.env,
model.class.table_name.to_s,
mounted_as.to_s,
model.id.to_s
)
end
This will create a path like: public/uploads/production/posts/image/1/image.png

Related

In a Rails engine Is it possible for Rspec to make use of Rspec support system helpers from another engine?

Given a Rails engine_one that has a spec support file engine_one/spec/support/system/order_functions.rb, containing functionality to support the testing of various order system tests such as simulating a logged in user, adding products to an order etc and contains methods such as log_visitor_in that get used extensively when testing order processing etc...
So now in engine_two that extends some ordering functionality from engine_one I wish to add a new system test that first has to log a visitor in. So how can I make use of that support method from from engine_one?
So far I have mounted the engines in the dummy app
I have required engine_one in engine_two/lib/engine.rb
I have required the support file in the relevant test but it can't be found and obviously I have added engine_one to engine_two.gemspec
engine_two/spec/rails_helper.rb
require 'engine_one' # and any other gems you need
engine_two/lib/engine_two/engine.rb
require 'engine_one'
in the relevant system test I have the following
engine_two/spec/system/new_payment_methods_spec.rb
require 'rails_helper'
include EngineOne::System
RSpec.describe "order_payment_feature", type: :system do
before do
driven_by(:rack_test)
end
it "has order payment options" do
log_visitor_in
end
end
This results in the following error
Failure/Error: include EngineOne::System
NameError:
uninitialized constant EngineOne::System
Did you mean? SystemExit
And the helper
module System
def log_visitor_in()
administrator = create(:visitor)
visit ccs_cms.login_url
fill_in 'login_name', with: visitor.login_name
fill_in 'Password', with: visitor.password
click_button 'Login'
end
end
I have tried with a require instead of an include but that results in a file not found error
Plus I have tried changing the include path to
include EngineOne::Spec::Support::System resulting in the same error
So I guess I'm looking for the correct path but I am stuck or missing some other way to include the helper.
These are Rails 7 engines.
When you require a file, ruby searches for it relative to paths in $LOAD_PATH; spec/ or test/ are not part of it.
app directory is a special one in rails, any subdirectory automatically becomes part of autoload_paths. Auto load paths can be seen here ActiveSupport::Dependencies.autoload_paths.
Any classes/modules defined inside app/* directories can be used without requiring corresponding files. Rails v7 uses zeitwerk to automatically load/reload files by relying on the 'file name' to 'constant name' relationship. That's why folders map to namespaces and files map to classes/modules.
To fix your issue put any shared code where it can be grabbed with require. Type $LOAD_PATH in the console:
>> $LOAD_PATH
=>
["/home/alex/code/stackoverflow/lib",
"/home/alex/code/stackoverflow/vendor",
"/home/alex/code/stackoverflow/app/channels",
"/home/alex/code/stackoverflow/app/controllers",
"/home/alex/code/stackoverflow/app/controllers/concerns",
"/home/alex/code/stackoverflow/app/helpers",
"/home/alex/code/stackoverflow/app/jobs",
"/home/alex/code/stackoverflow/app/mailers",
"/home/alex/code/stackoverflow/app/models",
"/home/alex/code/stackoverflow/app/models/concerns",
"/home/alex/code/stackoverflow/engines/question/lib", # <= engine's lib looks good
"/home/alex/code/stackoverflow/engines/question/app/components",
"/home/alex/code/stackoverflow/engines/question/app/controllers",
"/home/alex/code/stackoverflow/engines/question/app/controllers/concerns",
...
Put shared files in engines's lib directory. Since we're outside of app directory, rails is not the boss anymore, any path and filename combination will work.
# question/lib/testing_support/blah.rb # <= note the filename
module System
def log_visitor_in
administrator = create(:visitor)
visit ccs_cms.login_url
fill_in 'login_name', with: visitor.login_name
fill_in 'Password', with: visitor.password
click_button 'Login'
end
end
Now that file can be required
# test/test_helper.rb or spec/rails_helper.rb
# after environment and rails requires
require "testing_support/blah" # => loads System module
# ...
That's it, use it in your spec
require 'rails_helper'
RSpec.describe "order_payment_feature", type: :system do
include System # include is for modules; now we have its functions in this spec
before { log_visitor_in }
it 'should accept this answer' do
visit 'questions/71362333'
expect(page).to have_content('accepted')
end
end
Additionally you can require your files any way you wish with an absolute path, regardless of $LOAD_PATH.
require EngineOne::Engine.root + 'spec/support/system/order_functions.rb'
# or something else
Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each { |f| require f }

VCR: "There is currently no cassette in use." when creating a second cassette

I'm attempting to do RSpec feature tests on an external API.
My VCR config is as follows:
VCR.configure do |config|
config.cassette_library_dir = 'spec/vcr'
config.hook_into :webmock
config.ignore_localhost = true
end
My test looks like this:
feature 'Visitor' do
scenario 'performs an archival', :clean => false, :js => true do
mock_login
visit '/dashboard'
click_link 'Test Company'
VCR.use_cassette('feature/archive') do
within '.nav-app' do
click_button 'Quicksave'
end
end
end
scenario 'performs a restore', :js => true do
mock_login
visit '/dashboard'
click_link 'Test Company'
VCR.use_cassette('feature/restore') do
within '.nav-app' do
click_button 'Quickload'
end
end
end
end
The archive cassette is created just fine, however the restore cassette throws an error:
An HTTP request has been made that VCR does not know how to handle:
GET http://SomeApiUrl/...
There is currently no cassette in use.
I've obviously told VCR to create and use another cassette with:
VCR.use_cassette('feature/restore')
So what gives?
It's also worth mentioning that I still receive this error even when I clean out my cassette folder and start fresh.
Try passing , :record => :new_episodes as an argument to use_cassete. If you don’t use the record new episodes. It thinks they are the same request.
Turns out I'm dumb. The issue seemed to be that I was using the filename vcr.rb to configure VCR; I figured it wouldn't matter being that it was in my spec/support folder, but that was not the case.
A simple rename to vcr_config.rb fixed the problem right up...

Rails carrierwave testing - how to remove file after test?

I am testing the carrierwave upload functionality using rspec and capybara. I have something like:
describe "attachment" do
let(:local_path) { "my/file/path" }
before do
attach_file('Attachment file', local_path)
click_button "Save changes"
end
specify {user.attachment.should_not be_nil}
it { should have_link('attachment', href: user.attachment_url) }
end
And this works great. The problem is that after testing the uploaded image remains in my public/uploads directory. How can I remove it after the test is done? I tried something like this:
after do
user.remove_attachment!
end
but it did not work.
You're not the only one having issues to delete the files in carrierwave.
I ended up doing:
user.remove_attachment = true
user.save
I got this tip reading this.
A cleaner solution that seems to work for me is the following in spec/support/carrierwave.rb:
uploads_test_path = Rails.root.join('uploads_test')
CarrierWave.configure do |config|
config.root = uploads_test_path
end
RSpec.configure do |config|
config.after(:suite) do
FileUtils.rm_rf(Dir[uploads_test_path])
end
end
This would set the whole root folder specific to the test environment and delete it all after the suite, so you do not have to worry about store_dir and cache_dir separately.
Ha! I found the answer to this today.
The auto-removal of the downloaded file is done in an after_commit hook.
These do not get run by default in rails tests. I never would have guessed that.
It is however documented offhandedly in a postscript note here:
http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#method-i-after_commit
I discovered this by deep diving into the carrierwave code with a
debugger and just happened to notice it in the comments above the
source code to after_commit when I stepped into it.
Thank goodness ruby libraries are not stripped of comments at runtime like JS. ;)
The workaround suggested in the docs is to include the 'test_after_commit'
gem in your Gemfile BUT ONLY IN THE TEST ENVIRONMENT.
i.e.
Gemfile:
...
gem 'test_after_commit', :group => :test
...
When I did this, it completely solved the problem for me.
Now, my post-destruction assertions of cleanup pass.
The latest CarrierWave documentation for this technique is as follows:
config.after(:suite) do
if Rails.env.test?
FileUtils.rm_rf(Dir["#{Rails.root}/spec/support/uploads"])
end
end
Note that the above simply assumes youre using spec/support/uploads/ for images and you dont mind deleting everything in that directory. If you have different locations for each uploader, you may want to derive the upload and cache directories straight from the (factory) model:
config.after(:suite) do
# Get rid of the linked images
if Rails.env.test? || Rails.env.cucumber?
tmp = Factory(:brand)
store_path = File.dirname(File.dirname(tmp.logo.url))
temp_path = tmp.logo.cache_dir
FileUtils.rm_rf(Dir["#{Rails.root}/public/#{store_path}/[^.]*"])
FileUtils.rm_rf(Dir["#{temp_path}/[^.]*"])
end
end
or, if you want to delete everything under the CarrierWave root that you set in an initializer, you can do this:
config.after(:suite) do
# Get rid of the linked images
if Rails.env.test? || Rails.env.cucumber?
FileUtils.rm_rf(CarrierWave::Uploader::Base.root)
end
end

How to get the current test filename from RSpec?

I'm trying to speed up a large RSpec project's tests. In addition to using RSpec's --profile option I wanted to get the longest running test files [1] printed out.
In my spec_helper.rb I dump the classes being tested and total time to a file, however as we have spec/model and spec/request directories I'd really like to be able to print the current test's filename and not just the class name (described_class), so that the user can disambiguate between model/foo_spec.rb and request/foo_spec.rb when optimizing.
In a before block in the spec/spec_helper.rb, how can I get the current test file's filename?
My (heavily trimmed) spec_helper looks like this:
config.before :all do
#start_time = Time.now
end
config.after :all do |test|
timings.push({ :name => test.described_class,
:file => 'I do not know how to get this',
:duration_in_seconds => (Time.now - #start_time) })
end
config.after :suite do
timing_logfile_name = 'log/rspec_file_times.log'
timing_logfile = "#{File.dirname(__FILE__)}/../#{timing_logfile_name}"
file = File.open(timing_logfile, 'w')
timings.sort_by{ |timing| timing[:duration_in_seconds].to_f }.reverse!.each do |timing|
file.write( sprintf("%-25.25s % 9.3f seconds\n",
timing[:name], timing[:duration_in_seconds]) )
end
file.close
tell_if_verbose("Overall test times are logged in '#{timing_logfile_name}'")
end
This doesn't seem to be available in the curretn RSpec meta-data, but I'm hoping someone more familiar with the internals can think of a way to expose it. Thanks,
Dave
[1] Often a file with, say, 100 examples in it yields more speed up than a single example from --profile - when that large file's before :each / before :all blocks are targetted, obviously even a ms saved is multiplied up by the number of tests in the file. Using this technique in addition to --profile helped me a lot.
As long as you're just using this for profiling your tests to identify which files need to be improved, you should be able to toss this into your spec_helper.rb file (and remove it afterwards). I fully understand that this is not pretty/clean/elegant/acceptible in production environments and I disavow that I ever wrote it :)
config.before(:each) do |example|
path = example.metadata[:example_group][:file_path]
curr_path = config.instance_variable_get(:#curr_file_path)
if (curr_path.nil? || path != curr_path)
config.instance_variable_set(:#curr_file_path, path)
puts path
end
end

Delete a folder after the cucumber scenario

I have 2 cucumber scenarios that simulate paperclip image upload. I want to remove those folders again once scenarios are complete.
I have the following attachment folder structure:
:url => "/system/:attachment/:listing_id/:id/:style_:filename"
Paperclip automatically deletes the :id/:style_:filename folder but not the parent folder.
I have a the following in my listings controller (1 listing has many images) which works great to remove the image folder with the listing id when the listing is deleted. I need to simulate the same in Cucumber after the step is run.
def destroy
#listing = Listing.find(params[:id])
# if destroy was a success, remove the listing image folder
if #listing.destroy
end
require 'fileutils'
dir = Rails.root + '/system/photos/' + #listing.id.to_s()
FileUtils.rm_rf(dir)
respond_to do |format|
format.html { redirect_to(listings_url) }
format.xml { head :ok }
end
end
I could a) tell cucumber to delete the :listing_id folder name after running through the scenario or b) tell cucumber to delete the listing as the final step?
I've tried adding this to my cucumber env.rb file:
AfterStep('#paperclip') do
# This will only run before steps within scenarios tagged
# with #cucumis AND #sativus.
# delete folders that were created with paperclip during the test
require 'fileutils'
##listing.id = 55
#dir = Rails.root + '/system/photos/' + #listing.id.to_s()
dir = Rails.root + '/system/photos/55' # NOT WORKING
FileUtils.rm_rf(dir)
end
But that causes problems because 1) I don't know how to get the #listing.id from that scenario, and 2) even when I hardcode it (as above) it doesn't remove it.
Any thoughts?
Already a bit older, but as I just ran over the same issue myself here is what I did:
dir = Rails.root + 'images/'
dir.rmtree if dir.directory?
# or the short form, if you know the directory will be there
(Rails.root + 'images/').rmtree
So I guess the problem was your '/' at the beginning of the folder. At least for me it didn't work with that slash.
I would leave a comment, but don't have the points (or so I would assume) to do so.
There really is no reason that this shouldn't work. Have you confirmed that FileUtils.rm_rf(dir) does in fact remove the directory in the test environment?
You can test this in 'script/console test'.
You can hook into Cucumber's at_exit to remove any folders you like. I'm using the attached code in features/support/uploads_cleaner.rb.
# Removes uploaded files when all scenarios for the current test process
# are finished. Ready for parallel_tests, too.
require 'fileutils'
at_exit do
directory_name = "#{ Rails.env }#{ ENV['TEST_ENV_NUMBER'] }"
uploads_path = Rails.root.join('public/system', directory_name)
FileUtils.remove_dir(uploads_path) if uploads_path.directory?
end
Reposted from this makandra Card.

Resources