myna_eventmachine 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'eventmachine'
4
+ gem 'em-http-request'
5
+ gem 'json'
data/Gemfile.lock ADDED
@@ -0,0 +1,19 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.2.6)
5
+ em-http-request (0.3.0)
6
+ addressable (>= 2.0.0)
7
+ escape_utils
8
+ eventmachine (>= 0.12.9)
9
+ escape_utils (0.2.4)
10
+ eventmachine (0.12.10)
11
+ json (1.6.1)
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ em-http-request
18
+ eventmachine
19
+ json
data/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # Myna Ruby/EventMachine Client
2
+
3
+ This is a Ruby client for the v1 Myna API, using EventMachine for asychronous goodness.
4
+
5
+ Tested in Ruby 1.9.2
6
+
7
+ ## Development Notes
8
+
9
+ The easiest way to install the dependencies is to install [Bundler](http://gembundler.com/) and then run `bundle install`
10
+
11
+ Run `rake test` to run the tests.
12
+
13
+
14
+ ## TODO
15
+
16
+ Futures need to handle failures, and need to rigourously trap exceptions and propagate them
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'rake/testtask'
3
+ require 'bundler'
4
+
5
+ Bundler::GemHelper.install_tasks
6
+
7
+ task :default => [:test]
8
+
9
+ Rake::TestTask.new do |t|
10
+ t.test_files = FileList['test/test*.rb']
11
+ t.verbose = false
12
+ end
@@ -0,0 +1,90 @@
1
+ # Copyright 2011 by Untyped Ltd. All Rights Reserved\
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # No portion of this Software shall be used in any application which does not
14
+ # use the ReportGrid platform to provide some subset of its functionality.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+ #
24
+
25
+ require 'thread'
26
+
27
+
28
+ module Future
29
+ class Future
30
+ def initialize()
31
+ @lock = Mutex.new
32
+ @signal = ConditionVariable.new
33
+ @value = nil
34
+ @delivered = false
35
+ @listeners = []
36
+ end
37
+
38
+ def deliver(value)
39
+ @lock.synchronize {
40
+ if @delivered
41
+ raise ArgumentError, "This future has already been delivered a value"
42
+ else
43
+ @value = value
44
+ @delivered = true
45
+ @signal.broadcast
46
+ end
47
+ }
48
+ @listeners.each do |block|
49
+ block.call(value)
50
+ end
51
+ end
52
+
53
+ def get
54
+ @lock.synchronize {
55
+ if !@delivered
56
+ @signal.wait(@lock)
57
+ @signal.broadcast
58
+ end
59
+
60
+ @value
61
+ }
62
+ end
63
+
64
+ def on_complete(&block)
65
+ return_value = false
66
+ @lock.synchronize {
67
+ if @delivered
68
+ return_value = true
69
+ else
70
+ @listeners.push(block)
71
+ end
72
+ }
73
+
74
+ # We can't call the block inside the synchronise block as the
75
+ # block may itself block on another lock. In this case it won't
76
+ # exit the synchronise block and we can get deadlock
77
+ if return_value
78
+ block.call(@value)
79
+ end
80
+ end
81
+
82
+ def map(&block)
83
+ future = Future.new
84
+ self.on_complete do |value|
85
+ future.deliver(block.call(value))
86
+ end
87
+ future
88
+ end
89
+ end
90
+ end
data/lib/myna/myna.rb ADDED
@@ -0,0 +1,230 @@
1
+ # Copyright 2011 by Untyped Ltd. All Rights Reserved\
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # No portion of this Software shall be used in any application which does not
14
+ # use the ReportGrid platform to provide some subset of its functionality.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+ #
24
+
25
+ require 'eventmachine'
26
+ require 'em-http-request'
27
+ require 'json'
28
+ require 'myna/future'
29
+ require 'myna/response'
30
+
31
+ module Myna
32
+ # Run EventMachine, if it is not already running. Returns a future.
33
+ # Block on the future to wait till EventMachine is up
34
+ def Myna.run()
35
+ future = Future::Future.new
36
+
37
+ if EventMachine::reactor_running?
38
+ future.deliver(true)
39
+ else
40
+ Thread.new do
41
+ EventMachine::run {}
42
+ end
43
+
44
+ EventMachine::next_tick do
45
+ future.deliver(true)
46
+ end
47
+ end
48
+
49
+ future
50
+ end
51
+
52
+ def Myna.stop()
53
+ EventMachine::stop_event_loop
54
+ end
55
+
56
+ module AuthorizedHost
57
+ SCHEME = "https"
58
+ HOST = 'api.mynaweb.com'
59
+ PORT = 443
60
+ end
61
+
62
+ module UnauthorizedHost
63
+ SCHEME = "http"
64
+ HOST = 'api.mynaweb.com'
65
+ PORT = 80
66
+ end
67
+
68
+ class Api
69
+ def initialize(api)
70
+ @api = api
71
+ end
72
+
73
+ def make_request(uri)
74
+ # puts "Contacting "+uri
75
+ future = Future::Future.new
76
+
77
+ EventMachine::schedule do
78
+ client = EventMachine::HttpRequest.new(uri)
79
+
80
+ http = yield(client)
81
+ http.errback { future.deliver(Response::NetworkError.new) }
82
+ http.callback { future.deliver(Response.parse(http.response)) }
83
+ end
84
+
85
+ future
86
+ end
87
+
88
+ def build_uri(path)
89
+ "#{@api::SCHEME}://#{@api::HOST}:#{@api::PORT}#{path}"
90
+ end
91
+
92
+ def create
93
+ build_uri("/v1/experiment/new")
94
+ end
95
+
96
+ def suggest(uuid)
97
+ build_uri("/v1/experiment/#{uuid}/suggest")
98
+ end
99
+
100
+ def reward(uuid)
101
+ build_uri("/v1/experiment/#{uuid}/reward")
102
+ end
103
+
104
+ def delete(uuid)
105
+ build_uri("/v1/experiment/#{uuid}/delete")
106
+ end
107
+
108
+ def info(uuid)
109
+ build_uri("/v1/experiment/#{uuid}/info")
110
+ end
111
+
112
+ def create_variant(uuid)
113
+ build_uri("/v1/experiment/#{uuid}/new-variant")
114
+ end
115
+
116
+ def delete_variant(uuid)
117
+ build_uri("/v1/experiment/#{uuid}/delete-variant")
118
+ end
119
+ end
120
+
121
+ # The simple API for unauthorized access
122
+ class Experiment
123
+ def initialize(uuid, host = UnauthorizedHost)
124
+ @uuid = uuid
125
+ @host = host
126
+ @api = Api.new(host)
127
+ end
128
+
129
+ attr_reader :uuid
130
+
131
+ def suggest()
132
+ @api.make_request(@api.suggest(@uuid)) do |client|
133
+ client.get(:head => {'Accept' => 'application/json'})
134
+ end
135
+ end
136
+
137
+ def reward(token, amount = 1.0)
138
+ @api.make_request(@api.reward(@uuid)) do |client|
139
+ client.post(:head => {'Accept' => 'application/json'},
140
+ :body => {:token => token, :amount => amount}.to_json)
141
+ end
142
+ end
143
+ end
144
+
145
+ def Myna.authorize(email, password, host = AuthorizedHost)
146
+ ExperimentFactory.new(email, password, host)
147
+ end
148
+
149
+ # The more complex API for authorized access
150
+ class ExperimentFactory
151
+ def initialize(email, password, host = AuthorizedHost)
152
+ @email = email
153
+ @password = password
154
+ @host = host
155
+ @api = Api.new(host)
156
+ end
157
+
158
+ def create(name)
159
+ future =
160
+ @api.make_request(@api.create()) do |client|
161
+ client.post(:head => {
162
+ 'Accept' => 'application/json',
163
+ 'Authorization' => [@email, @password]
164
+ },
165
+ :body => {:experiment => name}.to_json)
166
+
167
+ end
168
+
169
+ future.map do |response|
170
+ if response.kind_of? Response::Uuid
171
+ AuthorizedExperiment.new(response.uuid, @email, @password, @host)
172
+ else
173
+ response
174
+ end
175
+ end
176
+ end
177
+
178
+ def experiment(uuid)
179
+ AuthorizedExperiment.new(uuid, @email, @password, @host)
180
+ end
181
+ end
182
+
183
+ class AuthorizedExperiment < Experiment
184
+ def initialize(uuid, email, password, host)
185
+ super(uuid, host)
186
+ @email = email
187
+ @password = password
188
+ end
189
+
190
+ def delete
191
+ @api.make_request(@api.delete(@uuid)) do |client|
192
+ client.get(:head => {
193
+ 'Accept' => 'application/json',
194
+ 'Authorization' => [@email, @password]
195
+ })
196
+ end
197
+ end
198
+
199
+ def info
200
+ @api.make_request(@api.info(@uuid)) do |client|
201
+ client.get(:head => {
202
+ 'Accept' => 'application/json',
203
+ 'Authorization' => [@email, @password]
204
+ })
205
+ end
206
+ end
207
+
208
+ def create_variant(name)
209
+ @api.make_request(@api.create_variant(@uuid)) do |client|
210
+ client.post(:head => {
211
+ 'Accept' => 'application/json',
212
+ 'Authorization' => [@email, @password]
213
+ },
214
+ :body => {:variant => name}.to_json)
215
+
216
+ end
217
+ end
218
+
219
+ def delete_variant(name)
220
+ @api.make_request(@api.delete_variant(@uuid)) do |client|
221
+ client.post(:head => {
222
+ 'Accept' => 'application/json',
223
+ 'Authorization' => [@email, @password]
224
+ },
225
+ :body => {:variant => name}.to_json)
226
+ end
227
+ end
228
+ end
229
+
230
+ end
@@ -0,0 +1,141 @@
1
+ # Copyright 2011 by Untyped Ltd. All Rights Reserved\
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # No portion of this Software shall be used in any application which does not
14
+ # use the ReportGrid platform to provide some subset of its functionality.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+ #
24
+
25
+ require 'json'
26
+ require 'singleton'
27
+
28
+ module Response
29
+ # String -> Response
30
+ #
31
+ # Parse a JSON string into a Response object
32
+ def Response.parse(str)
33
+ json = JSON.parse str
34
+ case json['typename']
35
+ when 'problem'
36
+ ApiError.from_json(json)
37
+ when 'ok'
38
+ Ok.instance
39
+ when 'suggestion'
40
+ Suggestion.from_json(json)
41
+ when 'uuid'
42
+ Uuid.from_json(json)
43
+ when 'experiment'
44
+ Experiment.from_json(json)
45
+ else
46
+ raise ArgumentError, "Typename "+json['typename']+" is not a recognized response.", caller
47
+ end
48
+ end
49
+
50
+
51
+ class Response
52
+ end
53
+
54
+ class MynaError < Response
55
+ end
56
+
57
+ class NetworkError < MynaError
58
+ end
59
+
60
+ class ApiError < MynaError
61
+ def self.from_json(json)
62
+ error = ApiError.new(json['subtype'], json['messages'])
63
+ end
64
+
65
+ attr_reader :subtype
66
+ attr_reader :messages
67
+
68
+ def initialize(subtype, messages)
69
+ @subtype = subtype
70
+ @messages = messages
71
+ end
72
+ end
73
+
74
+ # Singleton
75
+ class Ok < Response
76
+ include Singleton
77
+ end
78
+
79
+ class Suggestion < Response
80
+ def self.from_json(json)
81
+ suggestion = Suggestion.new(json['token'], json['choice'])
82
+ end
83
+
84
+ attr_reader :token
85
+ attr_reader :choice
86
+
87
+ def initialize(token, choice)
88
+ @token = token
89
+ @choice = choice
90
+ end
91
+ end
92
+
93
+ class Uuid < Response
94
+ def self.from_json(json)
95
+ uuid = Uuid.new(json['uuid'])
96
+ end
97
+
98
+ attr_reader :uuid
99
+
100
+ def initialize(uuid)
101
+ @uuid = uuid
102
+ end
103
+ end
104
+
105
+ class Experiment < Response
106
+ def self.from_json(json)
107
+ variants = json['variants'].map { |variant| Variant.from_json(variant) }
108
+ experiment = Experiment.new(json['name'], json['uuid'], json['accountId'], variants)
109
+ end
110
+
111
+ attr_reader :name
112
+ attr_reader :uuid
113
+ attr_reader :accountId
114
+ attr_reader :variants
115
+
116
+ def initialize(name, uuid, accountId, variants)
117
+ @name = name
118
+ @uuid = uuid
119
+ @accountId = accountId
120
+ @variants = variants
121
+ end
122
+ end
123
+
124
+ class Variant < Response
125
+ def self.from_json(json)
126
+ variant = Variant.new(json['name'], json['views'], json['totalReward'], json['confidenceBound'])
127
+ end
128
+
129
+ attr_reader :name
130
+ attr_reader :views
131
+ attr_reader :totalReward
132
+ attr_reader :confidenceBound
133
+
134
+ def initialize(name, views, totalReward, confidenceBound)
135
+ @name = name
136
+ @views = views
137
+ @totalReward = totalReward
138
+ @confidenceBound = confidenceBound
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "myna/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'myna_eventmachine'
7
+ s.version = Myna::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.date = '2012-05-11'
10
+ s.summary = 'Ruby/EventMachine client library for Myna (http://www.mynaweb.com)'
11
+ s.authors = ["Noel Welsh"]
12
+ s.description = 'An EventMachine based client for '
13
+ s.email = ["noel [at] untyped [dot] com"]
14
+ s.files = ["lib/myna.rb"]
15
+ s.homepage = 'http://www.mynaweb.com/'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,78 @@
1
+ # Copyright 2011 by Untyped Ltd. All Rights Reserved\
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # No portion of this Software shall be used in any application which does not
14
+ # use the ReportGrid platform to provide some subset of its functionality.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+ #
24
+
25
+ require 'minitest/autorun'
26
+ require 'myna'
27
+
28
+ describe Myna do
29
+
30
+ before do
31
+ Myna.run.get
32
+ end
33
+
34
+ module TestHost
35
+ SCHEME = "http"
36
+ HOST = 'localhost'
37
+ PORT = 8080
38
+ end
39
+
40
+ describe "create and delete" do
41
+ it "must return an Experiment and delete same experiment" do
42
+ myna = Myna.authorize("test@example.com", "themostsecretpassword")
43
+
44
+ expt = myna.create("dummy experimento").get
45
+ expt.must_be_kind_of Myna::AuthorizedExperiment
46
+
47
+ expt.delete().get.must_be_kind_of Response::Ok
48
+ end
49
+ end
50
+
51
+ describe "create_variant and delete_variant" do
52
+ it "must create and delete variants" do
53
+ myna = Myna.authorize("test@example.com", "themostsecretpassword")
54
+
55
+ expt = myna.create("dummy experimento").get
56
+
57
+ expt.create_variant("An awesome variant").get.must_be_kind_of Response::Ok
58
+ expt.delete_variant("An awesome variant").get.must_be_kind_of Response::Ok
59
+
60
+ expt.delete().get.must_be_kind_of Response::Ok
61
+ end
62
+ end
63
+
64
+ describe "info" do
65
+ it "must return the expected info" do
66
+ myna = Myna.authorize("test@example.com", "themostsecretpassword")
67
+
68
+ info = myna.experiment('45923780-80ed-47c6-aa46-15e2ae7a0e8c').info().get
69
+
70
+ info.must_be_kind_of Response::Experiment
71
+ info.name.must_equal "test"
72
+ info.uuid.must_equal '45923780-80ed-47c6-aa46-15e2ae7a0e8c'
73
+ info.variants.length.must_equal 2
74
+ info.variants[0].name.must_equal 'variant1'
75
+ info.variants[1].name.must_equal 'variant2'
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,54 @@
1
+ # Copyright 2011 by Untyped Ltd. All Rights Reserved\
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # No portion of this Software shall be used in any application which does not
14
+ # use the ReportGrid platform to provide some subset of its functionality.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+ #
24
+
25
+ require 'minitest/autorun'
26
+ require 'myna/future'
27
+
28
+ describe Future do
29
+ describe "Future.map" do
30
+ it "must run the given block and complete the returned future" do
31
+ f1 = Future::Future.new
32
+ f2 = f1.map do |v|
33
+ v + 3
34
+ end
35
+
36
+ f1.deliver(1)
37
+ f2.get.must_equal 4
38
+ end
39
+
40
+ it "must run the given block and complete the returned future from another thread" do
41
+ f1 = Future::Future.new
42
+ f2 = f1.map do |v|
43
+ v + 3
44
+ end
45
+
46
+ Thread.new do
47
+ sleep(1)
48
+ f1.deliver(1)
49
+ end
50
+
51
+ f2.get.must_equal 4
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,101 @@
1
+ # Copyright 2011 by Untyped Ltd. All Rights Reserved\
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # No portion of this Software shall be used in any application which does not
14
+ # use the ReportGrid platform to provide some subset of its functionality.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+ #
24
+
25
+ require 'minitest/autorun'
26
+ require 'myna/response'
27
+
28
+ describe Response do
29
+
30
+ describe "parse" do
31
+ it "must parse an error" do
32
+ txt = '{"typename":"problem","subtype":400,"messages":{"typename":"message", "info":"Experiment cannot be found"}}'
33
+ msg = {"typename" => "message", "info" => "Experiment cannot be found"}
34
+ ans = Response.parse(txt)
35
+
36
+
37
+ ans.must_be_kind_of Response::ApiError
38
+ ans.subtype.must_equal 400
39
+ ans.messages.must_equal msg
40
+ end
41
+
42
+ it "must parse ok" do
43
+ ans = Response.parse('{"typename":"ok"}')
44
+
45
+ ans.must_be_kind_of Response::Ok
46
+ end
47
+
48
+ it "must parse a suggestion" do
49
+ txt = '{"typename":"suggestion","token":"871ec6f8-bea7-4a51-ba48-33bdfea9feb2","choice":"variant2"}'
50
+ ans = Response.parse(txt)
51
+
52
+ ans.must_be_kind_of Response::Suggestion
53
+ ans.token.must_equal '871ec6f8-bea7-4a51-ba48-33bdfea9feb2'
54
+ ans.choice.must_equal 'variant2'
55
+ end
56
+
57
+ it "must parse a uuid" do
58
+ ans = Response.parse('{"typename":"uuid","uuid":"f8d9eb79-3d8f-41b0-9044-605faddd959c"}')
59
+
60
+ ans.must_be_kind_of Response::Uuid
61
+ ans.uuid.must_equal 'f8d9eb79-3d8f-41b0-9044-605faddd959c'
62
+ end
63
+
64
+ it "must parse an experiment" do
65
+ txt = '{"typename":"experiment",
66
+ "name":"test",
67
+ "uuid":"45923780-80ed-47c6-aa46-15e2ae7a0e8c",
68
+ "accountId":"eb1fb383-4172-422a-b680-8031cf26a23e",
69
+ "variants":[
70
+ {"typename":"variant",
71
+ "name":"variant1",
72
+ "views":36,
73
+ "totalReward":1.0,
74
+ "confidenceBound":0.252904521564191},
75
+ {"typename":"variant",
76
+ "name":"variant2",
77
+ "views":9672,
78
+ "totalReward":0.0,
79
+ "confidenceBound":0.015429423531830728}]}'
80
+ ans = Response.parse(txt)
81
+
82
+ ans.must_be_kind_of Response::Experiment
83
+ ans.name.must_equal "test"
84
+ ans.uuid.must_equal "45923780-80ed-47c6-aa46-15e2ae7a0e8c"
85
+ ans.accountId.must_equal "eb1fb383-4172-422a-b680-8031cf26a23e"
86
+
87
+ v0 = ans.variants[0]
88
+ v1 = ans.variants[1]
89
+
90
+ v0.name.must_equal "variant1"
91
+ v0.views.must_equal 36
92
+ v0.totalReward.must_equal 1.0
93
+ v0.confidenceBound.must_equal 0.252904521564191
94
+
95
+ v1.name.must_equal "variant2"
96
+ v1.views.must_equal 9672
97
+ v1.totalReward.must_equal 0.0
98
+ v1.confidenceBound.must_equal 0.015429423531830728
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,62 @@
1
+ # Copyright 2011 by Untyped Ltd. All Rights Reserved\
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # No portion of this Software shall be used in any application which does not
14
+ # use the ReportGrid platform to provide some subset of its functionality.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+ #
24
+
25
+ require 'minitest/autorun'
26
+ require 'myna'
27
+
28
+ describe Myna do
29
+
30
+ before do
31
+ Myna.run.get
32
+ end
33
+
34
+ after do
35
+ end
36
+
37
+ describe "suggest" do
38
+ it "must return a suggestion for a valid experiment" do
39
+ expt = Myna::Experiment.new('45923780-80ed-47c6-aa46-15e2ae7a0e8c')
40
+ ans = expt.suggest
41
+ ans.get.must_be_kind_of Response::Suggestion
42
+ end
43
+
44
+ it "must return an error for an invalid experiment" do
45
+ expt = Myna::Experiment.new('bogus')
46
+ ans = expt.suggest
47
+ ans.get.must_be_kind_of Response::ApiError
48
+ end
49
+ end
50
+
51
+ describe "reward" do
52
+ it "must succeed when given correct token and amount" do
53
+ expt = Myna::Experiment.new('45923780-80ed-47c6-aa46-15e2ae7a0e8c')
54
+ suggestion = expt.suggest.get
55
+
56
+ ans = expt.reward(suggestion.token, 0.5)
57
+ ans.get
58
+ ans.get.must_be_kind_of Response::Ok
59
+ end
60
+ end
61
+
62
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: myna_eventmachine
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Noel Welsh
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-11 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: ! 'An EventMachine based client for '
15
+ email:
16
+ - noel [at] untyped [dot] com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - Gemfile
22
+ - Gemfile.lock
23
+ - README.md
24
+ - Rakefile
25
+ - lib/myna/future.rb
26
+ - lib/myna/myna.rb
27
+ - lib/myna/response.rb
28
+ - myna_eventmachine.gemspec
29
+ - test/test_authorized.rb
30
+ - test/test_future.rb
31
+ - test/test_response.rb
32
+ - test/test_suggest.rb
33
+ homepage: http://www.mynaweb.com/
34
+ licenses: []
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project:
53
+ rubygems_version: 1.8.10
54
+ signing_key:
55
+ specification_version: 3
56
+ summary: Ruby/EventMachine client library for Myna (http://www.mynaweb.com)
57
+ test_files:
58
+ - test/test_authorized.rb
59
+ - test/test_future.rb
60
+ - test/test_response.rb
61
+ - test/test_suggest.rb