Commit 22adfbaa by andrew morton

Create separate class for building test requests

parent 8c27e79d
......@@ -3,6 +3,7 @@ require 'rspec/swagger/configuration'
require 'rspec/swagger/document'
require 'rspec/swagger/formatter'
require 'rspec/swagger/helpers'
require 'rspec/swagger/request_builder'
require 'rspec/swagger/version'
module RSpec
......
......@@ -34,7 +34,6 @@ module RSpec
config.extend Operation, swagger_object: :operation
config.extend Parameters, swagger_object: :operation
config.extend Response, swagger_object: :response
config.include Resolver, :swagger_object
end
module Paths
......@@ -93,6 +92,14 @@ module RSpec
parameters_for_object[key] = param
end
def resolve_document metadata
# TODO: It's really inefficient to keep recreating this. It'd be nice
# if we could cache them some place.
name = metadata[:swagger_document]
Document.new(RSpec.configuration.swagger_docs[name])
end
private
# This key ensures uniqueness based on the 'name' and 'in' values.
......@@ -163,18 +170,20 @@ module RSpec
describe(status_code, meta) do
self.module_exec(&block) if block_given?
# TODO: describe the wacky ness to get the metadata and access to let() defined values...
before do |example|
method = example.metadata[:swagger_operation][:method]
path = resolve_path(example.metadata, self)
headers = resolve_headers(example.metadata)
builder = RequestBuilder.new(example.metadata, self)
method = builder.method
path = builder.path + builder.query
headers = builder.headers
params = resolve_params(example.metadata, self)
# Run the request
args = if ::Rails::VERSION::MAJOR >= 5
[path, {params: params, headers: headers}]
if ::Rails::VERSION::MAJOR >= 5
self.send(method, path, {params: params, headers: headers})
else
[path, params, headers]
self.send(method, path, params, headers)
end
self.send(method, *args)
if example.metadata[:capture_example]
examples = example.metadata[:swagger_response][:examples] ||= {}
......@@ -208,61 +217,6 @@ module RSpec
metadata[:capture_example] = true
end
end
module Resolver
def resolve_document metadata
# TODO: It's really inefficient to keep recreating this. It'd be nice
# if we could cache them some place.
name = metadata[:swagger_document]
Document.new(RSpec.configuration.swagger_docs[name])
end
def resolve_produces metadata
document = resolve_document metadata
metadata[:swagger_operation][:produces] || document[:produces]
end
def resolve_consumes metadata
document = resolve_document metadata
metadata[:swagger_operation][:consumes] || document[:consumes]
end
def resolve_headers metadata
headers = {}
# Match the names that Rails uses internally
if produces = resolve_produces(metadata)
headers['HTTP_ACCEPT'] = produces.join(';')
end
if consumes = resolve_consumes(metadata)
headers['CONTENT_TYPE'] = consumes.first
end
headers
end
def resolve_params metadata, group_instance
path_item = metadata[:swagger_path_item] || {}
operation = metadata[:swagger_operation] || {}
params = path_item.fetch(:parameters, {}).merge(operation.fetch(:parameters, {}))
params.keys.map do |key|
location, name = key.split('&')
{name: name, in: location.to_sym, value: group_instance.send(name)}
end
end
def resolve_path metadata, group_instance
document = resolve_document metadata
base_path = document[:basePath] || ''
# Find params in the path and replace them with values defined in
# in the example group.
path = metadata[:swagger_path_item][:path].gsub(/(\{.*?\})/) do |match|
# QUESTION: Should check that the parameter is actually defined in
# `metadata[:swagger_*][:parameters]` before fetch a value?
group_instance.send(match[1...-1])
end
base_path + path
end
end
end
end
end
module RSpec
module Swagger
class RequestBuilder
attr_reader :metadata, :instance
def initialize(metadata, instance)
@metadata, @instance = metadata, instance
end
def document
@document ||= begin
name = metadata[:swagger_document]
Document.new(RSpec.configuration.swagger_docs[name])
end
end
def method
metadata[:swagger_operation][:method]
end
def produces
metadata[:swagger_operation][:produces] || document[:produces]
end
def consumes
metadata[:swagger_operation][:consumes] || document[:consumes]
end
def parameters
path_item = metadata[:swagger_path_item] || {}
operation = metadata[:swagger_operation] || {}
path_item.fetch(:parameters, {}).merge(operation.fetch(:parameters, {}))
end
def headers
headers = {}
# Match the names that Rails uses internally
headers['HTTP_ACCEPT'] = produces.join(';') if produces.present?
headers['CONTENT_TYPE'] = consumes.first if consumes.present?
# TODO needs to pull in parameters with in: :header set.
headers
end
def path
base_path = document[:basePath] || ''
# Find params in the path and replace them with values defined in
# in the example group.
path = base_path + metadata[:swagger_path_item][:path].gsub(/(\{.*?\})/) do |match|
# QUESTION: Should check that the parameter is actually defined in
# `metadata[:swagger_*][:parameters]` before fetch a value?
instance.send(match[1...-1])
end
end
def query
# Don't bother with the parameter bodies since all we need is location
# and name which make up the key.
query_params = parameters.keys
.map{ |k| k.split('&')}
.select{ |location, name| location == 'query' }
.map{ |location, name| [name, instance.send(name)] }
'?' + Hash[query_params].to_query unless query_params.empty?
end
def body
if key = parameters.keys.find{ |k| k.starts_with? 'body&' }
instance.send(key.split('&').last).to_json
end
end
end
end
end
......@@ -15,6 +15,7 @@ Gem::Specification.new do |s|
'lib/rspec/swagger/document.rb',
'lib/rspec/swagger/formatter.rb',
'lib/rspec/swagger/helpers.rb',
'lib/rspec/swagger/request_builder.rb',
'lib/rspec/swagger/version.rb',
]
s.homepage = 'https://github.com/drewish/rspec-swagger'
......
......@@ -187,130 +187,3 @@ RSpec.describe RSpec::Swagger::Helpers::Operation do
end
end
end
RSpec.describe RSpec::Swagger::Helpers::Resolver do
# Tthis helper is an include rather than an extend we can get it pulled into
# the test just by matching the filter metadata.
describe("#resolve_params", :swagger_object) do
let(:metadata) do
{
swagger_document: 'example.json',
swagger_operation: {parameters: params}
}
end
let(:document) { { } }
before { allow(self).to receive(:resolve_document) { document } }
describe "with a missing value" do
let(:params) { {"path&post_id" => {name: "post_id", in: :path}} }
# TODO best thing would be to lazily evaulate the params so we'd only
# hit this if something was trying to use it.
it "raises an error" do
expect{resolve_params(metadata, self)}.to raise_exception(NoMethodError)
end
end
describe "with a valid value" do
let(:params) { {"path&post_id" => {name: "post_id", in: :path, description: "long"}} }
let(:post_id) { 123 }
it "returns it" do
expect(resolve_params(metadata, self)).to eq([{name: "post_id", in: :path, value: 123}])
end
end
describe "with a $ref" do
let(:document) do
{ parameters: { skipParam: {name: "skipper", in: "path", required: true, type: "integer"} } }
end
let(:params) { {"path&skipper" => { '$ref' => '#/parameters/skipParam'}} }
let(:skipper) { true }
it "uses the parameters in the document" do
expect(resolve_params(metadata, self)).to eq([{name: "skipper", in: :path, value: true}])
end
end
end
describe "#resolve_path", :swagger_object do
let(:metadata) do
{
swagger_document: 'example.json',
swagger_path_item: {path: template},
}
end
let(:document) { { } }
before { expect(self).to receive(:resolve_document) { document } }
describe "with a missing value" do
let(:template) { '/sites/{site_id}' }
it "raises an error" do
expect{ resolve_path(metadata, self) }.to raise_exception(NoMethodError)
end
end
describe "with values" do
let(:template) { '/sites/{site_id}/accounts/{accountId}' }
let(:site_id) { 1001 }
let(:accountId) { "pickles" }
it "substitutes them into the path" do
expect(resolve_path(metadata, self)).to eq('/sites/1001/accounts/pickles')
end
end
describe "with a base path" do
let(:document) { { basePath: '/base' } }
let(:template) { '/sites/' }
it "prefixes the path" do
expect(resolve_path(metadata, self)).to eq('/base/sites/')
end
end
end
describe "#resolve_headers", :swagger_object do
context "with consumes/produces in the operation" do
let(:metadata) do
{swagger_operation: {consumes: ['application/json'], produces: ['application/xml']}}
end
before do
allow(self).to receive(:resolve_document) { {} }
end
it "sets the Content-Type header" do
expect(resolve_headers(metadata)).to include('CONTENT_TYPE' => 'application/json')
end
it "sets the Accepts header" do
expect(resolve_headers(metadata)).to include('HTTP_ACCEPT' => 'application/xml')
end
end
context "with consumes/produces in the document" do
let(:metadata) { {swagger_document: 'test.json', swagger_operation: {}} }
before do
allow(self).to receive(:resolve_document) do
{ consumes: ['text/plain; charset=utf-8'],
produces: ['application/vnd.github.v3.raw+json']}
end
end
it "sets the Content-Type header" do
expect(resolve_headers(metadata)).to include('CONTENT_TYPE' => 'text/plain; charset=utf-8')
end
it "sets the Accepts header" do
expect(resolve_headers(metadata)).to include('HTTP_ACCEPT' => 'application/vnd.github.v3.raw+json')
end
end
xit "includes paramters" do
end
end
end
require 'swagger_helper'
RSpec.describe RSpec::Swagger::RequestBuilder do
describe '#initialize' do
it 'stores metadata and instance' do
metadata = { foo: :bar }
instance = double
subject = described_class.new(metadata, instance)
expect(subject.metadata).to eq metadata
expect(subject.instance).to eq instance
end
end
describe '#document' do
subject { described_class.new(metadata, double('instance')) }
let(:metadata) { { swagger_document: 'example.json' } }
it 'loads the document' do
allow(RSpec.configuration.swagger_docs).to receive(:[]).with('example.json').and_return({foo: :bar})
expect(subject.document[:foo]).to eq :bar
end
end
describe '#method' do
subject { described_class.new(metadata, double('instance')) }
let(:metadata) { { swagger_operation: {method: 'get' } } }
it "returns the operation's method" do
expect(subject.method).to eq 'get'
end
end
describe '#produces' do
subject { described_class.new(metadata, double('instance')) }
let(:document) { double }
before { allow(subject).to receive(:document) { document } }
context 'with value in operation' do
let(:metadata) { { swagger_operation: {produces: 'something' } } }
it 'uses that value' do
expect(subject.produces).to eq 'something'
end
end
context 'with no value in operation' do
let(:metadata) { { swagger_operation: {} } }
it 'uses the value from the document' do
expect(document).to receive(:[]).with(:produces) { 'or other' }
expect(subject.produces).to eq 'or other'
end
end
end
describe '#consumes' do
subject { described_class.new(metadata, double('instance')) }
let(:document) { double }
before { allow(subject).to receive(:document) { document } }
context 'with value in operation' do
let(:metadata) { { swagger_operation: {consumes: 'something' } } }
it 'uses that value' do
expect(subject.consumes).to eq 'something'
end
end
context 'with no value in operation' do
let(:metadata) { { swagger_operation: {} } }
it 'uses the value from the document' do
expect(document).to receive(:[]).with(:consumes) { 'or other' }
expect(subject.consumes).to eq 'or other'
end
end
end
describe '#parameters' do
subject { described_class.new(metadata, double('instance')) }
let(:metadata) { {
swagger_path_item: { parameters: {
'path&petId' => { name: 'petId', in: :path, description: 'path' },
'query&site' => { name: 'site', in: :query }
} },
swagger_operation: { parameters: {
'path&petId' => { name: 'petId', in: :path, description: 'op' }
} },
} }
it 'merges values from the path and operation' do
expect(subject.parameters).to eq({
'path&petId' => { name: 'petId', in: :path, description: 'op' },
'query&site' => { name: 'site', in: :query }
})
end
end
describe '#headers' do
subject { described_class.new(double('metadata'), double('instance')) }
let(:produces) { }
let(:consumes) { }
before do
allow(subject).to receive(:produces) { produces }
allow(subject).to receive(:consumes) { consumes }
end
context 'when produces is present' do
let(:produces) { ['foo/bar'] }
it 'sets the Accept header' do
expect(subject.headers).to include('HTTP_ACCEPT' => 'foo/bar')
end
end
context 'when produces is blank' do
it 'does not set the Accept header' do
expect(subject.headers.keys).not_to include('HTTP_ACCEPT')
end
end
context 'when consumes is present' do
let(:consumes) { ['bar/baz'] }
it 'sets the Content-Type header' do
expect(subject.headers).to include('CONTENT_TYPE' => 'bar/baz')
end
end
context 'when consumes is blank' do
it 'does not set the Content-Type header' do
expect(subject.headers.keys).not_to include('CONTENT_TYPE')
end
end
xit "includes parameters with in: :header" do
end
end
describe '#path' do
subject { described_class.new(metadata, instance) }
let(:instance) { double('instance') }
context 'when document includes basePath' do
let(:metadata) { { swagger_path_item: { path: '/path' } } }
it 'is used as path prefix' do
allow(subject).to receive(:document) { { basePath: '/base' } }
expect(subject.path).to eq('/base/path')
end
end
context 'when is templated' do
let(:metadata) { { swagger_path_item: { path: '/sites/{site_id}/accounts/{accountId}' } } }
it 'variables are replaced with calls to instance' do
allow(subject).to receive(:document) { {} }
expect(instance).to receive(:site_id) { 123 }
expect(instance).to receive(:accountId) { 456 }
expect(subject.path).to eq('/sites/123/accounts/456')
end
end
end
describe '#query' do
subject { described_class.new(double('metadata'), instance) }
let(:instance) { double('instance') }
context 'with no query params' do
it 'returns nil' do
expect(subject).to receive(:parameters) { {
'path&petId' => { this_is: :ignored },
'body&site' => { same: :here }
} }
expect(subject.query).to be_nil
end
end
context 'with query params' do
it 'returns them in a string' do
expect(subject).to receive(:parameters) { {
'path&petId' => { this_is: :ignored },
'query&site' => { same: :here }
} }
expect(instance).to receive(:site) { :pickles }
expect(subject.query).to eq('?site=pickles')
end
end
end
describe '#body' do
subject { described_class.new(double('metadata'), instance) }
let(:instance) { double('instance') }
context 'with no body param' do
it 'returns nil' do
expect(subject).to receive(:parameters) { {
'path&petId' => { this_is: :ignored },
'query&site' => { same: :here }
} }
expect(subject.body).to be_nil
end
end
context 'with a body param' do
it 'returns a serialized JSON string' do
expect(subject).to receive(:parameters) { {
'path&petId' => { this_is: :ignored },
'body&site' => { same: :here }
} }
expect(instance).to receive(:site) { { name: :pickles, team: :cowboys } }
expect(subject.body).to eq '{"name":"pickles","team":"cowboys"}'
end
end
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment