rod-rest 0.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/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +69 -0
- data/Rakefile +21 -0
- data/Readme.md +3 -0
- data/lib/rod/rest.rb +14 -0
- data/lib/rod/rest/api.rb +100 -0
- data/lib/rod/rest/client.rb +215 -0
- data/lib/rod/rest/collection_proxy.rb +52 -0
- data/lib/rod/rest/constants.rb +5 -0
- data/lib/rod/rest/exception.rb +8 -0
- data/lib/rod/rest/json_serializer.rb +59 -0
- data/lib/rod/rest/metadata.rb +38 -0
- data/lib/rod/rest/naming.rb +20 -0
- data/lib/rod/rest/property_metadata.rb +20 -0
- data/lib/rod/rest/proxy.rb +121 -0
- data/lib/rod/rest/proxy_factory.rb +30 -0
- data/lib/rod/rest/resource_metadata.rb +32 -0
- data/rod-rest.gemspec +32 -0
- data/test/int/end_to_end.rb +138 -0
- data/test/spec/api.rb +210 -0
- data/test/spec/client.rb +248 -0
- data/test/spec/collection_proxy.rb +109 -0
- data/test/spec/json_serializer.rb +108 -0
- data/test/spec/metadata.rb +37 -0
- data/test/spec/property_metadata.rb +63 -0
- data/test/spec/proxy.rb +87 -0
- data/test/spec/proxy_factory.rb +44 -0
- data/test/spec/resource_metadata.rb +96 -0
- data/test/spec/test_helper.rb +28 -0
- metadata +173 -0
data/test/spec/api.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require_relative 'test_helper'
|
3
|
+
require 'rod/rest/api'
|
4
|
+
|
5
|
+
module Rod
|
6
|
+
module Rest
|
7
|
+
describe API do
|
8
|
+
include Rack::Test::Methods
|
9
|
+
|
10
|
+
def app
|
11
|
+
Rod::Rest::API
|
12
|
+
end
|
13
|
+
|
14
|
+
context "resource API" do
|
15
|
+
# We need different resource name for each test due to Sinatra.
|
16
|
+
let(:resource_name) { "cars_" + rand.to_s }
|
17
|
+
let(:serializer) { stub!.serialize(nil) { nil_body }.subject }
|
18
|
+
let(:nil_body) { nil }
|
19
|
+
let(:plural_associations) { [] }
|
20
|
+
let(:resource) { resource = stub!.name { resource_name }.subject
|
21
|
+
stub(resource).plural_associations { plural_associations }
|
22
|
+
resource
|
23
|
+
}
|
24
|
+
|
25
|
+
before do
|
26
|
+
Rod::Rest::API.build_api_for(resource,serializer: serializer,resource_name: resource_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "GET /cars" do
|
30
|
+
let(:count) { 3 }
|
31
|
+
let(:count_body) { Object.new.to_s }
|
32
|
+
|
33
|
+
before do
|
34
|
+
stub(resource).count { count }
|
35
|
+
stub(serializer).serialize({count: count}) { count_body }
|
36
|
+
end
|
37
|
+
|
38
|
+
it "returns count of cars" do
|
39
|
+
get "/#{resource_name}"
|
40
|
+
last_response.status.should == 200
|
41
|
+
last_response.body.should == count_body
|
42
|
+
end
|
43
|
+
|
44
|
+
it "returns 404 for non-existing car" do
|
45
|
+
get "/non_existing"
|
46
|
+
last_response.status.should == 404
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "GET /cars?name=Mercedes" do
|
51
|
+
let(:property_name) { "name" }
|
52
|
+
let(:invalid_property_name) { "surname" }
|
53
|
+
let(:property_value) { "Mercedes" }
|
54
|
+
let(:empty_property_value) { "Audi" }
|
55
|
+
let(:mercedes_300) { Object.new }
|
56
|
+
let(:mercedes_180) { Object.new }
|
57
|
+
let(:cars_body) { Object.new.to_s }
|
58
|
+
let(:empty_collection_body) { Object.new.to_s }
|
59
|
+
|
60
|
+
before do
|
61
|
+
stub(resource).find_all_by_name(property_value) { [mercedes_300,mercedes_180] }
|
62
|
+
stub(resource).find_all_by_name(empty_property_value) { [] }
|
63
|
+
stub(serializer).serialize([mercedes_300,mercedes_180]) { cars_body }
|
64
|
+
stub(serializer).serialize([]) { empty_collection_body }
|
65
|
+
end
|
66
|
+
|
67
|
+
it "returns serialized cars matching given indexed property" do
|
68
|
+
get "/#{resource_name}?#{property_name}=#{property_value}"
|
69
|
+
last_response.status.should == 200
|
70
|
+
last_response.body.should == cars_body
|
71
|
+
end
|
72
|
+
|
73
|
+
it "returns an empty array if there are no matching objects" do
|
74
|
+
get "/#{resource_name}?#{property_name}=#{empty_property_value}"
|
75
|
+
last_response.status.should == 200
|
76
|
+
last_response.body.should == empty_collection_body
|
77
|
+
end
|
78
|
+
|
79
|
+
it "returns 404 for non-indexed property" do
|
80
|
+
get "/#{resource_name}?#{invalid_property_name}=#{property_value}"
|
81
|
+
last_response.status.should == 404
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "GET /cars/1" do
|
86
|
+
let(:mercedes_300_id) { 1 }
|
87
|
+
let(:audi_a4_id) { 2 }
|
88
|
+
let(:mercedes_300) { Object.new }
|
89
|
+
let(:mercedes_300_response) { Object.new.to_s }
|
90
|
+
|
91
|
+
before do
|
92
|
+
stub(resource).find_by_rod_id(mercedes_300_id) { mercedes_300 }
|
93
|
+
stub(resource).find_by_rod_id(audi_a4_id) { nil }
|
94
|
+
stub(serializer).serialize(mercedes_300) { mercedes_300_response }
|
95
|
+
end
|
96
|
+
|
97
|
+
it "returns serialized car" do
|
98
|
+
get "/#{resource_name}/#{mercedes_300_id}"
|
99
|
+
last_response.status.should == 200
|
100
|
+
last_response.body.should == mercedes_300_response
|
101
|
+
end
|
102
|
+
|
103
|
+
it "returns 404 for non-existing car" do
|
104
|
+
get "/#{resource_name}/#{audi_a4_id}"
|
105
|
+
last_response.status.should == 404
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "GET /cars/1/drivers" do
|
110
|
+
let(:relation_name) { "drivers" }
|
111
|
+
let(:mercedes_300_id) { 1 }
|
112
|
+
let(:mercedes_300) { stub!.drivers_count { drivers_count }.subject }
|
113
|
+
let(:audi_a4_id) { 2 }
|
114
|
+
let(:drivers_count) { 4 }
|
115
|
+
let(:drivers_response){ Object.new.to_s }
|
116
|
+
let(:plural_associations) { [ property1 ] }
|
117
|
+
let(:property1) { stub!.name { relation_name }.subject }
|
118
|
+
|
119
|
+
before do
|
120
|
+
stub(resource).find_by_rod_id(mercedes_300_id) { mercedes_300 }
|
121
|
+
stub(resource).find_by_rod_id(audi_a4_id) { nil }
|
122
|
+
stub(serializer).serialize({count: drivers_count}) { drivers_response }
|
123
|
+
end
|
124
|
+
|
125
|
+
it "returns number of the drivers" do
|
126
|
+
get "/#{resource_name}/#{mercedes_300_id}/#{relation_name}"
|
127
|
+
last_response.status.should == 200
|
128
|
+
last_response.body.should == drivers_response
|
129
|
+
end
|
130
|
+
|
131
|
+
it "returns 404 for non-existing car" do
|
132
|
+
get "/#{resource_name}/#{audi_a4_id}/#{relation_name}"
|
133
|
+
last_response.status.should == 404
|
134
|
+
end
|
135
|
+
|
136
|
+
it "returns 404 for non-existing relation" do
|
137
|
+
get "/#{resource_name}/#{mercedes_300_id}/non_existing"
|
138
|
+
last_response.status.should == 404
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "GET /cars/1/drivers/0" do
|
143
|
+
let(:relation_name) { "drivers" }
|
144
|
+
let(:plural_associations) { [ drivers_property ] }
|
145
|
+
let(:drivers_property) { stub!.name { relation_name }.subject }
|
146
|
+
|
147
|
+
let(:mercedes_300_id) { 1 }
|
148
|
+
let(:audi_a4_id) { 2 }
|
149
|
+
let(:driver_index) { 0 }
|
150
|
+
let(:invalid_driver_index){ 10 }
|
151
|
+
|
152
|
+
before do
|
153
|
+
stub(resource).find_by_rod_id(mercedes_300_id) { mercedes_300 }
|
154
|
+
stub(resource).find_by_rod_id(audi_a4_id) { nil }
|
155
|
+
end
|
156
|
+
|
157
|
+
it "returns 404 for non-existing car" do
|
158
|
+
get "/#{resource_name}/#{audi_a4_id}/#{relation_name}/#{driver_index}"
|
159
|
+
last_response.status.should == 404
|
160
|
+
end
|
161
|
+
|
162
|
+
it "returns 404 for non-existing relation" do
|
163
|
+
get "/#{resource_name}/#{mercedes_300_id}/non_existing/#{driver_index}"
|
164
|
+
last_response.status.should == 404
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "with one driver" do
|
168
|
+
let(:mercedes_300) { stub!.drivers { drivers }.subject }
|
169
|
+
let(:drivers) { [schumaher] }
|
170
|
+
let(:schumaher) { Object.new }
|
171
|
+
let(:schumaher_response) { Object.new.to_s }
|
172
|
+
|
173
|
+
before do
|
174
|
+
stub(serializer).serialize(schumaher) { schumaher_response }
|
175
|
+
end
|
176
|
+
|
177
|
+
it "returns the serialized driver" do
|
178
|
+
get "/#{resource_name}/#{mercedes_300_id}/#{relation_name}/#{driver_index}"
|
179
|
+
last_response.status.should == 200
|
180
|
+
last_response.body.should == schumaher_response
|
181
|
+
end
|
182
|
+
|
183
|
+
it "returns 404 for out-of-bounds driver" do
|
184
|
+
get "/#{resource_name}/#{mercedes_300_id}/#{relation_name}/#{invalid_driver_index}"
|
185
|
+
last_response.status.should == 404
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context "metadata API" do
|
192
|
+
before do
|
193
|
+
API.build_metadata_api(metadata,serializer: serializer)
|
194
|
+
end
|
195
|
+
|
196
|
+
let(:metadata) { Object.new }
|
197
|
+
let(:serializer) { stub!.dump(metadata) { dumped_metadata }.subject }
|
198
|
+
let(:dumped_metadata) { "metadata" }
|
199
|
+
|
200
|
+
describe "GET /metadata" do
|
201
|
+
it "retunrs the metadata" do
|
202
|
+
get "/metadata"
|
203
|
+
last_response.status == 200
|
204
|
+
last_response.body == dumped_metadata
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
data/test/spec/client.rb
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require_relative 'test_helper'
|
3
|
+
require 'rod/rest/client'
|
4
|
+
require 'json'
|
5
|
+
require 'cgi'
|
6
|
+
|
7
|
+
stub_class 'Rod::Rest::ProxyFactory'
|
8
|
+
|
9
|
+
module Rod
|
10
|
+
module Rest
|
11
|
+
describe Client do
|
12
|
+
let(:factory_class) { stub!.new([resource1],is_a(Client)) { factory }.subject }
|
13
|
+
let(:factory) { Object.new }
|
14
|
+
let(:metadata) { stub!.resources { [resource1] }.subject }
|
15
|
+
let(:resource1) { resource = stub!.name { resource_name }.subject
|
16
|
+
stub(resource).indexed_properties { indexed_properties }
|
17
|
+
stub(resource).plural_associations { plural_associations }
|
18
|
+
resource
|
19
|
+
}
|
20
|
+
let(:resource_name) { "Car" }
|
21
|
+
let(:indexed_properties) { [] }
|
22
|
+
let(:plural_associations) { [] }
|
23
|
+
let(:car_type) { resource_name }
|
24
|
+
let(:response) { stub!.status{ 200 }.subject }
|
25
|
+
let(:web_client) { Object.new }
|
26
|
+
|
27
|
+
describe "without metadata provided to the client" do
|
28
|
+
let(:client) { Client.new(http_client: web_client,metadata_factory: metadata_factory, factory: factory_class) }
|
29
|
+
let(:metadata_factory) { stub!.new(description: metadata_description) { metadata }.subject }
|
30
|
+
let(:metadata_description) { "{}" }
|
31
|
+
|
32
|
+
before do
|
33
|
+
stub(web_client).get("/metadata") { response }
|
34
|
+
stub(response).body { metadata_description }
|
35
|
+
end
|
36
|
+
|
37
|
+
it "fetches the metadata via the API" do
|
38
|
+
client.metadata.should == metadata
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "when fetching the data via the API" do
|
42
|
+
let(:json_cars_count) { { count: 3 }.to_json }
|
43
|
+
let(:cars_response) { response = stub!.status { 200 }.subject
|
44
|
+
stub(response).body { json_cars_count }
|
45
|
+
response
|
46
|
+
}
|
47
|
+
|
48
|
+
before do
|
49
|
+
stub(web_client).get("/cars") { cars_response }
|
50
|
+
end
|
51
|
+
|
52
|
+
it "fetches the metadata before the call" do
|
53
|
+
client.cars_count.should == 3
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "with metadata provided to the client" do
|
59
|
+
let(:client) { Client.new(http_client: web_client,metadata: metadata, factory: factory_class) }
|
60
|
+
|
61
|
+
let(:invalid_id) { 1000 }
|
62
|
+
let(:invalid_index) { 2000 }
|
63
|
+
let(:invalid_response) { stub!.status{ 404 }.subject }
|
64
|
+
|
65
|
+
describe "#cars_count" do
|
66
|
+
let(:json_cars_count) { { count: 3 }.to_json }
|
67
|
+
|
68
|
+
before do
|
69
|
+
stub(web_client).get("/cars") { response }
|
70
|
+
stub(response).body { json_cars_count }
|
71
|
+
end
|
72
|
+
|
73
|
+
it "returns the number of cars" do
|
74
|
+
client.cars_count.should == 3
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "with two cars defined" do
|
79
|
+
let(:mercedes_300_id) { 1 }
|
80
|
+
let(:mercedes_300_hash) { {rod_id: mercedes_300_id, type: car_type } }
|
81
|
+
let(:mercedes_180_hash) { {rod_id: 2, type: car_type } }
|
82
|
+
let(:mercedes_300_proxy){ Object.new }
|
83
|
+
let(:mercedes_180_proxy){ Object.new }
|
84
|
+
let(:factory) { factory = stub!.build(mercedes_300_hash) { mercedes_300_proxy }.subject
|
85
|
+
stub(factory).build(mercedes_180_hash) { mercedes_180_proxy }
|
86
|
+
factory
|
87
|
+
}
|
88
|
+
|
89
|
+
describe "#find_cars_by_name(name)" do
|
90
|
+
let(:car_name) { "Mercedes" }
|
91
|
+
let(:property_name) { "name" }
|
92
|
+
let(:json_cars) { [mercedes_300_hash,mercedes_180_hash].to_json }
|
93
|
+
let(:indexed_properties){ [indexed_property] }
|
94
|
+
let(:indexed_property) { stub!.name { property_name }.subject }
|
95
|
+
|
96
|
+
before do
|
97
|
+
stub(web_client).get("/cars?#{property_name}=#{car_name}") { response }
|
98
|
+
stub(response).body { json_cars }
|
99
|
+
end
|
100
|
+
|
101
|
+
it "finds the cars by their name" do
|
102
|
+
cars = client.find_cars_by_name(car_name)
|
103
|
+
expected_cars = [mercedes_300_proxy,mercedes_180_proxy]
|
104
|
+
cars.size.should == expected_cars.size
|
105
|
+
cars.zip(expected_cars).each do |result,expected|
|
106
|
+
result.should == expected
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "with car response defined" do
|
112
|
+
let(:json_mercedes_300) { mercedes_300_hash.to_json }
|
113
|
+
|
114
|
+
before do
|
115
|
+
stub(web_client).get("/cars/#{mercedes_300_id}") { response }
|
116
|
+
stub(web_client).get("/cars/#{invalid_id}") { invalid_response }
|
117
|
+
stub(response).body { json_mercedes_300 }
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "#find_car(rod_id)" do
|
121
|
+
it "finds the car by its rod_id" do
|
122
|
+
client.find_car(mercedes_300_id).should == mercedes_300_proxy
|
123
|
+
end
|
124
|
+
|
125
|
+
it "raises MissingResource exception for invalid car rod_id" do
|
126
|
+
lambda { client.find_car(invalid_id)}.should raise_exception(MissingResource)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "#fetch_object(car_stub)" do
|
131
|
+
let(:car_stub) { { rod_id: mercedes_300_id, type: car_type } }
|
132
|
+
let(:invalid_id_stub) { { rod_id: invalid_id, type: car_type } }
|
133
|
+
let(:invalid_type_stub) { { rod_id: mercedes_300_id, type: invalid_type } }
|
134
|
+
let(:invalid_type) { "InvalidType" }
|
135
|
+
|
136
|
+
it "finds the car by its stub" do
|
137
|
+
client.fetch_object(car_stub).should == mercedes_300_proxy
|
138
|
+
end
|
139
|
+
|
140
|
+
it "raises MissingResource execption for invalid car rod_id" do
|
141
|
+
lambda { client.fetch_object(invalid_id_stub)}.should raise_exception(MissingResource)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "raises APIError execption for invalid type" do
|
145
|
+
lambda { client.fetch_object(invalid_type_stub)}.should raise_exception(APIError)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "with associations" do
|
151
|
+
let(:plural_associations) { [plural_association] }
|
152
|
+
let(:plural_association) { stub!.name { association_name}.subject }
|
153
|
+
let(:association_name) { "drivers" }
|
154
|
+
|
155
|
+
describe "#car_drivers_count(rod_id)" do
|
156
|
+
let(:drivers_count) { 3 }
|
157
|
+
let(:json_driver_count) { { count: drivers_count }.to_json }
|
158
|
+
|
159
|
+
|
160
|
+
before do
|
161
|
+
stub(web_client).get("/cars/#{mercedes_300_id}/#{association_name}") { response }
|
162
|
+
stub(web_client).get("/cars/#{invalid_id}/#{association_name}") { invalid_response }
|
163
|
+
stub(response).body { json_driver_count }
|
164
|
+
end
|
165
|
+
|
166
|
+
it "returns the number of car drivers" do
|
167
|
+
client.car_drivers_count(mercedes_300_id).should == drivers_count
|
168
|
+
end
|
169
|
+
|
170
|
+
it "raises MissingResource exception for invalid car rod_id" do
|
171
|
+
lambda { client.car_drivers_count(invalid_id)}.should raise_exception(MissingResource)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "with reponse defined" do
|
176
|
+
let(:schumaher_index) { 0 }
|
177
|
+
let(:schumaher_hash) { { rod_id: schumaher_id, name: "Schumaher", type: "Driver" } }
|
178
|
+
let(:schumaher_json) { schumaher_hash.to_json }
|
179
|
+
let(:schumaher_proxy) { Object.new }
|
180
|
+
let(:schumaher_id) { 1 }
|
181
|
+
|
182
|
+
before do
|
183
|
+
stub(web_client).get("/cars/#{mercedes_300_id}/#{association_name}/#{schumaher_index}") { response }
|
184
|
+
stub(web_client).get("/cars/#{invalid_id}/#{association_name}/#{schumaher_index}") { invalid_response }
|
185
|
+
stub(web_client).get("/cars/#{mercedes_300_id}/#{association_name}/#{invalid_index}") { invalid_response }
|
186
|
+
stub(response).body { schumaher_json }
|
187
|
+
stub(factory).build(schumaher_hash) { schumaher_proxy }
|
188
|
+
end
|
189
|
+
|
190
|
+
describe "#car_driver(rod_id,index)" do
|
191
|
+
it "returns the driver" do
|
192
|
+
client.car_driver(mercedes_300_id,schumaher_index).should == schumaher_proxy
|
193
|
+
end
|
194
|
+
|
195
|
+
it "raises MissingResource exception for invalid car rod_id" do
|
196
|
+
lambda { client.car_driver(invalid_id,schumaher_index)}.should raise_exception(MissingResource)
|
197
|
+
end
|
198
|
+
|
199
|
+
it "raises MissingResource exception for invalid index" do
|
200
|
+
lambda { client.car_driver(mercedes_300_id,invalid_index)}.should raise_exception(MissingResource)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "#fetch_related_object(subject,relation,index)" do
|
205
|
+
let(:association_name) { "drivers" }
|
206
|
+
let(:invalid_association_name){ "owners" }
|
207
|
+
let(:invalid_type) { "InvalidType" }
|
208
|
+
let(:proxy_with_invalid_id) { proxy = stub!.rod_id { invalid_id }.subject
|
209
|
+
stub(proxy).type { car_type }
|
210
|
+
proxy
|
211
|
+
}
|
212
|
+
let(:proxy_with_invalid_type) { proxy = stub!.rod_id { mercedes_300_id }.subject
|
213
|
+
stub(proxy).type { invalid_type }
|
214
|
+
proxy
|
215
|
+
}
|
216
|
+
|
217
|
+
before do
|
218
|
+
stub(mercedes_300_proxy).rod_id { mercedes_300_id }
|
219
|
+
stub(mercedes_300_proxy).type { car_type }
|
220
|
+
end
|
221
|
+
|
222
|
+
it "returns the driver" do
|
223
|
+
client.fetch_related_object(mercedes_300_proxy,association_name,schumaher_index).should == schumaher_proxy
|
224
|
+
end
|
225
|
+
|
226
|
+
it "raises MissingResource exception for invalid car proxy id" do
|
227
|
+
lambda { client.fetch_related_object(proxy_with_invalid_id,association_name,schumaher_index)}.should raise_exception(MissingResource)
|
228
|
+
end
|
229
|
+
|
230
|
+
it "raises MissingResource exception for invalid index" do
|
231
|
+
lambda { client.fetch_related_object(mercedes_300_proxy,association_name,invalid_index)}.should raise_exception(MissingResource)
|
232
|
+
end
|
233
|
+
|
234
|
+
it "raises APIError exception for invalid resource type" do
|
235
|
+
lambda { client.fetch_related_object(proxy_with_invalid_type,association_name,schumaher_index)}.should raise_exception(APIError)
|
236
|
+
end
|
237
|
+
|
238
|
+
it "raises APIError exception for invalid association name" do
|
239
|
+
lambda { client.fetch_related_object(mercedes_300_proxy,invalid_association_name,schumaher_index)}.should raise_exception(APIError)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|