Module: Miniswag::DSL

Included in:
TestCase
Defined in:
lib/miniswag/dsl.rb

Overview

Class-level DSL methods that mirror rswag’s ExampleGroupHelpers.

The DSL builds a tree of metadata on the test class. Each ‘path` block creates a path context, each verb block creates an operation context, and each `response` block creates a response context. `run_test!` generates a Minitest test method from the accumulated metadata.

Metadata is stored in class instance variables and accumulated via a context stack so nested blocks see their parent metadata.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(base) ⇒ Object



17
18
19
20
21
# File 'lib/miniswag/dsl.rb', line 17

def self.extended(base)
  base.instance_variable_set(:@_miniswag_context_stack, [])
  base.instance_variable_set(:@_miniswag_test_definitions, [])
  base.instance_variable_set(:@_miniswag_openapi_spec_name, nil)
end

Instance Method Details

#before(&block) ⇒ Object

Register a block to run before the test request (within response context). Replaces RSpec’s ‘let!` blocks for test data setup.



145
146
147
148
# File 'lib/miniswag/dsl.rb', line 145

def before(&block)
  ctx = @_miniswag_context_stack.last
  ctx[:before_blocks] << block if ctx && ctx.key?(:before_blocks)
end

#description(value) ⇒ Object



59
60
61
# File 'lib/miniswag/dsl.rb', line 59

def description(value)
  current_operation[:description] = value
end

#example(mime, name, value, summary = nil, description = nil) ⇒ Object



125
126
127
128
129
130
131
132
133
# File 'lib/miniswag/dsl.rb', line 125

def example(mime, name, value, summary = nil, description = nil)
  current_response[:content] = {} if current_response[:content].blank?
  if current_response[:content][mime].blank?
    current_response[:content][mime] = {}
    current_response[:content][mime][:examples] = {}
  end
  example_object = { value: value, summary: summary, description: description }.compact
  current_response[:content][mime][:examples].merge!(name.to_sym => example_object)
end

#examples(examples_hash = nil) ⇒ Object



117
118
119
120
121
122
123
# File 'lib/miniswag/dsl.rb', line 117

def examples(examples_hash = nil)
  return if examples_hash.nil?

  examples_hash.each_with_index do |(mime, example_object), index|
    example(mime, "example_#{index}", example_object)
  end
end

#header(name, attributes) ⇒ Object



112
113
114
115
# File 'lib/miniswag/dsl.rb', line 112

def header(name, attributes)
  current_response[:headers] ||= {}
  current_response[:headers][name] = attributes
end

#metadataObject

── Metadata access (for direct manipulation like metadata[“x-public-docs”]) ─



137
138
139
# File 'lib/miniswag/dsl.rb', line 137

def 
  @_miniswag_context_stack.last || {}
end

#openapi_spec(name) ⇒ Object

Set which openapi spec file this test class targets (e.g. “admin.yaml”)



24
25
26
# File 'lib/miniswag/dsl.rb', line 24

def openapi_spec(name)
  @_miniswag_openapi_spec_name = name
end

#parameter(attributes) ⇒ Object

── Parameters ──────────────────────────────────────────────────────



71
72
73
74
75
76
77
78
79
80
81
# File 'lib/miniswag/dsl.rb', line 71

def parameter(attributes)
  attributes[:required] = true if attributes[:in] && attributes[:in].to_sym == :path
  scope = current_scope
  if scope == :operation
    current_operation[:parameters] ||= []
    current_operation[:parameters] << attributes
  else
    current_path_item[:parameters] ||= []
    current_path_item[:parameters] << attributes
  end
end

#params(&block) ⇒ Object

Register a block that returns a hash of parameter values. Keys should match parameter names (including Authorization, path params, etc.)



152
153
154
155
# File 'lib/miniswag/dsl.rb', line 152

def params(&block)
  ctx = @_miniswag_context_stack.last
  ctx[:params_block] = block if ctx
end

#path(template, &block) ⇒ Object

── Path block ──────────────────────────────────────────────────────



30
31
32
33
# File 'lib/miniswag/dsl.rb', line 30

def path(template, &block)
  ctx = { path_item: { template: template, parameters: [] }, scope: :path }
  push_context(ctx, &block)
end

#request_body_example(value:, summary: nil, name: nil) ⇒ Object



83
84
85
86
87
88
89
90
91
# File 'lib/miniswag/dsl.rb', line 83

def request_body_example(value:, summary: nil, name: nil)
  return unless current_scope == :operation

  current_operation[:request_examples] ||= []
  example_entry = { value: value }
  example_entry[:summary] = summary if summary
  example_entry[:name] = name || current_operation[:request_examples].length
  current_operation[:request_examples] << example_entry
end

#response(code, description, **options, &block) ⇒ Object

── Response block ──────────────────────────────────────────────────



95
96
97
98
99
100
101
102
103
104
# File 'lib/miniswag/dsl.rb', line 95

def response(code, description, **options, &block)
  ctx = {
    response: { code: code, description: description }.merge(options),
    scope: :response,
    before_blocks: [],
    params_block: nil,
    after_test_block: nil
  }
  push_context(ctx, &block)
end

#run_test!(test_description = nil, &after_block) ⇒ Object

── Test generation ─────────────────────────────────────────────────



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/miniswag/dsl.rb', line 159

def run_test!(test_description = nil, &after_block)
  # Snapshot all the accumulated metadata at this point
  path_item = deep_dup(current_path_item)
  operation = deep_dup(current_operation)
  response_meta = deep_dup(current_response)
  openapi_spec_name = @_miniswag_openapi_spec_name
  before_blocks = (@_miniswag_context_stack.last[:before_blocks] || []).dup
  params_block = @_miniswag_context_stack.last[:params_block]

  test_description ||= "returns a #{response_meta[:code]} response"

  # Build a unique test name from path + verb + response code + description
  verb = operation[:verb]
  path_template = path_item[:template]
  test_name = "test_#{verb}_#{path_template}_#{response_meta[:code]}_#{test_description}"
              .gsub(/[^a-zA-Z0-9_]/, '_')
              .gsub(/_+/, '_')
              .downcase

  # Build full metadata hash (mirrors rswag's metadata structure)
   = {
    path_item: path_item,
    operation: operation,
    response: response_meta,
    openapi_spec: openapi_spec_name
  }

  # Register for OpenAPI generation
  @_miniswag_test_definitions ||= []
  @_miniswag_test_definitions << 

  # Register this class with the global registry for OpenAPI generation
  Miniswag.register_test_class(self)

  # Define the actual Minitest test method
  user_block = after_block
  captured_before_blocks = before_blocks
  captured_params_block = params_block
   = 

  define_method(test_name) do
    # Run before blocks in instance context
    captured_before_blocks.each { |blk| instance_exec(&blk) }

    # Collect params from the params block
    test_params = captured_params_block ? instance_exec(&captured_params_block) : {}
    test_params ||= {}

    # Merge instance variable @_miniswag_params if set (from setup blocks)
    if defined?(@_miniswag_params) && @_miniswag_params.is_a?(Hash)
      test_params = @_miniswag_params.merge(test_params)
    end

    # Build and send request
    factory = Miniswag::RequestFactory.new(, test_params)
    request = factory.build_request

    send(
      request[:verb],
      request[:path],
      params: request[:payload],
      headers: request[:headers]
    )

    # Validate response
    validator = Miniswag::ResponseValidator.new
    validator.validate!(, response)

    # Register a Minitest assertion so tests are not flagged as "missing assertions"
    expected_code = [:response][:code].to_s
    assert_equal expected_code, response.code,
                 "Expected response code #{expected_code} but got #{response.code}"

    # Run user's additional assertions
    instance_exec(response, &user_block) if user_block
  end
end

#schema(value) ⇒ Object

── Response-level attributes ───────────────────────────────────────



108
109
110
# File 'lib/miniswag/dsl.rb', line 108

def schema(value)
  current_response[:schema] = value
end