rainforest 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.travis.yml +10 -0
  4. data/CONTRIBUTORS +1 -0
  5. data/Gemfile +2 -0
  6. data/History.txt +4 -0
  7. data/LICENSE +21 -0
  8. data/README.md +50 -0
  9. data/Rakefile +15 -0
  10. data/VERSION +1 -0
  11. data/bin/rainforest-console +7 -0
  12. data/gemfiles/default-with-activesupport.gemfile +3 -0
  13. data/gemfiles/json.gemfile +4 -0
  14. data/gemfiles/yajl.gemfile +4 -0
  15. data/lib/.DS_Store +0 -0
  16. data/lib/data/ca-certificates.crt +3918 -0
  17. data/lib/rainforest.rb +271 -0
  18. data/lib/rainforest/api_operations/create.rb +16 -0
  19. data/lib/rainforest/api_operations/delete.rb +11 -0
  20. data/lib/rainforest/api_operations/list.rb +18 -0
  21. data/lib/rainforest/api_operations/update.rb +61 -0
  22. data/lib/rainforest/api_resource.rb +33 -0
  23. data/lib/rainforest/errors/api_connection_error.rb +4 -0
  24. data/lib/rainforest/errors/api_error.rb +4 -0
  25. data/lib/rainforest/errors/authentication_error.rb +4 -0
  26. data/lib/rainforest/errors/invalid_request_error.rb +10 -0
  27. data/lib/rainforest/errors/rainforest_error.rb +20 -0
  28. data/lib/rainforest/json.rb +21 -0
  29. data/lib/rainforest/list_object.rb +35 -0
  30. data/lib/rainforest/rainforest_object.rb +168 -0
  31. data/lib/rainforest/run.rb +8 -0
  32. data/lib/rainforest/singleton_api_resource.rb +20 -0
  33. data/lib/rainforest/test.rb +14 -0
  34. data/lib/rainforest/util.rb +101 -0
  35. data/lib/rainforest/version.rb +3 -0
  36. data/rainforest.gemspec +26 -0
  37. data/test/stripe/account_test.rb +14 -0
  38. data/test/stripe/api_resource_test.rb +345 -0
  39. data/test/stripe/charge_test.rb +67 -0
  40. data/test/stripe/coupon_test.rb +11 -0
  41. data/test/stripe/customer_test.rb +70 -0
  42. data/test/stripe/invoice_test.rb +20 -0
  43. data/test/stripe/list_object_test.rb +16 -0
  44. data/test/stripe/metadata_test.rb +114 -0
  45. data/test/stripe/util_test.rb +29 -0
  46. data/test/test_helper.rb +356 -0
  47. metadata +191 -0
@@ -0,0 +1,20 @@
1
+ module Rainforest
2
+ class RainforestError < StandardError
3
+ attr_reader :message
4
+ attr_reader :http_status
5
+ attr_reader :http_body
6
+ attr_reader :json_body
7
+
8
+ def initialize(message=nil, http_status=nil, http_body=nil, json_body=nil)
9
+ @message = message
10
+ @http_status = http_status
11
+ @http_body = http_body
12
+ @json_body = json_body
13
+ end
14
+
15
+ def to_s
16
+ status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "
17
+ "#{status_string}#{@message}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ module Rainforest
2
+ module JSON
3
+ if MultiJson.respond_to?(:dump)
4
+ def self.dump(*args)
5
+ MultiJson.dump(*args)
6
+ end
7
+
8
+ def self.load(*args)
9
+ MultiJson.load(*args)
10
+ end
11
+ else
12
+ def self.dump(*args)
13
+ MultiJson.encode(*args)
14
+ end
15
+
16
+ def self.load(*args)
17
+ MultiJson.decode(*args)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ module Rainforest
2
+ class ListObject < RainforestObject
3
+
4
+ def [](k)
5
+ case k
6
+ when String, Symbol
7
+ super
8
+ else
9
+ raise ArgumentError.new("You tried to access the #{k.inspect} index, but ListObject types only support String keys. (HINT: List calls return an object with a 'data' (which is the data array). You likely want to call #data[#{k.inspect}])")
10
+ end
11
+ end
12
+
13
+ def each(&blk)
14
+ self.data.each(&blk)
15
+ end
16
+
17
+ def retrieve(id, api_key=nil)
18
+ api_key ||= @api_key
19
+ response, api_key = Rainforest.request(:get,"#{url}/#{CGI.escape(id)}", api_key)
20
+ Util.convert_to_rainforest_object(response, api_key)
21
+ end
22
+
23
+ def create(params={}, api_key=nil)
24
+ api_key ||= @api_key
25
+ response, api_key = Rainforest.request(:post, url, api_key, params)
26
+ Util.convert_to_rainforest_object(response, api_key)
27
+ end
28
+
29
+ def all(params={}, api_key=nil)
30
+ api_key ||= @api_key
31
+ response, api_key = Rainforest.request(:get, url, api_key, params)
32
+ Util.convert_to_rainforest_object(response, api_key)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,168 @@
1
+ module Rainforest
2
+ class RainforestObject
3
+ include Enumerable
4
+
5
+ attr_accessor :api_key
6
+ @@permanent_attributes = Set.new([:api_key, :id])
7
+
8
+ # The default :id method is deprecated and isn't useful to us
9
+ if method_defined?(:id)
10
+ undef :id
11
+ end
12
+
13
+ def initialize(id=nil, api_key=nil)
14
+ # parameter overloading!
15
+ if id.kind_of?(Hash)
16
+ @retrieve_options = id.dup
17
+ @retrieve_options.delete(:id)
18
+ id = id[:id]
19
+ else
20
+ @retrieve_options = {}
21
+ end
22
+
23
+ @api_key = api_key
24
+ @values = {}
25
+ # This really belongs in APIResource, but not putting it there allows us
26
+ # to have a unified inspect method
27
+ @unsaved_values = Set.new
28
+ @transient_values = Set.new
29
+ @values[:id] = id if id
30
+ end
31
+
32
+ def self.construct_from(values, api_key=nil)
33
+ obj = self.new(values[:id], api_key)
34
+ obj.refresh_from(values, api_key)
35
+ obj
36
+ end
37
+
38
+ def to_s(*args)
39
+ Rainforest::JSON.dump(@values, :pretty => true)
40
+ end
41
+
42
+ def inspect()
43
+ id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
44
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> JSON: " + Rainforest::JSON.dump(@values, :pretty => true)
45
+ end
46
+
47
+ def refresh_from(values, api_key, partial=false)
48
+ @api_key = api_key
49
+
50
+ @previous_metadata = values[:metadata]
51
+ removed = partial ? Set.new : Set.new(@values.keys - values.keys)
52
+ added = Set.new(values.keys - @values.keys)
53
+ # Wipe old state before setting new. This is useful for e.g. updating a
54
+ # customer, where there is no persistent card parameter. Mark those values
55
+ # which don't persist as transient
56
+
57
+ instance_eval do
58
+ remove_accessors(removed)
59
+ add_accessors(added)
60
+ end
61
+ removed.each do |k|
62
+ @values.delete(k)
63
+ @transient_values.add(k)
64
+ @unsaved_values.delete(k)
65
+ end
66
+ values.each do |k, v|
67
+ @values[k] = Util.convert_to_rainforest_object(v, api_key)
68
+ @transient_values.delete(k)
69
+ @unsaved_values.delete(k)
70
+ end
71
+ end
72
+
73
+ def [](k)
74
+ @values[k.to_sym]
75
+ end
76
+
77
+ def []=(k, v)
78
+ send(:"#{k}=", v)
79
+ end
80
+
81
+ def keys
82
+ @values.keys
83
+ end
84
+
85
+ def values
86
+ @values.values
87
+ end
88
+
89
+ def to_json(*a)
90
+ Rainforest::JSON.dump(@values)
91
+ end
92
+
93
+ def as_json(*a)
94
+ @values.as_json(*a)
95
+ end
96
+
97
+ def to_hash
98
+ @values
99
+ end
100
+
101
+ def each(&blk)
102
+ @values.each(&blk)
103
+ end
104
+
105
+ protected
106
+
107
+ def metaclass
108
+ class << self; self; end
109
+ end
110
+
111
+ def remove_accessors(keys)
112
+ metaclass.instance_eval do
113
+ keys.each do |k|
114
+ next if @@permanent_attributes.include?(k)
115
+ k_eq = :"#{k}="
116
+ remove_method(k) if method_defined?(k)
117
+ remove_method(k_eq) if method_defined?(k_eq)
118
+ end
119
+ end
120
+ end
121
+
122
+ def add_accessors(keys)
123
+ metaclass.instance_eval do
124
+ keys.each do |k|
125
+ next if @@permanent_attributes.include?(k)
126
+ k_eq = :"#{k}="
127
+ define_method(k) { @values[k] }
128
+ define_method(k_eq) do |v|
129
+ if v == ""
130
+ raise ArgumentError.new(
131
+ "You cannot set #{k} to an empty string." +
132
+ "We interpret empty strings as nil in requests." +
133
+ "You may set #{self}.#{k} = nil to delete the property.")
134
+ end
135
+ @values[k] = v
136
+ @unsaved_values.add(k)
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ def method_missing(name, *args)
143
+ # TODO: only allow setting in updateable classes.
144
+ if name.to_s.end_with?('=')
145
+ attr = name.to_s[0...-1].to_sym
146
+ add_accessors([attr])
147
+ begin
148
+ mth = method(name)
149
+ rescue NameError
150
+ raise NoMethodError.new("Cannot set #{attr} on this object. HINT: you can't set: #{@@permanent_attributes.to_a.join(', ')}")
151
+ end
152
+ return mth.call(args[0])
153
+ else
154
+ return @values[name] if @values.has_key?(name)
155
+ end
156
+
157
+ begin
158
+ super
159
+ rescue NoMethodError => e
160
+ if @transient_values.include?(name)
161
+ raise NoMethodError.new(e.message + ". HINT: The '#{name}' attribute was set in the past, however. It was then wiped when refreshing the object with the result returned by Rainforest's API, probably as a result of a save(). The attributes currently available on this object are: #{@values.keys.join(', ')}")
162
+ else
163
+ raise
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,8 @@
1
+ module Rainforest
2
+ class Run < APIResource
3
+ include Rainforest::APIOperations::Create
4
+ include Rainforest::APIOperations::Delete
5
+ include Rainforest::APIOperations::Update
6
+ include Rainforest::APIOperations::List
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ module Rainforest
2
+ class SingletonAPIResource < APIResource
3
+ def self.url()
4
+ if self == SingletonAPIResource
5
+ raise NotImplementedError.new('SingletonAPIResource is an abstract class. You should perform actions on its subclasses (Account, etc.)')
6
+ end
7
+ "/v1/#{CGI.escape(class_name.downcase)}"
8
+ end
9
+
10
+ def url
11
+ self.class.url
12
+ end
13
+
14
+ def self.retrieve(api_key=nil)
15
+ instance = self.new(nil, api_key)
16
+ instance.refresh
17
+ instance
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ module Rainforest
2
+ class Test < APIResource
3
+ include Rainforest::APIOperations::Create
4
+ include Rainforest::APIOperations::Delete
5
+ include Rainforest::APIOperations::Update
6
+ include Rainforest::APIOperations::List
7
+
8
+ def self.tags(filters={}, api_key=nil)
9
+ response, api_key = Rainforest.request(:get, url + "/tags", api_key, filters)
10
+ response
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,101 @@
1
+ module Rainforest
2
+ module Util
3
+ def self.objects_to_ids(h)
4
+ case h
5
+ when APIResource
6
+ h.id
7
+ when Hash
8
+ res = {}
9
+ h.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
10
+ res
11
+ when Array
12
+ h.map { |v| objects_to_ids(v) }
13
+ else
14
+ h
15
+ end
16
+ end
17
+
18
+ def self.object_classes
19
+ @object_classes ||= {
20
+ 'test' => Test,
21
+ 'run' => Run,
22
+ 'list' => ListObject
23
+ }
24
+ end
25
+
26
+ # TODO(jon): Suggest an object attribute be returned instead of this.
27
+ def self.convert_to_rainforest_object(resp, api_key, klass = nil)
28
+ case resp
29
+ when Array
30
+ resp.map { |i| convert_to_rainforest_object(i, api_key, klass) }
31
+ when Hash
32
+ # Try converting to a known object class. If none available, fall back to generic RainforestObject
33
+ object_classes.fetch(klass || resp[:object], RainforestObject).construct_from(resp, api_key)
34
+ else
35
+ resp
36
+ end
37
+ end
38
+
39
+ def self.file_readable(file)
40
+ # This is nominally equivalent to File.readable?, but that can
41
+ # report incorrect results on some more oddball filesystems
42
+ # (such as AFS)
43
+ begin
44
+ File.open(file) { |f| }
45
+ rescue
46
+ false
47
+ else
48
+ true
49
+ end
50
+ end
51
+
52
+ def self.symbolize_names(object)
53
+ case object
54
+ when Hash
55
+ new = {}
56
+ object.each do |key, value|
57
+ key = (key.to_sym rescue key) || key
58
+ new[key] = symbolize_names(value)
59
+ end
60
+ new
61
+ when Array
62
+ object.map { |value| symbolize_names(value) }
63
+ else
64
+ object
65
+ end
66
+ end
67
+
68
+ def self.url_encode(key)
69
+ URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
70
+ end
71
+
72
+ def self.flatten_params(params, parent_key=nil)
73
+ result = []
74
+ params.each do |key, value|
75
+ calculated_key = parent_key ? "#{parent_key}[#{url_encode(key)}]" : url_encode(key)
76
+ if value.is_a?(Hash)
77
+ result += flatten_params(value, calculated_key)
78
+ elsif value.is_a?(Array)
79
+ result += flatten_params_array(value, calculated_key)
80
+ else
81
+ result << [calculated_key, value]
82
+ end
83
+ end
84
+ result
85
+ end
86
+
87
+ def self.flatten_params_array(value, calculated_key)
88
+ result = []
89
+ value.each do |elem|
90
+ if elem.is_a?(Hash)
91
+ result += flatten_params(elem, calculated_key)
92
+ elsif elem.is_a?(Array)
93
+ result += flatten_params_array(elem, calculated_key)
94
+ else
95
+ result << ["#{calculated_key}[]", elem]
96
+ end
97
+ end
98
+ result
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,3 @@
1
+ module Rainforest
2
+ VERSION = File.open(File.expand_path("../../../VERSION", __FILE__)).read()
3
+ end
@@ -0,0 +1,26 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+
3
+ require 'rainforest/version'
4
+
5
+ spec = Gem::Specification.new do |s|
6
+ s.name = 'rainforest'
7
+ s.version = Rainforest::VERSION
8
+ s.summary = 'Ruby bindings for the Rainforest API'
9
+ s.description = 'Rainforest allows you to create tests for your website in plain English, then run them across all major browsers with a single click. See https://www.rainforestqa.com/ for details.'
10
+ s.authors = ['Jon Calhon']
11
+ s.email = ['joncalhoun@gmail.com']
12
+ s.homepage = 'https://app.rainforestqa.com/docs/ruby'
13
+
14
+ s.add_dependency('rest-client', '~> 1.4')
15
+ s.add_dependency('multi_json', '>= 1.0.4', '< 2')
16
+
17
+ s.add_development_dependency('mocha', '~> 0.13.2')
18
+ s.add_development_dependency('shoulda', '~> 3.4.0')
19
+ s.add_development_dependency('test-unit')
20
+ s.add_development_dependency('rake')
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- test/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ['lib']
26
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ module Rainforest
4
+ class AccountTest < Test::Unit::TestCase
5
+ should "account should be retrievable" do
6
+ resp = {:email => "test+bindings@rainforest.com", :charge_enabled => false, :details_submitted => false}
7
+ @mock.expects(:get).once.returns(test_response(resp))
8
+ a = Rainforest::Account.retrieve
9
+ assert_equal "test+bindings@rainforest.com", a.email
10
+ assert !a.charge_enabled
11
+ assert !a.details_submitted
12
+ end
13
+ end
14
+ end