rod-rest 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require_relative 'test_helper'
|
3
|
+
require 'rod/rest/collection_proxy'
|
4
|
+
|
5
|
+
module Rod
|
6
|
+
module Rest
|
7
|
+
describe CollectionProxy do
|
8
|
+
let(:collection) { CollectionProxy.new(mercedes_proxy,association_name,size,client) }
|
9
|
+
let(:mercedes_proxy) { Object.new }
|
10
|
+
let(:association_name) { "drivers" }
|
11
|
+
let(:size) { 0 }
|
12
|
+
let(:client) { Object.new }
|
13
|
+
|
14
|
+
describe "#empty?" do
|
15
|
+
describe "with 0 elements" do
|
16
|
+
it "is empty" do
|
17
|
+
collection.empty?.should == true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "with 1 element" do
|
22
|
+
let(:size) { 1 }
|
23
|
+
|
24
|
+
it "is not empty" do
|
25
|
+
collection.empty?.should == false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#size" do
|
31
|
+
describe "with 5 elements" do
|
32
|
+
let(:size) { 5 }
|
33
|
+
|
34
|
+
it "has size of 5" do
|
35
|
+
collection.size.should == size
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "with car proxy" do
|
41
|
+
let(:car_type) { "Car" }
|
42
|
+
let(:mercedes_300_id) { 1 }
|
43
|
+
|
44
|
+
describe "with 3 drivers" do
|
45
|
+
let(:size) { 3 }
|
46
|
+
let(:schumaher) { Object.new }
|
47
|
+
let(:kubica) { Object.new }
|
48
|
+
let(:alonzo) { Object.new }
|
49
|
+
|
50
|
+
describe "#[index]" do
|
51
|
+
before do
|
52
|
+
stub(client).fetch_related_object(mercedes_proxy,association_name,1) { kubica }
|
53
|
+
stub(client).fetch_related_object(mercedes_proxy,association_name,5) { raise MissingResource.new("/cars/#{mercedes_300_id}/drivers/5") }
|
54
|
+
end
|
55
|
+
|
56
|
+
it "returns drivers by index" do
|
57
|
+
collection[1].should == kubica
|
58
|
+
end
|
59
|
+
|
60
|
+
it "returns nil in case of out of bounds driver" do
|
61
|
+
collection[5].should == nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#first" do
|
66
|
+
before do
|
67
|
+
stub(client).fetch_related_object(mercedes_proxy,association_name,0) { schumaher }
|
68
|
+
end
|
69
|
+
|
70
|
+
it "returns the first driver" do
|
71
|
+
collection.first.should == schumaher
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "#last" do
|
76
|
+
before do
|
77
|
+
stub(client).fetch_related_object(mercedes_proxy,association_name,2) { alonzo }
|
78
|
+
end
|
79
|
+
|
80
|
+
it "returns the last driver" do
|
81
|
+
collection.last.should == alonzo
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#each" do
|
86
|
+
before do
|
87
|
+
stub(client).fetch_related_object(mercedes_proxy,association_name,0) { schumaher }
|
88
|
+
stub(client).fetch_related_object(mercedes_proxy,association_name,1) { kubica }
|
89
|
+
stub(client).fetch_related_object(mercedes_proxy,association_name,2) { alonzo }
|
90
|
+
end
|
91
|
+
|
92
|
+
it "iterates over the drivers" do
|
93
|
+
drivers = [schumaher,kubica,alonzo]
|
94
|
+
collection.each do |driver|
|
95
|
+
driver.should == drivers.shift
|
96
|
+
end
|
97
|
+
drivers.size.should == 0
|
98
|
+
end
|
99
|
+
|
100
|
+
it "allows to chain the calls" do
|
101
|
+
drivers = [schumaher,kubica,alonzo]
|
102
|
+
collection.each.map{|e| e }.should == drivers
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require_relative 'test_helper'
|
3
|
+
require 'rod/rest/json_serializer'
|
4
|
+
|
5
|
+
stub_class 'Rod::Model'
|
6
|
+
|
7
|
+
module Rod
|
8
|
+
module Rest
|
9
|
+
describe JsonSerializer do
|
10
|
+
let(:serializer) { JsonSerializer.new }
|
11
|
+
let(:object) { object = stub!.class { resource }.subject
|
12
|
+
stub(object).rod_id { rod_id }
|
13
|
+
stub(object).is_a?(Rod::Model) { true }
|
14
|
+
object
|
15
|
+
}
|
16
|
+
let(:rod_id) { 1 }
|
17
|
+
let(:type) { "Car" }
|
18
|
+
let(:resource) { resource = stub!.fields { fields }.subject
|
19
|
+
stub(resource).singular_associations { singular_associations }
|
20
|
+
stub(resource).plural_associations { plural_associations }
|
21
|
+
stub(resource).to_s { type }
|
22
|
+
resource
|
23
|
+
}
|
24
|
+
let(:fields) { [] }
|
25
|
+
let(:singular_associations) { [] }
|
26
|
+
let(:plural_associations) { [] }
|
27
|
+
let(:result) { JSON.parse(serializer.serialize(object),symbolize_names: true) }
|
28
|
+
|
29
|
+
|
30
|
+
describe "resource without properties" do
|
31
|
+
it "serializes its rod_id" do
|
32
|
+
result[:rod_id].should == rod_id
|
33
|
+
end
|
34
|
+
|
35
|
+
it "serializes its type" do
|
36
|
+
result[:type].should == type
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "resource with name field" do
|
41
|
+
let(:fields) { [name_field] }
|
42
|
+
let(:name_field) { stub!.name { field_name }.subject }
|
43
|
+
let(:field_name) { "brand" }
|
44
|
+
let(:brand) { "Mercedes" }
|
45
|
+
|
46
|
+
before do
|
47
|
+
stub(object).brand { brand }
|
48
|
+
end
|
49
|
+
|
50
|
+
it "serializes its name field" do
|
51
|
+
result[:brand].should == brand
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "resource with 'owner' singular association" do
|
56
|
+
let(:singular_associations) { [owner_association] }
|
57
|
+
let(:owner_association) { stub!.name { owner_association_name }.subject }
|
58
|
+
let(:owner_association_name){ "owner" }
|
59
|
+
let(:owner) { owner = stub!.rod_id { owner_rod_id }.subject
|
60
|
+
stub(owner).class { owner_type }.subject
|
61
|
+
owner
|
62
|
+
}
|
63
|
+
let(:owner_rod_id) { 10 }
|
64
|
+
let(:owner_type) { "Person" }
|
65
|
+
|
66
|
+
describe "with existing owner" do
|
67
|
+
before do
|
68
|
+
stub(object).owner { owner }
|
69
|
+
end
|
70
|
+
|
71
|
+
it "serializes rod_id of the owner" do
|
72
|
+
result[:owner][:rod_id] == owner_rod_id
|
73
|
+
end
|
74
|
+
|
75
|
+
it "serializes type of the owner" do
|
76
|
+
result[:owner][:type] == owner_type
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "without owner" do
|
81
|
+
before do
|
82
|
+
stub(object).owner { nil }
|
83
|
+
end
|
84
|
+
|
85
|
+
it "serializes the owner as nil" do
|
86
|
+
result[:owner].should == nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "resource with 'drivers' plural association" do
|
92
|
+
let(:plural_associations) { [drivers_association] }
|
93
|
+
let(:drivers_association) { stub!.name { driver_association_name }.subject }
|
94
|
+
let(:driver_association_name) { "drivers" }
|
95
|
+
let(:drivers) { stub!.size { drivers_count }.subject }
|
96
|
+
let(:drivers_count) { 1 }
|
97
|
+
|
98
|
+
before do
|
99
|
+
stub(object).drivers { drivers }
|
100
|
+
end
|
101
|
+
|
102
|
+
it "serializes the number of associated objects" do
|
103
|
+
result[:drivers][:count].should == drivers_count
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require_relative 'test_helper'
|
3
|
+
require 'rod/rest/metadata'
|
4
|
+
|
5
|
+
module Rod
|
6
|
+
module Rest
|
7
|
+
describe Metadata do
|
8
|
+
let(:metadata) { Metadata.new(description: description, parser: parser, resource_metadata_factory: resource_metadata_factory) }
|
9
|
+
let(:parser) { stub!.parse(description,is_a(Hash)) { hash_description }.subject }
|
10
|
+
let(:description) { Object.new }
|
11
|
+
let(:hash_description) { { resource_name => resource_description, "Rod" => rod_description } }
|
12
|
+
let(:resource_description) { Object.new }
|
13
|
+
let(:rod_description) { Object.new }
|
14
|
+
let(:resource_metadata_factory) { stub!.new(resource_name,resource_description) { resource_metadata }.subject }
|
15
|
+
let(:resource_metadata) { Object.new }
|
16
|
+
let(:resource_name) { "Resource" }
|
17
|
+
|
18
|
+
it "creates the metadata from the description" do
|
19
|
+
metadata.resources
|
20
|
+
expect(parser).to have_received.parse(description,is_a(Hash))
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns collection of resource descriptions" do
|
24
|
+
metadata.resources.should respond_to(:each)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "skips Rod pseudo-resource description" do
|
28
|
+
metadata.resources.size.should == hash_description.size - 1
|
29
|
+
end
|
30
|
+
|
31
|
+
it "creates the metadata description using the metadata factory" do
|
32
|
+
metadata.resources.first
|
33
|
+
expect(resource_metadata_factory).to have_received.new(resource_name,resource_description)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require_relative 'test_helper'
|
3
|
+
require 'rod/rest/property_metadata'
|
4
|
+
|
5
|
+
module Rod
|
6
|
+
module Rest
|
7
|
+
describe PropertyMetadata do
|
8
|
+
let(:property_metadata) { PropertyMetadata.new(name,options) }
|
9
|
+
|
10
|
+
describe "constructor" do
|
11
|
+
it "forbids to create property without name" do
|
12
|
+
lambda { PropertyMetadata.new(nil,{}) }.should raise_error(ArgumentError)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#name" do
|
17
|
+
let(:options) { { type: :integer } }
|
18
|
+
let(:name) { :age }
|
19
|
+
|
20
|
+
it "converts its name to string" do
|
21
|
+
property_metadata.name.should be_a(String)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns the name of the poperty" do
|
25
|
+
property_metadata.name.should == name.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#symbolic_name" do
|
30
|
+
let(:options) { { type: :string } }
|
31
|
+
let(:name) { :name }
|
32
|
+
|
33
|
+
it "converts its symbolic name to string" do
|
34
|
+
property_metadata.symbolic_name.should be_a(Symbol)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns the symbolic name of the poperty" do
|
38
|
+
property_metadata.symbolic_name.should == name.to_sym
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#indexed?" do
|
43
|
+
let(:options) { { type: :string, index: index } }
|
44
|
+
let(:name) { :brand }
|
45
|
+
|
46
|
+
describe "with index" do
|
47
|
+
let(:index) { :hash }
|
48
|
+
|
49
|
+
it "returns true" do
|
50
|
+
property_metadata.should be_indexed
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "without index" do
|
55
|
+
let(:index) { nil }
|
56
|
+
it "returns false" do
|
57
|
+
property_metadata.should_not be_indexed
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/test/spec/proxy.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require_relative 'test_helper'
|
3
|
+
require 'rod/rest/proxy'
|
4
|
+
|
5
|
+
module Rod
|
6
|
+
module Rest
|
7
|
+
describe Proxy do
|
8
|
+
let(:proxy) { Proxy.new(metadata,client,collection_proxy_factory: collection_proxy_factory) }
|
9
|
+
let(:metadata) { metadata = stub!.fields { [id_field,name_field] }.subject
|
10
|
+
stub(metadata).singular_associations { [owner_association] }
|
11
|
+
stub(metadata).plural_associations { [drivers_association] }
|
12
|
+
stub(metadata).name { car_type }
|
13
|
+
metadata
|
14
|
+
}
|
15
|
+
let(:client) { client = stub!.fetch_object(schumaher_hash) { schumaher_object }.subject }
|
16
|
+
let(:collection_proxy_factory) { stub!.new(anything,drivers_association_name,drivers_count,client) { collection_proxy }.subject }
|
17
|
+
let(:collection_proxy) { Object.new }
|
18
|
+
let(:id_field) { property = stub!.symbolic_name { :rod_id }.subject
|
19
|
+
stub(property).name { "rod_id" }
|
20
|
+
property
|
21
|
+
}
|
22
|
+
let(:name_field) { property = stub!.symbolic_name { :name }.subject
|
23
|
+
stub(property).name { "name" }
|
24
|
+
property
|
25
|
+
}
|
26
|
+
let(:owner_association) { property = stub!.symbolic_name { :owner }.subject
|
27
|
+
stub(property).name { "owner" }
|
28
|
+
property
|
29
|
+
}
|
30
|
+
let(:drivers_association) { property = stub!.symbolic_name { drivers_association_name.to_sym }.subject
|
31
|
+
stub(property).name { drivers_association_name }
|
32
|
+
property
|
33
|
+
}
|
34
|
+
let(:drivers_association_name) { "drivers"}
|
35
|
+
|
36
|
+
let(:car_type) { "Test::Car" }
|
37
|
+
let(:mercedes_300_hash) { { rod_id: mercedes_300_id, name: mercedes_300_name, type: car_type,
|
38
|
+
owner: { rod_id: schumaher_id, type: person_type}, drivers: { count: drivers_count } } }
|
39
|
+
let(:mercedes_300_id) { 1 }
|
40
|
+
let(:mercedes_300_name) { "Mercedes 300" }
|
41
|
+
|
42
|
+
let(:person_type) { "Test::Person" }
|
43
|
+
let(:drivers_count) { 1 }
|
44
|
+
let(:schumaher_hash) { { rod_id: schumaher_id, type: person_type } }
|
45
|
+
let(:schumaher_id) { 2 }
|
46
|
+
let(:schumaher_object) { Object.new }
|
47
|
+
let(:owner_object) { schumaher_object }
|
48
|
+
let(:first_driver_object) { schumaher_object }
|
49
|
+
|
50
|
+
it "creates new instances" do
|
51
|
+
proxy.new(mercedes_300_hash).should_not == nil
|
52
|
+
end
|
53
|
+
|
54
|
+
it "refuses to create instances with missing rod_id" do
|
55
|
+
lambda { proxy.new({}) }.should raise_error(InvalidData)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "refuses to create instances with missing type" do
|
59
|
+
lambda { proxy.new({rod_id: mercedes_300_id}) }.should raise_error(InvalidData)
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "created instance" do
|
63
|
+
let(:mercedes_300) { proxy.new(mercedes_300_hash) }
|
64
|
+
|
65
|
+
it "has a type" do
|
66
|
+
mercedes_300.type.should == car_type
|
67
|
+
end
|
68
|
+
|
69
|
+
it "has an id" do
|
70
|
+
mercedes_300.rod_id.should == mercedes_300_id
|
71
|
+
end
|
72
|
+
|
73
|
+
it "has a valid 'name' field" do
|
74
|
+
mercedes_300.name.should == mercedes_300_name
|
75
|
+
end
|
76
|
+
|
77
|
+
it "has an valid 'owner' singular association" do
|
78
|
+
mercedes_300.owner.should == owner_object
|
79
|
+
end
|
80
|
+
|
81
|
+
it "has a valid 'drivers' plural association" do
|
82
|
+
mercedes_300.drivers.should == collection_proxy
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require_relative 'test_helper'
|
3
|
+
require 'rod/rest/proxy_factory'
|
4
|
+
|
5
|
+
module Rod
|
6
|
+
module Rest
|
7
|
+
describe ProxyFactory do
|
8
|
+
let(:factory) { ProxyFactory.new(metadata,client,proxy_class: proxy_class) }
|
9
|
+
let(:metadata) { [car_metadata,person_metadata] }
|
10
|
+
let(:car_metadata) { stub!.name { car_type }.subject }
|
11
|
+
let(:person_metadata) { stub!.name { person_type }.subject }
|
12
|
+
let(:client) { Object.new }
|
13
|
+
let(:car_type) { "Car" }
|
14
|
+
let(:person_type) { "Person" }
|
15
|
+
let(:unknown_type) { "Unknown" }
|
16
|
+
let(:proxy_class) { klass = stub!.new(car_metadata,client) { car_proxy_factory }.subject
|
17
|
+
stub(klass).new(person_metadata,client) { person_proxy_factory }
|
18
|
+
klass
|
19
|
+
}
|
20
|
+
let(:car_proxy_factory) { stub!.new(mercedes_300_hash) { mercedes_300 }.subject }
|
21
|
+
let(:person_proxy_factory) { stub!.new(schumaher_hash) { schumaher }.subject }
|
22
|
+
let(:mercedes_300_hash) { { rod_id: mercedes_300_id, type: car_type } }
|
23
|
+
let(:schumaher_hash) { { rod_id: schumaher_id, type: person_type } }
|
24
|
+
let(:unknown_hash) { { rod_id: unknown_id, type: unknown_type } }
|
25
|
+
let(:mercedes_300_id) { 1 }
|
26
|
+
let(:schumaher_id) { 2 }
|
27
|
+
let(:unknown_id) { 3 }
|
28
|
+
let(:mercedes_300) { Object.new }
|
29
|
+
let(:schumaher) { Object.new }
|
30
|
+
|
31
|
+
it "builds new car from hash" do
|
32
|
+
factory.build(mercedes_300_hash).should == mercedes_300
|
33
|
+
end
|
34
|
+
|
35
|
+
it "builds new person proxy from hash" do
|
36
|
+
factory.build(schumaher_hash).should == schumaher
|
37
|
+
end
|
38
|
+
|
39
|
+
it "raises UnknownResource for unknown resource type" do
|
40
|
+
lambda { factory.build(unknown_hash) }.should raise_error(UnknownResource)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|