3scale_client 2.0.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.
- 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
|