Committee::InvalidRequest:
       #/paths/~1contracts/post/requestBody/content/multipart~1form-data/schema/properties/original_file expected string, but received ActionDispatch::Http::UploadedFile: #<ActionDispatch::Http::UploadedFile:0x00007fa77e1f36a0>
It defines an API that receives file uploads in the multipart / form-data format.
The API is designed based on the OpenAPI 3.0 specifications, for example:
requestBody:
  original_file:
    image/png:
      schema:
        type: string
        format: binary
You can also find it in the Swagger documentation.
I want to write Rspec for this API using committee gem.
some_controller_spec.rb
#form parameters
let(:file_upload_form) {
  {
    article_id: 1_000,
    name: 'This is good article',
    # set real existing file
    original_file: fixture_file_upload(
      Rails.root.join('sample_files/sample.docx'),
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    )
  }
}
The parameters used as Request are defined as above, and the file to be uploaded is the Docx file prepared for testing. This is POSTed as follows and the API format is verified by the ʻassert_schema_conform` method.
some_controller_spec.rb
it 'uploads file' do
  post api_v1_images_path,
         headers: authenticated_header(user),
         params: file_upload_form
  expect(response).to have_http_status(:success)
  assert_schema_conform
end
The API Request specification is type: string, but in the test, the object of ʻActionDispatch` is set, so I am getting an error that it is not what I expected.
However, since it is a file upload test, the internal processing does not work with String, so it cannot be changed. This was a problem.
Situation
The error occurs in the following ʻOpenAPIParser` process. [lib/openapi_parser/schema_validators/string_validator.rb#L11] (https://github.com/ota42y/openapi_parser/blob/44c640cc103bbbb9e8029e41a8889e8fd9350902/lib/openapi_parser/schema_validators/string_validator.rb#L11)
lib/openapi_parser/schema_validators/string_validator.rb
    def coerce_and_validate(value, schema, **_keyword_args)
      return OpenAPIParser::ValidateError.build_error_result(value, schema) unless value.kind_of?(String)
      # ...Omitted below...
    end
It simply looks at whether value is a String class, and if it is not String, it is an Error. This time, since this is an object of ʻActionDispatch, value.kind_of? (String)` becomes false and an error occurs.
type: string, if it is format: binary, it allows other than the String class.Like a file, in the case of format: binay, you can change the conditions so as not to raise the error. Create a patch for that.
Create an arbitrary Module that defines the corresponding method coerce_and_validate (value, schema, ** _keyword_args) as a batch.
module StringValidatorPatch
  def coerce_and_validate(value, schema, **keyword_args)
    #Change this process
    # https://github.com/ota42y/openapi_parser/blob/61874f0190a86c09bdfb78de5f51cfb6ae16068b/lib/openapi_parser/schema_validators/string_validator.rb#L11
    if !value.is_a?(String) && schema.format != 'binary'
      return OpenAPIParser::ValidateError.build_error_result(value, schema)
    end
    # ---So far
    value, err = check_enum_include(value, schema)
    return [nil, err] if err
    value, err = pattern_validate(value, schema)
    return [nil, err] if err
    unless @datetime_coerce_class.nil?
      value, err = coerce_date_time(value, schema)
      return [nil, err] if err
    end
    value, err = validate_max_min_length(value, schema)
    return [nil, err] if err
    value, err = validate_email_format(value, schema)
    return [nil, err] if err
    value, err = validate_uuid_format(value, schema)
    return [nil, err] if err
    [value, nil]
  end
end
In this way, define a Module that redefines only the method you want to change. Reopen the class you want to patch this Module, this time the ʻOpenAPIParser :: SchemaValidator :: StringValidator class, and use Module # prepend` to overwrite the method.
Module # prepend reference
class OpenAPIParser::SchemaValidator::StringValidator
  prepend StringValidatorPatch
end
This will eliminate the Committee :: InvalidRequest error
You can avoid the Committee :: InvalidRequest error by applying a patch, but applying a patch in a global scope will affect the whole thing.
I want this change to take effect only for tests involving file uploads.
Therefore, consider defining it in context'some context' do ... end so that it is reflected only in the required context of Rspec.
some_controller_spec.rb
RSpec.describe SomeController, type: :request do
  context 'some context' do
    #Apply the patch in context
    module StringValidatorPatch
      def coerce_and_validate(value, schema, **keyword_args)
        if !value.is_a?(String) && schema.format != 'binary'
          return OpenAPIParser::ValidateError.build_error_result(value, schema)
        end
        # (The following is omitted)
      end
    end
    class OpenAPIParser::SchemaValidator::StringValidator
      prepend StringValidatorPatch
    end
    #Patch so far
    it 'uploads file' do
      post api_v1_images_path,
             headers: authenticated_header(user),
             params: file_upload_form
      expect(response).to have_http_status(:success)
      assert_schema_conform
    end
  end
end
However, since the scope of the block does not separate constants or set namespaces, we want to avoid processing such as class definition inside the block.
It also gets stuck in Rubocop's Lint / ConstantDefinitionInBlock.
If you want to define a similar constant in Rspec, use stub_const () to define it.
stub_const () to patch where you need itUse Class.new to reopen a class without the class keyword ..
You can also define a class by passing a block
Foo = Class.new {|c|
  def hello; 'hello'; end
}
puts Foo.new.hello # => 'hello'
Use this to define a patched class. Save the StringValidatorPatchmodule for the patch in the spec / support directory as string_validator_patch.rb so that it can be loaded.
patched = Class.new(OpenAPIParser::SchemaValidator::StringValidator) do |klass|
  klass.prepend StringValidatorPatch
end
stub_const('OpenAPIParser::SchemaValidator::StringValidator', patched)
If you pass a class to the argument of Class.new (), it will be treated as a parent class, so in the above case patched will be a child class of ʻOpenAPIParser :: SchemaValidator :: StringValidator.  If you define a constant using stub_const ()`, you can use the patched class.
Implement this process with before or let as needed.
--Patch the Validator class to clear the Committee :: InvalidRequest error
--To apply a patch, define a Module that implements only the method, and use prepend to reflect it.
--RSpec uses Class.new () and stub_const () to patch locally
spec/support/string_validator_patch.rb
module StringValidatorPatch
  def coerce_and_validate(value, schema, **keyword_args)
    #Change this process
    # https://github.com/ota42y/openapi_parser/blob/61874f0190a86c09bdfb78de5f51cfb6ae16068b/lib/openapi_parser/schema_validators/string_validator.rb#L11
    if !value.is_a?(String) && schema.format != 'binary'
      return OpenAPIParser::ValidateError.build_error_result(value, schema)
    end
    # ---So far
    value, err = check_enum_include(value, schema)
    return [nil, err] if err
    value, err = pattern_validate(value, schema)
    return [nil, err] if err
    unless @datetime_coerce_class.nil?
      value, err = coerce_date_time(value, schema)
      return [nil, err] if err
    end
    value, err = validate_max_min_length(value, schema)
    return [nil, err] if err
    value, err = validate_email_format(value, schema)
    return [nil, err] if err
    value, err = validate_uuid_format(value, schema)
    return [nil, err] if err
    [value, nil]
  end
end
some_controller_spec.rb
RSpec.describe SomeController, type: :request do
  context 'some context' do
    let(:file_upload_form) {
      {
        article_id: 1_000,
        name: 'This is good article',
        original_file: fixture_file_upload(
          Rails.root.join('sample_files/sample.docx'),
          'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
        )
      }
    }
    before do
      #Apply the patch in context
      patched = Class.new(OpenAPIParser::SchemaValidator::StringValidator) do |klass|
        klass.prepend StringValidatorPatch
      end
      stub_const('OpenAPIParser::SchemaValidator::StringValidator', patched)
    end
    it 'uploads file' do
      post api_v1_images_path,
           headers: authenticated_header(user),
           params: file_upload_form
      expect(response).to have_http_status(:success)
      assert_schema_conform
    end
  end
end
        Recommended Posts