savon-SU 2.11.1b

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.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.travis.yml +15 -0
  4. data/.yardopts +6 -0
  5. data/CHANGELOG.md +1098 -0
  6. data/CONTRIBUTING.md +46 -0
  7. data/Gemfile +18 -0
  8. data/LICENSE +20 -0
  9. data/README.md +69 -0
  10. data/Rakefile +14 -0
  11. data/donate.png +0 -0
  12. data/lib/savon.rb +27 -0
  13. data/lib/savon/block_interface.rb +26 -0
  14. data/lib/savon/builder.rb +205 -0
  15. data/lib/savon/client.rb +102 -0
  16. data/lib/savon/core_ext/string.rb +29 -0
  17. data/lib/savon/header.rb +93 -0
  18. data/lib/savon/http_error.rb +27 -0
  19. data/lib/savon/log_message.rb +52 -0
  20. data/lib/savon/message.rb +37 -0
  21. data/lib/savon/mock.rb +5 -0
  22. data/lib/savon/mock/expectation.rb +80 -0
  23. data/lib/savon/mock/spec_helper.rb +62 -0
  24. data/lib/savon/model.rb +84 -0
  25. data/lib/savon/operation.rb +144 -0
  26. data/lib/savon/options.rb +410 -0
  27. data/lib/savon/qualified_message.rb +50 -0
  28. data/lib/savon/request.rb +97 -0
  29. data/lib/savon/request_logger.rb +48 -0
  30. data/lib/savon/response.rb +113 -0
  31. data/lib/savon/soap_fault.rb +50 -0
  32. data/lib/savon/version.rb +3 -0
  33. data/savon-SU.gemspec +46 -0
  34. data/spec/fixtures/gzip/message.gz +0 -0
  35. data/spec/fixtures/response/another_soap_fault.xml +14 -0
  36. data/spec/fixtures/response/authentication.xml +14 -0
  37. data/spec/fixtures/response/f5.xml +39 -0
  38. data/spec/fixtures/response/header.xml +13 -0
  39. data/spec/fixtures/response/list.xml +18 -0
  40. data/spec/fixtures/response/multi_ref.xml +39 -0
  41. data/spec/fixtures/response/soap_fault.xml +8 -0
  42. data/spec/fixtures/response/soap_fault12.xml +18 -0
  43. data/spec/fixtures/response/soap_fault_funky.xml +8 -0
  44. data/spec/fixtures/response/taxcloud.xml +1 -0
  45. data/spec/fixtures/ssl/client_cert.pem +16 -0
  46. data/spec/fixtures/ssl/client_encrypted_key.pem +30 -0
  47. data/spec/fixtures/ssl/client_encrypted_key_cert.pem +24 -0
  48. data/spec/fixtures/ssl/client_key.pem +15 -0
  49. data/spec/fixtures/wsdl/authentication.xml +63 -0
  50. data/spec/fixtures/wsdl/betfair.xml +2981 -0
  51. data/spec/fixtures/wsdl/edialog.xml +15416 -0
  52. data/spec/fixtures/wsdl/interhome.xml +2137 -0
  53. data/spec/fixtures/wsdl/lower_camel.xml +52 -0
  54. data/spec/fixtures/wsdl/multiple_namespaces.xml +92 -0
  55. data/spec/fixtures/wsdl/multiple_types.xml +60 -0
  56. data/spec/fixtures/wsdl/no_message_tag.xml +1267 -0
  57. data/spec/fixtures/wsdl/taxcloud.xml +934 -0
  58. data/spec/fixtures/wsdl/team_software.xml +1 -0
  59. data/spec/fixtures/wsdl/vies.xml +176 -0
  60. data/spec/fixtures/wsdl/wasmuth.xml +153 -0
  61. data/spec/integration/centra_spec.rb +66 -0
  62. data/spec/integration/email_example_spec.rb +32 -0
  63. data/spec/integration/random_quote_spec.rb +23 -0
  64. data/spec/integration/ratp_example_spec.rb +28 -0
  65. data/spec/integration/stockquote_example_spec.rb +28 -0
  66. data/spec/integration/support/application.rb +82 -0
  67. data/spec/integration/support/server.rb +84 -0
  68. data/spec/integration/temperature_example_spec.rb +46 -0
  69. data/spec/integration/zipcode_example_spec.rb +42 -0
  70. data/spec/savon/builder_spec.rb +137 -0
  71. data/spec/savon/client_spec.rb +271 -0
  72. data/spec/savon/core_ext/string_spec.rb +37 -0
  73. data/spec/savon/features/message_tag_spec.rb +61 -0
  74. data/spec/savon/http_error_spec.rb +49 -0
  75. data/spec/savon/log_message_spec.rb +44 -0
  76. data/spec/savon/message_spec.rb +70 -0
  77. data/spec/savon/mock_spec.rb +174 -0
  78. data/spec/savon/model_spec.rb +182 -0
  79. data/spec/savon/observers_spec.rb +92 -0
  80. data/spec/savon/operation_spec.rb +230 -0
  81. data/spec/savon/options_spec.rb +1064 -0
  82. data/spec/savon/qualified_message_spec.rb +20 -0
  83. data/spec/savon/request_logger_spec.rb +37 -0
  84. data/spec/savon/request_spec.rb +496 -0
  85. data/spec/savon/response_spec.rb +270 -0
  86. data/spec/savon/soap_fault_spec.rb +131 -0
  87. data/spec/spec_helper.rb +30 -0
  88. data/spec/support/adapters.rb +48 -0
  89. data/spec/support/endpoint.rb +25 -0
  90. data/spec/support/fixture.rb +39 -0
  91. data/spec/support/integration.rb +9 -0
  92. data/spec/support/stdout.rb +25 -0
  93. metadata +317 -0
@@ -0,0 +1,29 @@
1
+
2
+ module Savon
3
+ module CoreExt
4
+ module String
5
+
6
+ def self.included(base)
7
+ unless "savon".respond_to?(:snakecase)
8
+ base.send(:include, Extension)
9
+ end
10
+ end
11
+
12
+ module Extension
13
+ def snakecase
14
+ str = dup
15
+ str.gsub! /::/, '/'
16
+ str.gsub! /([A-Z]+)([A-Z][a-z])/, '\1_\2'
17
+ str.gsub! /([a-z\d])([A-Z])/, '\1_\2'
18
+ str.tr! ".", "_"
19
+ str.tr! "-", "_"
20
+ str.downcase!
21
+ str
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+
29
+ String.send :include, Savon::CoreExt::String
@@ -0,0 +1,93 @@
1
+ require "akami"
2
+ require "gyoku"
3
+ require "securerandom"
4
+
5
+ module Savon
6
+ class Header
7
+
8
+ def initialize(globals, locals)
9
+ @gyoku_options = { :key_converter => globals[:convert_request_keys_to] }
10
+
11
+ @wsse_auth = locals[:wsse_auth].nil? ? globals[:wsse_auth] : locals[:wsse_auth]
12
+ @wsse_timestamp = locals[:wsse_timestamp].nil? ? globals[:wsse_timestamp] : locals[:wsse_timestamp]
13
+ @wsse_signature = locals[:wsse_signature].nil? ? globals[:wsse_signature] : locals[:wsse_signature]
14
+
15
+ @global_header = globals[:soap_header]
16
+ @local_header = locals[:soap_header]
17
+
18
+ @globals = globals
19
+ @locals = locals
20
+
21
+ @header = build
22
+ end
23
+
24
+ attr_reader :local_header, :global_header, :gyoku_options,
25
+ :wsse_auth, :wsse_timestamp, :wsse_signature
26
+
27
+ def empty?
28
+ @header.empty?
29
+ end
30
+
31
+ def to_s
32
+ @header
33
+ end
34
+
35
+ private
36
+
37
+ def build
38
+ build_header + build_wsa_header + build_wsse_header
39
+ end
40
+
41
+ def build_header
42
+ header =
43
+ if global_header.kind_of?(Hash) && local_header.kind_of?(Hash)
44
+ global_header.merge(local_header)
45
+ elsif local_header
46
+ local_header
47
+ else
48
+ global_header
49
+ end
50
+
51
+ convert_to_xml(header)
52
+ end
53
+
54
+ def build_wsse_header
55
+ wsse_header = akami
56
+ wsse_header.respond_to?(:to_xml) ? wsse_header.to_xml : ""
57
+ end
58
+
59
+ def build_wsa_header
60
+ return '' unless @globals[:use_wsa_headers]
61
+ convert_to_xml({
62
+ 'wsa:Action' => @locals[:soap_action],
63
+ 'wsa:To' => @globals[:endpoint],
64
+ 'wsa:MessageID' => "urn:uuid:#{SecureRandom.uuid}",
65
+ attributes!: {
66
+ 'wsa:MessageID' => {
67
+ "xmlns:wsa" => "http://schemas.xmlsoap.org/ws/2004/08/addressing"
68
+ }
69
+ }
70
+ })
71
+ end
72
+
73
+ def convert_to_xml(hash_or_string)
74
+ if hash_or_string.kind_of? Hash
75
+ Gyoku.xml(hash_or_string, gyoku_options)
76
+ else
77
+ hash_or_string.to_s
78
+ end
79
+ end
80
+
81
+ def akami
82
+ wsse = Akami.wsse
83
+ wsse.credentials(*wsse_auth) if wsse_auth
84
+ wsse.timestamp = wsse_timestamp if wsse_timestamp
85
+ if wsse_signature && wsse_signature.have_document?
86
+ wsse.signature = wsse_signature
87
+ end
88
+
89
+ wsse
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,27 @@
1
+ require "savon"
2
+
3
+ module Savon
4
+ class HTTPError < Error
5
+
6
+ def self.present?(http)
7
+ http.error?
8
+ end
9
+
10
+ def initialize(http)
11
+ @http = http
12
+ end
13
+
14
+ attr_reader :http
15
+
16
+ def to_s
17
+ message = "HTTP error (#{@http.code})"
18
+ message << ": #{@http.body}" unless @http.body.empty?
19
+ message
20
+ end
21
+
22
+ def to_hash
23
+ { :code => @http.code, :headers => @http.headers, :body => @http.body }
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,52 @@
1
+ require "nokogiri"
2
+
3
+ module Savon
4
+ class LogMessage
5
+
6
+ def initialize(message, filters = [], pretty_print = false)
7
+ @message = message
8
+ @filters = filters
9
+ @pretty_print = pretty_print
10
+ end
11
+
12
+ def to_s
13
+ message_is_xml = @message =~ /^</
14
+ has_filters = @filters.any?
15
+ pretty_print = @pretty_print
16
+
17
+ return @message unless message_is_xml
18
+ return @message unless has_filters || pretty_print
19
+
20
+ document = Nokogiri.XML(@message)
21
+ document = apply_filter(document) if has_filters
22
+ document.to_xml(nokogiri_options)
23
+ end
24
+
25
+ private
26
+
27
+ def apply_filter(document)
28
+ return document unless document.errors.empty?
29
+
30
+ @filters.each do |filter|
31
+ apply_filter! document, filter
32
+ end
33
+
34
+ document
35
+ end
36
+
37
+ def apply_filter!(document, filter)
38
+ if filter.instance_of? Proc
39
+ filter.call document
40
+ else
41
+ document.xpath("//*[local-name()='#{filter}']").each do |node|
42
+ node.content = "***FILTERED***"
43
+ end
44
+ end
45
+ end
46
+
47
+ def nokogiri_options
48
+ @pretty_print ? { :indent => 2 } : {}
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,37 @@
1
+ require "savon/qualified_message"
2
+ require "gyoku"
3
+
4
+ module Savon
5
+ class Message
6
+
7
+ def initialize(message_tag, namespace_identifier, types, used_namespaces, message, element_form_default, key_converter, unwrap)
8
+ @message_tag = message_tag
9
+ @namespace_identifier = namespace_identifier
10
+ @types = types
11
+ @used_namespaces = used_namespaces
12
+
13
+ @message = message
14
+ @element_form_default = element_form_default
15
+ @key_converter = key_converter
16
+ @unwrap = unwrap
17
+ end
18
+
19
+ def to_s
20
+ return @message.to_s unless @message.kind_of? Hash
21
+
22
+ if @element_form_default == :qualified
23
+ @message = QualifiedMessage.new(@types, @used_namespaces, @key_converter).to_hash(@message, [@message_tag.to_s])
24
+ end
25
+
26
+ gyoku_options = {
27
+ :element_form_default => @element_form_default,
28
+ :namespace => @namespace_identifier,
29
+ :key_converter => @key_converter,
30
+ :unwrap => @unwrap
31
+ }
32
+
33
+ Gyoku.xml(@message, gyoku_options)
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ module Savon
2
+ class ExpectationError < StandardError; end
3
+ end
4
+
5
+ require "savon/mock/expectation"
@@ -0,0 +1,80 @@
1
+ require "httpi"
2
+
3
+ module Savon
4
+ class MockExpectation
5
+
6
+ def initialize(operation_name)
7
+ @expected = { :operation_name => operation_name }
8
+ @actual = nil
9
+ end
10
+
11
+ def with(locals)
12
+ @expected[:message] = locals[:message]
13
+ self
14
+ end
15
+
16
+ def returns(response)
17
+ response = { :code => 200, :headers => {}, :body => response } if response.kind_of?(String)
18
+ @response = response
19
+ self
20
+ end
21
+
22
+ def actual(operation_name, builder, globals, locals)
23
+ @actual = {
24
+ :operation_name => operation_name,
25
+ :message => locals[:message]
26
+ }
27
+ end
28
+
29
+ def verify!
30
+ unless @actual
31
+ raise ExpectationError, "Expected a request to the #{@expected[:operation_name].inspect} operation, " \
32
+ "but no request was executed."
33
+ end
34
+
35
+ verify_operation_name!
36
+ verify_message!
37
+ end
38
+
39
+ def response!
40
+ unless @response
41
+ raise ExpectationError, "This expectation was not set up with a response."
42
+ end
43
+
44
+ HTTPI::Response.new(@response[:code], @response[:headers], @response[:body])
45
+ end
46
+
47
+ private
48
+
49
+ def verify_operation_name!
50
+ unless @expected[:operation_name] == @actual[:operation_name]
51
+ raise ExpectationError, "Expected a request to the #{@expected[:operation_name].inspect} operation.\n" \
52
+ "Received a request to the #{@actual[:operation_name].inspect} operation instead."
53
+ end
54
+ end
55
+
56
+ def verify_message!
57
+ return if @expected[:message].eql? :any
58
+ unless equals_except_any(@expected[:message], @actual[:message])
59
+ expected_message = " with this message: #{@expected[:message].inspect}" if @expected[:message]
60
+ expected_message ||= " with no message."
61
+
62
+ actual_message = " with this message: #{@actual[:message].inspect}" if @actual[:message]
63
+ actual_message ||= " with no message."
64
+
65
+ raise ExpectationError, "Expected a request to the #{@expected[:operation_name].inspect} operation\n#{expected_message}\n" \
66
+ "Received a request to the #{@actual[:operation_name].inspect} operation\n#{actual_message}"
67
+ end
68
+ end
69
+
70
+ def equals_except_any(msg_expected, msg_real)
71
+ return true if msg_expected === msg_real
72
+ return false if (msg_expected.nil? || msg_real.nil?) # If both are nil has returned true
73
+ msg_expected.each do |key, expected_value|
74
+ next if (expected_value == :any && msg_real.include?(key))
75
+ return false if expected_value != msg_real[key]
76
+ end
77
+ return true
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,62 @@
1
+ require "savon/mock"
2
+
3
+ module Savon
4
+ module SpecHelper
5
+
6
+ class Interface
7
+
8
+ def mock!
9
+ Savon.observers << self
10
+ end
11
+
12
+ def unmock!
13
+ Savon.observers.clear
14
+ end
15
+
16
+ def expects(operation_name)
17
+ expectation = MockExpectation.new(operation_name)
18
+ expectations << expectation
19
+ expectation
20
+ end
21
+
22
+ def expectations
23
+ @expectations ||= []
24
+ end
25
+
26
+ def notify(operation_name, builder, globals, locals)
27
+ expectation = expectations.shift
28
+
29
+ if expectation
30
+ expectation.actual(operation_name, builder, globals, locals)
31
+
32
+ expectation.verify!
33
+ expectation.response!
34
+ else
35
+ raise ExpectationError, "Unexpected request to the #{operation_name.inspect} operation."
36
+ end
37
+ rescue ExpectationError
38
+ @expectations.clear
39
+ raise
40
+ end
41
+
42
+ def verify!
43
+ return if expectations.empty?
44
+ expectations.each(&:verify!)
45
+ rescue ExpectationError
46
+ @expectations.clear
47
+ raise
48
+ end
49
+
50
+ end
51
+
52
+ def savon
53
+ @savon ||= Interface.new
54
+ end
55
+
56
+ def verify_mocks_for_rspec
57
+ super if defined? super
58
+ savon.verify!
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,84 @@
1
+ module Savon
2
+ module Model
3
+
4
+ def self.extended(base)
5
+ base.setup
6
+ end
7
+
8
+ def setup
9
+ class_operation_module
10
+ instance_operation_module
11
+ end
12
+
13
+ # Accepts one or more SOAP operations and generates both class and instance methods named
14
+ # after the given operations. Each generated method accepts an optional SOAP message Hash.
15
+ def operations(*operations)
16
+ operations.each do |operation|
17
+ define_class_operation(operation)
18
+ define_instance_operation(operation)
19
+ end
20
+ end
21
+
22
+ def all_operations
23
+ operations(*client.operations)
24
+ end
25
+
26
+ private
27
+
28
+ # Defines a class-level SOAP operation.
29
+ def define_class_operation(operation)
30
+ class_operation_module.module_eval %{
31
+ def #{operation.to_s.snakecase}(locals = {})
32
+ client.call #{operation.inspect}, locals
33
+ end
34
+ }
35
+ end
36
+
37
+ # Defines an instance-level SOAP operation.
38
+ def define_instance_operation(operation)
39
+ instance_operation_module.module_eval %{
40
+ def #{operation.to_s.snakecase}(locals = {})
41
+ self.class.#{operation.to_s.snakecase} locals
42
+ end
43
+ }
44
+ end
45
+
46
+ # Class methods.
47
+ def class_operation_module
48
+ @class_operation_module ||= Module.new {
49
+
50
+ def client(globals = {})
51
+ @client ||= Savon::Client.new(globals)
52
+ rescue InitializationError
53
+ raise_initialization_error!
54
+ end
55
+
56
+ def global(option, *value)
57
+ client.globals[option] = value
58
+ end
59
+
60
+ def raise_initialization_error!
61
+ raise InitializationError,
62
+ "Expected the model to be initialized with either a WSDL document or the SOAP endpoint and target namespace options.\n" \
63
+ "Make sure to setup the model by calling the .client class method before calling the .global method.\n\n" \
64
+ "client(wsdl: '/Users/me/project/service.wsdl') # to use a local WSDL document\n" \
65
+ "client(wsdl: 'http://example.com?wsdl') # to use a remote WSDL document\n" \
66
+ "client(endpoint: 'http://example.com', namespace: 'http://v1.example.com') # if you don't have a WSDL document"
67
+ end
68
+
69
+ }.tap { |mod| extend(mod) }
70
+ end
71
+
72
+ # Instance methods.
73
+ def instance_operation_module
74
+ @instance_operation_module ||= Module.new {
75
+
76
+ def client
77
+ self.class.client
78
+ end
79
+
80
+ }.tap { |mod| include(mod) }
81
+ end
82
+
83
+ end
84
+ end