savon_with_adapter 2.4.1

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 (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.travis.yml +11 -0
  4. data/.yardopts +6 -0
  5. data/CHANGELOG.md +1042 -0
  6. data/CONTRIBUTING.md +46 -0
  7. data/Gemfile +18 -0
  8. data/LICENSE +20 -0
  9. data/README.md +81 -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 +166 -0
  15. data/lib/savon/client.rb +89 -0
  16. data/lib/savon/core_ext/string.rb +29 -0
  17. data/lib/savon/header.rb +70 -0
  18. data/lib/savon/http_error.rb +27 -0
  19. data/lib/savon/log_message.rb +48 -0
  20. data/lib/savon/message.rb +35 -0
  21. data/lib/savon/mock.rb +5 -0
  22. data/lib/savon/mock/expectation.rb +71 -0
  23. data/lib/savon/mock/spec_helper.rb +62 -0
  24. data/lib/savon/model.rb +80 -0
  25. data/lib/savon/operation.rb +127 -0
  26. data/lib/savon/options.rb +336 -0
  27. data/lib/savon/qualified_message.rb +49 -0
  28. data/lib/savon/request.rb +89 -0
  29. data/lib/savon/request_logger.rb +48 -0
  30. data/lib/savon/response.rb +112 -0
  31. data/lib/savon/soap_fault.rb +48 -0
  32. data/lib/savon/version.rb +3 -0
  33. data/savon.gemspec +52 -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/header.xml +13 -0
  38. data/spec/fixtures/response/list.xml +18 -0
  39. data/spec/fixtures/response/multi_ref.xml +39 -0
  40. data/spec/fixtures/response/soap_fault.xml +8 -0
  41. data/spec/fixtures/response/soap_fault12.xml +18 -0
  42. data/spec/fixtures/response/taxcloud.xml +1 -0
  43. data/spec/fixtures/ssl/client_cert.pem +16 -0
  44. data/spec/fixtures/ssl/client_encrypted_key.pem +30 -0
  45. data/spec/fixtures/ssl/client_encrypted_key_cert.pem +24 -0
  46. data/spec/fixtures/ssl/client_key.pem +15 -0
  47. data/spec/fixtures/wsdl/authentication.xml +63 -0
  48. data/spec/fixtures/wsdl/betfair.xml +2981 -0
  49. data/spec/fixtures/wsdl/edialog.xml +15416 -0
  50. data/spec/fixtures/wsdl/interhome.xml +2137 -0
  51. data/spec/fixtures/wsdl/lower_camel.xml +52 -0
  52. data/spec/fixtures/wsdl/multiple_namespaces.xml +92 -0
  53. data/spec/fixtures/wsdl/multiple_types.xml +60 -0
  54. data/spec/fixtures/wsdl/taxcloud.xml +934 -0
  55. data/spec/fixtures/wsdl/team_software.xml +1 -0
  56. data/spec/fixtures/wsdl/vies.xml +176 -0
  57. data/spec/fixtures/wsdl/wasmuth.xml +153 -0
  58. data/spec/integration/centra_spec.rb +72 -0
  59. data/spec/integration/email_example_spec.rb +32 -0
  60. data/spec/integration/random_quote_spec.rb +23 -0
  61. data/spec/integration/ratp_example_spec.rb +28 -0
  62. data/spec/integration/stockquote_example_spec.rb +28 -0
  63. data/spec/integration/support/application.rb +82 -0
  64. data/spec/integration/support/server.rb +84 -0
  65. data/spec/integration/temperature_example_spec.rb +46 -0
  66. data/spec/integration/zipcode_example_spec.rb +42 -0
  67. data/spec/savon/builder_spec.rb +86 -0
  68. data/spec/savon/client_spec.rb +198 -0
  69. data/spec/savon/core_ext/string_spec.rb +37 -0
  70. data/spec/savon/features/message_tag_spec.rb +61 -0
  71. data/spec/savon/http_error_spec.rb +49 -0
  72. data/spec/savon/log_message_spec.rb +33 -0
  73. data/spec/savon/message_spec.rb +40 -0
  74. data/spec/savon/mock_spec.rb +157 -0
  75. data/spec/savon/model_spec.rb +154 -0
  76. data/spec/savon/observers_spec.rb +92 -0
  77. data/spec/savon/operation_spec.rb +211 -0
  78. data/spec/savon/options_spec.rb +772 -0
  79. data/spec/savon/request_spec.rb +493 -0
  80. data/spec/savon/response_spec.rb +258 -0
  81. data/spec/savon/soap_fault_spec.rb +126 -0
  82. data/spec/spec_helper.rb +30 -0
  83. data/spec/support/endpoint.rb +25 -0
  84. data/spec/support/fixture.rb +39 -0
  85. data/spec/support/integration.rb +9 -0
  86. data/spec/support/stdout.rb +25 -0
  87. metadata +310 -0
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ describe String do
4
+
5
+ describe "snakecase" do
6
+ it "lowercases one word CamelCase" do
7
+ "Merb".snakecase.should == "merb"
8
+ end
9
+
10
+ it "makes one underscore snakecase two word CamelCase" do
11
+ "MerbCore".snakecase.should == "merb_core"
12
+ end
13
+
14
+ it "handles CamelCase with more than 2 words" do
15
+ "SoYouWantContributeToMerbCore".snakecase.should == "so_you_want_contribute_to_merb_core"
16
+ end
17
+
18
+ it "handles CamelCase with more than 2 capital letter in a row" do
19
+ "CNN".snakecase.should == "cnn"
20
+ "CNNNews".snakecase.should == "cnn_news"
21
+ "HeadlineCNNNews".snakecase.should == "headline_cnn_news"
22
+ end
23
+
24
+ it "does NOT change one word lowercase" do
25
+ "merb".snakecase.should == "merb"
26
+ end
27
+
28
+ it "leaves snake_case as is" do
29
+ "merb_core".snakecase.should == "merb_core"
30
+ end
31
+
32
+ it "converts period characters to underscores" do
33
+ "User.GetEmail".snakecase.should == "user_get_email"
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Savon do
4
+
5
+ it 'knows the message tag for :authentication' do
6
+ message_tag = message_tag_for(:authentication, :authenticate)
7
+ expect(message_tag).to eq(['http://v1_0.ws.auth.order.example.com/', 'authenticate'])
8
+ end
9
+
10
+ it 'knows the message tag for :taxcloud' do
11
+ message_tag = message_tag_for(:taxcloud, :verify_address)
12
+ expect(message_tag).to eq(['http://taxcloud.net', 'VerifyAddress'])
13
+ end
14
+
15
+ it 'knows the message tag for :team_software' do
16
+ message_tag = message_tag_for(:team_software, :login)
17
+ expect(message_tag).to eq(['http://tempuri.org/', 'Login'])
18
+ end
19
+
20
+ it 'knows the message tag for :interhome' do
21
+ message_tag = message_tag_for(:interhome, :price_list)
22
+ expect(message_tag).to eq(['http://www.interhome.com/webservice', 'PriceList'])
23
+ end
24
+
25
+ it 'knows the message tag for :betfair' do
26
+ message_tag = message_tag_for(:betfair, :get_bet)
27
+ expect(message_tag).to eq(['http://www.betfair.com/publicapi/v5/BFExchangeService/', 'getBet'])
28
+ end
29
+
30
+ it 'knows the message tag for :vies' do
31
+ message_tag = message_tag_for(:vies, :check_vat)
32
+ expect(message_tag).to eq(['urn:ec.europa.eu:taxud:vies:services:checkVat:types', 'checkVat'])
33
+ end
34
+
35
+ it 'knows the message tag for :wasmuth' do
36
+ message_tag = message_tag_for(:wasmuth, :get_st_tables)
37
+ expect(message_tag).to eq(['http://ws.online.msw/', 'getStTables'])
38
+ end
39
+
40
+ def message_tag_for(fixture, operation_name)
41
+ globals = Savon::GlobalOptions.new(:log => false)
42
+ wsdl = Wasabi::Document.new Fixture.wsdl(fixture)
43
+ operation = Savon::Operation.create(operation_name, wsdl, globals)
44
+ request_xml = operation.build.to_s
45
+
46
+ nsid, local = extract_message_tag_from_request(request_xml)
47
+ namespace = extract_namespace_from_request(nsid, request_xml)
48
+
49
+ [namespace, local]
50
+ end
51
+
52
+ def extract_message_tag_from_request(xml)
53
+ match = xml.match(/<\w+?:Body><(.+?):(.+?)>/)
54
+ [ match[1], match[2] ]
55
+ end
56
+
57
+ def extract_namespace_from_request(nsid, xml)
58
+ xml.match(/xmlns:#{nsid}="(.+?)"/)[1]
59
+ end
60
+
61
+ end
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+
3
+ describe Savon::HTTPError do
4
+ let(:http_error) { Savon::HTTPError.new new_response(:code => 404, :body => "Not Found") }
5
+ let(:no_error) { Savon::HTTPError.new new_response }
6
+
7
+ it "inherits from Savon::Error" do
8
+ expect(Savon::HTTPError.ancestors).to include(Savon::Error)
9
+ end
10
+
11
+ describe ".present?" do
12
+ it "returns true if there was an HTTP error" do
13
+ http = new_response(:code => 404, :body => "Not Found")
14
+ expect(Savon::HTTPError.present? http).to be_true
15
+ end
16
+
17
+ it "returns false unless there was an HTTP error" do
18
+ expect(Savon::HTTPError.present? new_response).to be_false
19
+ end
20
+ end
21
+
22
+ describe "#http" do
23
+ it "returns the HTTPI::Response" do
24
+ expect(http_error.http).to be_a(HTTPI::Response)
25
+ end
26
+ end
27
+
28
+ [:message, :to_s].each do |method|
29
+ describe "##{method}" do
30
+ it "returns the HTTP error message" do
31
+ expect(http_error.send method).to eq("HTTP error (404): Not Found")
32
+ end
33
+ end
34
+ end
35
+
36
+ describe "#to_hash" do
37
+ it "returns the HTTP response details as a Hash" do
38
+ expect(http_error.to_hash).to eq(:code => 404, :headers => {}, :body => "Not Found")
39
+ end
40
+ end
41
+
42
+ def new_response(options = {})
43
+ defaults = { :code => 200, :headers => {}, :body => Fixture.response(:authentication) }
44
+ response = defaults.merge options
45
+
46
+ HTTPI::Response.new response[:code], response[:headers], response[:body]
47
+ end
48
+
49
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+ describe Savon::LogMessage do
4
+
5
+ it "returns the message if it's not XML" do
6
+ message = log_message("hello", [:password], :pretty_print).to_s
7
+ expect(message).to eq("hello")
8
+ end
9
+
10
+ it "returns the message if it shouldn't be filtered or pretty printed" do
11
+ Nokogiri.expects(:XML).never
12
+
13
+ message = log_message("<hello/>", [], false).to_s
14
+ expect(message).to eq("<hello/>")
15
+ end
16
+
17
+ it "pretty prints a given message" do
18
+ message = log_message("<envelope><body>hello</body></envelope>", [], :pretty_print).to_s
19
+
20
+ expect(message).to include("\n<envelope>")
21
+ expect(message).to include("\n <body>")
22
+ end
23
+
24
+ it "filters tags in a given message" do
25
+ message = log_message("<root><password>secret</password></root>", [:password], false).to_s
26
+ expect(message).to include("<password>***FILTERED***</password>")
27
+ end
28
+
29
+ def log_message(*args)
30
+ Savon::LogMessage.new(*args)
31
+ end
32
+
33
+ end
@@ -0,0 +1,40 @@
1
+ require "spec_helper"
2
+ require "integration/support/server"
3
+
4
+ describe Savon::Message do
5
+
6
+ before do
7
+ @server = IntegrationServer.run
8
+ end
9
+
10
+ after do
11
+ @server.stop
12
+ end
13
+
14
+ context "with a qualified message" do
15
+ it "converts request Hash keys for which there is not namespace" do
16
+ client = Savon.client(
17
+ :endpoint => @server.url(:repeat),
18
+ :namespace => 'http://example.com',
19
+ :log => false,
20
+
21
+ :element_form_default => :qualified,
22
+ :convert_request_keys_to => :camelcase,
23
+
24
+ :convert_response_tags_to => nil
25
+ )
26
+
27
+ message = {
28
+ :email_count => 3,
29
+ :user_name => 'josh',
30
+ :order! => [:user_name, :email_count]
31
+ }
32
+
33
+ response = client.call(:something, :message => message)
34
+ body = response.hash['Envelope']['Body']
35
+
36
+ expect(response.xml).to include('<wsdl:UserName>josh</wsdl:UserName><wsdl:EmailCount>3</wsdl:EmailCount>')
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,157 @@
1
+ require "spec_helper"
2
+ require "savon/mock/spec_helper"
3
+
4
+ describe "Savon's mock interface" do
5
+ include Savon::SpecHelper
6
+
7
+ before :all do
8
+ savon.mock!
9
+ end
10
+
11
+ after :all do
12
+ savon.unmock!
13
+ end
14
+
15
+ it "can verify a request and return a fixture response" do
16
+ message = { :username => "luke", :password => "secret" }
17
+ savon.expects(:authenticate).with(:message => message).returns("<fixture/>")
18
+
19
+ response = new_client.call(:authenticate) do
20
+ message(:username => "luke", :password => "secret")
21
+ end
22
+
23
+ expect(response.http.body).to eq("<fixture/>")
24
+ end
25
+
26
+ it "accepts a Hash to specify the response code, headers and body" do
27
+ soap_fault = Fixture.response(:soap_fault)
28
+ response = { :code => 500, :headers => { "X-Result" => "invalid" }, :body => soap_fault }
29
+
30
+ savon.expects(:authenticate).returns(response)
31
+ response = new_client(:raise_errors => false).call(:authenticate)
32
+
33
+ expect(response).to_not be_successful
34
+ expect(response).to be_a_soap_fault
35
+
36
+ expect(response.http.code).to eq(500)
37
+ expect(response.http.headers).to eq("X-Result" => "invalid")
38
+ expect(response.http.body).to eq(soap_fault)
39
+ end
40
+
41
+ it "works with multiple requests" do
42
+ authentication_message = { :username => "luke", :password => "secret" }
43
+ savon.expects(:authenticate).with(:message => authentication_message).returns("")
44
+
45
+ find_user_message = { :by_username => "lea" }
46
+ savon.expects(:find_user).with(:message => find_user_message).returns("")
47
+
48
+ new_client.call(:authenticate, :message => authentication_message)
49
+ new_client.call(:find_user, :message => find_user_message)
50
+ end
51
+
52
+ it "fails when the expected operation was not called" do
53
+ # TODO: find out how to test this! [dh, 2012-12-17]
54
+ #savon.expects(:authenticate)
55
+ end
56
+
57
+ it "fails when the return value for an expectation was not specified" do
58
+ savon.expects(:authenticate)
59
+
60
+ expect { new_client.call(:authenticate) }.
61
+ to raise_error(Savon::ExpectationError, "This expectation was not set up with a response.")
62
+ end
63
+
64
+ it "fails with an unexpected request" do
65
+ expect { new_client.call(:authenticate) }.
66
+ to raise_error(Savon::ExpectationError, "Unexpected request to the :authenticate operation.")
67
+ end
68
+
69
+ it "fails with multiple requests" do
70
+ authentication_message = { :username => "luke", :password => "secret" }
71
+ savon.expects(:authenticate).with(:message => authentication_message).returns("")
72
+
73
+ create_user_message = { :username => "lea" }
74
+ savon.expects(:create_user).with(:message => create_user_message).returns("")
75
+
76
+ find_user_message = { :by_username => "lea" }
77
+ savon.expects(:find_user).with(:message => find_user_message).returns("")
78
+
79
+ # reversed order from previous spec
80
+ new_client.call(:authenticate, :message => authentication_message)
81
+
82
+ expect { new_client.call(:find_user, :message => find_user_message) }.
83
+ to raise_error(Savon::ExpectationError, "Expected a request to the :create_user operation.\n" \
84
+ "Received a request to the :find_user operation instead.")
85
+ end
86
+
87
+ it "fails when the expected SOAP operation does not match the actual one" do
88
+ savon.expects(:logout).returns("<fixture/>")
89
+
90
+ expect { new_client.call(:authenticate) }.
91
+ to raise_error(Savon::ExpectationError, "Expected a request to the :logout operation.\n" \
92
+ "Received a request to the :authenticate operation instead.")
93
+ end
94
+
95
+ it "fails when there is no actual message to match" do
96
+ message = { :username => "luke" }
97
+ savon.expects(:find_user).with(:message => message).returns("<fixture/>")
98
+
99
+ expect { new_client.call(:find_user) }.
100
+ to raise_error(Savon::ExpectationError, "Expected a request to the :find_user operation\n" \
101
+ " with this message: #{message.inspect}\n" \
102
+ "Received a request to the :find_user operation\n" \
103
+ " with no message.")
104
+ end
105
+
106
+ it "fails when there is no expect but an actual message" do
107
+ savon.expects(:find_user).returns("<fixture/>")
108
+ message = { :username => "luke" }
109
+
110
+ expect { new_client.call(:find_user, :message => message) }.
111
+ to raise_error(Savon::ExpectationError, "Expected a request to the :find_user operation\n" \
112
+ " with no message.\n" \
113
+ "Received a request to the :find_user operation\n" \
114
+ " with this message: #{message.inspect}")
115
+ end
116
+
117
+ it "does not fail when any message is expected and an actual message" do
118
+ savon.expects(:find_user).with(:message => :any).returns("<fixture/>")
119
+ message = { :username => "luke" }
120
+
121
+ expect { new_client.call(:find_user, :message => message) }.to_not raise_error
122
+ end
123
+
124
+ it "does not fail when any message is expected and no actual message" do
125
+ savon.expects(:find_user).with(:message => :any).returns("<fixture/>")
126
+
127
+ expect { new_client.call(:find_user) }.to_not raise_error
128
+ end
129
+
130
+
131
+ it "allows code to rescue Savon::Error and still report test failures" do
132
+ message = { :username => "luke" }
133
+ savon.expects(:find_user).with(:message => message).returns("<fixture/>")
134
+
135
+ expect {
136
+ begin
137
+ new_client.call(:find_user)
138
+ rescue Savon::Error => e
139
+ puts "any real error (e.g. SOAP fault or HTTP error) is OK in the big picture, move on"
140
+ end
141
+ }.to raise_error(Savon::ExpectationError, "Expected a request to the :find_user operation\n" \
142
+ " with this message: #{message.inspect}\n" \
143
+ "Received a request to the :find_user operation\n" \
144
+ " with no message.")
145
+ end
146
+
147
+ def new_client(globals = {})
148
+ defaults = {
149
+ :endpoint => "http://example.com",
150
+ :namespace => "http://v1.example.com",
151
+ :log => false
152
+ }
153
+
154
+ Savon.client defaults.merge(globals)
155
+ end
156
+
157
+ end
@@ -0,0 +1,154 @@
1
+ require "spec_helper"
2
+ require "integration/support/server"
3
+
4
+ describe Savon::Model do
5
+
6
+ before :all do
7
+ @server = IntegrationServer.run
8
+ end
9
+
10
+ after :all do
11
+ @server.stop
12
+ end
13
+
14
+ describe ".client" do
15
+ it "returns the memoized client" do
16
+ model = Class.new {
17
+ extend Savon::Model
18
+ client :wsdl => Fixture.wsdl(:authentication)
19
+ }
20
+
21
+ expect(model.client).to be_a(Savon::Client)
22
+ expect(model.client).to equal(model.client)
23
+ end
24
+
25
+ it "raises if the client was not initialized properly" do
26
+ model = Class.new { extend Savon::Model }
27
+
28
+ expect { model.client }.
29
+ to raise_error(Savon::InitializationError, /^Expected the model to be initialized/)
30
+ end
31
+ end
32
+
33
+ describe ".global" do
34
+ it "sets global options" do
35
+ model = Class.new {
36
+ extend Savon::Model
37
+
38
+ client :wsdl => Fixture.wsdl(:authentication)
39
+
40
+ global :soap_version, 2
41
+ global :open_timeout, 71
42
+ global :wsse_auth, "luke", "secret", :digest
43
+ }
44
+
45
+ expect(model.client.globals[:soap_version]).to eq(2)
46
+ expect(model.client.globals[:open_timeout]).to eq(71)
47
+ expect(model.client.globals[:wsse_auth]).to eq(["luke", "secret", :digest])
48
+ end
49
+ end
50
+
51
+ describe ".operations" do
52
+ it "defines class methods for each operation" do
53
+ model = Class.new {
54
+ extend Savon::Model
55
+
56
+ client :wsdl => Fixture.wsdl(:authentication)
57
+ operations :authenticate
58
+ }
59
+
60
+ expect(model).to respond_to(:authenticate)
61
+ end
62
+
63
+ it "executes class-level SOAP operations" do
64
+ repeat_url = @server.url(:repeat)
65
+
66
+ model = Class.new {
67
+ extend Savon::Model
68
+
69
+ client :endpoint => repeat_url, :namespace => "http://v1.example.com"
70
+ global :log, false
71
+
72
+ operations :authenticate
73
+ }
74
+
75
+ response = model.authenticate(:xml => Fixture.response(:authentication))
76
+ expect(response.body[:authenticate_response][:return]).to include(:authentication_value)
77
+ end
78
+
79
+ it "defines instance methods for each operation" do
80
+ model = Class.new {
81
+ extend Savon::Model
82
+
83
+ client :wsdl => Fixture.wsdl(:authentication)
84
+ operations :authenticate
85
+ }
86
+
87
+ model_instance = model.new
88
+ expect(model_instance).to respond_to(:authenticate)
89
+ end
90
+
91
+ it "executes instance-level SOAP operations" do
92
+ repeat_url = @server.url(:repeat)
93
+
94
+ model = Class.new {
95
+ extend Savon::Model
96
+
97
+ client :endpoint => repeat_url, :namespace => "http://v1.example.com"
98
+ global :log, false
99
+
100
+ operations :authenticate
101
+ }
102
+
103
+ model_instance = model.new
104
+ response = model_instance.authenticate(:xml => Fixture.response(:authentication))
105
+ expect(response.body[:authenticate_response][:return]).to include(:authentication_value)
106
+ end
107
+ end
108
+
109
+ it "allows to overwrite class operations" do
110
+ repeat_url = @server.url(:repeat)
111
+
112
+ model = Class.new {
113
+ extend Savon::Model
114
+ client :endpoint => repeat_url, :namespace => "http://v1.example.com"
115
+ }
116
+
117
+ supermodel = model.dup
118
+ supermodel.operations :authenticate
119
+
120
+ def supermodel.authenticate(locals = {})
121
+ p "super"
122
+ super
123
+ end
124
+
125
+ supermodel.client.expects(:call).with(:authenticate, :message => { :username => "luke", :password => "secret" })
126
+ supermodel.expects(:p).with("super") # stupid, but works
127
+
128
+ supermodel.authenticate(:message => { :username => "luke", :password => "secret" })
129
+ end
130
+
131
+ it "allows to overwrite instance operations" do
132
+ repeat_url = @server.url(:repeat)
133
+
134
+ model = Class.new {
135
+ extend Savon::Model
136
+ client :endpoint => repeat_url, :namespace => "http://v1.example.com"
137
+ }
138
+
139
+ supermodel = model.dup
140
+ supermodel.operations :authenticate
141
+ supermodel = supermodel.new
142
+
143
+ def supermodel.authenticate(lcoals = {})
144
+ p "super"
145
+ super
146
+ end
147
+
148
+ supermodel.client.expects(:call).with(:authenticate, :message => { :username => "luke", :password => "secret" })
149
+ supermodel.expects(:p).with("super") # stupid, but works
150
+
151
+ supermodel.authenticate(:message => { :username => "luke", :password => "secret" })
152
+ end
153
+
154
+ end