3scale-3scale_ws_api_for_ruby 0.4.7

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.
data/README ADDED
@@ -0,0 +1,63 @@
1
+ = Interface for 3scale web service API monitoring system
2
+
3
+ This plugin provides interface for communication with 3scale monitoring system.
4
+
5
+ == Configuration
6
+
7
+ If you are using rails 2.1 or newer, put this into your config/environment.rb
8
+ file:
9
+
10
+ config.gem "3scale_interface"
11
+
12
+ If you are using older version, put there this instead:
13
+
14
+ require "3scale_interface"
15
+
16
+ == Usage
17
+
18
+ First, create new interface object with 3scale backed hostname and Your
19
+ private authentication key:
20
+
21
+ interface = ThreeScale::Interface.new("http://3scale.net", "a3b034...")
22
+
23
+ Because the object is stateless, you can create just one and store it globally.
24
+
25
+ Then for each request to Your service:
26
+
27
+ 1. Start the transaction with user key and (optionally) estimated resource
28
+ usage (in this example it is: 1 hit and 42000 kilobytes of storage space),
29
+
30
+ transaction = interface.start(user_key, 'hits' => 1, 'storage' => 42000)
31
+
32
+ This will return transaction data (if successful). It is a hash containing
33
+ these fields:
34
+
35
+ <tt>:id</tt>::
36
+ transaction id necessary for confirmation or cancellation of transaction
37
+ (see following steps).
38
+
39
+ <tt>:provider_verification_key</tt>::
40
+ the provider should send this key back to user so he/she can verify the
41
+ authenticity of the response.
42
+
43
+ <tt>:contract_name</tt>::
44
+ name of contract the user is signed for. This can be used to send
45
+ different response according to contract type, if that is desired.
46
+
47
+ 2. Process the request.
48
+
49
+ 2a. If the processing was successful:
50
+ Call +confirm+:
51
+
52
+ interface.confirm(transaction[:id])
53
+
54
+ Or call it with actual resource usage, if it differs from predicted one:
55
+
56
+ interface.confirm(transaction[:id], 'hits' => 1, 'storage' => 40500)
57
+
58
+ 2b. If there was some error, call +cancel+:
59
+
60
+ interface.cancel(transaction[:id])
61
+
62
+ 3. Send response back to the user together with +provider_verification_key+.
63
+
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+
6
+ desc 'Default: run unit tests.'
7
+ task :default => :test
8
+
9
+ desc 'Run unit tests.'
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << 'lib'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = '3scale interface'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
24
+
25
+ spec = eval(File.read(File.dirname(__FILE__) + '/3scale_ws_api_for_ruby.gemspec'))
26
+
27
+ Rake::GemPackageTask.new(spec) do |package|
28
+ package.need_tar = true
29
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "#{File.dirname(__FILE__)}/lib/3scale_interface"
@@ -0,0 +1,236 @@
1
+ require 'cgi'
2
+ require 'hpricot'
3
+ require 'net/http'
4
+
5
+ module ThreeScale # :nodoc:
6
+ class Error < StandardError; end
7
+
8
+ # Base class for exceptions caused by user.
9
+ class UserError < Error; end
10
+
11
+ # Exception raised when contract between user and provider is not active.
12
+ # Contract can be inactive when it is pending (requires confirmation from
13
+ # provider), suspended or canceled.
14
+ class ContractNotActive < UserError; end
15
+
16
+ # Exception raised when usage limits configured for contract are already
17
+ # exceeded.
18
+ class LimitsExceeded < UserError; end
19
+
20
+ # Exception raised when +user_key+ is not valid. This can mean that contract
21
+ # between provider and user does not exists, or the passed +user_key+ does
22
+ # not correspond to the key associated with this contract.
23
+ class UserKeyInvalid < UserError; end
24
+
25
+
26
+ # Base class for exceptions caused by provider.
27
+ class ProviderError < Error; end
28
+
29
+ # Exception raised when some metric name in provider +usage+ hash does not
30
+ # correspond to metric configured for the service.
31
+ class MetricInvalid < ProviderError; end
32
+
33
+ # Exception raised when provider authentication key is not valid. The provider
34
+ # needs to make sure that the key used is the same as the one that was
35
+ # generated for him/her when he/she published a service on 3scale.
36
+ class ProviderKeyInvalid < ProviderError; end
37
+
38
+ # Exception raised when transaction corresponding to given +transaction_id+
39
+ # does not exists. Methods +confirm+ and +cancel+ need valid transaction id
40
+ # that is obtained by preceding call to +start+.
41
+ class TransactionNotFound < ProviderError; end
42
+
43
+
44
+ # Base class for exceptions caused by 3scale backend system.
45
+ class SystemError < Error; end
46
+
47
+ # Other error.
48
+ class UnknownError < SystemError; end
49
+
50
+
51
+
52
+ # This class provides interface to 3scale monitoring system.
53
+ #
54
+ # Objects of this class are stateless and can be shared through multiple
55
+ # transactions and by multiple clients.
56
+ class Interface
57
+
58
+ # Hostname of 3scale server.
59
+ attr_accessor :host
60
+
61
+ # Key that uniquely identifies the provider. This key is known only to the
62
+ # provider and to 3scale.
63
+ attr_accessor :provider_authentication_key
64
+
65
+ # Create a 3scale interface object.
66
+ #
67
+ # == Arguments
68
+ # +host+:: Hostname of 3scale backend server.
69
+ # +provider_authentication_key+:: Unique key that identifies this provider.
70
+ def initialize(host = nil, provider_authentication_key = nil)
71
+ @host = host
72
+ @provider_authentication_key = provider_authentication_key
73
+ end
74
+
75
+
76
+ # Starts a transaction. This can be used also to report estimated resource
77
+ # usage of the request.
78
+ #
79
+ # == Arguments
80
+ # +user_key+:: Key that uniquely identifies an user of the service.
81
+ # +usage+::
82
+ # A hash of metric names/values pairs that contains predicted resource
83
+ # usage of this request.
84
+ #
85
+ # For example, if this request is going to take 10MB of storage space,
86
+ # then this parameter could contain {'storage' => 10}. The values may be
87
+ # only approximate or they can be missing altogether. In these cases, the
88
+ # real values must be reported using method +confirm+.
89
+ #
90
+ # == Return values
91
+ # A hash containing there keys:
92
+ # <tt>:id</tt>::
93
+ # Transaction id. This is required for confirmation/cancellation of the
94
+ # transaction later.
95
+ # <tt>:provider_verification_key</tt>::
96
+ # This key should be sent back to the user so he/she can use it to verify
97
+ # the authenticity of the provider.
98
+ # <tt>:contract_name</tt>::
99
+ # This is name of the contract the user is singed for. This information
100
+ # can be used to serve different responses according to contract types,
101
+ # if that is desirable.
102
+ #
103
+ # == Exceptions
104
+ #
105
+ # ThreeScale::UserKeyInvalid:: +user_key+ is not valid
106
+ # ThreeScale::ProviderKeyInvalid:: +provider_authentication_key+ is not valid
107
+ # ThreeScale::MetricInvalid:: +usage+ contains invalid metrics
108
+ # ThreeScale::ContractNotActive:: contract is not active
109
+ # ThreeScale::LimitsExceeded:: usage limits are exceeded
110
+ # ThreeScale::UnknownError:: some other unexpected error
111
+ #
112
+ def start(user_key, usage = {})
113
+ uri = URI.parse("#{host}/transactions.xml")
114
+ params = {
115
+ 'user_key' => prepare_key(user_key),
116
+ 'provider_key' => provider_authentication_key
117
+ }
118
+ params.merge!(encode_params(usage, 'usage'))
119
+ response = Net::HTTP.post_form(uri, params)
120
+
121
+ if response.is_a?(Net::HTTPSuccess)
122
+ element = Hpricot::XML(response.body).at('transaction')
123
+ [:id, :provider_verification_key, :contract_name].inject({}) do |memo, key|
124
+ memo[key] = element.at(key).inner_text if element.at(key)
125
+ memo
126
+ end
127
+ else
128
+ handle_error(response.body)
129
+ end
130
+ end
131
+
132
+ # Confirms a transaction.
133
+ #
134
+ # == Arguments
135
+ #
136
+ # +transaction_id+::
137
+ # A transaction id obtained from previous call to +start+.
138
+ # +usage+::
139
+ # A hash of metric names/values pairs containing actual resource usage
140
+ # of this request. This parameter is required only if no usage information
141
+ # was passed to method +start+ for this transaction, or if it was only
142
+ # approximate.
143
+ #
144
+ # == Return values
145
+ #
146
+ # If there were no exceptions raised, returns true.
147
+ #
148
+ # == Exceptions
149
+ #
150
+ # ThreeScale::TransactionNotFound:: transactions does not exits
151
+ # ThreeScale::ProviderKeyInvalid:: +provider_authentication_key+ is not valid
152
+ # ThreeScale::MetricInvalid:: +usage+ contains invalid metrics
153
+ # ThreeScale::UnknownError:: some other unexpected error
154
+ #
155
+ def confirm(transaction_id, usage = {})
156
+ uri = URI.parse("#{host}/transactions/#{CGI.escape(transaction_id.to_s)}/confirm.xml")
157
+ params = {
158
+ 'provider_key' => provider_authentication_key
159
+ }
160
+ params.merge!(encode_params(usage, 'usage'))
161
+
162
+ response = Net::HTTP.post_form(uri, params)
163
+ response.is_a?(Net::HTTPSuccess) ? true : handle_error(response.body)
164
+ end
165
+
166
+ # Cancels a transaction.
167
+ #
168
+ # Use this if request processing failed. Any estimated resource usage
169
+ # reported by preceding call to +start+ will be deleted. You don't have to
170
+ # call this if call to +start+ itself failed.
171
+ #
172
+ # == Arguments
173
+ #
174
+ # +transaction_id+::
175
+ # A transaction id obtained from previous call to +start+.
176
+ #
177
+ # == Return values
178
+ #
179
+ # If there were no exceptions raised, returns true.
180
+ #
181
+ # == Exceptions
182
+ #
183
+ # ThreeScale::TransactionNotFound:: transactions does not exits
184
+ # ThreeScale::ProviderKeyInvalid:: +provider_authentication_key+ is not valid
185
+ # ThreeScale::UnknownError:: some other unexpected error
186
+ #
187
+ def cancel(transaction_id)
188
+ uri = URI.parse("#{host}/transactions/#{CGI.escape(transaction_id.to_s)}.xml" +
189
+ "?provider_key=#{CGI.escape(provider_authentication_key)}")
190
+
191
+ response = Net::HTTP.start(uri.host, uri.port) do |http|
192
+ http.delete("#{uri.path}?#{uri.query}")
193
+ end
194
+
195
+ response.is_a?(Net::HTTPSuccess) ? true : handle_error(response.body)
196
+ end
197
+
198
+ KEY_PREFIX = '3scale-' # :nodoc:
199
+
200
+ # This can be used to quickly distinguish between keys used with 3scale
201
+ # system and any other keys the provider might use. Returns true if the key
202
+ # is for 3scale system.
203
+ def system_key?(key)
204
+ # Key should start with prefix
205
+ key.index(KEY_PREFIX) == 0
206
+ end
207
+
208
+ private
209
+
210
+ # Encode hash into form suitable for sending it as params of HTTP request.
211
+ def encode_params(params, prefix)
212
+ params.inject({}) do |memo, (key, value)|
213
+ memo["#{prefix}[#{CGI.escape(key)}]"] = CGI.escape(value.to_s)
214
+ memo
215
+ end
216
+ end
217
+
218
+ def prepare_key(key)
219
+ system_key?(key) ? key[KEY_PREFIX.length..-1] : key
220
+ end
221
+
222
+ CODES_TO_EXCEPTIONS = {
223
+ 'user.exceeded_limits' => LimitsExceeded,
224
+ 'user.invalid_key' => UserKeyInvalid,
225
+ 'user.inactive_contract' => ContractNotActive,
226
+ 'provider.invalid_key' => ProviderKeyInvalid,
227
+ 'provider.invalid_metric' => MetricInvalid,
228
+ 'provider.invalid_transaction_id' => TransactionNotFound} # :nodoc:
229
+
230
+ def handle_error(response)
231
+ element = Hpricot::XML(response).at('error')
232
+ raise UnknownError unless element
233
+ raise CODES_TO_EXCEPTIONS[element[:id]] || UnknownError, element.inner_text
234
+ end
235
+ end
236
+ end
@@ -0,0 +1 @@
1
+ require "#{File.dirname(__FILE__)}/3scale/interface"
@@ -0,0 +1,218 @@
1
+ require 'rubygems'
2
+ require 'activesupport'
3
+ require 'fake_web'
4
+ require 'mocha'
5
+ require 'test/unit'
6
+
7
+ require "#{File.dirname(__FILE__)}/../lib/3scale/interface"
8
+
9
+ class InterfaceTest < Test::Unit::TestCase
10
+ def setup
11
+ @interface = ThreeScale::Interface.new('http://3scale.net', 'some_key')
12
+ end
13
+
14
+ def test_start_should_raise_exception_on_invalid_user_key
15
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
16
+ :status => ['403', 'Forbidden'],
17
+ :string => stub_error('user.invalid_key'))
18
+
19
+ assert_raise ThreeScale::UserKeyInvalid do
20
+ @interface.start('invalid_key')
21
+ end
22
+ end
23
+
24
+ def test_start_should_raise_exception_on_invalid_provider_key
25
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
26
+ :status => ['403', 'Forbidden'],
27
+ :string => stub_error('provider.invalid_key'))
28
+
29
+ assert_raise ThreeScale::ProviderKeyInvalid do
30
+ @interface.start('valid_key')
31
+ end
32
+ end
33
+
34
+ def test_start_should_raise_exception_on_inactive_contract
35
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
36
+ :status => ['403', 'Forbidden'],
37
+ :string => stub_error('user.inactive_contract'))
38
+
39
+ assert_raise ThreeScale::ContractNotActive do
40
+ @interface.start('valid_key', 'clicks' => 1)
41
+ end
42
+ end
43
+
44
+ def test_start_should_raise_exception_on_invalid_metric
45
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
46
+ :status => ['400', 'Bad Request'],
47
+ :string => stub_error('provider.invalid_metric'))
48
+
49
+ assert_raise ThreeScale::MetricInvalid do
50
+ @interface.start('valid_key', 'clicks' => 1)
51
+ end
52
+ end
53
+
54
+ def test_start_should_raise_exception_on_exceeded_limits
55
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
56
+ :status => ['403', 'Forbidden'],
57
+ :string => stub_error('user.exceeded_limits'))
58
+
59
+ assert_raise ThreeScale::LimitsExceeded do
60
+ @interface.start('valid_key', 'clicks' => 1)
61
+ end
62
+ end
63
+
64
+ def test_start_should_raise_exception_on_unexpected_error
65
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
66
+ :status => ['500', 'Internal Server Error'])
67
+
68
+ assert_raise ThreeScale::UnknownError do
69
+ @interface.start('valid_key', 'clicks' => 1)
70
+ end
71
+ end
72
+
73
+ def test_start_should_send_usage_data
74
+ Net::HTTP.expects(:post_form).
75
+ with(anything, has_entries('usage[hits]' => '1')).
76
+ returns(stub_response)
77
+
78
+ @interface.start('valid_key', 'hits' => 1)
79
+ end
80
+
81
+ def test_start_should_return_transaction_data_on_success
82
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
83
+ :status => ['200', 'OK'],
84
+ :string => {:id => '42', :provider_verification_key => 'some_key',
85
+ :contract_name => 'ultimate'}.to_xml(:root => 'transaction',
86
+ :dasherize => false))
87
+
88
+ result = @interface.start('valid_key', {'clicks' => 1})
89
+
90
+ assert_equal '42', result[:id]
91
+ assert_equal 'some_key', result[:provider_verification_key]
92
+ assert_equal 'ultimate', result[:contract_name]
93
+ end
94
+
95
+ def test_start_should_strip_3scale_prefix_from_user_key_before_sending
96
+ Net::HTTP.expects(:post_form).with(anything,
97
+ has_entries('user_key' => 'foo')).returns(stub_response)
98
+
99
+ @interface.start('3scale-foo')
100
+ end
101
+
102
+ def test_start_should_leave_user_key_unchanged_if_it_does_not_contain_3scale_prefix
103
+ Net::HTTP.expects(:post_form).with(anything,
104
+ has_entries('user_key' => 'foo')).returns(stub_response)
105
+
106
+ @interface.start('foo')
107
+ end
108
+
109
+ def test_confirm_should_raise_exception_on_invalid_transaction
110
+ FakeWeb.register_uri('http://3scale.net/transactions/42/confirm.xml',
111
+ :status => ['404', 'Not Found'],
112
+ :string => stub_error('provider.invalid_transaction_id'))
113
+
114
+ assert_raise ThreeScale::TransactionNotFound do
115
+ @interface.confirm(42)
116
+ end
117
+ end
118
+
119
+ def test_confirm_should_raise_exception_on_invalid_provider_key
120
+ FakeWeb.register_uri('http://3scale.net/transactions/42/confirm.xml',
121
+ :status => ['403', 'Forbidden'],
122
+ :string => stub_error('provider.invalid_key'))
123
+
124
+ assert_raise ThreeScale::ProviderKeyInvalid do
125
+ @interface.confirm(42)
126
+ end
127
+ end
128
+
129
+ def test_confirm_should_raise_exception_on_invalid_metric
130
+ FakeWeb.register_uri('http://3scale.net/transactions/42/confirm.xml',
131
+ :status => ['400', 'Bad Request'],
132
+ :string => stub_error('provider.invalid_metric'))
133
+
134
+ assert_raise ThreeScale::MetricInvalid do
135
+ @interface.confirm(42, 'clicks' => 1)
136
+ end
137
+ end
138
+
139
+ def test_confirm_should_raise_exception_on_unexpected_error
140
+ FakeWeb.register_uri('http://3scale.net/transactions/42/confirm.xml',
141
+ :status => ['500', 'Internal Server Error'])
142
+
143
+ assert_raise ThreeScale::UnknownError do
144
+ @interface.confirm(42)
145
+ end
146
+ end
147
+
148
+ def test_confirm_should_return_true_on_success
149
+ FakeWeb.register_uri('http://3scale.net/transactions/42/confirm.xml',
150
+ :status => ['200', 'OK'])
151
+
152
+ result = @interface.confirm(42, 'clicks' => 1)
153
+ assert_equal true, result
154
+ end
155
+
156
+ def test_confirm_should_send_usage_data
157
+ Net::HTTP.expects(:post_form).
158
+ with(anything, has_entries('usage[hits]' => '1')).
159
+ returns(stub_response)
160
+
161
+ @interface.confirm(42, 'hits' => 1)
162
+ end
163
+
164
+ def test_cancel_should_raise_exception_on_invalid_transaction
165
+ FakeWeb.register_uri('http://3scale.net/transactions/42.xml?provider_key=some_key',
166
+ :status => ['404', 'Not Found'],
167
+ :string => stub_error('provider.invalid_transaction_id'))
168
+
169
+ assert_raise ThreeScale::TransactionNotFound do
170
+ @interface.cancel(42)
171
+ end
172
+ end
173
+
174
+ def test_cancel_should_raise_exception_on_invalid_provider_key
175
+ FakeWeb.register_uri('http://3scale.net/transactions/42.xml?provider_key=some_key',
176
+ :status => ['403', 'Forbidden'],
177
+ :string => stub_error('provider.invalid_key'))
178
+
179
+ assert_raise ThreeScale::ProviderKeyInvalid do
180
+ @interface.cancel(42)
181
+ end
182
+ end
183
+
184
+ def test_cancel_should_raise_exception_on_unexpected_error
185
+ FakeWeb.register_uri('http://3scale.net/transactions/42.xml?provider_key=some_key',
186
+ :status => ['500', 'Internal Server Error'])
187
+
188
+ assert_raise ThreeScale::UnknownError do
189
+ @interface.cancel(42)
190
+ end
191
+ end
192
+
193
+ def test_cancel_should_return_true_on_success
194
+ FakeWeb.register_uri('http://3scale.net/transactions/42.xml?provider_key=some_key',
195
+ :status => ['200', 'OK'])
196
+
197
+ result = @interface.cancel(42)
198
+ assert_equal true, result
199
+ end
200
+
201
+ def test_should_identify_3scale_keys
202
+ assert @interface.system_key?('3scale-foo')
203
+ assert !@interface.system_key?('foo')
204
+ end
205
+
206
+ private
207
+
208
+ def stub_error(id)
209
+ "<error id=\"#{id}\">blah blah</error>"
210
+ end
211
+
212
+ def stub_response
213
+ response = stub
214
+ response.stubs(:is_a?).with(Net::HTTPSuccess).returns(true)
215
+ response.stubs(:body).returns('<transaction></transaction>')
216
+ response
217
+ end
218
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: 3scale-3scale_ws_api_for_ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.7
5
+ platform: ruby
6
+ authors:
7
+ - "Adam Cig\xC3\xA1nek"
8
+ - Josep M. Pujol
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2008-10-27 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: hpricot
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.6.161
24
+ version:
25
+ description:
26
+ email: adam@3scale.net
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ files:
34
+ - init.rb
35
+ - lib/3scale/interface.rb
36
+ - lib/3scale_interface.rb
37
+ - README
38
+ - Rakefile
39
+ - test/interface_test.rb
40
+ has_rdoc: true
41
+ homepage: http://www.3scale.net
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.2.0
63
+ signing_key:
64
+ specification_version: 2
65
+ summary: 3scale web service management API for Ruby.
66
+ test_files:
67
+ - test/interface_test.rb