module RopenPi::Specs::ExampleGroupHelpers
rubocop:disable Metrics/ModuleLength
Public Instance Methods
NOTE: 'description' requires special treatment because ExampleGroup already defines a method with that name. Provide an override that supports the existing functionality while also setting the appropriate metadata if applicable
# File lib/ropen_pi/specs/example_group_helpers.rb, line 31 def description(value = nil) return super() if value.nil? metadata[:operation][:description] = value end
NOTE: Similar to 'description', 'examples' need to handle the case when being invoked with no params to avoid overriding 'examples' method of rspec-core ExampleGroup
# File lib/ropen_pi/specs/example_group_helpers.rb, line 189 def examples(example = nil) return super() if example.nil? metadata[:response][:examples] = example end
rubocop:disable Metrics/MethodLength
# File lib/ropen_pi/specs/example_group_helpers.rb, line 67 def handle_examples(schema:, examples:, required:) # the request_factory is going to have to resolve the different ways that the example can be given # it can contain a 'value' key which is a direct hash (easiest) # it can contain a 'external_value' key which makes an external call to load the json # it can contain a '$ref' key. Which points to #/components/examples/blog # rubocop:disable Metrics/BlockLength examples.each do |passed_example| param_attributes = if passed_example.is_a?(Symbol) example_key_name = passed_example # TODO: write more tests around this adding to the parameter # if symbol try and use save_request_example { name: example_key_name, in: :body, required: required, param_value: example_key_name, schema: schema } elsif passed_example.is_a?(Hash) && passed_example[:externalValue] { name: passed_example, in: :body, required: required, param_value: passed_example[:externalValue], schema: schema } elsif passed_example.is_a?(Hash) && passed_example['$ref'] { name: passed_example, in: :body, required: required, param_value: passed_example['$ref'], schema: schema } end parameter(param_attributes) end # rubocop:enable Metrics/BlockLength end
# File lib/ropen_pi/specs/example_group_helpers.rb, line 175 def header(name, attributes) metadata[:response][:headers] ||= {} if attributes[:type] && attributes[:schema].nil? attributes[:schema] = { type: attributes[:type] } attributes.delete(:type) end metadata[:response][:headers][name] = attributes end
checks the examples in the parameters should be able to add $ref and externalValue examples. This syntax would look something like this in the integration _spec.rb file
request_body_json
schema: { '$ref' => '#/components/schemas/blog' },
examples: [:blog, {name: :external_blog, externalValue: 'http://api.sample.org/myjson_example'}, {name: :another_example, '$ref' => '#/components/examples/flexible_blog_example'}]
The first value :blog, points to a let param of the same name, and is used to make the request in the integration test (it is used to build the request payload)
The second item in the array shows how to add an externalValue for the examples in the requestBody section The third item shows how to add a $ref item that points to the components/examples section of the swagger spec.
NOTE: that the externalValue will produce valid example syntax in the swagger output, but swagger-ui will not show it yet
# File lib/ropen_pi/specs/example_group_helpers.rb, line 211 def merge_other_examples!(example_metadata) # example.metadata[:operation][:requestBody][:content]['application/json'][:examples] content_node = example_metadata[:operation][:requestBody][:content]['application/json'] return unless content_node external_example = example_metadata[:operation]&.dig(:parameters)&.detect do |p| p[:in] == :body && p[:name].is_a?(Hash) && p[:name][:externalValue] end || {} ref_example = example_metadata[:operation]&.dig(:parameters)&.detect do |p| p[:in] == :body && p[:name].is_a?(Hash) && p[:name]['$ref'] end || {} examples_node = content_node[:examples] ||= {} nodes_to_add = [] nodes_to_add << external_example unless external_example.empty? nodes_to_add << ref_example unless ref_example.empty? nodes_to_add.each do |node| json_request_examples = examples_node ||= {} other_name = node[:name][:name] other_key = node[:name][:externalValue] ? :externalValue : '$ref' json_request_examples.merge!(other_name => { other_key => node[:param_value] }) if other_name end end
rubocop:enable Metrics/MethodLength
# File lib/ropen_pi/specs/example_group_helpers.rb, line 152 def parameter(attributes) attributes[:required] = true if attributes[:in] && attributes[:in].to_sym == :path attributes[:schema] = { type: attributes[:type] } if attributes[:type] && attributes[:schema].nil? if metadata.key?(:operation) metadata[:operation][:parameters] ||= [] metadata[:operation][:parameters] << attributes else metadata[:path_item][:parameters] ||= [] metadata[:path_item][:parameters] << attributes end end
# File lib/ropen_pi/specs/example_group_helpers.rb, line 10 def path(template, metadata = {}, &block) metadata[:path_item] = { template: template } describe(template, metadata, &block) end
# File lib/ropen_pi/specs/example_group_helpers.rb, line 44 def request_body(attributes) # can make this generic, and accept any incoming hash (like parameter method) attributes.compact! if metadata[:operation][:requestBody].blank? metadata[:operation][:requestBody] = attributes elsif metadata[:operation][:requestBody] && metadata[:operation][:requestBody][:content] # merge in content_hash = metadata[:operation][:requestBody][:content] incoming_content_hash = attributes[:content] content_hash.merge!(incoming_content_hash) if incoming_content_hash end end
# File lib/ropen_pi/specs/example_group_helpers.rb, line 58 def request_body_json(schema:, required: true, description: nil, examples: nil) passed_examples = Array(examples) content_hash = { 'application/json' => { schema: schema, examples: examples }.compact! || {} } request_body(description: description, required: required, content: content_hash) handle_examples(schema: schema, examples: passed_examples, required: required) if passed_examples.any? end
rubocop:disable Metrics/MethodLength
# File lib/ropen_pi/specs/example_group_helpers.rb, line 122 def request_body_multipart(schema:, description: nil) content_hash = { 'multipart/form-data' => { schema: schema } } request_body(description: description, content: content_hash) schema.extend(Hashie::Extensions::DeepLocate) file_properties = schema.deep_locate ->(_k, v, _obj) { v == :binary } hash_locator = [] file_properties.each do |match| hash_match = schema.deep_locate ->(_k, v, _obj) { v == match } hash_locator.concat(hash_match) unless hash_match.empty? end property_hashes = hash_locator.flat_map do |locator| locator.select { |_k, v| file_properties.include?(v) } end existing_keys = [] property_hashes.each do |property_hash| property_hash.keys.each do |k| next if existing_keys.include?(k) file_name = k existing_keys << k parameter name: file_name, in: :formData, type: :file, required: true end end end
rubocop:enable Metrics/MethodLength
# File lib/ropen_pi/specs/example_group_helpers.rb, line 108 def request_body_text_plain(required: false, description: nil, examples: nil) content_hash = { 'test/plain' => { schema: { type: :string }, examples: examples }.compact! || {} } request_body(description: description, required: required, content: content_hash) end
TODO: add examples to this like we can for json, might be large lift as many assumptions are made on content-type
# File lib/ropen_pi/specs/example_group_helpers.rb, line 115 def request_body_xml(schema:, required: false, description: nil, examples: nil) # passed_examples = Array(examples) content_hash = { 'application/xml' => { schema: schema, examples: examples }.compact! || {} } request_body(description: description, required: required, content: content_hash) end
# File lib/ropen_pi/specs/example_group_helpers.rb, line 165 def response(code, description, metadata = {}, &block) metadata[:response] = { code: code, description: description } context(description, metadata, &block) end
# File lib/ropen_pi/specs/example_group_helpers.rb, line 239 def run_test!(&block) app_json = 'application/json' before do |example| submit_request(example.metadata) end it "returns a #{metadata[:response][:code]} response" do |example| assert_response_matches_metadata(example.metadata, &block) example.instance_exec(response, &block) if block_given? end after do |example| body_parameter = example.metadata[:operation]&.dig(:parameters)&.detect do |p| p[:in] == :body && p[:required] end if body_parameter && respond_to?(body_parameter[:name]) && \ example.metadata[:operation][:requestBody][:content][app_json] # save response examples by default if example.metadata[:response][:examples].nil? || example.metadata[:response][:examples].empty? unless response.body.to_s.empty? example.metadata[:response][:examples] = { app_json => JSON.parse(response.body, symbolize_names: true) } end end # save request examples using the let(:param_name) { REQUEST_BODY_HASH } syntax in the test if response.code.to_s =~ /^2\d{2}$/ example.metadata[:operation][:requestBody][:content][app_json] = { examples: {} } \ unless example.metadata[:operation][:requestBody][:content][app_json][:examples] json_request_examples = example.metadata[:operation][:requestBody][:content][app_json][:examples] json_request_examples[body_parameter[:name]] = { value: send(body_parameter[:name]) } example.metadata[:operation][:requestBody][:content][app_json][:examples] = json_request_examples end end self.class.merge_other_examples!(example.metadata) if example.metadata[:operation][:requestBody] end end
# File lib/ropen_pi/specs/example_group_helpers.rb, line 170 def schema(value, content_type: 'application/json') content_hash = { content_type => { schema: value } } metadata[:response][:content] = content_hash end