Capybara integration tests can't create signed cookie in Spree 2.3

217 views
Skip to first unread message

Jason Fleetwood-Boldt

unread,
Apr 1, 2015, 11:23:17 AM4/1/15
to spree...@googlegroups.com
Spree List --

[Note I have asked same question to Capybara list]

We're having some problem with a suite of integration tests when upgrading from Spree 2.2 to Spree 2.3

We used to use page.set_rack_session but due to some changes in Spree code (2.3 upgrade), this no longer works because we now need to create a signed cookie to set the current order on the session. 

Unfortunately I need direct access to the Spree code into order to create the signed cookie, which I do not have because Capybara is operating in a different session environment as the Rails app it is hitting. 

Unfortunately, nearly all of my integration tests won't work any more because I was relying on being able to do this in my set up:

   before(:each) do
      page.set_rack_session(:order_id => order.id)
      page.set_rack_session(:access_token => order.guest_token)
    end

I realize that the "right" way to do this would be to create Capy setup that logs the user in via the real Log In interface, adds an item their cart, etc. 

However, in my Spree object chain I have objects that have levels and levels of nested related data -- all relying on a (large, well-written and maintained) catalog of Factories. That's why -- in my pre-Spree 2.3 tests -- I was setting the rack session information. The idea here was that what you see in the "before" filters are setup logic -- not the subject of the tests themselves -- and so in a way it can be thought of similar to mocking (without an actual mock). Essentially, what I'm saying here is that how the order_id or access_token get set on the session doesn't actually matter, it's a precursor to the expectations I will assert during the test. 

Basically it would be prohibitively inefficient as the developer to not be able to use my Factories in my Capybara tests as "setup" expectations, and since Spree 2.3 needs a signed cookie, I can't see anyway to set up the signed cookie for the user without access to the Spree rails code.

Are there any good work-arounds for this problem or insight into best practices here?

Thanks,
Jason

Jeff Dutil

unread,
Apr 1, 2015, 4:06:03 PM4/1/15
to spree...@googlegroups.com
I could have sworn I've seen ways to set this, but doing a quick search through the projects I thought I did it on I'm not seeing it...  Looks like I resorted back to doing an actual login.  Perhaps try out https://github.com/nruth/show_me_the_cookies but that might not sign them, but this article talks about it http://turriate.com/articles/2011/feb/how-to-generate-signed-rails-session-cookie/ let us know what you come up with.

Jason Fleetwood-Boldt

unread,
Apr 2, 2015, 6:31:36 PM4/2/15
to spree...@googlegroups.com


In many tests I am actually logged in. 

But this particular test I’m testing the functionality for a logged out user to able to get to the first step of my checkout (where they log in).

The problem here is that Spree creates a random, signed cookie, like so:

https://github.com/spree/spree/blob/2-3-stable/core/lib/spree/core/controller_helpers/auth.rb#L26

def set_guest_token
  unless cookies.signed[:guest_token].present?
    cookies.permanent.signed[:guest_token] = SecureRandom.urlsafe_base64(nil, false)
  end
end


I think http://turriate.com/articles/2011/feb/how-to-generate-signed-rails-session-cookie/ looks promising, and we’ve also looked into show_me_the_cookies (which doesn’t do much because it’s just a wrapper).

My problem is that the cookie is random — it gets set by the  line of code above, and I don’t know how I can access it from my test. Assuming I could actually tell my Rack Test driver to use the signed cookie (which the link above or show_me_the_cookies looks like it might do), how can I get it out of my Rails app and into my test?


-Jason

Jeff Dutil

unread,
Apr 3, 2015, 4:17:47 PM4/3/15
to spree...@googlegroups.com
You can try to stub out what the random token is to always return a predictable value.

allow(SecureRandom).to receive(:urlsafe_base64).with(nil, false).and_return('test')

Jason Fleetwood-Boldt

unread,
Apr 5, 2015, 5:17:03 PM4/5/15
to spree...@googlegroups.com
Brilliant. Almost exactly right, except I had to stub the whole object.  On the controller stub-out I tried Spree::OrdersController.any_instance.should_receive(:cookies) but it didn't play as nicely as .stub(:cookies) because two different instances of a Spree::OrdersController receive that message, which Rspec doesn't like very much when using any_instance (counter-intuitively). 
  
Also I was never able to figure out how to actually set a signed cookie (lots of examples of how to set a regular cookie). I asked on show_me_the_cookies about that. If anyone know how to set a signed cookie, then I could remove the stub on the Spree::OrdersController.any_instance which I think would be a little cleaner

however, even without seeing the matching cookie on the browser driver, I got this working with the following code (see below):

Thanks!
-Jason


describe "cart to registration page", :type => :feature do
  let(:order) { FactoryGirl.create(:order_below_100,
                                   :guest_token => "xyz") }
  # user should be nil for logged out user


  describe "as someone not logged in" do
    before(:each) do
      order
      SecureRandom.stub!(:urlsafe_base64)
                      .with(any_args)
                      .and_return("xyz")

      Spree::OrdersController.any_instance
                      .stub(:cookies)
                      .and_return(mock(:cookies, :signed => {:guest_token => "xyz"}))
    end

    it "should let me load the shopping cart page" do
      visit '/cart'
      page.status_code.should eq(200)
      expect(page).to have_content 'Some Product'
    end
end
Reply all
Reply to author
Forward
0 new messages