savon 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/CHANGELOG.md +119 -104
  2. data/README.md +12 -11
  3. data/Rakefile +0 -6
  4. data/lib/savon.rb +16 -14
  5. data/lib/savon/block_interface.rb +26 -0
  6. data/lib/savon/builder.rb +142 -0
  7. data/lib/savon/client.rb +36 -135
  8. data/lib/savon/header.rb +42 -0
  9. data/lib/savon/http_error.rb +27 -0
  10. data/lib/savon/log_message.rb +23 -25
  11. data/lib/savon/message.rb +35 -0
  12. data/lib/savon/mock.rb +5 -0
  13. data/lib/savon/mock/expectation.rb +70 -0
  14. data/lib/savon/mock/spec_helper.rb +62 -0
  15. data/lib/savon/model.rb +39 -61
  16. data/lib/savon/operation.rb +62 -0
  17. data/lib/savon/options.rb +265 -0
  18. data/lib/savon/qualified_message.rb +49 -0
  19. data/lib/savon/request.rb +92 -0
  20. data/lib/savon/response.rb +97 -0
  21. data/lib/savon/soap_fault.rb +40 -0
  22. data/lib/savon/version.rb +1 -1
  23. data/savon.gemspec +10 -8
  24. data/spec/integration/options_spec.rb +536 -0
  25. data/spec/integration/request_spec.rb +31 -16
  26. data/spec/integration/support/application.rb +80 -0
  27. data/spec/integration/support/server.rb +84 -0
  28. data/spec/savon/builder_spec.rb +81 -0
  29. data/spec/savon/client_spec.rb +90 -488
  30. data/spec/savon/http_error_spec.rb +49 -0
  31. data/spec/savon/log_message_spec.rb +33 -0
  32. data/spec/savon/mock_spec.rb +127 -0
  33. data/spec/savon/model_spec.rb +110 -99
  34. data/spec/savon/observers_spec.rb +92 -0
  35. data/spec/savon/operation_spec.rb +49 -0
  36. data/spec/savon/request_spec.rb +145 -0
  37. data/spec/savon/{soap/response_spec.rb → response_spec.rb} +22 -59
  38. data/spec/savon/soap_fault_spec.rb +94 -0
  39. data/spec/spec_helper.rb +5 -3
  40. data/spec/support/fixture.rb +5 -1
  41. metadata +202 -197
  42. data/lib/savon/config.rb +0 -46
  43. data/lib/savon/error.rb +0 -6
  44. data/lib/savon/hooks/group.rb +0 -68
  45. data/lib/savon/hooks/hook.rb +0 -61
  46. data/lib/savon/http/error.rb +0 -42
  47. data/lib/savon/logger.rb +0 -39
  48. data/lib/savon/null_logger.rb +0 -10
  49. data/lib/savon/soap.rb +0 -21
  50. data/lib/savon/soap/fault.rb +0 -59
  51. data/lib/savon/soap/invalid_response_error.rb +0 -13
  52. data/lib/savon/soap/request.rb +0 -86
  53. data/lib/savon/soap/request_builder.rb +0 -205
  54. data/lib/savon/soap/response.rb +0 -117
  55. data/lib/savon/soap/xml.rb +0 -257
  56. data/spec/savon/config_spec.rb +0 -38
  57. data/spec/savon/hooks/group_spec.rb +0 -71
  58. data/spec/savon/hooks/hook_spec.rb +0 -16
  59. data/spec/savon/http/error_spec.rb +0 -52
  60. data/spec/savon/logger_spec.rb +0 -51
  61. data/spec/savon/savon_spec.rb +0 -33
  62. data/spec/savon/soap/fault_spec.rb +0 -89
  63. data/spec/savon/soap/request_builder_spec.rb +0 -207
  64. data/spec/savon/soap/request_spec.rb +0 -112
  65. data/spec/savon/soap/xml_spec.rb +0 -357
  66. data/spec/savon/soap_spec.rb +0 -16
@@ -0,0 +1,42 @@
1
+ require "akami"
2
+ require "gyoku"
3
+
4
+ module Savon
5
+ class Header
6
+
7
+ def initialize(globals, locals)
8
+ @globals = globals
9
+ @locals = locals
10
+ @wsse = create_wsse
11
+ end
12
+
13
+ def empty?
14
+ to_s.empty?
15
+ end
16
+
17
+ def to_s
18
+ return @header if @header
19
+
20
+ gyoku_options = { :key_converter => @globals[:convert_request_keys_to] }
21
+ @header = (Hash === header ? Gyoku.xml(header, gyoku_options) : header) + wsse_header
22
+ end
23
+
24
+ private
25
+
26
+ def create_wsse
27
+ wsse = Akami.wsse
28
+ wsse.credentials(*@globals[:wsse_auth]) if @globals.include? :wsse_auth
29
+ wsse.timestamp = @globals[:wsse_timestamp] if @globals.include? :wsse_timestamp
30
+ wsse
31
+ end
32
+
33
+ def header
34
+ @header ||= @globals.include?(:soap_header) ? @globals[:soap_header] : {}
35
+ end
36
+
37
+ def wsse_header
38
+ @wsse.respond_to?(:to_xml) ? @wsse.to_xml : ""
39
+ end
40
+
41
+ end
42
+ 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
@@ -1,49 +1,47 @@
1
+ require "nokogiri"
2
+
1
3
  module Savon
2
4
  class LogMessage
3
5
 
4
- def initialize(message, filters, options = {})
5
- @message = message
6
- @filters = filters
7
- @options = options
6
+ def initialize(message, filters = [], pretty_print = false)
7
+ @message = message
8
+ @filters = filters
9
+ @pretty_print = pretty_print
8
10
  end
9
11
 
10
12
  def to_s
11
- return @message unless pretty? || filter?
12
-
13
- doc = Nokogiri.XML(@message)
14
- doc = apply_filter(doc) if filter?
15
- doc.to_xml(pretty_options)
16
- end
13
+ message_is_xml = @message =~ /^</
14
+ has_filters = @filters.any?
15
+ pretty_print = @pretty_print
17
16
 
18
- private
17
+ return @message unless message_is_xml
18
+ return @message unless has_filters || pretty_print
19
19
 
20
- def filter?
21
- @options[:filter] && @filters.any?
20
+ document = Nokogiri.XML(@message)
21
+ document = apply_filter(document) if has_filters
22
+ document.to_xml(nokogiri_options)
22
23
  end
23
24
 
24
- def pretty?
25
- @options[:pretty]
26
- end
25
+ private
27
26
 
28
- def apply_filter(doc)
29
- return doc unless doc.errors.empty?
27
+ def apply_filter(document)
28
+ return document unless document.errors.empty?
30
29
 
31
30
  @filters.each do |filter|
32
- apply_filter!(doc, filter)
31
+ apply_filter! document, filter
33
32
  end
34
33
 
35
- doc
34
+ document
36
35
  end
37
36
 
38
- def apply_filter!(doc, filter)
39
- doc.xpath("//*[local-name()='#{filter}']").each do |node|
37
+ def apply_filter!(document, filter)
38
+ document.xpath("//*[local-name()='#{filter}']").each do |node|
40
39
  node.content = "***FILTERED***"
41
40
  end
42
41
  end
43
42
 
44
- def pretty_options
45
- return {} unless pretty?
46
- { :indent => 2 }
43
+ def nokogiri_options
44
+ @pretty_print ? { :indent => 2 } : {}
47
45
  end
48
46
 
49
47
  end
@@ -0,0 +1,35 @@
1
+ require "savon/qualified_message"
2
+ require "gyoku"
3
+
4
+ module Savon
5
+ class Message
6
+
7
+ def initialize(operation_name, namespace_identifier, types, used_namespaces, message, element_form_default, key_converter)
8
+ @operation_name = operation_name
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
+ end
17
+
18
+ def to_s
19
+ return @message.to_s unless @message.kind_of? Hash
20
+
21
+ if @element_form_default == :qualified
22
+ @message = QualifiedMessage.new(@types, @used_namespaces, @request_key_converter).to_hash(@message, [@operation_name.to_s])
23
+ end
24
+
25
+ gyoku_options = {
26
+ :element_form_default => @element_form_default,
27
+ :namespace => @namespace_identifier,
28
+ :key_converter => @key_converter
29
+ }
30
+
31
+ Gyoku.xml(@message, gyoku_options)
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ module Savon
2
+ class ExpectationError < Error; end
3
+ end
4
+
5
+ require "savon/mock/expectation"
@@ -0,0 +1,70 @@
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
+ unless @expected[:message] == @actual[:message]
58
+ expected_message = " with this message: #{@expected[:message].inspect}" if @expected[:message]
59
+ expected_message ||= " with no message."
60
+
61
+ actual_message = " with this message: #{@actual[:message].inspect}" if @actual[:message]
62
+ actual_message ||= " with no message."
63
+
64
+ raise ExpectationError, "Expected a request to the #{@expected[:operation_name].inspect} operation\n#{expected_message}\n" \
65
+ "Received a request to the #{@actual[:operation_name].inspect} operation\n#{actual_message}"
66
+ end
67
+ end
68
+
69
+ end
70
+ 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
@@ -1,8 +1,4 @@
1
1
  module Savon
2
-
3
- # = Savon::Model
4
- #
5
- # Model for SOAP service oriented applications.
6
2
  module Model
7
3
 
8
4
  def self.extended(base)
@@ -10,92 +6,74 @@ module Savon
10
6
  end
11
7
 
12
8
  def setup
13
- class_action_module
14
- instance_action_module
9
+ class_operation_module
10
+ instance_operation_module
15
11
  end
16
12
 
17
- # Accepts one or more SOAP actions and generates both class and instance methods named
18
- # after the given actions. Each generated method accepts an optional SOAP body Hash and
19
- # a block to be passed to <tt>Savon::Client#request</tt> and executes a SOAP request.
20
- def actions(*actions)
21
- actions.each do |action|
22
- define_class_action(action)
23
- define_instance_action(action)
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)
24
19
  end
25
20
  end
26
21
 
27
22
  private
28
23
 
29
- # Defines a class-level SOAP action method.
30
- def define_class_action(action)
31
- class_action_module.module_eval %{
32
- def #{action.to_s.snakecase}(body = nil, &block)
33
- client.request :wsdl, #{action.inspect}, :body => body, &block
24
+ # Defines a class-level SOAP operation.
25
+ def define_class_operation(operation)
26
+ class_operation_module.module_eval %{
27
+ def #{operation.to_s.snakecase}(locals = {})
28
+ client.call #{operation.inspect}, locals
34
29
  end
35
30
  }
36
31
  end
37
32
 
38
- # Defines an instance-level SOAP action method.
39
- def define_instance_action(action)
40
- instance_action_module.module_eval %{
41
- def #{action.to_s.snakecase}(body = nil, &block)
42
- self.class.#{action.to_s.snakecase} body, &block
33
+ # Defines an instance-level SOAP operation.
34
+ def define_instance_operation(operation)
35
+ instance_operation_module.module_eval %{
36
+ def #{operation.to_s.snakecase}(locals = {})
37
+ self.class.#{operation.to_s.snakecase} locals
43
38
  end
44
39
  }
45
40
  end
46
41
 
47
42
  # Class methods.
48
- def class_action_module
49
- @class_action_module ||= Module.new do
50
-
51
- # Returns the memoized <tt>Savon::Client</tt>.
52
- def client(&block)
53
- @client ||= Savon::Client.new(&block)
54
- end
55
-
56
- # Sets the SOAP endpoint to the given +uri+.
57
- def endpoint(uri)
58
- client.wsdl.endpoint = uri
59
- end
60
-
61
- # Sets the target namespace.
62
- def namespace(uri)
63
- client.wsdl.namespace = uri
64
- end
65
-
66
- # Sets the WSDL document to the given +uri+.
67
- def document(uri)
68
- client.wsdl.document = uri
69
- end
43
+ def class_operation_module
44
+ @class_operation_module ||= Module.new {
70
45
 
71
- # Sets the HTTP headers.
72
- def headers(headers)
73
- client.http.headers = headers
46
+ def client(globals = {})
47
+ @client ||= Savon::Client.new(globals)
48
+ rescue InitializationError
49
+ raise_initialization_error!
74
50
  end
75
51
 
76
- # Sets basic auth +login+ and +password+.
77
- def basic_auth(login, password)
78
- client.http.auth.basic(login, password)
52
+ def global(option, *value)
53
+ client.globals[option] = value
79
54
  end
80
55
 
81
- # Sets WSSE auth credentials.
82
- def wsse_auth(*args)
83
- client.wsse.credentials(*args)
56
+ def raise_initialization_error!
57
+ raise InitializationError,
58
+ "Expected the model to be initialized with either a WSDL document or the SOAP endpoint and target namespace options.\n" \
59
+ "Make sure to setup the model by calling the .client class method before calling the .global method.\n\n" \
60
+ "client(wsdl: '/Users/me/project/service.wsdl') # to use a local WSDL document\n" \
61
+ "client(wsdl: 'http://example.com?wsdl') # to use a remote WSDL document\n" \
62
+ "client(endpoint: 'http://example.com', namespace: 'http://v1.example.com') # if you don't have a WSDL document"
84
63
  end
85
64
 
86
- end.tap { |mod| extend(mod) }
65
+ }.tap { |mod| extend(mod) }
87
66
  end
88
67
 
89
68
  # Instance methods.
90
- def instance_action_module
91
- @instance_action_module ||= Module.new do
69
+ def instance_operation_module
70
+ @instance_operation_module ||= Module.new {
92
71
 
93
- # Returns the <tt>Savon::Client</tt> from the class instance.
94
- def client(&block)
95
- self.class.client(&block)
72
+ def client
73
+ self.class.client
96
74
  end
97
75
 
98
- end.tap { |mod| include(mod) }
76
+ }.tap { |mod| include(mod) }
99
77
  end
100
78
 
101
79
  end