New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RSpec/Rails] Help setting up feature test with authentication #441

Closed
rhjones opened this Issue Oct 4, 2016 · 18 comments

Comments

Projects
None yet
3 participants
@rhjones

rhjones commented Oct 4, 2016

Preliminaries:

I'm trying to write a feature test for GET /lists, which—when a user is logged in—should return all of the lists they own. Users have a one-to-many relationship with lists.

Functional curl script for this request is here:

curl --include --request GET http://localhost:3000/lists \
  --header "Authorization: Token token=$TOKEN"

lists#index method is defined here

  def index
    @lists = current_user.lists.all.order(updated_at: :desc)
    render json: @lists
  end

I'm struggling with the right way to set up my specs/requests/lists_spec.rb file so that I can create a user, create a list that belongs to that user (and, I'm guessing, a list that belongs to another user?), and test that my GET request returns that list (and only that list).

Here's my current test:

require 'rails_helper'

RSpec.describe 'Lists API' do
  def user_params
    {
      email: 'alice@example.com',
      password: 'foobarbaz',
      password_confirmation: 'foobarbaz'
    }
  end

  after(:all) do
    List.delete_all
    User.delete_all
  end

  context 'while authenticated' do
    before(:all) do
      @user = User.create!(user_params)
      @token = @user.token

      def list_params
        {
          title: 'Example List',
          user: @user
        }
      end

      def headers
        {
          'HTTP_AUTHORIZATION' => "Token token=#{@token}"
        }
      end

      List.create!(list_params)
    end

    describe 'GET /lists' do
      it 'lists all lists' do
        get '/lists', headers

        expect(response).to be_success

        lists_response = JSON.parse(response.body)
        expect(lists_response.length).to eq(lists.count)
        expect(lists_response.first['title']).to eq(list[:title])
      end
    end
  end
end

And here's the error I'm getting when I run bundle exec rspec spec/requests/lists_spec.rb

1) Lists API while authenticated GET /lists lists all lists
     Failure/Error: expect(response).to be_success
       expected `#<ActionDispatch::TestResponse:0x007fae1f181ff8 @mon_owner=nil,
@mon_count=0, @mon_mutex=#<Thread::Mutex:0x007fae1f181fa8>, 
@stream=<ActionDispatch::Response::Buffer:0x007fae1f181f58 @response=#
<ActionDispatch::TestResponse:0x007fae1f181ff8 ...>, @buf=["HTTP Token: Access denied.\n"],
 @closed=false>, @header={"X-Frame-Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1; 
mode=block", "X-Content-Type-Options"=>"nosniff", "WWW-Authenticate"=>"Token 
realm=\"Application\"", "Content-Type"=>"text/html; charset=utf-8", "Vary"=>"Origin", "Cache-
Control"=>"no-cache", "X-Request-Id"=>"71a9e50f-2324-494a-bdd1-0a14cfc54da6", "X-
Runtime"=>"0.038913", "Content-Length"=>"27"}, @status=401, @sending_file=false, 
@blank=false, @cv=#<MonitorMixin::ConditionVariable:0x007fae1f181f30 @monitor=#
<ActionDispatch::TestResponse:0x007fae1f181ff8 ...>, @cond=#
<Thread::ConditionVariable:0x007fae1f181f08>>, @committed=false, @sending=false, 
@sent=false, @content_type=#<Mime::Type:0x007fae1ca6b608 @synonyms=
["application/xhtml+xml"], @symbol=:html, @string="text/html", 
@hash=-1703860041416950160>, @charset="utf-8", @cache_control={:no_cache=>true}, 
@etag=nil>.success?` to return true, got false

Any chance I could get a few minutes to talk through an initial approach to this? In case it makes things easier, I installed the shoulda-matchers gem and have successfully used it to test my List model.

@rhjones rhjones changed the title from Help setting up feature test with authentication to [RSpec/Rails] Help setting up feature test with authentication Oct 4, 2016

@rhjones

This comment has been minimized.

Show comment
Hide comment
@rhjones

rhjones Oct 4, 2016

WHOOPS.

I need to log in, not just create a user:

before(:all) do
        post '/sign-up', credentials: user_params
        post '/sign-in', credentials: user_params
        @token = JSON.parse(response.body)['user']['token']
        @user_id = JSON.parse(response.body)['user']['id']
        @user = User.first


        def list_params
          {
            title: 'Example List',
            user: @user
          }
        end

        def headers
          {
            'HTTP_AUTHORIZATION' => "Token token=#{@token}"
          }
        end

        List.create!(list_params)
      end

I feel like I'm getting closer, but I'm still getting an error:

  1) Lists API while authenticated GET /lists lists all lists
     Failure/Error: expect(response).to be_success
expected `#<ActionDispatch::TestResponse:0x007ff3597ab940 @mon_owner=nil, 
@mon_count=0, @mon_mutex=#<Thread::Mutex:0x007ff3597ab8f0>, 
@stream=#<ActionDispatch::Response::Buffer:0x007ff3597ab8a0 
@response=#<ActionDispatch::TestResponse:0x007ff3597ab940 ...>, 
@buf=["HTTP Token: Access denied.\n"], @closed=false>, @header={"X-Frame-
Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1; mode=block", "X-Content-Type-
Options"=>"nosniff", "WWW-Authenticate"=>"Token realm=\"Application\"", "Content-
Type"=>"text/html; charset=utf-8", "Vary"=>"Origin", "Cache-Control"=>"no-cache", "X-
Request-Id"=>"e31aa2d7-dc6c-4c63-88de-e126417e95a9", "X-Runtime"=>"0.019383", 
"Content-Length"=>"27"}, @status=401, @sending_file=false, @blank=false, @cv=#
<MonitorMixin::ConditionVariable:0x007ff3597ab878 @monitor=#
<ActionDispatch::TestResponse:0x007ff3597ab940 ...>, @cond=#
<Thread::ConditionVariable:0x007ff3597ab850>>, @committed=false, @sending=false, 
@sent=false, @content_type=#<Mime::Type:0x007ff359967ae0 @synonyms=
["application/xhtml+xml"], @symbol=:html, @string="text/html", 
@hash=1837517924790437652>, @charset="utf-8", @cache_control={:no_cache=>true}, 
@etag=nil>.success?` to return true, got false

rhjones commented Oct 4, 2016

WHOOPS.

I need to log in, not just create a user:

before(:all) do
        post '/sign-up', credentials: user_params
        post '/sign-in', credentials: user_params
        @token = JSON.parse(response.body)['user']['token']
        @user_id = JSON.parse(response.body)['user']['id']
        @user = User.first


        def list_params
          {
            title: 'Example List',
            user: @user
          }
        end

        def headers
          {
            'HTTP_AUTHORIZATION' => "Token token=#{@token}"
          }
        end

        List.create!(list_params)
      end

I feel like I'm getting closer, but I'm still getting an error:

  1) Lists API while authenticated GET /lists lists all lists
     Failure/Error: expect(response).to be_success
expected `#<ActionDispatch::TestResponse:0x007ff3597ab940 @mon_owner=nil, 
@mon_count=0, @mon_mutex=#<Thread::Mutex:0x007ff3597ab8f0>, 
@stream=#<ActionDispatch::Response::Buffer:0x007ff3597ab8a0 
@response=#<ActionDispatch::TestResponse:0x007ff3597ab940 ...>, 
@buf=["HTTP Token: Access denied.\n"], @closed=false>, @header={"X-Frame-
Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1; mode=block", "X-Content-Type-
Options"=>"nosniff", "WWW-Authenticate"=>"Token realm=\"Application\"", "Content-
Type"=>"text/html; charset=utf-8", "Vary"=>"Origin", "Cache-Control"=>"no-cache", "X-
Request-Id"=>"e31aa2d7-dc6c-4c63-88de-e126417e95a9", "X-Runtime"=>"0.019383", 
"Content-Length"=>"27"}, @status=401, @sending_file=false, @blank=false, @cv=#
<MonitorMixin::ConditionVariable:0x007ff3597ab878 @monitor=#
<ActionDispatch::TestResponse:0x007ff3597ab940 ...>, @cond=#
<Thread::ConditionVariable:0x007ff3597ab850>>, @committed=false, @sending=false, 
@sent=false, @content_type=#<Mime::Type:0x007ff359967ae0 @synonyms=
["application/xhtml+xml"], @symbol=:html, @string="text/html", 
@hash=1837517924790437652>, @charset="utf-8", @cache_control={:no_cache=>true}, 
@etag=nil>.success?` to return true, got false
@jrhorn424

This comment has been minimized.

Show comment
Hide comment
@jrhorn424

jrhorn424 Oct 4, 2016

Member

Looks like it's the wrong token.

@buf=["HTTP Token: Access denied.\n"], ...

I'm a bit perplexed. I'm still thinking on this.

Member

jrhorn424 commented Oct 4, 2016

Looks like it's the wrong token.

@buf=["HTTP Token: Access denied.\n"], ...

I'm a bit perplexed. I'm still thinking on this.

@jrhorn424

This comment has been minimized.

Show comment
Hide comment
@jrhorn424

jrhorn424 Oct 4, 2016

Member

Can you confirm for me that @user_id and @user.id point to the same thing?

Member

jrhorn424 commented Oct 4, 2016

Can you confirm for me that @user_id and @user.id point to the same thing?

@jrhorn424

This comment has been minimized.

Show comment
Hide comment
@jrhorn424

jrhorn424 Oct 4, 2016

Member

If that's not the issue, would you try RAILS_ENV=test bundle exec rake db:nuke_pave, then re-run the tests?

Member

jrhorn424 commented Oct 4, 2016

If that's not the issue, would you try RAILS_ENV=test bundle exec rake db:nuke_pave, then re-run the tests?

@rhjones

This comment has been minimized.

Show comment
Hide comment
@rhjones

rhjones Oct 4, 2016

@user_id and @user.id return the same thing.

Running RAILS_ENV=test bundle exec rake db:nuke_pave then re-running bundle exec rspec spec/requests/lists_spec.rb gives the same error.

rhjones commented Oct 4, 2016

@user_id and @user.id return the same thing.

Running RAILS_ENV=test bundle exec rake db:nuke_pave then re-running bundle exec rspec spec/requests/lists_spec.rb gives the same error.

@jrhorn424

This comment has been minimized.

Show comment
Hide comment
@jrhorn424

jrhorn424 Oct 4, 2016

Member

What about if you look at User.find_by(token: @token).id == @user.id?

Member

jrhorn424 commented Oct 4, 2016

What about if you look at User.find_by(token: @token).id == @user.id?

@rhjones

This comment has been minimized.

Show comment
Hide comment
@rhjones

rhjones Oct 4, 2016

User.find_by(token: @token).id is returning nil

rspec error:

Failure/Error: User.find_by(token: @token).id

     NoMethodError:
       undefined method `id' for nil:NilClass

rhjones commented Oct 4, 2016

User.find_by(token: @token).id is returning nil

rspec error:

Failure/Error: User.find_by(token: @token).id

     NoMethodError:
       undefined method `id' for nil:NilClass
@rhjones

This comment has been minimized.

Show comment
Hide comment
@rhjones

rhjones Oct 4, 2016

FWIW happy to table this for now and schedule a one-on-one later. I'd like to get this working, but it's not essential in order for my project to function as it should tomorrow.

rhjones commented Oct 4, 2016

FWIW happy to table this for now and schedule a one-on-one later. I'd like to get this working, but it's not essential in order for my project to function as it should tomorrow.

@jrhorn424

This comment has been minimized.

Show comment
Hide comment
@jrhorn424

jrhorn424 Oct 4, 2016

Member

That indicates there's no user with that token. What is @token?

Member

jrhorn424 commented Oct 4, 2016

That indicates there's no user with that token. What is @token?

@rhjones

This comment has been minimized.

Show comment
Hide comment
@rhjones

rhjones Oct 4, 2016

Output from running the test:

user.id 14
@user_id 14
@user.id 14
user.token 2132533e989408ce403534278eb274d0
current @user.token 2132533e989408ce403534278eb274d0
current @token BAhJIiUyMTMyNTMzZTk4OTQwOGNlNDAzNTM0Mjc4ZWIyNzRkMAY6BkVG--20d3ea0440a32f4586169666c0505c2460de84e8

Also reposting the entirety of my spec, in case there's something I'm missing elsewhere. I know that some of this is redundant; I've been looking at a couple of different examples (the user spec that's part of the template; this rails-api-bdd-iteration repo that Lauren referenced in #225 ).

require 'rails_helper'

RSpec.describe 'Lists API' do
  def list_params
    {
      title: 'New Example List',
      user: user
    }
  end

  def lists
    List.all
  end

  def list
    List.first
  end

  def user_params
    {
      email: 'alice@example.com',
      password: 'foobarbaz',
      password_confirmation: 'foobarbaz'
    }
  end

  def user
    User.first
  end

  before(:all) do
    User.create!(user_params)
    List.create!(list_params)
  end

  after(:all) do
    List.destroy_all
    User.destroy_all
  end

  context 'while authenticated' do
    def headers
      {
        'HTTP_AUTHORIZATION' => "Token token=#{@token}"
      }
    end

    before(:each) do
      # post '/sign-up', credentials: user_params
      post '/sign-in', credentials: user_params

      @token = JSON.parse(response.body)['user']['token']
      @user_id = JSON.parse(response.body)['user']['id']
      @user = User.first
      puts "user.id #{user.id}"
      puts "@user_id #{@user_id}"
      puts "@user.id #{@user.id}"

      puts "current user.token #{user.token}"
      puts "current @user.token #{@user.token}"
      puts "current @token #{@token}"

      def user_by_token
        User.find_by(token: @token).id
      end

      puts "user_by_token #{user_by_token}"

      # List.create!(list_params)
    end

    describe 'GET /lists' do
      it 'lists all lists' do
        get '/lists', headers

        expect(response).to be_success

        lists_response = JSON.parse(response.body)
        expect(lists_response.length).to eq(lists.count)
        expect(lists_response.first['title']).to eq(list[:title])
      end
    end
  end
end

rhjones commented Oct 4, 2016

Output from running the test:

user.id 14
@user_id 14
@user.id 14
user.token 2132533e989408ce403534278eb274d0
current @user.token 2132533e989408ce403534278eb274d0
current @token BAhJIiUyMTMyNTMzZTk4OTQwOGNlNDAzNTM0Mjc4ZWIyNzRkMAY6BkVG--20d3ea0440a32f4586169666c0505c2460de84e8

Also reposting the entirety of my spec, in case there's something I'm missing elsewhere. I know that some of this is redundant; I've been looking at a couple of different examples (the user spec that's part of the template; this rails-api-bdd-iteration repo that Lauren referenced in #225 ).

require 'rails_helper'

RSpec.describe 'Lists API' do
  def list_params
    {
      title: 'New Example List',
      user: user
    }
  end

  def lists
    List.all
  end

  def list
    List.first
  end

  def user_params
    {
      email: 'alice@example.com',
      password: 'foobarbaz',
      password_confirmation: 'foobarbaz'
    }
  end

  def user
    User.first
  end

  before(:all) do
    User.create!(user_params)
    List.create!(list_params)
  end

  after(:all) do
    List.destroy_all
    User.destroy_all
  end

  context 'while authenticated' do
    def headers
      {
        'HTTP_AUTHORIZATION' => "Token token=#{@token}"
      }
    end

    before(:each) do
      # post '/sign-up', credentials: user_params
      post '/sign-in', credentials: user_params

      @token = JSON.parse(response.body)['user']['token']
      @user_id = JSON.parse(response.body)['user']['id']
      @user = User.first
      puts "user.id #{user.id}"
      puts "@user_id #{@user_id}"
      puts "@user.id #{@user.id}"

      puts "current user.token #{user.token}"
      puts "current @user.token #{@user.token}"
      puts "current @token #{@token}"

      def user_by_token
        User.find_by(token: @token).id
      end

      puts "user_by_token #{user_by_token}"

      # List.create!(list_params)
    end

    describe 'GET /lists' do
      it 'lists all lists' do
        get '/lists', headers

        expect(response).to be_success

        lists_response = JSON.parse(response.body)
        expect(lists_response.length).to eq(lists.count)
        expect(lists_response.first['title']).to eq(list[:title])
      end
    end
  end
end
@jrhorn424

This comment has been minimized.

Show comment
Hide comment
@jrhorn424

jrhorn424 Oct 5, 2016

Member

This is going to sound weird.

Replace the token: @token with @user.token.

Member

jrhorn424 commented Oct 5, 2016

This is going to sound weird.

Replace the token: @token with @user.token.

@jrhorn424

This comment has been minimized.

Show comment
Hide comment
@jrhorn424

jrhorn424 Oct 5, 2016

Member

token: @user.token

Member

jrhorn424 commented Oct 5, 2016

token: @user.token

@jrhorn424

This comment has been minimized.

Show comment
Hide comment
@jrhorn424

jrhorn424 Oct 5, 2016

Member

I think this will solve it. I'm about to go to bed so I just wanted to congratulate you on excellent debugging and also to thank you for reposting the spec.

I hope my congratulations aren't premature.

Member

jrhorn424 commented Oct 5, 2016

I think this will solve it. I'm about to go to bed so I just wanted to congratulate you on excellent debugging and also to thank you for reposting the spec.

I hope my congratulations aren't premature.

@rhjones

This comment has been minimized.

Show comment
Hide comment
@rhjones

rhjones Oct 5, 2016

Commented out this line:

@token = JSON.parse(response.body)['user']['token']

And replaced with:

@token = @user.token

Still getting the same error.

Tried @token = user.token, too. Same error.

Test output from the "puts" statements in both cases looks like this:

user.id 17
@user_id 17
@user.id 17
current user.token 5d82f1b3f6d3ff294cb74f2d3e257965
current @user.token 5d82f1b3f6d3ff294cb74f2d3e257965
current @token 5d82f1b3f6d3ff294cb74f2d3e257965
user_by_token 17

rhjones commented Oct 5, 2016

Commented out this line:

@token = JSON.parse(response.body)['user']['token']

And replaced with:

@token = @user.token

Still getting the same error.

Tried @token = user.token, too. Same error.

Test output from the "puts" statements in both cases looks like this:

user.id 17
@user_id 17
@user.id 17
current user.token 5d82f1b3f6d3ff294cb74f2d3e257965
current @user.token 5d82f1b3f6d3ff294cb74f2d3e257965
current @token 5d82f1b3f6d3ff294cb74f2d3e257965
user_by_token 17
@jrhorn424

This comment has been minimized.

Show comment
Hide comment
@jrhorn424

jrhorn424 Oct 5, 2016

Member

Sorry @rebekahheacock. That's not what I intended. My fault for trying to answer via mobile and be lazy.

Go back to your previous code and try this instead:

    def headers
      {
-        'HTTP_AUTHORIZATION' => "Token token=#{@token}"
+        'HTTP_AUTHORIZATION' => "Token token=#{@user.token}"
      }
    end
Member

jrhorn424 commented Oct 5, 2016

Sorry @rebekahheacock. That's not what I intended. My fault for trying to answer via mobile and be lazy.

Go back to your previous code and try this instead:

    def headers
      {
-        'HTTP_AUTHORIZATION' => "Token token=#{@token}"
+        'HTTP_AUTHORIZATION' => "Token token=#{@user.token}"
      }
    end
@rhjones

This comment has been minimized.

Show comment
Hide comment
@rhjones

rhjones Oct 6, 2016

Just tried this out—still no dice.

rhjones commented Oct 6, 2016

Just tried this out—still no dice.

@payne-chris-r

This comment has been minimized.

Show comment
Hide comment
@payne-chris-r

payne-chris-r Nov 14, 2016

Contributor

Closing even though there was no solution. :-(

Contributor

payne-chris-r commented Nov 14, 2016

Closing even though there was no solution. :-(

@rhjones

This comment has been minimized.

Show comment
Hide comment
@rhjones

rhjones Nov 14, 2016

@jrhorn424 and I talked about this and finally figured out that I wasn't using the get method correctly. It needs parameters—changing get '/lists', headers to get '/lists', nil, headers solved the problem.

rhjones commented Nov 14, 2016

@jrhorn424 and I talked about this and finally figured out that I wasn't using the get method correctly. It needs parameters—changing get '/lists', headers to get '/lists', nil, headers solved the problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment