rainforest 1.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.
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