exoego / rspec-openapi

Generate OpenAPI schema from RSpec request specs
MIT License
433 stars 64 forks source link

Error when executing POST json request #20

Closed nicopaez closed 3 years ago

nicopaez commented 3 years ago

I am trying to generate the spec for a POST request that sends json message in the body and I getting the error below.

 ActionDispatch::Http::Parameters::ParseError:
            no implicit conversion of nil into String
          # /usr/local/bundle/gems/actionpack-6.0.3.3/lib/action_dispatch/http/parameters.rb:114:in `rescue in parse_formatted_parameters'
          # /usr/local/bundle/gems/actionpack-6.0.3.3/lib/action_dispatch/http/parameters.rb:110:in `parse_formatted_parameters'
          # /usr/local/bundle/gems/actionpack-6.0.3.3/lib/action_dispatch/http/request.rb:389:in `block in POST'
          # /usr/local/bundle/gems/rack-2.2.3/lib/rack/request.rb:69:in `fetch'
          # /usr/local/bundle/gems/rack-2.2.3/lib/rack/request.rb:69:in `fetch_header'
          # /usr/local/bundle/gems/actionpack-6.0.3.3/lib/action_dispatch/http/request.rb:388:in `POST'
          # /usr/local/bundle/gems/rspec-openapi-0.3.13/lib/rspec/openapi/record_builder.rb:94:in `raw_request_params'
          # /usr/local/bundle/gems/rspec-openapi-0.3.13/lib/rspec/openapi/record_builder.rb:41:in `build'
          # /usr/local/bundle/gems/rspec-openapi-0.3.13/lib/rspec/openapi/hooks.rb:12:in `block in <top (required)>'
          # ------------------
          # --- Caused by: ---
          # TypeError:
          #   no implicit conversion of nil into String
          #   /usr/local/bundle/gems/activesupport-6.0.3.3/lib/active_support/json/decoding.rb:23:in `decode'

this is my rspec:


      it 'create a user' do
        header 'CONTENT_TYPE', 'application/json'
        json_data = { name: 'juan'}.to_json
        post '/users', json_data, { 'Content-Type' => 'application/json' }
        expect(last_response.status).to eq(201)
      end

I notice that if I remove the fist line (header 'CONTENT_TYPE', 'application/json') then the execution completes successfully but the result yaml spec is wrong, it specifies request.content type as application/x-www-form-urlencoded instead of application/json.

I am running ruby 2.5.7p206 and my application is padrino application (not rails).

If anyone can give some guidance I my try to submit a fix.

k0kubun commented 3 years ago

Thanks for trying that out with Padrino. I tried to reproduce it with a request spec for Rails, but header was not defined.

May I ask you to create a small repository that reproduces your issue?

nicopaez commented 3 years ago

Sure. Here you have a sample webapi project I am working on.

To reproduce the issue you can:

  1. Clone this repository: git@gitlab.com:fiuba-memo2/ejemplos/webapi-example.git
  2. Switch to branch "openapi-generator"
  3. Run the ./start_dev_containers.sh script to have a docker dev environment ready to work
  4. Run the ./generate_apispec.sh script to reproduce the issue

Please tell me If there is any other thing I could help.

Nico

k0kubun commented 3 years ago

OK, I kind of understood why it's happening. Usually, when you write a request spec with rack-test, last_request.env['rack.input'] is ready to be read:

From: /home/k0kubun/src/github.com/k0kubun/rspec-openapi/spec/requests/roda_spec.rb:24 :

    19:   describe '/roda' do
    20:     context 'when id is given' do
    21:       it 'returns 200' do
    22:         header 'CONTENT_TYPE', 'application/json'
    23:         post '/roda', { id: 1 }.to_json, { 'Content-Type' => 'application/json' }
 => 24:         require "pry";binding.pry
    25:         expect(last_response.status).to eq(200)
    26:       end
    27:     end
    28:   end
    29: end

[1] 3.0.0-p0 (#<RSpec::ExampleGroups::Roda::Roda::WhenIdIsGiven>)> last_request.env['rack.input'].read(128)
=> "{\"id\":1}"

but in this repository's environment, it's not readable unless you rewind it by yourself:

From: /app/spec/app/api_spec.rb:13 :

     8:   describe 'Users' do
     9:     it 'POST /users' do
    10:       header 'CONTENT_TYPE', 'application/json'
    11:       json_data = { name: 'juan'}.to_json
    12:       post '/users', json_data, { 'Content-Type' => 'application/json' }
 => 13:       require "pry";binding.pry
    14:       expect(last_response.status).to eq(201)
    15:     end
    16:
    17:     it 'GET /users' do
    18:       get '/users'

[1] pry(#<RSpec::ExampleGroups::API::Users>)> last_request.env['rack.input'].read(128)
=> nil
[2] pry(#<RSpec::ExampleGroups::API::Users>)> last_request.env['rack.input'].tap(&:rewind).read(128)
=> "{\"name\":\"juan\"}"

Do you have any idea why this happens? Obviously we can fix this by just calling rewind, so I'm okay with it, but I'm curious about what makes the difference.

nicopaez commented 3 years ago

I think that is because of the way I am reading the json when the padrino application wants to access the posted data.

      def user_params
        @body ||= request.body.read
        JSON.parse(@body).symbolize_keys
      end

Up to what I know this is usual method when using padrino/sinatra.

k0kubun commented 3 years ago

Fixed it in v0.3.14. Please try the version.