Module: OpenapiRuby::Adapters::Minitest::DSL

Defined in:
lib/openapi_ruby/adapters/minitest.rb

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



12
13
14
15
16
17
18
# File 'lib/openapi_ruby/adapters/minitest.rb', line 12

def self.included(base)
  base.extend ClassMethods
  base.class_attribute :_openapi_contexts, default: []
  base.class_attribute :_openapi_schema_name, default: nil

  install_rack_test!(base)
end

.install_rack_test!(base) ⇒ Object

On Rails the test class already inherits ActionDispatch's integration helpers. Every other host drives requests through rack-test, and the class defines app itself.



23
24
25
26
27
28
29
30
31
# File 'lib/openapi_ruby/adapters/minitest.rb', line 23

def self.install_rack_test!(base)
  return if OpenapiRuby.rails_host?
  return if base.method_defined?(:last_response)

  require "rack/test"
  base.include ::Rack::Test::Methods
rescue LoadError
  nil
end

Instance Method Details

#assert_api_response(method, expected_status, params: {}, headers: {}, body: nil, path_params: {}, api_path: nil, &block) ⇒ Object

Raises:



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/openapi_ruby/adapters/minitest.rb', line 60

def assert_api_response(method, expected_status, params: {}, headers: {}, body: nil, path_params: {},
  api_path: nil, &block)
  context = find_context_for(method, path_params, params, expected_status, api_path)
  raise OpenapiRuby::Error, "No api_path defined for #{method.upcase} in #{self.class}" unless context

  operation = context.operations[method.to_s]
  raise OpenapiRuby::Error, "No #{method.upcase} operation defined" unless operation

  response_ctx = operation.responses[expected_status.to_s]
  raise OpenapiRuby::Error, "No response #{expected_status} defined for #{method.upcase}" unless response_ctx

  # Build the request path with base path from schema server URL
  base_path = resolve_base_path(context.schema_name)
  path = "#{base_path}#{expand_path(context.path_template, params.merge(path_params))}"

  # Resolve security scheme parameters
  security_params = resolve_security_params(operation, context.schema_name)
  security_params.each do |param|
    val = params[param[:name].to_sym] || params[param[:name]]
    next if val.nil?

    case param[:in].to_s
    when "header" then headers[param[:name]] = val
    when "query" then params[param[:name]] = val
    when "cookie" then headers["Cookie"] = "#{param[:name]}=#{val}"
    end
  end

  # Default Accept header for API requests
  headers["Accept"] ||= "application/json"

  # Build query params (exclude path params)
  query_params = params.reject { |k, _| path_param_names(context).include?(k.to_s) }

  # Execute the request
  if body
    content_type = operation.request_body_definition&.dig("content")&.keys&.first || "application/json"
    request_args = if content_type.include?("form-data") || content_type.include?("x-www-form-urlencoded")
      {params: body, headers: headers}
    else
      {
        params: body.is_a?(String) ? body : body.to_json,
        headers: headers.merge("Content-Type" => content_type)
      }
    end
    # Append query params to path when body is present
    if query_params.any?
      query_string = query_params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&")
      path = "#{path}?#{query_string}"
    end
  else
    request_args = {params: query_params, headers: headers}
  end

  # Validate the request against the declared operation (skip for error responses,
  # since those tests intentionally send invalid data)
  if OpenapiRuby.configuration.test_request_validation && expected_status < 400
    document_hash = build_validation_document(context.schema_name)
    req_errors = Testing::RequestValidator.new(document_hash).validate(
      operation: operation,
      path_context: context,
      params: params,
      headers: headers,
      body: body,
      path_params: path_params
    )
    assert req_errors.empty?, "Request validation failed:\n#{req_errors.join("\n")}"
  end

  openapi_transport.dispatch(method, path, **request_args)

  # Validate response
  assert_equal expected_status, openapi_response.status,
    "Expected status #{expected_status}, got #{openapi_response.status}\nResponse body: #{openapi_response.body}"

  if response_ctx.schema_definition
    validator = Testing::ResponseValidator.new
    body_data = parse_response_body
    errors = validator.validate(
      response_body: body_data,
      status_code: openapi_response.status,
      response_context: response_ctx
    )
    assert errors.empty?, "Response validation failed:\n#{errors.join("\n")}"
  end

  # Execute additional assertions
  instance_eval(&block) if block
end

#openapi_responseObject



161
162
163
# File 'lib/openapi_ruby/adapters/minitest.rb', line 161

def openapi_response
  openapi_transport.response
end

#openapi_transportObject

The seam between the DSL and the host's request API. Public so specs that drive requests themselves (rate limiting, pagination loops) can reach the same dispatcher and response the assertions use.



157
158
159
# File 'lib/openapi_ruby/adapters/minitest.rb', line 157

def openapi_transport
  @openapi_transport ||= Testing::Transport.for(self)
end

#parsed_bodyObject



150
151
152
# File 'lib/openapi_ruby/adapters/minitest.rb', line 150

def parsed_body
  parse_response_body
end