module RopenPi::Specs::ExampleGroupHelpers

rubocop:disable Metrics/ModuleLength

Public Instance Methods

description(value = nil) click to toggle source

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

Calls superclass method
# 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
examples(example = nil) click to toggle source

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

Calls superclass method
# 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
handle_examples(schema:, examples:, required:) click to toggle source

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
header(name, attributes) click to toggle source
# 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
merge_other_examples!(example_metadata) click to toggle source

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
parameter(attributes) click to toggle source

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
path(template, metadata = {}, &block) click to toggle source
# 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
request_body(attributes) click to toggle source
# 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
request_body_json(schema:, required: true, description: nil, examples: nil) click to toggle source
# 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
request_body_multipart(schema:, description: nil) click to toggle source

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
request_body_text_plain(required: false, description: nil, examples: nil) click to toggle source

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
request_body_xml(schema:, required: false, description: nil, examples: nil) click to toggle source

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
response(code, description, metadata = {}, &block) click to toggle source
# 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
run_test!(&block) click to toggle source
# 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
schema(value, content_type: 'application/json') click to toggle source
# 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