3scale_client 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/3scale_client.gemspec +55 -0
- data/LICENCE +22 -0
- data/README.rdoc +131 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/lib/three_scale/authorize_response.rb +39 -0
- data/lib/three_scale/client.rb +213 -0
- data/lib/three_scale/response.rb +21 -0
- data/test/client_test.rb +169 -0
- data/test/remote_test.rb +41 -0
- metadata +86 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{3scale_client}
|
8
|
+
s.version = "2.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Adam Cig\303\241nek"]
|
12
|
+
s.date = %q{2010-04-28}
|
13
|
+
s.description = %q{This gem allows to easily connect an application that provides a Web Service with the 3scale API Management System to authorize it's users and report the usage.
|
14
|
+
}
|
15
|
+
s.email = %q{adam@3scale.net}
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"3scale_client.gemspec",
|
22
|
+
"LICENCE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/three_scale/authorize_response.rb",
|
27
|
+
"lib/three_scale/client.rb",
|
28
|
+
"lib/three_scale/response.rb",
|
29
|
+
"test/client_test.rb",
|
30
|
+
"test/remote_test.rb"
|
31
|
+
]
|
32
|
+
s.homepage = %q{http://www.3scale.net}
|
33
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
s.rubygems_version = %q{1.3.6}
|
36
|
+
s.summary = %q{Client for 3scale Web Service Management System API}
|
37
|
+
s.test_files = [
|
38
|
+
"test/remote_test.rb",
|
39
|
+
"test/client_test.rb"
|
40
|
+
]
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
47
|
+
s.add_runtime_dependency(%q<nokogiri>, [">= 0"])
|
48
|
+
else
|
49
|
+
s.add_dependency(%q<nokogiri>, [">= 0"])
|
50
|
+
end
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<nokogiri>, [">= 0"])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
data/LICENCE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2010 3scale networks S.L.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
22
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
= Client for 3scale web service management system API
|
2
|
+
|
3
|
+
This library provides client for the 3scale web service management API.
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
|
7
|
+
This library is distributed as a gem. First, add gemcutter to your gem source list,
|
8
|
+
unless it's already there:
|
9
|
+
|
10
|
+
gem source --add http://gemcutter.org
|
11
|
+
|
12
|
+
Then install the gem:
|
13
|
+
|
14
|
+
gem install 3scale_client
|
15
|
+
|
16
|
+
Or alternatively, download the source code from github:
|
17
|
+
http://github.com/3scale/3scale_ws_api_for_ruby
|
18
|
+
|
19
|
+
If you are using Rails, put this into your config/environment.rb
|
20
|
+
|
21
|
+
config.gem "3scale_client"
|
22
|
+
|
23
|
+
Otherwise, require the gem in whatever way is natural to your framework of choice.
|
24
|
+
|
25
|
+
== Usage
|
26
|
+
|
27
|
+
First, create an instance of the client, giving it your provider API key:
|
28
|
+
|
29
|
+
client = ThreeScale::Client.new(:provider_key => "your provider key")
|
30
|
+
|
31
|
+
Because the object is stateless, you can create just one and store it globally.
|
32
|
+
|
33
|
+
=== Authorize
|
34
|
+
|
35
|
+
To authorize a particular user, call the +authorize+ method passing it the user key
|
36
|
+
identifiing the user:
|
37
|
+
|
38
|
+
response = client.authorize(:user_key => "the user key")
|
39
|
+
|
40
|
+
Then call the +success?+ method on the returned object to see if the authorization was
|
41
|
+
successful.
|
42
|
+
|
43
|
+
if response.success?
|
44
|
+
# All fine, proceeed.
|
45
|
+
else
|
46
|
+
# Something's wrong with this user.
|
47
|
+
end
|
48
|
+
|
49
|
+
If the authorization succeeded, the response object contains additional information about
|
50
|
+
the status of the user:
|
51
|
+
|
52
|
+
# Returns the name of the plan the user is signed up to.
|
53
|
+
response.plan
|
54
|
+
|
55
|
+
If the plan has defined usage limits, the response contains details about how close the user
|
56
|
+
is to meet these limits:
|
57
|
+
|
58
|
+
# The usages array contains one element per each usage limit defined on the plan.
|
59
|
+
usage = response.usages[0]
|
60
|
+
|
61
|
+
# The metric
|
62
|
+
usage.metric # "hits"
|
63
|
+
|
64
|
+
# The period the limit applies to
|
65
|
+
usage.period # :day
|
66
|
+
usage.period_start # "Wed Apr 28 00:00:00 +0200 2010"
|
67
|
+
usage.period_end # "Wed Apr 28 23:59:59 +0200 2010"
|
68
|
+
|
69
|
+
# The current value the user already consumed in the period
|
70
|
+
usage.current_value # 8032
|
71
|
+
|
72
|
+
# The maximal value allowed by the limit in the period
|
73
|
+
usage.max_value # 10000
|
74
|
+
|
75
|
+
If the authorization failed, the +errors+ method contains one or more errors with more
|
76
|
+
detailed information:
|
77
|
+
|
78
|
+
error = response.errors[0]
|
79
|
+
|
80
|
+
# Error code
|
81
|
+
error.code # "user.exceeded_limits"
|
82
|
+
|
83
|
+
# Human readable error message
|
84
|
+
error.message # "Usage limits are exceeded"
|
85
|
+
|
86
|
+
=== Report
|
87
|
+
|
88
|
+
To report usage, use the +report+ method. You can report multiple transaction at the same time:
|
89
|
+
|
90
|
+
response = client.report({:user_key => "first user's key", :usage => {'hits' => 1}},
|
91
|
+
{:user_key => "second user's key", :usage => {'hits' => 1}})
|
92
|
+
|
93
|
+
The :user_key and :usage parameters are required. Additionaly, you can specify a timestamp
|
94
|
+
of transaction:
|
95
|
+
|
96
|
+
response = client.report({:user_key => "user's key", :usage => {'hits' => 1},
|
97
|
+
:timestamp => Time.local(2010, 4, 28, 12, 36)})
|
98
|
+
|
99
|
+
The timestamp can be either a Time object (from ruby's standard library) or something that
|
100
|
+
"quacks" like it (for example, the ActiveSupport::TimeWithZone from Rails) or a string. The
|
101
|
+
string has to be in a format parseable by the Time.parse method. For example:
|
102
|
+
|
103
|
+
"2010-04-28 12:38:33 +0200"
|
104
|
+
|
105
|
+
If the timestamp is not in UTC, you have to specify a time offset. That's the "+0200"
|
106
|
+
(two hours ahead of the Universal Coordinate Time) in the example abowe.
|
107
|
+
|
108
|
+
Then call the +success?+ method on the returned response object to see if the report was
|
109
|
+
successful.
|
110
|
+
|
111
|
+
if response.success?
|
112
|
+
# All OK.
|
113
|
+
else
|
114
|
+
# There were some errors
|
115
|
+
end
|
116
|
+
|
117
|
+
If the report was successful, you are done. Otherwise, the +errors+ method will return array
|
118
|
+
of errors with more detailed information. Each of these errors has the +index+ attribute that
|
119
|
+
returns numeric index of the transaction this error corresponds to. For example, if you report
|
120
|
+
three transactions, first two are ok and the last one has invalid user key, there will be
|
121
|
+
an error in the +errors+ array with the +index+ equal to 2 (the indices start at 0):
|
122
|
+
|
123
|
+
error = response.errors[0]
|
124
|
+
error.index # 2
|
125
|
+
error.code # "user.invalid_key"
|
126
|
+
error.message # "User key is invalid"
|
127
|
+
|
128
|
+
== Legal
|
129
|
+
|
130
|
+
Copyright (c) 2010 3scale networks S.L., released under the MIT license.
|
131
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Run unit tests.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.pattern = 'test/**/*_test.rb'
|
11
|
+
t.verbose = true
|
12
|
+
t.ruby_opts << '-rubygems'
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = '3scale API Management Client'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
require 'jeweler'
|
26
|
+
|
27
|
+
Jeweler::Tasks.new do |gemspec|
|
28
|
+
gemspec.name = '3scale_client'
|
29
|
+
gemspec.summary = 'Client for 3scale Web Service Management System API'
|
30
|
+
gemspec.description = <<END
|
31
|
+
This gem allows to easily connect an application that provides a Web Service with the 3scale API Management System to authorize it's users and report the usage.
|
32
|
+
END
|
33
|
+
gemspec.email = 'adam@3scale.net'
|
34
|
+
gemspec.homepage = 'http://www.3scale.net'
|
35
|
+
gemspec.authors = ['Adam Cigánek']
|
36
|
+
|
37
|
+
gemspec.add_dependency 'nokogiri'
|
38
|
+
end
|
39
|
+
|
40
|
+
Jeweler::GemcutterTasks.new
|
41
|
+
rescue LoadError
|
42
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
43
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.1
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module ThreeScale
|
4
|
+
class AuthorizeResponse < Response
|
5
|
+
def initialize(options = {})
|
6
|
+
super({:success => true}.merge(options))
|
7
|
+
@usages = options[:usages] || []
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :plan
|
11
|
+
|
12
|
+
class Usage
|
13
|
+
attr_reader :metric
|
14
|
+
attr_reader :period
|
15
|
+
attr_reader :current_value
|
16
|
+
attr_reader :max_value
|
17
|
+
|
18
|
+
def initialize(options = {})
|
19
|
+
options.each do |name, value|
|
20
|
+
instance_variable_set("@#{name}", value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def period_start
|
25
|
+
@parsed_period_start ||= @period_start && Time.parse(@period_start)
|
26
|
+
end
|
27
|
+
|
28
|
+
def period_end
|
29
|
+
@parsed_period_end ||= @period_end && Time.parse(@period_end)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :usages
|
34
|
+
|
35
|
+
def add_usage(options)
|
36
|
+
@usages << Usage.new(options)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'uri'
|
3
|
+
require 'net/http'
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
require 'three_scale/response'
|
7
|
+
require 'three_scale/authorize_response'
|
8
|
+
|
9
|
+
module ThreeScale
|
10
|
+
Error = Class.new(RuntimeError)
|
11
|
+
|
12
|
+
class ServerError < Error
|
13
|
+
def initialize(response)
|
14
|
+
super('server error')
|
15
|
+
@response = response
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :response
|
19
|
+
end
|
20
|
+
|
21
|
+
# Wrapper for 3scale Web Service Management API.
|
22
|
+
#
|
23
|
+
# == Example
|
24
|
+
#
|
25
|
+
# client = ThreeScale::Client.new(:provider_key => "your provider key")
|
26
|
+
#
|
27
|
+
# response = client.authorize(:user_key => "yout user's key")
|
28
|
+
#
|
29
|
+
# if response.success?
|
30
|
+
# response = client.report(:user_key => "your user's key", :usage => {"hits" => 1})
|
31
|
+
#
|
32
|
+
# if response.success?
|
33
|
+
# # all fine.
|
34
|
+
# else
|
35
|
+
# # something's wrong.
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
class Client
|
40
|
+
DEFAULT_HOST = 'server.3scale.net'
|
41
|
+
|
42
|
+
def initialize(options)
|
43
|
+
if options[:provider_key].nil? || options[:provider_key] =~ /^\s*$/
|
44
|
+
raise ArgumentError, 'missing :provider_key'
|
45
|
+
end
|
46
|
+
|
47
|
+
@provider_key = options[:provider_key]
|
48
|
+
@host = options[:host] || DEFAULT_HOST
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_reader :provider_key
|
52
|
+
attr_reader :host
|
53
|
+
|
54
|
+
# Report transaction(s).
|
55
|
+
#
|
56
|
+
# == Parameters
|
57
|
+
#
|
58
|
+
# The parameters the transactions to report. Each transaction is a hash with
|
59
|
+
# these elements:
|
60
|
+
#
|
61
|
+
# user_key:: API key of the user to report the transaction for. This parameter is
|
62
|
+
# required.
|
63
|
+
# usage:: Hash of usage values. The keys are metric names and values are
|
64
|
+
# correspoding numeric values. Example: {'hits' => 1, 'transfer' => 1024}.
|
65
|
+
# This parameter is required.
|
66
|
+
# timestamp:: Timestamp of the transaction. This can be either a object of the
|
67
|
+
# ruby's Time class, or a string in the "YYYY-MM-DD HH:MM:SS" format
|
68
|
+
# (if the time is in the UTC), or a string in
|
69
|
+
# the "YYYY-MM-DD HH:MM:SS ZZZZZ" format, where the ZZZZZ is the time offset
|
70
|
+
# from the UTC. For example, "US Pacific Time" has offset -0800, "Tokyo"
|
71
|
+
# has offset +0900. This parameter is optional, and if not provided, equals
|
72
|
+
# to the current time.
|
73
|
+
#
|
74
|
+
# == Return
|
75
|
+
#
|
76
|
+
# A Response object with method +success?+ that returns true if the report was successful,
|
77
|
+
# or false if there was an error. See ThreeScale::Response class for more information.
|
78
|
+
#
|
79
|
+
# In case of unexpected internal server error, this method raises a ThreeScale::ServerError
|
80
|
+
# exception.
|
81
|
+
#
|
82
|
+
# == Examples
|
83
|
+
#
|
84
|
+
# # Report two transactions of two users.
|
85
|
+
# client.report({:user_key => 'foo', :usage => {'hits' => 1}},
|
86
|
+
# {:user_key => 'bar', :usage => {'hits' => 1}})
|
87
|
+
#
|
88
|
+
# # Report one transaction with timestamp.
|
89
|
+
# client.report({:user_key => 'foo',
|
90
|
+
# :timestamp => Time.local(2010, 4, 27, 15, 14),
|
91
|
+
# :usage => {'hits' => 1})
|
92
|
+
#
|
93
|
+
def report(*transactions)
|
94
|
+
raise ArgumentError, 'no transactions to report' if transactions.empty?
|
95
|
+
|
96
|
+
payload = encode_transactions(transactions)
|
97
|
+
payload['provider_key'] = CGI.escape(provider_key)
|
98
|
+
|
99
|
+
uri = URI.parse("http://#{host}/transactions.xml")
|
100
|
+
|
101
|
+
process_response(Net::HTTP.post_form(uri, payload)) do |http_response|
|
102
|
+
Response.new(:success => true)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Authorize a user.
|
107
|
+
#
|
108
|
+
# == Parameters
|
109
|
+
#
|
110
|
+
# Hash with options:
|
111
|
+
#
|
112
|
+
# user_key:: API key of the user to authorize. This is required.
|
113
|
+
#
|
114
|
+
# == Return
|
115
|
+
#
|
116
|
+
# An ThreeScale::AuthorizeResponse object. It's +success?+ method returns true if
|
117
|
+
# the authorization is successful. In that case, it contains additional information
|
118
|
+
# about the status of the use. See the ThreeScale::AuthorizeResponse for more information.
|
119
|
+
# In case of error, the +success?+ method returns false and the +errors+ contains list
|
120
|
+
# of errors with more details.
|
121
|
+
#
|
122
|
+
# In case of unexpected internal server error, this method raises a ThreeScale::ServerError
|
123
|
+
# exception.
|
124
|
+
#
|
125
|
+
# == Examples
|
126
|
+
#
|
127
|
+
# response = client.authorize(:user_key => 'foo')
|
128
|
+
#
|
129
|
+
# if response.success?
|
130
|
+
# # All good. Proceed...
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
def authorize(options)
|
134
|
+
path = "/transactions/authorize.xml" +
|
135
|
+
"?provider_key=#{CGI.escape(provider_key)}" +
|
136
|
+
"&user_key=#{CGI.escape(options[:user_key].to_s)}"
|
137
|
+
|
138
|
+
uri = URI.parse("http://#{host}#{path}")
|
139
|
+
|
140
|
+
process_response(Net::HTTP.get_response(uri)) do |http_response|
|
141
|
+
build_authorize_response(http_response.body)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def process_response(http_response)
|
148
|
+
case http_response
|
149
|
+
when Net::HTTPSuccess
|
150
|
+
yield(http_response)
|
151
|
+
when Net::HTTPClientError
|
152
|
+
build_error_response(http_response.body)
|
153
|
+
else
|
154
|
+
raise ServerError.new(http_response)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def encode_transactions(transactions)
|
159
|
+
result = {}
|
160
|
+
|
161
|
+
transactions.each_with_index do |transaction, index|
|
162
|
+
append_encoded_value(result, index, [:user_key], transaction[:user_key])
|
163
|
+
append_encoded_value(result, index, [:timestamp], transaction[:timestamp])
|
164
|
+
append_encoded_value(result, index, [:client_ip], transaction[:client_ip])
|
165
|
+
|
166
|
+
transaction[:usage].each do |name, value|
|
167
|
+
append_encoded_value(result, index, [:usage, name], value)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
result
|
172
|
+
end
|
173
|
+
|
174
|
+
def append_encoded_value(result, index, names, value)
|
175
|
+
result["transactions[#{index}][#{names.join('][')}]"] = CGI.escape(value.to_s) if value
|
176
|
+
end
|
177
|
+
|
178
|
+
def build_authorize_response(body)
|
179
|
+
response = AuthorizeResponse.new
|
180
|
+
doc = Nokogiri::XML(body)
|
181
|
+
|
182
|
+
response.plan = doc.at_css('plan').content.to_s.strip
|
183
|
+
|
184
|
+
doc.css('usage').each do |node|
|
185
|
+
response.add_usage(:metric => node['metric'].to_s.strip,
|
186
|
+
:period => node['period'].to_s.strip.to_sym,
|
187
|
+
:period_start => node.at('period_start').content,
|
188
|
+
:period_end => node.at('period_end').content,
|
189
|
+
:current_value => node.at('current_value').content.to_i,
|
190
|
+
:max_value => node.at('max_value').content.to_i)
|
191
|
+
end
|
192
|
+
|
193
|
+
response
|
194
|
+
end
|
195
|
+
|
196
|
+
def build_error_response(body)
|
197
|
+
response = Response.new(:success => false)
|
198
|
+
doc = Nokogiri::XML(body)
|
199
|
+
|
200
|
+
doc.css('error').each do |node|
|
201
|
+
response.add_error(node['index'].to_i,
|
202
|
+
|
203
|
+
# Backwards compatibility: error code is sometimes in
|
204
|
+
# the "id" attribute.
|
205
|
+
node['code'] || node['id'],
|
206
|
+
|
207
|
+
node.content.to_s.strip)
|
208
|
+
end
|
209
|
+
|
210
|
+
response
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ThreeScale
|
2
|
+
class Response
|
3
|
+
def initialize(options)
|
4
|
+
@success = options[:success]
|
5
|
+
@errors = options[:errors] || []
|
6
|
+
end
|
7
|
+
|
8
|
+
def success?
|
9
|
+
@success
|
10
|
+
end
|
11
|
+
|
12
|
+
Error = Struct.new(:index, :code, :message)
|
13
|
+
|
14
|
+
attr_reader :errors
|
15
|
+
|
16
|
+
def add_error(*args)
|
17
|
+
@errors << Error.new(*args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
data/test/client_test.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'fakeweb'
|
3
|
+
require 'mocha'
|
4
|
+
|
5
|
+
require 'three_scale/client'
|
6
|
+
|
7
|
+
class ThreeScale::ClientTest < Test::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
FakeWeb.clean_registry
|
10
|
+
FakeWeb.allow_net_connect = false
|
11
|
+
|
12
|
+
@client = ThreeScale::Client.new(:provider_key => '1234abcd')
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_raises_exception_if_provider_key_is_missing
|
16
|
+
assert_raise ArgumentError do
|
17
|
+
ThreeScale::Client.new({})
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_default_host
|
22
|
+
client = ThreeScale::Client.new(:provider_key => '1234abcd')
|
23
|
+
|
24
|
+
assert_equal 'server.3scale.net', client.host
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_successful_authorize
|
28
|
+
body = '<status>
|
29
|
+
<plan>Ultimate</plan>
|
30
|
+
|
31
|
+
<usage metric="hits" period="day">
|
32
|
+
<period_start>2010-04-26 00:00:00</period_start>
|
33
|
+
<period_end>2010-04-26 23:59:59</period_end>
|
34
|
+
<current_value>10023</current_value>
|
35
|
+
<max_value>50000</max_value>
|
36
|
+
</usage>
|
37
|
+
|
38
|
+
<usage metric="hits" period="month">
|
39
|
+
<period_start>2010-04-01 00:00:00</period_start>
|
40
|
+
<period_end>2010-04-30 23:59:59</period_end>
|
41
|
+
<current_value>999872</current_value>
|
42
|
+
<max_value>150000</max_value>
|
43
|
+
</usage>
|
44
|
+
</status>'
|
45
|
+
|
46
|
+
FakeWeb.register_uri(:get, 'http://server.3scale.net/transactions/authorize.xml?provider_key=1234abcd&user_key=foo', :status => ['200', 'OK'], :body => body)
|
47
|
+
|
48
|
+
response = @client.authorize(:user_key => 'foo')
|
49
|
+
|
50
|
+
assert response.success?
|
51
|
+
assert_equal 'Ultimate', response.plan
|
52
|
+
assert_equal 2, response.usages.size
|
53
|
+
|
54
|
+
assert_equal :day, response.usages[0].period
|
55
|
+
assert_equal Time.local(2010, 4, 26), response.usages[0].period_start
|
56
|
+
assert_equal Time.local(2010, 4, 26, 23, 59, 59), response.usages[0].period_end
|
57
|
+
assert_equal 10023, response.usages[0].current_value
|
58
|
+
assert_equal 50000, response.usages[0].max_value
|
59
|
+
|
60
|
+
assert_equal :month, response.usages[1].period
|
61
|
+
assert_equal Time.local(2010, 4, 1), response.usages[1].period_start
|
62
|
+
assert_equal Time.local(2010, 4, 30, 23, 59, 59), response.usages[1].period_end
|
63
|
+
assert_equal 999872, response.usages[1].current_value
|
64
|
+
assert_equal 150000, response.usages[1].max_value
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_failed_authorize
|
68
|
+
error_body = '<error code="user.exceeded_limits">
|
69
|
+
usage limits are exceeded
|
70
|
+
</error>'
|
71
|
+
|
72
|
+
FakeWeb.register_uri(:get, 'http://server.3scale.net/transactions/authorize.xml?provider_key=1234abcd&user_key=foo', :status => ['403', 'Forbidden'], :body => error_body)
|
73
|
+
|
74
|
+
response = @client.authorize(:user_key => 'foo')
|
75
|
+
|
76
|
+
assert !response.success?
|
77
|
+
assert_equal 1, response.errors.size
|
78
|
+
assert_equal 'user.exceeded_limits', response.errors[0].code
|
79
|
+
assert_equal 'usage limits are exceeded', response.errors[0].message
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_authorize_with_server_error
|
83
|
+
FakeWeb.register_uri(:get, 'http://server.3scale.net/transactions/authorize.xml?provider_key=1234abcd&user_key=foo', :status => ['500', 'Internal Server Error'], :body => 'OMG! WTF!')
|
84
|
+
|
85
|
+
assert_raise ThreeScale::ServerError do
|
86
|
+
@client.authorize(:user_key => 'foo')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_report_raises_an_exception_if_no_transactions_given
|
91
|
+
assert_raise ArgumentError do
|
92
|
+
@client.report
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_successful_report
|
97
|
+
FakeWeb.register_uri(:post, 'http://server.3scale.net/transactions.xml',
|
98
|
+
:status => ['200', 'OK'])
|
99
|
+
|
100
|
+
response = @client.report({:user_key => 'foo',
|
101
|
+
:timestamp => Time.local(2010, 4, 27, 15, 00),
|
102
|
+
:usage => {'hits' => 1}})
|
103
|
+
|
104
|
+
assert response.success?
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_report_encodes_transactions
|
108
|
+
http_response = stub
|
109
|
+
Net::HTTPSuccess.stubs(:===).with(http_response).returns(true)
|
110
|
+
|
111
|
+
Net::HTTP.expects(:post_form).
|
112
|
+
with(anything,
|
113
|
+
'provider_key' => '1234abcd',
|
114
|
+
'transactions[0][user_key]' => 'foo',
|
115
|
+
'transactions[0][usage][hits]' => '1',
|
116
|
+
'transactions[0][timestamp]' => CGI.escape('2010-04-27 15:42:17 0200'),
|
117
|
+
'transactions[1][user_key]' => 'bar',
|
118
|
+
'transactions[1][usage][hits]' => '1',
|
119
|
+
'transactions[1][timestamp]' => CGI.escape('2010-04-27 15:55:12 0200')).
|
120
|
+
returns(http_response)
|
121
|
+
|
122
|
+
@client.report({:user_key => 'foo',
|
123
|
+
:usage => {'hits' => 1},
|
124
|
+
:timestamp => '2010-04-27 15:42:17 0200'},
|
125
|
+
|
126
|
+
{:user_key => 'bar',
|
127
|
+
:usage => {'hits' => 1},
|
128
|
+
:timestamp => '2010-04-27 15:55:12 0200'})
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_failed_report
|
132
|
+
error_body = '<errors>
|
133
|
+
<error code="user.invalid_key" index="0">
|
134
|
+
user key is invalid
|
135
|
+
</error>
|
136
|
+
<error code="provider.invalid_metric" index="1">
|
137
|
+
metric does not exist
|
138
|
+
</error>
|
139
|
+
</errors>'
|
140
|
+
|
141
|
+
FakeWeb.register_uri(:post, 'http://server.3scale.net/transactions.xml',
|
142
|
+
:status => ['403', 'Forbidden'],
|
143
|
+
:body => error_body)
|
144
|
+
|
145
|
+
response = @client.report({:user_key => 'bogus', :usage => {'hits' => 1}},
|
146
|
+
{:user_key => 'bar', :usage => {'monkeys' => 1000000000}})
|
147
|
+
|
148
|
+
assert !response.success?
|
149
|
+
assert_equal 2, response.errors.size
|
150
|
+
|
151
|
+
assert_equal 0, response.errors[0].index
|
152
|
+
assert_equal 'user.invalid_key', response.errors[0].code
|
153
|
+
assert_equal 'user key is invalid', response.errors[0].message
|
154
|
+
|
155
|
+
assert_equal 1, response.errors[1].index
|
156
|
+
assert_equal 'provider.invalid_metric', response.errors[1].code
|
157
|
+
assert_equal 'metric does not exist', response.errors[1].message
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_report_with_server_error
|
161
|
+
FakeWeb.register_uri(:post, 'http://server.3scale.net/transactions.xml',
|
162
|
+
:status => ['500', 'Internal Server Error'],
|
163
|
+
:body => 'OMG! WTF!')
|
164
|
+
|
165
|
+
assert_raise ThreeScale::ServerError do
|
166
|
+
@client.report({:user_key => 'foo', :usage => {'hits' => 1}})
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
data/test/remote_test.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'three_scale/client'
|
3
|
+
|
4
|
+
if ENV['TEST_3SCALE_PROVIDER_KEY'] && ENV['TEST_3SCALE_USER_KEYS']
|
5
|
+
class ThreeScale::RemoteTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@provider_key = ENV['TEST_3SCALE_PROVIDER_KEY']
|
8
|
+
@user_keys = ENV['TEST_3SCALE_USER_KEYS'].split(',').map { |key| key.strip }
|
9
|
+
|
10
|
+
@client = ThreeScale::Client.new(:provider_key => @provider_key)
|
11
|
+
|
12
|
+
if defined?(FakeWeb)
|
13
|
+
FakeWeb.clean_registry
|
14
|
+
FakeWeb.allow_net_connect = true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_successful_authorize
|
19
|
+
response = @client.authorize(:user_key => @user_keys[0])
|
20
|
+
assert response.success?
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_failed_authorize
|
24
|
+
response = @client.authorize(:user_key => 'invalid-user-key')
|
25
|
+
assert !response.success?
|
26
|
+
assert_equal 'user.invalid_key', response.errors[0].code
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_successful_report
|
30
|
+
transactions = @user_keys.map do |user_key|
|
31
|
+
{:user_key => user_key, :usage => {'hits' => 1}}
|
32
|
+
end
|
33
|
+
|
34
|
+
response = @client.report(*transactions)
|
35
|
+
assert response.success?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
else
|
40
|
+
puts "You need to set enviroment variables TEST_3SCALE_PROVIDER_KEY and TEST_3SCALE_USER_KEYS to run this remote test."
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: 3scale_client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 2
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 2.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- "Adam Cig\xC3\xA1nek"
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-04-28 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: nokogiri
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
version_requirements: *id001
|
32
|
+
description: |
|
33
|
+
This gem allows to easily connect an application that provides a Web Service with the 3scale API Management System to authorize it's users and report the usage.
|
34
|
+
|
35
|
+
email: adam@3scale.net
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- README.rdoc
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- 3scale_client.gemspec
|
45
|
+
- LICENCE
|
46
|
+
- README.rdoc
|
47
|
+
- Rakefile
|
48
|
+
- VERSION
|
49
|
+
- lib/three_scale/authorize_response.rb
|
50
|
+
- lib/three_scale/client.rb
|
51
|
+
- lib/three_scale/response.rb
|
52
|
+
- test/client_test.rb
|
53
|
+
- test/remote_test.rb
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://www.3scale.net
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options:
|
60
|
+
- --charset=UTF-8
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.3.6
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: Client for 3scale Web Service Management System API
|
84
|
+
test_files:
|
85
|
+
- test/remote_test.rb
|
86
|
+
- test/client_test.rb
|