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
@@ -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
|