conduit 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/conduit +5 -0
- data/lib/conduit.rb +2 -0
- data/lib/conduit/blueprints/actions/base.tt +21 -0
- data/lib/conduit/blueprints/base.tt +9 -0
- data/lib/conduit/blueprints/bin/generate_action.tt +5 -0
- data/lib/conduit/blueprints/decorators.tt +2 -0
- data/lib/conduit/blueprints/decorators/base.tt +9 -0
- data/lib/conduit/blueprints/driver.tt +14 -0
- data/lib/conduit/blueprints/generators/action.tt +61 -0
- data/lib/conduit/blueprints/generators/blueprints/action.tt +13 -0
- data/lib/conduit/blueprints/generators/blueprints/decorator.tt +6 -0
- data/lib/conduit/blueprints/generators/blueprints/mock.tt +7 -0
- data/lib/conduit/blueprints/generators/blueprints/parser.tt +8 -0
- data/lib/conduit/blueprints/generators/blueprints/rspec_action.tt +4 -0
- data/lib/conduit/blueprints/parsers/base.tt +121 -0
- data/lib/conduit/blueprints/request_mocker.tt +2 -0
- data/lib/conduit/blueprints/request_mocker/base.tt +15 -0
- data/lib/conduit/blueprints/spec_helper.tt +8 -0
- data/lib/conduit/blueprints/support/helpers.tt +12 -0
- data/lib/conduit/blueprints/views/layout.tt +1 -0
- data/lib/conduit/cli.rb +146 -0
- data/lib/conduit/core/action.rb +90 -26
- data/lib/conduit/core/decorator.rb +22 -0
- data/lib/conduit/core/request_mocker.rb +89 -0
- data/lib/conduit/version.rb +1 -1
- data/spec/classes/core/action_spec.rb +67 -0
- data/spec/classes/core/request_mocker_spec.rb +45 -0
- data/spec/support/my_driver/request_mockers/fixtures/foo/failure.xml.erb +4 -0
- data/spec/support/my_driver/request_mockers/fixtures/foo/success.xml.erb +4 -0
- data/spec/support/my_driver/request_mockers/foo.rb +13 -0
- data/spec/support/xml/failure_mock.xml +4 -0
- data/spec/support/xml/success_mock.xml +4 -0
- data/spec/support/xml/xml_modified_request.xml +7 -0
- data/spec/support/xml/xml_request.xml +2 -2
- metadata +69 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ff4f0e5f7f702375659f970ab9ed86f2ce673ca7
|
4
|
+
data.tar.gz: 88ebd51ff6273ba6dad93c01d60f0b78851ddfec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6106383b60779d51424c96d3319c439a76498c6bc33918f4cdf5363deb6da42666144054210b93bcc0bdb44f4f24e75aa80b9cfd2138f2472d65c977b596a129
|
7
|
+
data.tar.gz: 2b6eeb302b22fc3d3e91eb46555d3ad5eeb3c6e698288f2c1624733b02cf39de54a22456a833f5fef969d9ac1e815fcd2d0d5b1d65ce696e9fcafd5669aedc37
|
data/bin/conduit
ADDED
data/lib/conduit.rb
CHANGED
@@ -23,6 +23,8 @@ module Conduit
|
|
23
23
|
#
|
24
24
|
autoload :Connection, 'conduit/core/connection'
|
25
25
|
autoload :Render, 'conduit/core/render'
|
26
|
+
autoload :Decorator, 'conduit/core/decorator'
|
27
|
+
autoload :RequestMocker, 'conduit/core/request_mocker'
|
26
28
|
autoload :Action, 'conduit/core/action'
|
27
29
|
autoload :Parser, 'conduit/core/parser'
|
28
30
|
autoload :Driver, 'conduit/core/driver'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# Base action class that all driver actions should extend.
|
3
|
+
# Common actions functionality goes here.
|
4
|
+
#
|
5
|
+
|
6
|
+
module Conduit::Driver::<%= classified_name %>
|
7
|
+
class Base < Conduit::Core::Action
|
8
|
+
class << self
|
9
|
+
def inherited(base)
|
10
|
+
base.send :required_attributes, *Conduit::Driver::<%= classified_name %>.credentials
|
11
|
+
base.send :required_attributes, *Conduit::Driver::<%= classified_name %>.required_attributes
|
12
|
+
base.send :optional_attributes, *Conduit::Driver::<%= classified_name %>.optional_attributes
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def remote_url
|
17
|
+
# TODO: Define API endpoint here:
|
18
|
+
# http://example.com/api
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require "conduit"
|
2
|
+
require "conduit/<%= underscored_name %>/driver"
|
3
|
+
require "conduit/<%= underscored_name %>/version"
|
4
|
+
require "conduit/<%= underscored_name %>/request_mocker"
|
5
|
+
require "conduit/<%= underscored_name %>/decorators"
|
6
|
+
|
7
|
+
Conduit.configure do |config|
|
8
|
+
config.driver_paths << File.join(File.dirname(__FILE__), "<%= underscored_name %>")
|
9
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "conduit/<%= underscored_name %>/actions/base"
|
2
|
+
|
3
|
+
module Conduit
|
4
|
+
module Driver
|
5
|
+
# Conduit Driver for <%= classified_name %>
|
6
|
+
module <%= classified_name %>
|
7
|
+
extend Conduit::Core::Driver
|
8
|
+
|
9
|
+
# TODO: replace `required_credentials` and `optional_attributes` as needed
|
10
|
+
required_credentials :username, :password
|
11
|
+
optional_attributes :mock_status
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "thor/group"
|
2
|
+
require "active_support"
|
3
|
+
require "byebug"
|
4
|
+
|
5
|
+
module Conduit
|
6
|
+
module <%= classified_name %>
|
7
|
+
module Generators
|
8
|
+
class Action < Thor::Group
|
9
|
+
include Thor::Actions
|
10
|
+
|
11
|
+
attr_reader :classified_name
|
12
|
+
attr_reader :underscored_name
|
13
|
+
|
14
|
+
argument :name, type: :string
|
15
|
+
|
16
|
+
def initialize(*args)
|
17
|
+
super
|
18
|
+
@classified_name = ActiveSupport::Inflector.classify name
|
19
|
+
@underscored_name = ActiveSupport::Inflector.underscore name
|
20
|
+
@driver_path = "#{File.dirname(__FILE__)}/.."
|
21
|
+
@spec_path = "#{@driver_path}/../../../spec"
|
22
|
+
end
|
23
|
+
|
24
|
+
def copy_action
|
25
|
+
template("action.tt", "#{@driver_path}/actions/#{@underscored_name}.rb")
|
26
|
+
end
|
27
|
+
|
28
|
+
def copy_decorator
|
29
|
+
template("decorator.tt", "#{@driver_path}/decorators/#{@underscored_name}.rb")
|
30
|
+
end
|
31
|
+
|
32
|
+
def copy_parser
|
33
|
+
template("parser.tt", "#{@driver_path}/parsers/#{@underscored_name}.rb")
|
34
|
+
end
|
35
|
+
|
36
|
+
def copy_mocks
|
37
|
+
template("mock.tt", "#{@driver_path}/request_mocker/#{@underscored_name}.rb")
|
38
|
+
create_file "#{@driver_path}/request_mocker/fixtures/responses/#{@underscored_name}/success.xml.erb"
|
39
|
+
create_file "#{@driver_path}/request_mocker/fixtures/responses/#{@underscored_name}/failure.xml.erb"
|
40
|
+
end
|
41
|
+
|
42
|
+
def copy_view
|
43
|
+
create_file "#{@driver_path}/views/#{@underscored_name}.erb"
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_action_to_driver
|
47
|
+
insert_into_file("#{@driver_path}/driver.rb", "\n action :#{@underscored_name}\n", after: "optional_attributes :mock_status\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_spec_files
|
51
|
+
template("rspec_action.tt", "#{@spec_path}/conduit/<%= underscored_name %>/actions/#{@underscored_name}_spec.rb")
|
52
|
+
create_file "#{@spec_path}/fixtures/requests/#{@underscored_name}/request.xml"
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.source_root
|
56
|
+
"#{File.dirname(__FILE__)}/blueprints"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
|
3
|
+
module Conduit::Driver::<%= classified_name %>
|
4
|
+
class <%%= classified_name %> < Conduit::Driver::<%= classified_name %>::Base
|
5
|
+
operation :<%%= underscored_name %>
|
6
|
+
|
7
|
+
# Required attributes go here
|
8
|
+
# required_attributes :required_attribute_name
|
9
|
+
|
10
|
+
# Optional attributes go here
|
11
|
+
# optional_attributes :optional_attribute_name
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
#
|
2
|
+
# Base parser class that all request parsers should extend.
|
3
|
+
# Common parser functionality goes here.
|
4
|
+
#
|
5
|
+
# It uses nokogiri to extract attributes from the xml response.
|
6
|
+
#
|
7
|
+
|
8
|
+
require "nokogiri"
|
9
|
+
require "active_support"
|
10
|
+
|
11
|
+
module Conduit::Driver::<%= classified_name %>
|
12
|
+
module Parser
|
13
|
+
# Provides the base functionality for parsing
|
14
|
+
# XML responses from the api
|
15
|
+
class Base < Conduit::Core::Parser
|
16
|
+
attr_accessor :xml
|
17
|
+
|
18
|
+
# Extracts the camelized attribute from the xml response
|
19
|
+
def self.attribute(attr_name, &block)
|
20
|
+
block ||= lambda do
|
21
|
+
content_for "//#{ActiveSupport::Inflector.camelize(attr_name, false)}"
|
22
|
+
end
|
23
|
+
super(attr_name, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(xml)
|
27
|
+
@xml = xml
|
28
|
+
end
|
29
|
+
|
30
|
+
def root
|
31
|
+
@root ||= Nokogiri::XML(xml).tap do |doc|
|
32
|
+
doc.remove_namespaces!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Get the attribute value at a certain path
|
37
|
+
def content_for(path, doc = root)
|
38
|
+
node = doc.at_xpath(path)
|
39
|
+
node.content if node
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the following:
|
43
|
+
#
|
44
|
+
# - `acknowledged` when async
|
45
|
+
# - `success` when successful
|
46
|
+
# - `failure` when unsuccessful
|
47
|
+
def response_status
|
48
|
+
if response_errors.any?
|
49
|
+
"failure"
|
50
|
+
elsif in_progress?
|
51
|
+
"acknowledged"
|
52
|
+
else
|
53
|
+
"success"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns normalized request errors if present.
|
58
|
+
def response_errors
|
59
|
+
if response_content?
|
60
|
+
if !content_for("//Fault").nil?
|
61
|
+
[normalized_fault]
|
62
|
+
else
|
63
|
+
# Returns empty array if no errors
|
64
|
+
normalized_errors
|
65
|
+
end
|
66
|
+
else
|
67
|
+
[Conduit::Error.new(message: "Unexpected response from server.")]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Indicates if content present.
|
72
|
+
def response_content?
|
73
|
+
# TODO: replace this if response not present in `Body`
|
74
|
+
!content_for("//Body").nil?
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# Indicates if a request is in progress. Used for async requests.
|
80
|
+
# Should be overwritten in the action's parser in case true.
|
81
|
+
def in_progress?
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns a new `Conduit::Error` instance containing the fault text
|
86
|
+
# and the fault code.
|
87
|
+
def normalized_fault
|
88
|
+
# TODO: replace this if error message and code not present in the
|
89
|
+
# below paths
|
90
|
+
Conduit::Error.new({
|
91
|
+
message: content_for("//Fault//Text"),
|
92
|
+
code: content_for("//Fault//Code//Value")
|
93
|
+
})
|
94
|
+
end
|
95
|
+
|
96
|
+
# Extracts the error from either `errorMessage` or `errorText`
|
97
|
+
def error_response
|
98
|
+
# TODO: replace this if error response not present in any of the following
|
99
|
+
# paths.
|
100
|
+
content_for("//errorMessage") || content_for("//errorText")
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns a new `Conduit::Error` instance containing the error message and
|
104
|
+
# its code.
|
105
|
+
def normalized_errors
|
106
|
+
# TODO: replace this if error not present in `error_message` and code
|
107
|
+
# not present in `errorNumber`
|
108
|
+
if error_response && !error_response.empty?
|
109
|
+
[
|
110
|
+
Conduit::Error.new({
|
111
|
+
message: error_response,
|
112
|
+
code: content_for("errorNumber")
|
113
|
+
})
|
114
|
+
]
|
115
|
+
else
|
116
|
+
[]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#
|
2
|
+
# Base request mocker class that all mockers should extend.
|
3
|
+
# Common mock functionality goes here.
|
4
|
+
#
|
5
|
+
|
6
|
+
module Conduit::<%= classified_name %>::RequestMocker
|
7
|
+
class Base < Conduit::Core::RequestMocker
|
8
|
+
private
|
9
|
+
|
10
|
+
# Default path for fixtures.
|
11
|
+
def fixtures_path
|
12
|
+
"#{__dir__}/fixtures/responses"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<%%= yield %>
|
data/lib/conduit/cli.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "active_support"
|
3
|
+
require "bundler"
|
4
|
+
|
5
|
+
module Conduit
|
6
|
+
class CLI < Thor
|
7
|
+
include Thor::Actions
|
8
|
+
|
9
|
+
attr_accessor :driver_name, :classified_name, :underscored_name, :base_path
|
10
|
+
|
11
|
+
desc "generate_driver DRIVER_NAME", "Generates a new xml `conduit-driver` gem"
|
12
|
+
long_desc <<-LONGDESC
|
13
|
+
`generate_driver DRIVER_NAME` will generate a new gem for the xml conduit
|
14
|
+
driver.
|
15
|
+
|
16
|
+
It will add the base `action`, `decorator`, `parser` and `request_mocker`
|
17
|
+
files along with generators and other basic files.
|
18
|
+
LONGDESC
|
19
|
+
def generate_driver driver_name
|
20
|
+
@driver_name = driver_name
|
21
|
+
@dasherized_name = ActiveSupport::Inflector.dasherize driver_name
|
22
|
+
@classified_name = ActiveSupport::Inflector.classify driver_name
|
23
|
+
@underscored_name = ActiveSupport::Inflector.underscore driver_name
|
24
|
+
@pwd = Dir.pwd
|
25
|
+
@base_path = "#{@pwd}/conduit-#{@dasherized_name}"
|
26
|
+
|
27
|
+
run("bundle gem conduit-#{@underscored_name} --no-coc --mit --test=rspec")
|
28
|
+
fix_names
|
29
|
+
copy_files
|
30
|
+
modify_files
|
31
|
+
run("chmod +x #{@base_path}/bin/generate_action")
|
32
|
+
bundle_install
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# In case the name of the driver is composed of multiple words we need to
|
38
|
+
# generate the gem with an underscored named (in order to avoid nested modules
|
39
|
+
# for each part of the name) and then replace the gem and base file name with
|
40
|
+
# the dasherized one.
|
41
|
+
# For example, if the driver is called `best-wireless` we create `conduit-best_driver`
|
42
|
+
# gem and we change the name to `conduit-best-driver`
|
43
|
+
def fix_names
|
44
|
+
if @underscored_name.split("_").length > 1
|
45
|
+
current_path = "#{@pwd}/conduit-#{@underscored_name}"
|
46
|
+
|
47
|
+
# Change gem name in gemspec file
|
48
|
+
gemspec_file = "#{current_path}/conduit-#{@underscored_name}.gemspec"
|
49
|
+
gsub_file(gemspec_file, /spec\.name(.*)/, "spec.name = \"conduit-#{@dasherized_name}\"")
|
50
|
+
|
51
|
+
# Rename gemspec file
|
52
|
+
run("mv #{gemspec_file} #{current_path}/conduit-#{@dasherized_name}.gemspec")
|
53
|
+
|
54
|
+
# Rename gem dir
|
55
|
+
run("mv #{current_path} #{@base_path}")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def bundle_install
|
60
|
+
# Taken from https://github.com/rails/rails/blob/92703a9ea5d8b96f30e0b706b801c9185ef14f0e/railties/lib/rails/generators/app_base.rb#L363
|
61
|
+
_bundle_command = Gem.bin_path("bundler", "bundle")
|
62
|
+
|
63
|
+
Bundler.with_clean_env do
|
64
|
+
inside(@base_path) do
|
65
|
+
full_command = %Q["#{Gem.ruby}" "#{_bundle_command}" install]
|
66
|
+
run(full_command)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Indicates where the template files are located
|
72
|
+
def self.source_root
|
73
|
+
"#{__dir__}/blueprints"
|
74
|
+
end
|
75
|
+
|
76
|
+
# List of the template files and their destination
|
77
|
+
def files_to_copy
|
78
|
+
lib_path = "#{@base_path}/lib/conduit/#{@underscored_name}"
|
79
|
+
|
80
|
+
{
|
81
|
+
"base.tt" => "#{@base_path}/lib/conduit/#{@underscored_name}.rb",
|
82
|
+
"actions/base.tt" => "#{lib_path}/actions/base.rb",
|
83
|
+
"decorators/base.tt" => "#{lib_path}/decorators/base.rb",
|
84
|
+
"parsers/base.tt" => "#{lib_path}/parsers/base.rb",
|
85
|
+
"request_mocker/base.tt" => "#{lib_path}/request_mocker/base.rb",
|
86
|
+
"views/layout.tt" => "#{lib_path}/views/layout.erb",
|
87
|
+
"decorators.tt" => "#{lib_path}/decorators.rb",
|
88
|
+
"driver.tt" => "#{lib_path}/driver.rb",
|
89
|
+
"request_mocker.tt" => "#{lib_path}/request_mocker.rb",
|
90
|
+
"generators/action.tt" => "#{lib_path}/generators/action.rb",
|
91
|
+
"generators/blueprints/action.tt" => "#{lib_path}/generators/blueprints/action.tt",
|
92
|
+
"generators/blueprints/decorator.tt" => "#{lib_path}/generators/blueprints/decorator.tt",
|
93
|
+
"generators/blueprints/mock.tt" => "#{lib_path}/generators/blueprints/mock.tt",
|
94
|
+
"generators/blueprints/parser.tt" => "#{lib_path}/generators/blueprints/parser.tt",
|
95
|
+
"generators/blueprints/rspec_action.tt" => "#{lib_path}/generators/blueprints/rspec_action.tt",
|
96
|
+
"bin/generate_action.tt" => "#{@base_path}/bin/generate_action",
|
97
|
+
"support/helpers.tt" => "#{@base_path}/spec/support/helpers.rb",
|
98
|
+
"spec_helper.tt" => "#{@base_path}/spec/spec_helper.rb"
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
# Copy template files
|
103
|
+
def copy_files
|
104
|
+
files_to_copy.each do |origin, destination|
|
105
|
+
template(origin, destination, force: true)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Adds missing lines to the files
|
110
|
+
def modify_files
|
111
|
+
gemspec_file = "#{@base_path}/conduit-#{@dasherized_name}.gemspec"
|
112
|
+
|
113
|
+
# add gemspec dependencies
|
114
|
+
str = " # Dependencies\n"\
|
115
|
+
" #\n"\
|
116
|
+
" spec.add_dependency \"conduit\", \"~> 1.0.3\"\n"\
|
117
|
+
" # xml parser\n"\
|
118
|
+
" spec.add_dependency \"nokogiri\"\n"\
|
119
|
+
" # to get string inflectors\n"\
|
120
|
+
" spec.add_dependency \"activesupport\"\n\n"\
|
121
|
+
" # Development Dependencies\n"\
|
122
|
+
" #\n"\
|
123
|
+
" spec.add_development_dependency \"shoulda-matchers\"\n"\
|
124
|
+
" # to compare xml files in tests\n"\
|
125
|
+
" spec.add_development_dependency \"equivalent-xml\"\n"\
|
126
|
+
" spec.add_development_dependency \"rspec-its\"\n"\
|
127
|
+
" # for building CLI\n"\
|
128
|
+
" spec.add_development_dependency \"thor\"\n"\
|
129
|
+
" # for debugging\n"\
|
130
|
+
" spec.add_development_dependency \"byebug\"\n"
|
131
|
+
|
132
|
+
insert_into_file gemspec_file, str, after: "spec.require_paths = [\"lib\"]\n\n"
|
133
|
+
|
134
|
+
# remove description
|
135
|
+
gsub_file(gemspec_file, /spec\.description(.*)\n/, "")
|
136
|
+
|
137
|
+
# update summary
|
138
|
+
new_summary = "spec.summary = \"#{ActiveSupport::Inflector.humanize @underscored_name} Driver for Conduit\""
|
139
|
+
gsub_file(gemspec_file, /spec\.summary(.*)/, new_summary)
|
140
|
+
|
141
|
+
# update homepage
|
142
|
+
new_homepage = "spec.homepage = \"http://www.github.com/conduit/conduit-#{@dasherized_name}\""
|
143
|
+
gsub_file(gemspec_file, /spec\.homepage(.*)/, new_homepage)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/conduit/core/action.rb
CHANGED
@@ -27,6 +27,7 @@ module Conduit
|
|
27
27
|
base.send :include, Conduit::Core::Connection
|
28
28
|
base.send :include, Conduit::Core::Render
|
29
29
|
base.send :include, InstanceMethods
|
30
|
+
|
30
31
|
base.extend ClassMethods
|
31
32
|
|
32
33
|
# TODO: Move this to the driver scope
|
@@ -87,22 +88,30 @@ module Conduit
|
|
87
88
|
def_delegator :'self.class', :requirements
|
88
89
|
def_delegator :'self.class', :attributes
|
89
90
|
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
|
94
|
-
#
|
95
|
-
# self.class.attributes
|
96
|
-
# end
|
97
|
-
|
98
|
-
# Object used for passing data to the view
|
99
|
-
# Only keys listed in attributes will be
|
100
|
-
# used.
|
91
|
+
# The view's context will be the action's decorator.
|
92
|
+
# The decorator name depends on the current action's name.
|
93
|
+
#
|
94
|
+
# For example `Conduit::MyDriver::Decorators::ActivateDecorator`
|
95
|
+
# will be the view's context for the `activate` action.
|
101
96
|
#
|
102
97
|
def view_context
|
103
|
-
|
104
|
-
|
105
|
-
|
98
|
+
view_decorator.new(
|
99
|
+
OpenStruct.new(attributes_with_values)
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns a hash of all the defined attributes and
|
104
|
+
# their values.
|
105
|
+
#
|
106
|
+
# If an attribute's value is not passed as an option it
|
107
|
+
# will default to `nil`.
|
108
|
+
#
|
109
|
+
def attributes_with_values
|
110
|
+
attributes.inject({}) do |hash, attribute|
|
111
|
+
hash.tap do |h|
|
112
|
+
h[attribute] = @options[attribute]
|
113
|
+
end
|
114
|
+
end
|
106
115
|
end
|
107
116
|
|
108
117
|
# Location where the view files can be found
|
@@ -121,24 +130,41 @@ module Conduit
|
|
121
130
|
render(tpl)
|
122
131
|
end
|
123
132
|
|
124
|
-
#
|
125
|
-
# use this to trigger the request.
|
133
|
+
# Method called to make the actual request.
|
126
134
|
#
|
127
135
|
# Override to customize.
|
128
136
|
#
|
129
|
-
def
|
137
|
+
def perform_request
|
130
138
|
response = request(body: view, method: :post)
|
131
|
-
|
139
|
+
parser_instance = parser.new(response.body)
|
140
|
+
|
141
|
+
Conduit::ApiResponse.new(raw_response: response, parser: parser_instance)
|
142
|
+
end
|
132
143
|
|
133
|
-
|
134
|
-
|
144
|
+
# Entry method. Calls either the mocker or the `perform_request`
|
145
|
+
# method.
|
146
|
+
#
|
147
|
+
def perform
|
148
|
+
# When testing we can mock the request. The mocker used
|
149
|
+
# will depend on the action's name. For example:
|
150
|
+
# `Conduit::MyDriver::RequestMocker::Activate` will be responsible for
|
151
|
+
# mocking the `activate` action.
|
152
|
+
#
|
153
|
+
# * To mock success pass `mock_status: 'success'` as an option.
|
154
|
+
# * To mock failure pass `mock_status: 'failure'` as an option.
|
155
|
+
if mock_mode?
|
156
|
+
mocker = request_mocker.new(self, @options)
|
157
|
+
mocker.with_mocking { perform_request }
|
158
|
+
else
|
159
|
+
perform_request
|
160
|
+
end
|
135
161
|
end
|
136
162
|
|
137
163
|
private
|
138
164
|
|
139
165
|
# Ensures that all required attributes are present
|
140
166
|
# If not all attributes are present, will raise
|
141
|
-
# an ArgumentError listing missing attributes
|
167
|
+
# an ArgumentError listing missing attributes.
|
142
168
|
#
|
143
169
|
def validate!(options)
|
144
170
|
missing_keys = (requirements.to_a - options.keys)
|
@@ -148,15 +174,53 @@ module Conduit
|
|
148
174
|
end
|
149
175
|
end
|
150
176
|
|
151
|
-
# Returns the
|
152
|
-
#
|
153
|
-
|
177
|
+
# Returns the capitalized action name.
|
178
|
+
#
|
179
|
+
def action_name
|
180
|
+
ActiveSupport::Inflector.demodulize(self.class)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Returns the name of the driver that this action belongs to.
|
184
|
+
#
|
185
|
+
def driver_name
|
186
|
+
self.class.to_s.deconstantize.demodulize.underscore
|
187
|
+
end
|
188
|
+
|
189
|
+
# Returns the parser for this action subclasses responsible for providing
|
190
|
+
# the response_body and response_status.
|
154
191
|
#
|
155
|
-
def
|
192
|
+
def parser
|
156
193
|
self.class.const_get(:Parser)
|
157
194
|
end
|
158
|
-
end
|
159
195
|
|
196
|
+
# Returns the mocker class to use based on the action's name.
|
197
|
+
#
|
198
|
+
def request_mocker
|
199
|
+
"Conduit::#{driver_name.classify}::RequestMocker::#{action_name}".constantize
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns the decorator class to use based on the action's name. If no
|
203
|
+
# decorator class defined, it will return the core decorator class.
|
204
|
+
#
|
205
|
+
def view_decorator
|
206
|
+
# In ruby 2.0.0 we can't do `const_defined? MyDriver::Decorators::FooDecorator`
|
207
|
+
# so we have to check each module one by one.
|
208
|
+
if Conduit.const_defined?(driver_name.classify) &&
|
209
|
+
Conduit.const_get(driver_name.classify).const_defined?("Decorators") &&
|
210
|
+
Conduit.const_get("#{driver_name.classify}::Decorators").const_defined?("#{action_name}Decorator")
|
211
|
+
|
212
|
+
Conduit.const_get("#{driver_name.classify}::Decorators::#{action_name}Decorator")
|
213
|
+
else
|
214
|
+
Conduit::Core::Decorator
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Indicates whether the request should be mocked or not.
|
219
|
+
#
|
220
|
+
def mock_mode?
|
221
|
+
@options.has_key?(:mock_status) && (!@options[:mock_status].nil? && !@options[:mock_status].empty?)
|
222
|
+
end
|
223
|
+
end
|
160
224
|
end
|
161
225
|
end
|
162
226
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#
|
2
|
+
# Decorators are used to act as the view's
|
3
|
+
# context. This is where most of the logic occurs,
|
4
|
+
# mainly where options are organized in a hash
|
5
|
+
# for the view to serialize.
|
6
|
+
#
|
7
|
+
# Extend this class for every action. For example,
|
8
|
+
# for the `activate` action, an `ActivateDecorator`
|
9
|
+
# is defined which exposes a `line_activation_attributes` method
|
10
|
+
# for the view.
|
11
|
+
#
|
12
|
+
|
13
|
+
require 'delegate'
|
14
|
+
require 'forwardable'
|
15
|
+
|
16
|
+
module Conduit
|
17
|
+
module Core
|
18
|
+
class Decorator < SimpleDelegator
|
19
|
+
extend Forwardable
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
#
|
2
|
+
# Mocks action responses for testing. We can choose
|
3
|
+
# the type of response (success, failure, error).
|
4
|
+
#
|
5
|
+
# Response fixtures are stored in a directory specified inside
|
6
|
+
# `fixtures_path`. Each action has its own folder and each response file
|
7
|
+
# is named according to the status we're mocking.
|
8
|
+
#
|
9
|
+
# In order to mock a request, pass the option `mock_status` to
|
10
|
+
# the action with its value being the returned status.
|
11
|
+
#
|
12
|
+
|
13
|
+
require 'erb'
|
14
|
+
require 'excon'
|
15
|
+
require 'tilt'
|
16
|
+
|
17
|
+
module Conduit
|
18
|
+
module Core
|
19
|
+
class RequestMocker
|
20
|
+
def initialize(base, options = nil)
|
21
|
+
@base = base
|
22
|
+
@options = options
|
23
|
+
@mock_status = options[:mock_status].to_sym || :success
|
24
|
+
@http_status = options[:http_status] || 200
|
25
|
+
end
|
26
|
+
|
27
|
+
# Starts mocking, override if using something different than
|
28
|
+
# excon to make the requests.
|
29
|
+
#
|
30
|
+
def mock
|
31
|
+
Excon.defaults[:mock] = true
|
32
|
+
Excon.stub({}, { body: response, status: @http_status })
|
33
|
+
end
|
34
|
+
|
35
|
+
# Ends mocking, override if using something different than
|
36
|
+
# excon to make the requests.
|
37
|
+
#
|
38
|
+
def unmock
|
39
|
+
Excon.defaults[:mock] = false
|
40
|
+
Excon.stubs.clear
|
41
|
+
end
|
42
|
+
|
43
|
+
# Wrapps the block inside a mocking state.
|
44
|
+
#
|
45
|
+
def with_mocking
|
46
|
+
mock and yield.tap { unmock }
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def render_response
|
52
|
+
Tilt::ERBTemplate.new(fixture).render(@base.view_context)
|
53
|
+
end
|
54
|
+
|
55
|
+
def action_name
|
56
|
+
ActiveSupport::Inflector.demodulize(self.class.name).underscore
|
57
|
+
end
|
58
|
+
|
59
|
+
# Directory where fixtures are stored.
|
60
|
+
#
|
61
|
+
def fixtures_path
|
62
|
+
raise NoMethodError, 'Please define fixtures_path in your request mocker.'
|
63
|
+
end
|
64
|
+
|
65
|
+
def fixture
|
66
|
+
"#{fixtures_path}/#{action_name}/#{@mock_status}.xml.erb"
|
67
|
+
end
|
68
|
+
|
69
|
+
def response
|
70
|
+
if response_statuses.include?(@mock_status)
|
71
|
+
return error_response if @mock_status == :error
|
72
|
+
render_response
|
73
|
+
else
|
74
|
+
raise(ArgumentError, "Mock status must be one of the following: #{response_statuses.join(', ')}")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Allowed statuses.
|
79
|
+
def response_statuses
|
80
|
+
%i(success failure error)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Mocked error response.
|
84
|
+
def error_response
|
85
|
+
'{"status": 500, "error": "Internal Service Error"}'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/conduit/version.rb
CHANGED
@@ -3,6 +3,7 @@ require 'spec_helper'
|
|
3
3
|
shared_examples_for Conduit::Core::Action do
|
4
4
|
|
5
5
|
let(:request) { read_support_file('xml/xml_request.xml') }
|
6
|
+
let(:modified_request) { read_support_file('xml/xml_modified_request.xml') }
|
6
7
|
let(:response) { read_support_file('xml/xml_response.xml') }
|
7
8
|
|
8
9
|
subject do
|
@@ -28,6 +29,19 @@ shared_examples_for Conduit::Core::Action do
|
|
28
29
|
end
|
29
30
|
|
30
31
|
context 'with an instance' do
|
32
|
+
describe '#attributes_with_values' do
|
33
|
+
context 'with optional attribute not set' do
|
34
|
+
let (:attrs) { request_attributes.merge(buz: nil) }
|
35
|
+
its(:attributes_with_values) { should eq(attrs) }
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with optional attribute set' do
|
39
|
+
let (:attrs) { request_attributes.merge(buz: "value for buz") }
|
40
|
+
subject { described_class.new(attrs) }
|
41
|
+
its(:attributes_with_values) { should eq(attrs) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
31
45
|
describe '#view' do
|
32
46
|
before do
|
33
47
|
Excon.stub({}, body: request, status: 200)
|
@@ -40,6 +54,59 @@ shared_examples_for Conduit::Core::Action do
|
|
40
54
|
end
|
41
55
|
end
|
42
56
|
|
57
|
+
describe '#view_context' do
|
58
|
+
context 'without decorator class' do
|
59
|
+
it 'returns an instance of core decorator' do
|
60
|
+
subject.view_context.should be_a_kind_of(Conduit::Core::Decorator)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'with decorator class' do
|
65
|
+
let(:driver) { subject.class.name.deconstantize.demodulize }
|
66
|
+
let(:action) { subject.class.name.demodulize }
|
67
|
+
|
68
|
+
# Define decorator module programmatically:
|
69
|
+
let(:decorator) do
|
70
|
+
# Create DriverName::Decorators module
|
71
|
+
module TheDriver
|
72
|
+
module Decorators
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Add action decorator to DriverName::Decorators
|
77
|
+
class TheDecorator < Conduit::Core::Decorator
|
78
|
+
def foo
|
79
|
+
"new #{super}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def baz
|
83
|
+
"modified baz"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Set the above defined modules and class in Conduit
|
88
|
+
stub_const("TheDriver::Decorators::#{action}Decorator", TheDecorator)
|
89
|
+
|
90
|
+
TheDriver
|
91
|
+
end
|
92
|
+
|
93
|
+
before do
|
94
|
+
stub_const("Conduit::#{driver}", decorator)
|
95
|
+
Excon.stub({}, body: modified_request, status: 200)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'returns an instance of core decorator' do
|
99
|
+
subject.view_context.should be_a_kind_of(Conduit.const_get(driver)::Decorators::const_get("#{action}Decorator"))
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'returns a new rendered view for an action' do
|
103
|
+
a = subject.view.gsub(/\s+/, '')
|
104
|
+
b = modified_request.gsub(/\s+/, '')
|
105
|
+
a.should == b
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
43
110
|
describe '#perform' do
|
44
111
|
before { Excon.stub({}, body: response, status: 200) }
|
45
112
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'excon'
|
3
|
+
|
4
|
+
shared_examples_for Conduit::Core::RequestMocker do
|
5
|
+
|
6
|
+
let(:success) { read_support_file('xml/success_mock.xml') }
|
7
|
+
let(:failure) { read_support_file('xml/failure_mock.xml') }
|
8
|
+
let(:fake_action) do
|
9
|
+
class FakeAction
|
10
|
+
def view_context
|
11
|
+
OpenStruct.new({ value: "Yay!" })
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
FakeAction.new
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'success mocking' do
|
19
|
+
subject { described_class.new(fake_action, { mock_status: 'success' }) }
|
20
|
+
let(:response) { subject.with_mocking { Excon.new('http://example.com').request }.body }
|
21
|
+
it 'return success response' do
|
22
|
+
response.should eq(success)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'failure mocking' do
|
27
|
+
subject { described_class.new(fake_action, { mock_status: 'failure' }) }
|
28
|
+
let(:response) { subject.with_mocking { Excon.new('http://example.com').request }.body }
|
29
|
+
it 'return failure response' do
|
30
|
+
response.should eq(failure)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with disallowed response status' do
|
35
|
+
subject { described_class.new(fake_action, { mock_status: 'fake_status' }) }
|
36
|
+
let(:response) { subject.with_mocking { Excon.new('http://example.com').request }.body }
|
37
|
+
it 'raises and error' do
|
38
|
+
lambda { response }.should raise_error(ArgumentError)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe Conduit::MyDriver::RequestMocker::Foo do
|
44
|
+
it_behaves_like Conduit::Core::RequestMocker
|
45
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: conduit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Kelley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -66,6 +66,34 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: thor
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: shoulda-matchers
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -125,20 +153,43 @@ dependencies:
|
|
125
153
|
description:
|
126
154
|
email:
|
127
155
|
- mike@codezombie.org
|
128
|
-
executables:
|
156
|
+
executables:
|
157
|
+
- conduit
|
129
158
|
extensions: []
|
130
159
|
extra_rdoc_files: []
|
131
160
|
files:
|
132
161
|
- Rakefile
|
162
|
+
- bin/conduit
|
133
163
|
- lib/conduit.rb
|
134
164
|
- lib/conduit/api_response.rb
|
165
|
+
- lib/conduit/blueprints/actions/base.tt
|
166
|
+
- lib/conduit/blueprints/base.tt
|
167
|
+
- lib/conduit/blueprints/bin/generate_action.tt
|
168
|
+
- lib/conduit/blueprints/decorators.tt
|
169
|
+
- lib/conduit/blueprints/decorators/base.tt
|
170
|
+
- lib/conduit/blueprints/driver.tt
|
171
|
+
- lib/conduit/blueprints/generators/action.tt
|
172
|
+
- lib/conduit/blueprints/generators/blueprints/action.tt
|
173
|
+
- lib/conduit/blueprints/generators/blueprints/decorator.tt
|
174
|
+
- lib/conduit/blueprints/generators/blueprints/mock.tt
|
175
|
+
- lib/conduit/blueprints/generators/blueprints/parser.tt
|
176
|
+
- lib/conduit/blueprints/generators/blueprints/rspec_action.tt
|
177
|
+
- lib/conduit/blueprints/parsers/base.tt
|
178
|
+
- lib/conduit/blueprints/request_mocker.tt
|
179
|
+
- lib/conduit/blueprints/request_mocker/base.tt
|
180
|
+
- lib/conduit/blueprints/spec_helper.tt
|
181
|
+
- lib/conduit/blueprints/support/helpers.tt
|
182
|
+
- lib/conduit/blueprints/views/layout.tt
|
183
|
+
- lib/conduit/cli.rb
|
135
184
|
- lib/conduit/configuration.rb
|
136
185
|
- lib/conduit/connection_error.rb
|
137
186
|
- lib/conduit/core/action.rb
|
138
187
|
- lib/conduit/core/connection.rb
|
188
|
+
- lib/conduit/core/decorator.rb
|
139
189
|
- lib/conduit/core/driver.rb
|
140
190
|
- lib/conduit/core/parser.rb
|
141
191
|
- lib/conduit/core/render.rb
|
192
|
+
- lib/conduit/core/request_mocker.rb
|
142
193
|
- lib/conduit/drivers/keep
|
143
194
|
- lib/conduit/error.rb
|
144
195
|
- lib/conduit/storage.rb
|
@@ -150,14 +201,21 @@ files:
|
|
150
201
|
- spec/classes/core/action_spec.rb
|
151
202
|
- spec/classes/core/driver_spec.rb
|
152
203
|
- spec/classes/core/parser_spec.rb
|
204
|
+
- spec/classes/core/request_mocker_spec.rb
|
153
205
|
- spec/classes/util_spec.rb
|
154
206
|
- spec/spec_helper.rb
|
155
207
|
- spec/support/helper.rb
|
156
208
|
- spec/support/my_driver/actions/foo.rb
|
157
209
|
- spec/support/my_driver/driver.rb
|
158
210
|
- spec/support/my_driver/parsers/foo.rb
|
211
|
+
- spec/support/my_driver/request_mockers/fixtures/foo/failure.xml.erb
|
212
|
+
- spec/support/my_driver/request_mockers/fixtures/foo/success.xml.erb
|
213
|
+
- spec/support/my_driver/request_mockers/foo.rb
|
159
214
|
- spec/support/my_driver/views/foo.erb
|
160
215
|
- spec/support/my_driver/views/layout.erb
|
216
|
+
- spec/support/xml/failure_mock.xml
|
217
|
+
- spec/support/xml/success_mock.xml
|
218
|
+
- spec/support/xml/xml_modified_request.xml
|
161
219
|
- spec/support/xml/xml_request.xml
|
162
220
|
- spec/support/xml/xml_response.xml
|
163
221
|
homepage: https://github.com/conduit/conduit
|
@@ -179,7 +237,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
179
237
|
version: '0'
|
180
238
|
requirements: []
|
181
239
|
rubyforge_project:
|
182
|
-
rubygems_version: 2.
|
240
|
+
rubygems_version: 2.4.5
|
183
241
|
signing_key:
|
184
242
|
specification_version: 4
|
185
243
|
summary: Conduit is an interface for debit platforms.
|
@@ -187,14 +245,20 @@ test_files:
|
|
187
245
|
- spec/classes/core/action_spec.rb
|
188
246
|
- spec/classes/core/driver_spec.rb
|
189
247
|
- spec/classes/core/parser_spec.rb
|
248
|
+
- spec/classes/core/request_mocker_spec.rb
|
190
249
|
- spec/classes/util_spec.rb
|
191
250
|
- spec/spec_helper.rb
|
192
251
|
- spec/support/helper.rb
|
193
252
|
- spec/support/my_driver/actions/foo.rb
|
194
253
|
- spec/support/my_driver/driver.rb
|
195
254
|
- spec/support/my_driver/parsers/foo.rb
|
255
|
+
- spec/support/my_driver/request_mockers/fixtures/foo/failure.xml.erb
|
256
|
+
- spec/support/my_driver/request_mockers/fixtures/foo/success.xml.erb
|
257
|
+
- spec/support/my_driver/request_mockers/foo.rb
|
196
258
|
- spec/support/my_driver/views/foo.erb
|
197
259
|
- spec/support/my_driver/views/layout.erb
|
260
|
+
- spec/support/xml/failure_mock.xml
|
261
|
+
- spec/support/xml/success_mock.xml
|
262
|
+
- spec/support/xml/xml_modified_request.xml
|
198
263
|
- spec/support/xml/xml_request.xml
|
199
264
|
- spec/support/xml/xml_response.xml
|
200
|
-
has_rdoc:
|