api_client 0.5.24-java → 0.6.0-java

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/lib/api_client/base.rb +77 -0
  4. data/lib/api_client/connection/abstract.rb +81 -0
  5. data/lib/api_client/connection/basic.rb +131 -0
  6. data/lib/api_client/connection/json.rb +14 -0
  7. data/lib/api_client/connection/middlewares/request/json.rb +34 -0
  8. data/lib/api_client/connection/middlewares/request/logger.rb +64 -0
  9. data/lib/api_client/connection/middlewares/request/oauth.rb +22 -0
  10. data/lib/api_client/connection/oauth.rb +18 -0
  11. data/lib/api_client/errors.rb +32 -0
  12. data/lib/api_client/mixins/configuration.rb +24 -0
  13. data/lib/api_client/mixins/connection_hooks.rb +24 -0
  14. data/lib/api_client/mixins/delegation.rb +23 -0
  15. data/lib/api_client/mixins/inheritance.rb +19 -0
  16. data/lib/api_client/mixins/instantiation.rb +29 -0
  17. data/lib/api_client/mixins/scoping.rb +49 -0
  18. data/lib/api_client/resource/base.rb +67 -0
  19. data/lib/api_client/resource/name_resolver.rb +37 -0
  20. data/lib/api_client/resource/scope.rb +73 -0
  21. data/lib/api_client/scope.rb +125 -0
  22. data/lib/api_client/utils.rb +18 -0
  23. data/lib/api_client/version.rb +3 -0
  24. data/spec/api_client/base/connection_hook_spec.rb +18 -0
  25. data/spec/api_client/base/delegation_spec.rb +15 -0
  26. data/spec/api_client/base/inheritance_spec.rb +44 -0
  27. data/spec/api_client/base/instantiation_spec.rb +55 -0
  28. data/spec/api_client/base/marshalling_spec.rb +33 -0
  29. data/spec/api_client/base/parsing_spec.rb +38 -0
  30. data/spec/api_client/base/scoping_spec.rb +60 -0
  31. data/spec/api_client/base_spec.rb +107 -0
  32. data/spec/api_client/connection/abstract_spec.rb +21 -0
  33. data/spec/api_client/connection/basic_spec.rb +198 -0
  34. data/spec/api_client/connection/oauth_spec.rb +23 -0
  35. data/spec/api_client/connection/request/json_spec.rb +30 -0
  36. data/spec/api_client/connection/request/logger_spec.rb +18 -0
  37. data/spec/api_client/connection/request/oauth_spec.rb +26 -0
  38. data/spec/api_client/resource/base_spec.rb +97 -0
  39. data/spec/api_client/resource/name_spec.rb +19 -0
  40. data/spec/api_client/resource/scope_spec.rb +122 -0
  41. data/spec/api_client/scope_spec.rb +204 -0
  42. data/spec/api_client/utils_spec.rb +32 -0
  43. data/spec/support/matchers.rb +5 -0
  44. metadata +72 -11
@@ -0,0 +1,67 @@
1
+ module ApiClient
2
+
3
+ module Resource
4
+
5
+ class Base < ApiClient::Base
6
+
7
+ class << self
8
+ extend ApiClient::Mixins::Delegation
9
+ extend ApiClient::Mixins::Configuration
10
+
11
+ delegate :find_all, :find, :create, :update, :destroy, :path, :to => :scope
12
+
13
+ dsl_accessor :prefix
14
+
15
+ def inherited(subclass)
16
+ super
17
+ small_name = NameResolver.resolve(subclass.name)
18
+ subclass.namespace small_name
19
+ subclass.prefix self.prefix
20
+ subclass.always do
21
+ name = small_name
22
+ pre_fix = prefix
23
+ path ["", prefix, "#{name}s"].compact.join('/')
24
+ end
25
+ end
26
+
27
+ def scope(options = {})
28
+ scope_in_thread || ApiClient::Resource::Scope.new(self).params(options)
29
+ end
30
+
31
+ end
32
+
33
+ def persisted?
34
+ !!self.id
35
+ end
36
+
37
+ def save
38
+ self.persisted? ? remote_update : remote_create
39
+ end
40
+
41
+ def destroy
42
+ get_scope.destroy(self.id)
43
+ end
44
+
45
+ def payload
46
+ hash = self.to_hash
47
+ hash.delete('id') # This key is never required
48
+ hash
49
+ end
50
+
51
+ def remote_update
52
+ get_scope.update(self.id, payload)
53
+ end
54
+
55
+ def remote_create
56
+ get_scope.create(payload)
57
+ end
58
+
59
+ def get_scope
60
+ original_scope || self.class
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,37 @@
1
+ module ApiClient
2
+ module Resource
3
+ class NameResolver
4
+ def self.resolve(ruby_path)
5
+ new(ruby_path).resolve
6
+ end
7
+
8
+ attr_reader :name
9
+
10
+ def initialize(name)
11
+ @name = name
12
+ end
13
+
14
+ def resolve
15
+ select_last_item
16
+ underscorize
17
+ lowercase
18
+ name
19
+ end
20
+
21
+ private
22
+ def select_last_item
23
+ @name = @name.split('::').last
24
+ end
25
+
26
+ #Inspired by ActiveSupport::Inflector#underscore
27
+ def underscorize
28
+ @name.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
29
+ @name.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
30
+ end
31
+
32
+ def lowercase
33
+ @name.downcase!
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,73 @@
1
+ # This class includes methods for calling restful APIs
2
+ module ApiClient
3
+
4
+ module Resource
5
+
6
+ class Scope < ApiClient::Scope
7
+
8
+ dsl_accessor :path, :return_self => true
9
+
10
+ def format
11
+ @scopeable.format
12
+ end
13
+
14
+ def append_format(path)
15
+ format ? [path, format].join('.') : path
16
+ end
17
+
18
+ def find(id)
19
+ path = [@path, id].join('/')
20
+ path = append_format(path)
21
+ response = get(path)
22
+ scoped(self) do
23
+ raw? ? response : @scopeable.build(response)
24
+ end
25
+ end
26
+
27
+ def find_all(params = {})
28
+ path = append_format(@path)
29
+ response = get(path, params)
30
+ scoped(self) do
31
+ raw? ? response : @scopeable.build(response)
32
+ end
33
+ end
34
+
35
+ def create(params = {})
36
+ path = append_format(@path)
37
+ hash = if @scopeable.namespace
38
+ { @scopeable.namespace => params }
39
+ else
40
+ params
41
+ end
42
+ response = post(path, hash)
43
+ scoped(self) do
44
+ raw? ? response : @scopeable.build(response)
45
+ end
46
+ end
47
+
48
+ def update(id, params = {})
49
+ path = [@path, id].join('/')
50
+ path = append_format(path)
51
+ hash = if @scopeable.namespace
52
+ { @scopeable.namespace => params }
53
+ else
54
+ params
55
+ end
56
+ response = put(path, hash)
57
+ scoped(self) do
58
+ raw? ? response : @scopeable.build(response)
59
+ end
60
+ end
61
+
62
+ def destroy(id)
63
+ path = [@path, id].join('/')
64
+ path = append_format(path)
65
+ delete(path)
66
+ true
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,125 @@
1
+ module ApiClient
2
+
3
+ class Scope
4
+ extend ApiClient::Mixins::Configuration
5
+ extend ApiClient::Mixins::Delegation
6
+
7
+ delegate :prefix, :scoped, :to => :scopeable
8
+
9
+ dsl_accessor :endpoint, :adapter, :return_self => true
10
+
11
+ attr_reader :scopeable
12
+
13
+ def initialize(scopeable)
14
+ @scopeable = scopeable
15
+ @params = {}
16
+ @headers = {}
17
+ @options = {}
18
+ @scopeable.default_scopes.each do |default_scope|
19
+ self.instance_eval(&default_scope)
20
+ end
21
+ end
22
+
23
+ def connection
24
+ klass = Connection.const_get((@adapter || Connection.default).to_s.capitalize)
25
+ @connection = klass.new(@endpoint , @options || {})
26
+ hooks = @scopeable.connection_hooks || []
27
+ hooks.each { |hook| hook.call(@connection, self) }
28
+ @connection
29
+ end
30
+
31
+ def raw
32
+ @raw = true
33
+ self
34
+ end
35
+
36
+ def raw?
37
+ !!@raw
38
+ end
39
+
40
+ # 3 Pillars of scoping
41
+ # options - passed on the the adapter
42
+ # params - converted to query or request body
43
+ # headers - passed on to the request
44
+ def options(new_options = nil)
45
+ return @options if new_options.nil?
46
+ ApiClient::Utils.deep_merge(@options, new_options)
47
+ self
48
+ end
49
+
50
+ def params(options = nil)
51
+ return @params if options.nil?
52
+ ApiClient::Utils.deep_merge(@params, options) if options
53
+ self
54
+ end
55
+ alias :scope :params
56
+
57
+ def headers(options = nil)
58
+ return @headers if options.nil?
59
+ ApiClient::Utils.deep_merge(@headers, options) if options
60
+ self
61
+ end
62
+
63
+ def raw_body(options = nil)
64
+ return @raw_body if options.nil?
65
+ @raw_body = options
66
+ self
67
+ end
68
+
69
+ def clone_only_headers
70
+ self.class.new(self.scopeable).headers(self.headers)
71
+ end
72
+
73
+ # Half-level :)
74
+ # This is a swiss-army knife kind of method, extremely useful
75
+ def fetch(path, options = {})
76
+ scoped(self) do
77
+ @scopeable.build get(path, options)
78
+ end
79
+ end
80
+
81
+ # Low-level connection methods
82
+
83
+ def request(method, path, options = {})
84
+ options = options.dup
85
+
86
+ raw = raw? || options.delete(:raw)
87
+ params(options)
88
+ response = connection.send method, path, (@raw_body || @params), @headers
89
+ raw ? response : @scopeable.parse(response)
90
+ end
91
+
92
+ def get(path, options = {})
93
+ request(:get, path, options)
94
+ end
95
+
96
+ def post(path, options = {})
97
+ request(:post, path, options)
98
+ end
99
+
100
+ def patch(path, options = {})
101
+ request(:patch, path, options)
102
+ end
103
+
104
+ def put(path, options = {})
105
+ request(:put, path, options)
106
+ end
107
+
108
+ def delete(path, options = {})
109
+ request(:delete, path, options)
110
+ end
111
+
112
+ # Dynamic delegation of scopeable methods
113
+ def method_missing(method, *args, &block)
114
+ if @scopeable.respond_to?(method)
115
+ @scopeable.scoped(self) do
116
+ @scopeable.send(method, *args, &block)
117
+ end
118
+ else
119
+ super
120
+ end
121
+ end
122
+
123
+ end
124
+
125
+ end
@@ -0,0 +1,18 @@
1
+ module ApiClient
2
+
3
+ module Utils
4
+
5
+ def self.deep_merge(hash, other_hash)
6
+ other_hash.each_pair do |key,v|
7
+ if hash[key].is_a?(::Hash) and v.is_a?(::Hash)
8
+ deep_merge hash[key], v
9
+ else
10
+ hash[key] = v
11
+ end
12
+ end
13
+ hash
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,3 @@
1
+ module ApiClient
2
+ VERSION = '0.6.0'
3
+ end
@@ -0,0 +1,18 @@
1
+ require "spec_helper"
2
+
3
+ describe ApiClient::Base do
4
+
5
+ describe '.connection' do
6
+
7
+ it "registers a new connection_hook" do
8
+ ConnectionHookTestProc = lambda {}
9
+ class ConnectionHookTest < ApiClient::Base
10
+ connection &ConnectionHookTestProc
11
+ end
12
+ ConnectionHookTest.connection_hooks.size.should == 1
13
+ ConnectionHookTest.connection_hooks.should == [ConnectionHookTestProc]
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,15 @@
1
+ require "spec_helper"
2
+
3
+ describe ApiClient::Base do
4
+
5
+ it "delegates methods to scope" do
6
+ scope = double
7
+ ApiClient::Base.stub(:scope).and_return(scope)
8
+ [:fetch, :get, :put, :post, :patch, :delete, :headers, :endpoint, :options, :adapter, :params, :raw].each do |method|
9
+ scope.should_receive(method)
10
+ ApiClient::Base.send(method)
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,44 @@
1
+ require "spec_helper"
2
+
3
+ describe ApiClient::Base do
4
+
5
+ describe "subclasses" do
6
+
7
+ it "inherit scopes, hooks, namespace and format" do
8
+
9
+ class Level1InheritanceTest < ApiClient::Base
10
+ end
11
+
12
+ Level1InheritanceTest.default_scopes.should == []
13
+ Level1InheritanceTest.connection_hooks.should == []
14
+ Level1InheritanceTest.namespace.should == nil
15
+ Level1InheritanceTest.format.should == :json
16
+
17
+ Level1InheritanceTest.default_scopes = ['scope1']
18
+ Level1InheritanceTest.connection_hooks = ['hook1']
19
+ Level1InheritanceTest.namespace 'level1'
20
+ Level1InheritanceTest.format :xml
21
+
22
+ ApiClient::Base.default_scopes.should == []
23
+ ApiClient::Base.connection_hooks.should == []
24
+ ApiClient::Base.namespace.should == nil
25
+ ApiClient::Base.format.should == :json
26
+
27
+ class Level2InheritanceTest < Level1InheritanceTest
28
+ namespace "level2"
29
+ format :yaml
30
+ end
31
+
32
+ Level2InheritanceTest.default_scopes.should == ['scope1']
33
+ Level2InheritanceTest.connection_hooks.should == ['hook1']
34
+ Level2InheritanceTest.namespace.should == 'level2'
35
+ Level2InheritanceTest.format.should == :yaml
36
+
37
+ Level1InheritanceTest.namespace.should == 'level1'
38
+ Level1InheritanceTest.format.should == :xml
39
+
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,55 @@
1
+ require "spec_helper"
2
+
3
+ describe ApiClient::Base do
4
+
5
+ describe "build" do
6
+
7
+ it "instantiates an array of objects and returns an array if passed an array" do
8
+ result = ApiClient::Base.build [{ :id => 1 }, { :id => 2}]
9
+ result.should be_an_instance_of(Array)
10
+ result.first.should be_an_instance_of(ApiClient::Base)
11
+ result.last.should be_an_instance_of(ApiClient::Base)
12
+ end
13
+
14
+ it "instantiates an object and returns an object if passed an object" do
15
+ result = ApiClient::Base.build({ :id => 1 })
16
+ result.should be_an_instance_of(ApiClient::Base)
17
+ end
18
+
19
+ end
20
+
21
+ describe "build_one" do
22
+
23
+ it "extracts the attributes from a namespace if a namespace is provided" do
24
+ ApiClient::Base.stub(:namespace).and_return("base")
25
+ result = ApiClient::Base.build({ "base" => { :id => 1 } })
26
+ result.should be_an_instance_of(ApiClient::Base)
27
+ result.keys.should == ['id']
28
+ result.id.should == 1
29
+ end
30
+
31
+ end
32
+
33
+ describe "sub hashes" do
34
+
35
+ it "are Hashie::Mashes" do
36
+ result = ApiClient::Base.build({ :id => 1, :subhash => { :foo => 1 } })
37
+ result.subhash.should be_a(Hashie::Mash)
38
+ end
39
+
40
+ end
41
+
42
+ describe "original_scope" do
43
+
44
+ it "holds the original scope it was created from" do
45
+ scope = ApiClient::Base.params(:foo => 1).headers('token' => 'aaa').options("some" => "option")
46
+
47
+ instance = scope.build :key => 'value'
48
+ instance.original_scope.headers.should == {'token' => 'aaa'}
49
+ instance.original_scope.params.should be_empty
50
+ instance.original_scope.options.should be_empty
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+ describe "Marshaling of ApiClient objects" do
4
+ ConnectionProc = Proc.new {}
5
+ AlwaysProc = Proc.new {}
6
+
7
+ class Entity < ApiClient::Base
8
+ connection &ConnectionProc
9
+ always &AlwaysProc
10
+
11
+ def mutated_state?
12
+ @state == "mutated"
13
+ end
14
+
15
+ def mutate_state!
16
+ @state = "mutated"
17
+ end
18
+ end
19
+
20
+ it "is marshallable by default" do
21
+ scope = Entity.params(:foo => 1).headers("token" => "aaa").options("some" => "option")
22
+ entity = scope.build :key => "value"
23
+
24
+ entity.mutated_state?.should == false
25
+ entity.mutate_state!
26
+ entity.mutated_state?.should == true
27
+
28
+ reloaded = Marshal.load(Marshal.dump(entity))
29
+
30
+ reloaded.should == entity
31
+ reloaded.mutated_state?.should == true
32
+ end
33
+ end
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ require "multi_xml"
4
+
5
+ describe ApiClient::Base do
6
+
7
+ it "parses json if json is set as format" do
8
+ ApiClient::Base.stub(:format).and_return(:json)
9
+ parsed = ApiClient::Base.parse('{"a":"1"}')
10
+ parsed.should == {"a"=> "1"}
11
+ end
12
+
13
+ it "parses xml if xml is set as format" do
14
+ ApiClient::Base.stub(:format).and_return(:xml)
15
+ parsed = ApiClient::Base.parse('<a>1</a>')
16
+ parsed.should == {"a"=> "1"}
17
+ end
18
+
19
+ it "returns the string if parser is not found" do
20
+ ApiClient::Base.stub(:format).and_return(:unknown)
21
+ parsed = ApiClient::Base.parse('a:1')
22
+ parsed.should == "a:1"
23
+ end
24
+
25
+ it "extracts the body of a Faraday::Response if it is provided" do
26
+ response = Faraday::Response.new(:body => '{"a": "1"}')
27
+ ApiClient::Base.stub(:format).and_return(:json)
28
+ parsed = ApiClient::Base.parse(response)
29
+ parsed.should == {"a"=> "1"}
30
+ end
31
+
32
+ it "returns nil if the response is 204" do
33
+ response = Faraday::Response.new(:body => nil, :status => 204)
34
+ ApiClient::Base.stub(:format).and_return(:json)
35
+ parsed = ApiClient::Base.parse(response)
36
+ parsed.should == nil
37
+ end
38
+ end
@@ -0,0 +1,60 @@
1
+ require "spec_helper"
2
+
3
+ describe ApiClient::Base do
4
+
5
+ describe '.always' do
6
+
7
+ it "registers a new default_scope" do
8
+ AlwaysTestProc = lambda {}
9
+ class AlwaysTest < ApiClient::Base
10
+ always &AlwaysTestProc
11
+ end
12
+ AlwaysTest.default_scopes.size.should == 1
13
+ AlwaysTest.default_scopes.should == [AlwaysTestProc]
14
+ end
15
+
16
+ end
17
+
18
+ describe '.scope' do
19
+
20
+ it "returns a ApiClient::Scope instance" do
21
+ ApiClient::Base.scope.should be_an_instance_of(ApiClient::Scope)
22
+ end
23
+
24
+ end
25
+
26
+ describe '.scope_thread_attribute_name' do
27
+
28
+ it "returns the key under which all .scoped calls should be stored" do
29
+ ApiClient::Base.scope_thread_attribute_name.should == "ApiClient::Base_scope"
30
+ end
31
+
32
+ end
33
+
34
+ describe '.scoped' do
35
+
36
+ it "stores the scope in the thread context, attached to class name" do
37
+ mock_scope3 = double
38
+ ApiClient::Base.scoped(mock_scope3) do
39
+ Thread.new {
40
+ mock_scope2 = double
41
+ ApiClient::Base.scoped(mock_scope2) do
42
+ ApiClient::Base.scope.should == mock_scope2
43
+ Thread.current[ApiClient::Base.scope_thread_attribute_name].should == [mock_scope2]
44
+ end
45
+ }
46
+ ApiClient::Base.scope.should == mock_scope3
47
+ Thread.current[ApiClient::Base.scope_thread_attribute_name].should == [mock_scope3]
48
+ end
49
+ Thread.new {
50
+ mock_scope = double
51
+ ApiClient::Base.scoped(mock_scope) do
52
+ ApiClient::Base.scope.should == mock_scope
53
+ Thread.current[ApiClient::Base.scope_thread_attribute_name].should == [mock_scope]
54
+ end
55
+ }
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,107 @@
1
+ require "spec_helper"
2
+
3
+ describe ApiClient::Base do
4
+
5
+ it "is a subclass of Hashie::Mash" do
6
+ ApiClient::Base.should inherit_from(Hashie::Mash)
7
+ end
8
+
9
+ it "responds to #id" do
10
+ subject.should respond_to("id")
11
+ end
12
+
13
+ class StrictApi < ApiClient::Base
14
+ def accessor_of_x
15
+ self.x
16
+ end
17
+
18
+ def strict_attr_reader?
19
+ true
20
+ end
21
+ end
22
+
23
+ class StrictDescendant < StrictApi
24
+ end
25
+
26
+ context "when used in an array" do
27
+
28
+ subject { [StrictApi.new] }
29
+
30
+ it "does not break Array#flatten" do
31
+ lambda { subject.flatten }.should_not raise_error
32
+ end
33
+
34
+ end
35
+
36
+ describe "#inspect" do
37
+
38
+ it "has a nice inspect" do
39
+ subject.update(:id => 1).inspect.should == '#<ApiClient::Base id: 1>'
40
+ end
41
+
42
+ it "presents all fields in inspect" do
43
+ subject.update(:id => 1, :foo => 'OMG')
44
+ subject.inspect.should == '#<ApiClient::Base id: 1, foo: "OMG">'
45
+ end
46
+
47
+ it "inspects subobjects properly" do
48
+ subject.update(:id => 1, :sub => [1,2])
49
+ subject.inspect.should == '#<ApiClient::Base id: 1, sub: #<Hashie::Array [1, 2]>>'
50
+ end
51
+
52
+ it "makes sure id is the first key" do
53
+
54
+ subject.update(:foo => 'OMG', :id => 1)
55
+ subject.inspect.should == '#<ApiClient::Base id: 1, foo: "OMG">'
56
+ end
57
+
58
+ end
59
+
60
+ describe "strict_read" do
61
+ it "fails if the key is missing and strict_read is set" do
62
+ api = StrictApi.new
63
+ lambda { api.missing }.should raise_error do |error|
64
+ error_type = error.class.to_s
65
+ error_type.should match(/KeyError|IndexError/)
66
+ end
67
+ end
68
+
69
+ it "doesn't fail if strict_read is not set" do
70
+ api = ApiClient::Base.new
71
+ api.missing
72
+ end
73
+
74
+ it "doesn't fail if the key was set after object was created" do
75
+ api = StrictApi.new
76
+ lambda { api.not_missing = 1 }.should_not raise_error
77
+ api.not_missing.should == 1
78
+ end
79
+
80
+ it "doesn't fail for predicate methods if key is not set" do
81
+ api = StrictApi.new
82
+ lambda { api.missing? }.should_not raise_error
83
+ api.missing?.should be_false
84
+ end
85
+
86
+ it "allows to call methods" do
87
+ api = StrictApi.new(:x => 14)
88
+ api.accessor_of_x.should == 14
89
+ end
90
+
91
+ it "calling method which asks for missing attribute fails" do
92
+ api = StrictApi.new
93
+ lambda { api.accessor_of_x }.should raise_error do |error|
94
+ error_type = error.class.to_s
95
+ error_type.should match(/KeyError|IndexError/)
96
+ end
97
+ end
98
+
99
+ it "passes strict api configuration to subclasses" do
100
+ api = StrictDescendant.new
101
+ lambda { api.missing }.should raise_error do |error|
102
+ error_type = error.class.to_s
103
+ error_type.should match(/KeyError|IndexError/)
104
+ end
105
+ end
106
+ end
107
+ end