Stubbing Paperclip S3 requests in specs - ruby-on-rails

I am using Paperclip and S3 for image uploads and am trying to stub out calls to S3 from my test suite. I found the thoughtbot post which mentions doing
a.cover { a.paperclip_fixture('album', 'cover', 'png') }
but that gives me a "wrong number of arguments (4 for 2)" error. I tried switching the arguments above to an array, which removes the original error, but gives an error saying "Attribute already defined: paperclip_fixture".
Has anyone been able to get this working? Also, I'd ideally like to use the local filesystem for the development environment. Is there an easy way to do this?

Okay, I've got the basic issue figured out. This is (I believe) as Eliza said, because I'm not using shoulda (I'm using rspec 2.6.0 and factory_girl 2.1.2).
Here's what worked for me (where Profile is the class that has attachements):
Profile.any_instance.stub(:save_attached_files).and_return(true)
#profile = Factory(:profile)
At the moment I just have this right in my before method of my rspec example. There's probably a better place to put it.

With latest paperclip(from github master branch) and aws-sdk version 2, I solved my issue with following configuration :
require "aws-sdk"
Aws.config[:s3] = {stub_responses: true}
For more information, please take a look at amazon sdk

Placing this in my 'spec/rails_helper.rb' file worked for me:
require 'aws'
AWS.stub!
AWS.config(:access_key_id => "TESTKEY", :secret_access_key => "TESTSECRET")

Are you using shoulda? If you aren't using shoulda the paperclip_fixture method that you're using may come from somewhere else and thus behave differently.
Potentially relevant: https://github.com/thoughtbot/paperclip/blob/master/shoulda_macros/paperclip.rb

Many of these techniques don't seem to work with the latest paperclip and S3. What finally worked for me is the combination of:
AWS.config(:access_key_id => "TESTKEY", :secret_access_key => "TESTSECRET", :stub_requests => true)
and
Mymodel.any_instance.stubs(:save_attached_files).returns(true)
But, actually, all you really need to do in many cases is the AWS :stub_requests and it will achieve what you want.

This is how I got this working. First you have to have the fakeweb gem or it will fail. You also have to have an empty file in the spec/support/paperclip/[model]/[attachment_name][ext] path.
What I did was to copy the code from Paperclip and paste it into my factory. I was unable to get the 'paperclip_fixture' working.
factory :attachment do
file do |a|
# Stubbed Paperclip attachment from: https://github.com/thoughtbot/paperclip/blob/master/shoulda_macros/paperclip.rb#L68
# FIX: This was the only way I made this work. Calling the paperclip_fixture directly didn't work.
# See: http://stackoverflow.com/questions/4941586/stubbing-paperclip-s3-requests-in-specs
model, attachment, extension = "customer_attachment", "file", "doc"
definition = model.gsub(" ", "_").classify.constantize.
attachment_definitions[attachment.to_sym]
path = "http://s3.amazonaws.com/:id/#{definition[:path]}"
path.gsub!(/:([^\/\.]+)/) do |match|
"([^\/\.]+)"
end
begin
FakeWeb.register_uri(:put, Regexp.new(path), :body => "OK")
rescue NameError
raise NameError, "the stub_paperclip_s3 shoulda macro requires the fakeweb gem."
end
base_path = File.join(Rails.root, "spec", "support", "paperclip")
File.new(File.join(base_path, model, "#{attachment}.#{extension}"))
end
end

This is how I stub a file from paperclip without using the shoulda helpers.
before(:each) do
#sheet = double('sheet')
#sheet.stub(:url).and_return(File.join(Rails.root, 'spec','fixtures','files', 'file.xls'))
active_record_object.stub(:sheet).and_return(#sheet)
end
Hope this helps someone.

I just came across this upgrading an app from Rails 2.3 to Rails 3.0 after having followed Testing Paperclip on S3 with Cucumber & Factory Girl.
Instead of including the Paperclip::Shoulda module into the Factory class, I had to include it into the FactoryGirl::DefinitionProxy class, so I changed this:
class Factory
include Paperclip::Shoulda
end
to
class FactoryGirl::DefinitionProxy
include Paperclip::Shoulda
end
For reference, I'm using paperclip 2.4.1 and factory_girl 2.0.5.

Related

undefined method `file_fixture_path' after upgrade to ruby 3 and rails 6.1

After upgrade to ruby 3 and rails 6.1 my tests break on the line
subject.avatar.attach(fixture_file_upload(Rails.root.join('spec', 'fixtures', 'images', 'avatar.jpg')))
with:
NoMethodError:
undefined method `file_fixture_path' for RSpec::Rails::FixtureFileUploadSupport::RailsFixtureFileWrapper:Class
Did you mean? fixture_path
the error stack points to
webmock-3.11.0/lib/webmock/rspec.rb:37
Any suggestions how to debug it?
Ran into the same error, but had to solve it differently as the post in a request spec doesn't accept the object returned by file_fixture.
Including include ActionDispatch::TestProcess::FixtureFile in my request solved it for me.
RSpec.describe "Attachments", type: :request do
include Rack::Test::Methods
include ActionDispatch::TestProcess::FixtureFile
#...
expect {
file = fixture_file_upload("image.jpg", "image/jpeg", :binary)
post collection_work_attachments_path(collection, work), {attachment: {file: file, name: image_name, visibility: [:admin]}}
}.to change(Attachment, :count).by(1)
#...
end
Anything above didn't worked for me, but I found another solution.
In the factory changed this:
photo { fixture_file_upload(Rails.root.join('spec/support/images/test_image.png'), 'image/png') }
to this:
photo { Rack::Test::UploadedFile.new('spec/support/images/test_image.png', 'image/png') }
But after that I faced with another error:
unknown attribute 'service_name' for ActiveStorage::Blob
And solved this with two commands:
rails active_storage:update
rails db:migrate
I hope this may be useful to anybody.
Adding the following initializer addresses the issue without potential side-effects of including the ActionDispatch::TestProcess::FixtureFile module.
# config/initializers/rspec.rb
module RSpec
module Rails
module FixtureFileUploadSupport
class RailsFixtureFileWrapper
class << self
attr_accessor :file_fixture_path
end
end
end
end
end
This is how the issue is actually fixed by RSpec maintainers. As of the date of this post, the patch is not yet released.
I was having the same issue for a long time and kept landing on this SO answer. My problem was that in a lot of my specs, one of my FactoryBot factories was calling fixture_file_upload and nothing suggested here fixed the issue.
I went into the ActionDispatch code and found that in the ActionDispatch::TestProcess::FixtureFile module's fixture_file_upload method, the factory object was queried for its fixture_path attribute, which it doesn't have. This is why the answer by #murb will work if your fixture_file_upload is within a spec file, but not in a factory file. For me, the answer was to add the following code to spec/support/factory_bot.rb in my Rails project:
FactoryBot::SyntaxRunner.instance_eval do
def fixture_path
File.absolute_path('spec/fixtures/files')
end
def file_fixture_path
'spec/fixtures/files'
end
end
Note that the second method (file_fixture_path) is to silence the warning in ActionDispatch::TestProcess::FixtureFile#fixture_file_upload.
I hope this helps someone, took me and another engineer a couple of days to land on this solution.
After changing to file_fixture it works just fine relishapp.com/rspec/rspec-rails/v/3-8/docs/file-fixture

Testing Carrierwave Uploads with Minitest

Just trying to add some basic tests to my Carrierwave Uploader. I am starting with the default generated tests and trying to go from there. I am getting a weird error and not sure where to go from here.
I have a polymorphic Upload model which has:
mount_uploader :file, DocumentUploader
My DocumentUploader has something like this:
def store_dir
"#{model.uploadable_type.downcase.pluralize.underscore}/#{model.parent_asset.id}/uploads/#{model.id}/"
end
This makes my store directories look like:
/locations/24/uploads/56
when I run the default tests for example:
test "should destroy upload" do
assert_difference('Upload.count', -1) do
delete :destroy, id: #upload
end
assert_redirected_to uploads_path
end
I get:
ERROR["test_should_destroy_upload", UploadsControllerTest,1.9893769259997498]
test_should_destroy_upload#UploadsControllerTest (1.99s)
NoMethodError: NoMethodError: undefined method `id' for nil:NilClass
app/uploaders/document_uploader.rb:34:in `store_dir'
In my Uploads fixtures I have the polymorphic associates set etc. Themodel is not being set to the Uploadable association in the DocumentUploader i.e. it's nil.
All my other tests work so far and my uploader works fine in production and development etc. I am sure I am missing something trivial in the set-up or it's a Carrierwave specific issue.
Chiming in really late here.
Using fog with carrierwave I have found to be a drag. carrierwave-aws gem is non-negotiable for me.
The Read Me provides a quick, overall stub to add to the uploads.rb configuration file:
config.aws_credentials = {
[...], # Required
stub_responses: Rails.env.test? # Optional, avoid hitting S3 actual during tests
}
For the presentation layer, I find this acceptable because, you often want to see results, and particularly the versions of such results. Once those run properly, they are hard to break if the method does not change - and those are rather static methods.

Rails3: HOWTO Override/Reopen a class within a Gem and the Rails initialization process

My question is very similar to this one: How do I add a method to a ruby gem without editing the gem source?. However, this question is almost a year old and the solution that was chosen isn't the cleanest, not to me at least.
The person who provided the answer offered 3 suggestions. The first suggestion was chosen as the answer, but I would really like to figure out how to do it the second way.
I need to override an instance method of a class that is defined by a Gem. More specifically, it is the SessionSerializer class in 1.1.2 Devise. The issue is that Devise doesn't respect non-standard primary key names. It always uses id. You can see that in warden_compat.rb on Line 30, it uses the following to find a model by it's ID:
klass.constantize.find(:first, :conditions => { :id => id })
In my case, the name of my id column is application_user_id, so it is obvious that this won't work. Devise has fixed this issue in 1.1.3, however, I cannot use 1.1.3 because the Devise LDAP Authenticatable plugin does not support 1.1.3.
So here's what I've done instead. I should mention first that I tested this fix by editing the Gem source directly, so now I simply want to move it into my project.
Created a session_serializer.rb file in lib/warden/ (i.e., lib/warden/session_serializer.rb), reopened the Warden::SessionSerializer class, and redefined the deserialize method.
Modified application.rb to include lib/ in config.autoload_paths
config.autoload_paths += ["#{config.root}/lib"]
However, this doesn't seem to do the trick. It is still using the same code that is defined in the Gem source. So I have couple questions that I hope that can be answered:
Questions
What am I doing wrong here?
Does Rails load files of the paths defined in config.autoload_paths before Gems, or is it the other way around?
Thanks for the help in advance!
lib/warden/session_serializer.rb
module Warden
class SessionSerializer
def deserialize(keys)
klass, id = keys
if klass.is_a?(Class)
raise "Devise changed how it stores objects in session. If you are seeing this message, " <<
"you can fix it by changing one character in your cookie secret, forcing all previous " <<
"cookies to expire, or cleaning up your database sessions if you are using a db store."
end
# NOTE: Original line code. Notice that it uses an :id symbol. It doesn't respect the primary key that explicity defined in the model
# klass.constantize.find(:first, :conditions => { :id => id })
# NOTE: THIS IS THE FIX
klass.constantize.find(:first, :conditions => { :application_user_id => id })
rescue NameError => e
if e.message =~ /uninitialized constant/
Rails.logger.debug "Trying to deserialize invalid class #{klass}"
nil
else
raise
end
end
end
end
I would create a file called warden.rb in initializers directory and put the monkey patch code inside the file. I use this technique often in my projects to patch a gem.
To put the patch under the lib directory, do the following:
config.autoload_paths += ["#{config.root}/lib/warden"]
PS: I know you have tried this, but it looks like your path is not correct.
PPS To understand the Rails 2.3 load sequence refer to this code.
Have you read:
http://guides.rubyonrails.org/configuring.html
?

Unit testing paperclip uploads with Rspec (Rails)

Total Rspec noob here. Writing my first tests tonight.
I've got a model called Image. Using paperclip I attach a file called photo. Standard stuff. I've run the paperclip generator and everything works fine in production and test modes.
Now I have a spec file called image.rb and it looks like this (it was created by ryanb's nifty_scaffold generator):
require File.dirname(__FILE__) + '/../spec_helper'
describe Image do
it "should be valid" do
Image.new.should be_valid
end
end
This test fails and I realise that it's because of my model validations (i.e. validates_attachment_presence)
The error that I get is:
Errors: Photo file name must be set., Photo file size file size must be between 0 and 1048576 bytes., Photo content type is not included in the list
So how do I tell rspec to upload a photo when it runs my test?
I'm guessing that it's got somethign to do with fixtures.... maybe not though. I've tried playing around with them but not having any luck. For the record, I've created a folder called images inside my fixtures folder and the two files I want to use in my tests are called rails.png and grid.png)
I've tried doing the following:
it "should be valid" do
image = Image.new :photo => fixture_file_upload('images/rails.png', 'image/png').should be_valid
# I've also tried adding stuff like this
#image.stub!(:has_attached_file).with(:photo).and_return( true )
#image.stub!(:save_attached_files).and_return true
#image.save.should be_true
end
But rspec complains about "fixture_file_upload" not being recognised... I am planning to get that Rspec book. And I've trawled around the net for an answer but can't seem to find anything. My test database DOES get populated with some data when I remove the validations from my model so I know that some of it works ok.
Thanks in advance,
EDIT:
images.yml looks like this:
one:
name: MyString
description: MyString
two:
name: MyString
description: MyString
This should work with Rails 2.X:
Image.new :photo => File.new(RAILS_ROOT + '/spec/fixtures/images/rails.png')
As of Rails 3, RAILS_ROOT is no longer used, instead you should use Rails.root.
This should work with Rails 3:
Image.new :photo => File.new(Rails.root + 'spec/fixtures/images/rails.png')
Definitely get the RSpec book, it's fantastic.
Rails.root is a pathname object so you can use it like this:
Image.new :photo => Rails.root.join("spec/fixtures/images/rails.png").open
Edit - probably does not work in Rails 3...
see answer by #Paul Rosania
In case anyone else finds this via Google, RAILS_ROOT is no longer valid in Rails 3.0. That line should read:
Image.new :photo => File.new(Rails.root + 'spec/fixtures/images/rails.png')
(Note the lack of leading slash!)
I use the multipart_body gem in my integration tests. Its a bit truer to BDD than testing.
http://steve.dynedge.co.uk/2010/09/19/multipart-body-a-gem-for-working-with-multipart-data/
With respect to rspec and paperclip, the has_attached_file :photo directive creates a virtual attribute of sorts i.e. :photo ... when you assign a file or a path to photo, paperclip takes over, stores the file, optionally does processing on it e.g. auto-create thumbnails, import a spreadsheet, etc. You aren't telling rspec to test paperclip. You are invoking code and telling rspec what the results of that code -should- be.
In $GEM_HOME/gems/paperclip-2.3.8/README.rdoc, about 76% of the way through the file under ==Post Processing (specifically lines 147 and 148):
---[ BEGIN QUOTE ]---
NOTE: Because processors operate by turning the original attachment into the styles, no processors will be run if there are no styles defined.
---[ END QUOTE ]---
Reading the code, you'll see support :original ... does your has_attached_file define a style?
I use a generic ":styles => { :original => { :this_key_and => :this_value_do_not_do_anything_unless_a_lib_paperclip_processors__foo_dot_rb__does_something_with_them } }" ... just to get paperclip to move the file from some temp directory into my has_attached_file :path
One would think that would be default or more obvious in the docs.

Cucumber default_url_options[:host] everytime "www.example.com" even if specified in environtemnts/test.rb

I specified the default_url_options in my environments/test.rb with
config.action_mailer.default_url_options = { :host => "www.xyu.at" }
This is quite ok and in my cucumber story, where i test registration of users,
the user-activation link gets generated right
invitation_activation_url(1)
=> "www.xyu.at/signup/1231hj23jh23"
But when I try to follow the link provided in the email with following code in features/steps/user_steps.rb (using email-rspec from http://github.com/bmabey/email-spec/tree/master):
When /^I follow the invitation link$/ do
When 'I follow "'+invitation_activation_url(1) + '" in the email'
end
Here the url gets created with the default-host:
invitation_activation_url(1)
=> "www.example.com/signup/1231hj23jh23"
Can anybody help me? I don't get what I'm doing wrong....
Thanks!
EDIT:
It seems to do with the method
current_url
but I dont know where it comes from..?
EDIT:
And I have the right environment specified in my features/support/env.rb
ENV["RAILS_ENV"] ||= "test"
EDIT:
My temporary solution is, what edbond said,
invitation_activation_url(1, :host => "www.xyz.at")
=> "www.xyz.at/signup/1231hj23jh23"
but I dont want to name the domain explicit this way
(i specified it already in my environments/test.rb file - that way it wouldnt be dry)
Use :host option in your url.
invitation_activation_url(1, :host => "www.xyz.at")
=> "www.xyz.at/signup/1231hj23jh23"
EDIT:
You can parse email body and get link
mail = YourMailer.deliveries.last
email_html = Nokogiri::HTML mail.body.to_s
approve_link = email_html.at_css("a")["href"]
I know its years since this was posted.. but I had this issue and took me hours to decipher until I got it figured out.
You should use instead
When /^I follow the invitation link$/ do
When 'I follow "'+invitation_activation_path(1) + '" in the email'
end
the _url generates the URL path; whilst the _path generates the URI path.
web_steps.rb uses the URI to determine the current_url which it uses to work out the host.
from http://api.rubyonrails.org/classes/ActionDispatch/Routing.html
Routes can be named by passing an :as option, allowing for easy
reference within your source as name_of_route_url for the full URL and
name_of_route_path for the URI path.
from web_steps.rb
Then /^(?:|I )should be on (.+)$/ do |page_name| | # end
current_path = URI.parse(current_url).path | #
if current_path.respond_to? :should | # collection do
current_path.should == path_to(page_name) | # get 'sold'
else | # end
assert_equal path_to(page_name), current_path | # end
end |
end
You say that you edited config/environments/test.rb. Are you sure that your Cucumber features are actually executing in the 'test' environment?
I recently added Cucumber to a project I'm working on, and it seems to set itself up to use a 'cucumber' environment by default.
In features/support/env.rb in my project there is this:
ENV["RAILS_ENV"] ||= "cucumber"
So if your project is similar, then you will need to customize config/environments/cucumber.rb as well.
I'm not terribly familiar with Cucumber, so I can't say with certainty where exactly you'll have to apply this fix. But the problem is that the default_url_options is not set in another place where you're trying to generate your url...
So my advice is to first find out in what context the faulty url is being generated. Before or after it, just output self.class. That's the class you'll have to monkey-patch. For the example, let's say 'ClassName' was printed out.
When you have that, in your config/environments/test.rb, just add the attribute accessor and then set it to what you want:
class ClassName
cattr_accessor :default_url_options
# or mattr_ if it's a module
end
and then set it the same way as your actionmailer
ClassName.default_url_options = { :host => "www.xyu.at" }
This whole process can be useful as well when you want to generate urls in models or in other esoteric places (then you'll also need to include ActionController::UrlWriter).
One solution (based on info here) is to have a step definition like
Given /^the domain "(.+?)"$/ do |domain|
host! domain
end
and use it like
Given the domain "www.foo.com"
in features.
With that, though, I ran into issues where redirects were not followed. I tried applying this patch but had no luck.
I ended up using a very simple workaround in Cucumber's env.rb file:
# There is a bug in internal_redirect? when used with explicit (non-example.com) domains.
# This is a simple workaround but may break if we start specing external redirects.
# https://webrat.lighthouseapp.com/projects/10503/tickets/140-current_url-should-be-fully-qualified
class Webrat::Session
alias internal_redirect? redirect?
end
As mentioned in the comment, this may of course break with external redirects, but we have none.

Resources