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 StringValidatorPatch
module 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