frodata 0.9.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 (128) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +2 -0
  3. data/.gitignore +24 -0
  4. data/.rspec +2 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +75 -0
  8. data/CHANGELOG.md +150 -0
  9. data/Gemfile +4 -0
  10. data/LICENSE.txt +23 -0
  11. data/README.md +427 -0
  12. data/Rakefile +7 -0
  13. data/TODO.md +55 -0
  14. data/frodata.gemspec +34 -0
  15. data/lib/frodata.rb +36 -0
  16. data/lib/frodata/entity.rb +332 -0
  17. data/lib/frodata/entity_container.rb +75 -0
  18. data/lib/frodata/entity_set.rb +161 -0
  19. data/lib/frodata/errors.rb +68 -0
  20. data/lib/frodata/navigation_property.rb +29 -0
  21. data/lib/frodata/navigation_property/proxy.rb +80 -0
  22. data/lib/frodata/properties.rb +32 -0
  23. data/lib/frodata/properties/binary.rb +50 -0
  24. data/lib/frodata/properties/boolean.rb +37 -0
  25. data/lib/frodata/properties/collection.rb +50 -0
  26. data/lib/frodata/properties/complex.rb +114 -0
  27. data/lib/frodata/properties/date.rb +27 -0
  28. data/lib/frodata/properties/date_time.rb +83 -0
  29. data/lib/frodata/properties/date_time_offset.rb +17 -0
  30. data/lib/frodata/properties/decimal.rb +50 -0
  31. data/lib/frodata/properties/enum.rb +62 -0
  32. data/lib/frodata/properties/float.rb +67 -0
  33. data/lib/frodata/properties/geography.rb +13 -0
  34. data/lib/frodata/properties/geography/base.rb +162 -0
  35. data/lib/frodata/properties/geography/line_string.rb +33 -0
  36. data/lib/frodata/properties/geography/point.rb +31 -0
  37. data/lib/frodata/properties/geography/polygon.rb +38 -0
  38. data/lib/frodata/properties/guid.rb +17 -0
  39. data/lib/frodata/properties/integer.rb +107 -0
  40. data/lib/frodata/properties/number.rb +14 -0
  41. data/lib/frodata/properties/string.rb +72 -0
  42. data/lib/frodata/properties/time.rb +40 -0
  43. data/lib/frodata/properties/time_of_day.rb +27 -0
  44. data/lib/frodata/property.rb +139 -0
  45. data/lib/frodata/property_registry.rb +41 -0
  46. data/lib/frodata/query.rb +233 -0
  47. data/lib/frodata/query/criteria.rb +92 -0
  48. data/lib/frodata/query/criteria/comparison_operators.rb +49 -0
  49. data/lib/frodata/query/criteria/date_functions.rb +61 -0
  50. data/lib/frodata/query/criteria/geography_functions.rb +21 -0
  51. data/lib/frodata/query/criteria/lambda_operators.rb +27 -0
  52. data/lib/frodata/query/criteria/string_functions.rb +40 -0
  53. data/lib/frodata/query/in_batches.rb +58 -0
  54. data/lib/frodata/railtie.rb +19 -0
  55. data/lib/frodata/schema.rb +155 -0
  56. data/lib/frodata/schema/complex_type.rb +79 -0
  57. data/lib/frodata/schema/enum_type.rb +95 -0
  58. data/lib/frodata/service.rb +254 -0
  59. data/lib/frodata/service/request.rb +85 -0
  60. data/lib/frodata/service/response.rb +162 -0
  61. data/lib/frodata/service/response/atom.rb +40 -0
  62. data/lib/frodata/service/response/json.rb +41 -0
  63. data/lib/frodata/service/response/plain.rb +36 -0
  64. data/lib/frodata/service/response/xml.rb +40 -0
  65. data/lib/frodata/service_registry.rb +52 -0
  66. data/lib/frodata/version.rb +3 -0
  67. data/spec/fixtures/files/entity_to_xml.xml +17 -0
  68. data/spec/fixtures/files/error.xml +5 -0
  69. data/spec/fixtures/files/metadata.xml +150 -0
  70. data/spec/fixtures/files/product_0.json +10 -0
  71. data/spec/fixtures/files/product_0.xml +28 -0
  72. data/spec/fixtures/files/products.json +106 -0
  73. data/spec/fixtures/files/products.xml +308 -0
  74. data/spec/fixtures/files/supplier_0.json +26 -0
  75. data/spec/fixtures/files/supplier_0.xml +32 -0
  76. data/spec/fixtures/vcr_cassettes/entity_set_specs.yml +1635 -0
  77. data/spec/fixtures/vcr_cassettes/entity_set_specs/bad_entry.yml +183 -0
  78. data/spec/fixtures/vcr_cassettes/entity_set_specs/existing_entry.yml +256 -0
  79. data/spec/fixtures/vcr_cassettes/entity_set_specs/new_entry.yml +185 -0
  80. data/spec/fixtures/vcr_cassettes/entity_specs.yml +285 -0
  81. data/spec/fixtures/vcr_cassettes/navigation_property_proxy_specs.yml +346 -0
  82. data/spec/fixtures/vcr_cassettes/query/result_specs.yml +189 -0
  83. data/spec/fixtures/vcr_cassettes/query_specs.yml +1060 -0
  84. data/spec/fixtures/vcr_cassettes/schema/complex_type_specs.yml +127 -0
  85. data/spec/fixtures/vcr_cassettes/service/request_specs.yml +193 -0
  86. data/spec/fixtures/vcr_cassettes/service_registry_specs.yml +129 -0
  87. data/spec/fixtures/vcr_cassettes/service_specs.yml +127 -0
  88. data/spec/fixtures/vcr_cassettes/usage_example_specs.yml +1330 -0
  89. data/spec/frodata/entity/shared_examples.rb +82 -0
  90. data/spec/frodata/entity_container_spec.rb +38 -0
  91. data/spec/frodata/entity_set_spec.rb +168 -0
  92. data/spec/frodata/entity_spec.rb +151 -0
  93. data/spec/frodata/errors_spec.rb +48 -0
  94. data/spec/frodata/navigation_property/proxy_spec.rb +44 -0
  95. data/spec/frodata/navigation_property_spec.rb +55 -0
  96. data/spec/frodata/properties/binary_spec.rb +50 -0
  97. data/spec/frodata/properties/boolean_spec.rb +72 -0
  98. data/spec/frodata/properties/collection_spec.rb +44 -0
  99. data/spec/frodata/properties/date_spec.rb +23 -0
  100. data/spec/frodata/properties/date_time_offset_spec.rb +30 -0
  101. data/spec/frodata/properties/date_time_spec.rb +23 -0
  102. data/spec/frodata/properties/decimal_spec.rb +51 -0
  103. data/spec/frodata/properties/float_spec.rb +45 -0
  104. data/spec/frodata/properties/geography/line_string_spec.rb +33 -0
  105. data/spec/frodata/properties/geography/point_spec.rb +29 -0
  106. data/spec/frodata/properties/geography/polygon_spec.rb +55 -0
  107. data/spec/frodata/properties/geography/shared_examples.rb +72 -0
  108. data/spec/frodata/properties/guid_spec.rb +17 -0
  109. data/spec/frodata/properties/integer_spec.rb +58 -0
  110. data/spec/frodata/properties/string_spec.rb +46 -0
  111. data/spec/frodata/properties/time_of_day_spec.rb +23 -0
  112. data/spec/frodata/properties/time_spec.rb +15 -0
  113. data/spec/frodata/property_registry_spec.rb +16 -0
  114. data/spec/frodata/property_spec.rb +71 -0
  115. data/spec/frodata/query/criteria_spec.rb +229 -0
  116. data/spec/frodata/query_spec.rb +199 -0
  117. data/spec/frodata/schema/complex_type_spec.rb +96 -0
  118. data/spec/frodata/schema/enum_type_spec.rb +112 -0
  119. data/spec/frodata/schema_spec.rb +97 -0
  120. data/spec/frodata/service/request_spec.rb +49 -0
  121. data/spec/frodata/service/response_spec.rb +85 -0
  122. data/spec/frodata/service_registry_spec.rb +18 -0
  123. data/spec/frodata/service_spec.rb +191 -0
  124. data/spec/frodata/usage_example_spec.rb +188 -0
  125. data/spec/spec_helper.rb +32 -0
  126. data/spec/support/coverage.rb +2 -0
  127. data/spec/support/vcr.rb +9 -0
  128. metadata +401 -0
@@ -0,0 +1,229 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples 'an operator criterium' do |operator, value, to_s|
4
+ it { expect(criteria).to eq(subject) }
5
+ it { expect(criteria.operator).to eq(operator) }
6
+ it { expect(criteria.value).to eq(value) }
7
+ it { expect(criteria.to_s).to eq(to_s) }
8
+ end
9
+
10
+ shared_examples 'a function criterium' do |function, argument, to_s|
11
+ it { expect(criteria).to eq(subject) }
12
+ it { expect(criteria.function).to eq(function) }
13
+ it { expect(criteria.argument).to eq(argument) }
14
+ it { expect(criteria.to_s).to eq(to_s) }
15
+ end
16
+
17
+ shared_examples 'an operator-function criterium' do |function, operator, value, to_s|
18
+ it { expect(criteria).to eq(subject) }
19
+ it { expect(criteria.function).to eq(function) }
20
+ it { expect(criteria.operator).to eq(operator) }
21
+ it { expect(criteria.value).to eq(value) }
22
+ it { expect(criteria.to_s).to eq(to_s) }
23
+ end
24
+
25
+ describe FrOData::Query::Criteria do
26
+ let(:string_property) { FrOData::Properties::String.new(:Name, nil) }
27
+ let(:integer_property) { FrOData::Properties::Integer.new(:Age, nil) }
28
+ let(:datetime_property) { FrOData::Properties::DateTime.new(:BirthDate, nil) }
29
+ let(:geo_property) { FrOData::Properties::Geography::Point.new(:Position, nil) }
30
+ # TODO: use actual `NavigationProperty` class here
31
+ let(:navigation_property) { FrOData::Property.new(:Items, nil) }
32
+
33
+ let(:subject) { FrOData::Query::Criteria.new(property: string_property) }
34
+
35
+ it { expect(subject).to respond_to(:property) }
36
+ it { expect(subject.property).to eq(string_property)}
37
+
38
+ it { expect(subject).to respond_to(:operator) }
39
+ it { expect(subject).to respond_to(:value) }
40
+ it { expect(subject).to respond_to(:to_s) }
41
+
42
+ describe 'comparison operators' do
43
+ describe '#eq' do
44
+ let(:criteria) { subject.eq('Bread') }
45
+
46
+ it { expect(subject).to respond_to(:eq) }
47
+ it_behaves_like 'an operator criterium', :eq, 'Bread', "Name eq 'Bread'"
48
+ end
49
+
50
+ describe '#ne' do
51
+ let(:criteria) { subject.ne('Bread') }
52
+
53
+ it { expect(subject).to respond_to(:ne) }
54
+ it_behaves_like 'an operator criterium', :ne, 'Bread', "Name ne 'Bread'"
55
+ end
56
+
57
+ describe '#gt' do
58
+ let(:subject) { FrOData::Query::Criteria.new(property: integer_property) }
59
+ let(:criteria) { subject.gt(5) }
60
+
61
+ it { expect(subject).to respond_to(:gt) }
62
+ it_behaves_like 'an operator criterium', :gt, 5, "Age gt 5"
63
+ end
64
+
65
+ describe '#ge' do
66
+ let(:subject) { FrOData::Query::Criteria.new(property: integer_property) }
67
+ let(:criteria) { subject.ge(5) }
68
+
69
+ it { expect(subject).to respond_to(:ge) }
70
+ it_behaves_like 'an operator criterium', :ge, 5, 'Age ge 5'
71
+ end
72
+
73
+ describe '#lt' do
74
+ let(:subject) { FrOData::Query::Criteria.new(property: integer_property) }
75
+ let(:criteria) { subject.lt(5) }
76
+
77
+ it { expect(subject).to respond_to(:lt) }
78
+ it_behaves_like 'an operator criterium', :lt, 5, 'Age lt 5'
79
+ end
80
+
81
+ describe '#le' do
82
+ let(:subject) { FrOData::Query::Criteria.new(property: integer_property) }
83
+ let(:criteria) { subject.le(5) }
84
+
85
+ it { expect(subject).to respond_to(:le) }
86
+ it_behaves_like 'an operator criterium', :le, 5, 'Age le 5'
87
+ end
88
+ end
89
+
90
+ describe 'string functions' do
91
+ describe '#contains' do
92
+ let(:criteria) { subject.contains('freds') }
93
+
94
+ it { expect(subject).to respond_to(:contains) }
95
+ it_behaves_like 'a function criterium', :contains, 'freds', "contains(Name,'freds')"
96
+ end
97
+
98
+ describe '#startswith' do
99
+ let(:criteria) { subject.startswith('Alfreds') }
100
+
101
+ it { expect(subject).to respond_to(:startswith) }
102
+ it_behaves_like 'a function criterium', :startswith, 'Alfreds', "startswith(Name,'Alfreds')"
103
+ end
104
+
105
+ describe '#endswith' do
106
+ let(:criteria) { subject.endswith('Futterkiste') }
107
+
108
+ it { expect(subject).to respond_to(:endswith) }
109
+ it_behaves_like 'a function criterium', :endswith, 'Futterkiste', "endswith(Name,'Futterkiste')"
110
+ end
111
+
112
+ describe '#tolower' do
113
+ let(:criteria) { subject.tolower.eq('alfreds futterkiste') }
114
+
115
+ it { expect(subject).to respond_to(:tolower) }
116
+ it_behaves_like 'an operator-function criterium', :tolower, :eq, 'alfreds futterkiste', "tolower(Name) eq 'alfreds futterkiste'"
117
+ end
118
+
119
+ describe '#toupper' do
120
+ let(:criteria) { subject.toupper.eq('ALFREDS FUTTERKISTE') }
121
+
122
+ it { expect(subject).to respond_to(:toupper) }
123
+ it_behaves_like 'an operator-function criterium', :toupper, :eq, 'ALFREDS FUTTERKISTE', "toupper(Name) eq 'ALFREDS FUTTERKISTE'"
124
+ end
125
+ end
126
+
127
+ describe 'date functions' do
128
+ let(:subject) { FrOData::Query::Criteria.new(property: datetime_property) }
129
+
130
+ describe '#year' do
131
+ let(:criteria) { subject.year.eq(1981) }
132
+
133
+ it { expect(subject).to respond_to(:year) }
134
+ it_behaves_like 'an operator-function criterium', :year, :eq, 1981, 'year(BirthDate) eq 1981'
135
+ end
136
+
137
+ describe '#month' do
138
+ let(:criteria) { subject.month.eq(8) }
139
+
140
+ it { expect(subject).to respond_to(:month) }
141
+ it_behaves_like 'an operator-function criterium', :month, :eq, 8, 'month(BirthDate) eq 8'
142
+ end
143
+
144
+ describe '#day' do
145
+ let(:criteria) { subject.day.eq(27) }
146
+
147
+ it { expect(subject).to respond_to(:day) }
148
+ it_behaves_like 'an operator-function criterium', :day, :eq, 27, 'day(BirthDate) eq 27'
149
+ end
150
+
151
+ describe '#hour' do
152
+ let(:criteria) { subject.hour.eq(9) }
153
+
154
+ it { expect(subject).to respond_to(:hour) }
155
+ it_behaves_like 'an operator-function criterium', :hour, :eq, 9, 'hour(BirthDate) eq 9'
156
+ end
157
+
158
+ describe '#minute' do
159
+ let(:criteria) { subject.minute.eq(35) }
160
+
161
+ it { expect(subject).to respond_to(:minute) }
162
+ it_behaves_like 'an operator-function criterium', :minute, :eq, 35, 'minute(BirthDate) eq 35'
163
+ end
164
+
165
+ describe '#second' do
166
+ let(:criteria) { subject.second.eq(11) }
167
+
168
+ it { expect(subject).to respond_to(:second) }
169
+ it_behaves_like 'an operator-function criterium', :second, :eq, 11, 'second(BirthDate) eq 11'
170
+ end
171
+
172
+ describe '#fractionalseconds' do
173
+ let(:criteria) { subject.fractionalseconds.eq(0) }
174
+
175
+ it { expect(subject).to respond_to(:fractionalseconds) }
176
+ it_behaves_like 'an operator-function criterium', :fractionalseconds, :eq, 0, 'fractionalseconds(BirthDate) eq 0'
177
+ end
178
+
179
+ describe '#date' do
180
+ let(:criteria) { subject.date.ne('date(EndTime)') }
181
+
182
+ it { expect(subject).to respond_to(:date) }
183
+ it_behaves_like 'an operator-function criterium', :date, :ne, 'date(EndTime)', 'date(BirthDate) ne date(EndTime)'
184
+ end
185
+
186
+ describe '#time' do
187
+ let(:criteria) { subject.time.le('StartOfDay') }
188
+
189
+ it { expect(subject).to respond_to(:time) }
190
+ it_behaves_like 'an operator-function criterium', :time, :le, 'StartOfDay', 'time(BirthDate) le StartOfDay'
191
+ end
192
+ end
193
+
194
+ describe 'geospatial functions' do
195
+ let(:subject) { FrOData::Query::Criteria.new(property: geo_property) }
196
+
197
+ describe '#distance' do
198
+ let(:criteria) { subject.distance('TargetPosition').le(42) }
199
+
200
+ it { expect(subject).to respond_to(:distance) }
201
+ it_behaves_like 'an operator-function criterium', :'geo.distance', :le, 42, 'geo.distance(Position,TargetPosition) le 42'
202
+ end
203
+
204
+ describe '#intersects' do
205
+ let(:criteria) { subject.intersects('TargetArea') }
206
+
207
+ it { expect(subject).to respond_to(:intersects) }
208
+ it_behaves_like 'a function criterium', :'geo.intersects', 'TargetArea', 'geo.intersects(Position,TargetArea)'
209
+ end
210
+ end
211
+
212
+ describe 'lambda operators' do
213
+ let(:subject) { FrOData::Query::Criteria.new(property: navigation_property) }
214
+
215
+ describe '#any' do
216
+ let(:criteria) { subject.any('Quantity').gt(100) }
217
+
218
+ it { expect(subject).to respond_to(:any) }
219
+ it_behaves_like 'an operator-function criterium', :any, :gt, 100, 'Items/any(d:d/Quantity gt 100)'
220
+ end
221
+
222
+ describe '#all' do
223
+ let(:criteria) { subject.all('Quantity').gt(100) }
224
+
225
+ it { expect(subject).to respond_to(:all) }
226
+ it_behaves_like 'an operator-function criterium', :all, :gt, 100, 'Items/all(d:d/Quantity gt 100)'
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,199 @@
1
+ require 'spec_helper'
2
+
3
+ describe FrOData::Query, vcr: {cassette_name: 'query_specs'} do
4
+ before(:example) do
5
+ FrOData::Service.new('http://services.odata.org/V4/OData/OData.svc', name: 'ODataDemo')
6
+ end
7
+
8
+ let(:subject) { FrOData::Query.new(entity_set) }
9
+ let(:entity_set) { FrOData::EntitySet.new(options) }
10
+ let(:options) { {
11
+ service_name: 'ODataDemo',
12
+ container: 'DemoService',
13
+ namespace: 'ODataDemo',
14
+ name: 'Products',
15
+ type: 'ODataDemo.Product'
16
+ } }
17
+
18
+ it { expect(subject).to respond_to(:to_s) }
19
+ it { expect(subject.to_s).to eq('Products')}
20
+
21
+ it { expect(subject).to respond_to(:[]) }
22
+ describe '#[]' do
23
+ it { expect(subject[:Name]).to be_a(FrOData::Query::Criteria) }
24
+ it { expect(subject[:Name].property).to be_a(FrOData::Property) }
25
+ end
26
+
27
+ it { expect(subject).to respond_to(:find) }
28
+ describe '#find' do
29
+ let(:product) { subject.find(0) }
30
+
31
+ it 'finds an entity by its ID' do
32
+ expect(product).to be_a(FrOData::Entity)
33
+ expect(product['ID']).to eq(0)
34
+ end
35
+
36
+ it 'allows expanding navigational properties' do
37
+ product_with_categories = subject.expand('Categories').find(0)
38
+ expect(product_with_categories['Categories']).to eq([
39
+ { "ID" => 0, "Name" => "Food" }
40
+ ])
41
+ end
42
+ end
43
+
44
+ it { expect(subject).to respond_to(:where) }
45
+ describe '#where' do
46
+ let(:criteria) { subject[:Name].eq('Bread') }
47
+ let(:query_string) { "Products?$filter=Name eq 'Bread'" }
48
+
49
+ it { expect(subject.where(criteria)).to eq(subject) }
50
+ it { expect(subject.where(criteria).to_s).to eq(query_string) }
51
+ end
52
+
53
+ it { expect(subject).to respond_to(:search) }
54
+ describe '#search' do
55
+ let(:term) { '"mountain bike"' }
56
+ let(:query_string) { 'Products?$search="mountain bike"' }
57
+
58
+ it { expect(subject.search(term)).to eq(subject) }
59
+ it { expect(subject.search(term).to_s).to eq(query_string) }
60
+
61
+ describe 'with multiple terms' do
62
+ let(:query_string) { 'Products?$search="mountain bike" AND NOT clothing' }
63
+
64
+ it { expect(subject.search(term).search('NOT clothing').to_s).to eq(query_string) }
65
+ end
66
+ end
67
+
68
+ #it { expect(subject).to respond_to(:and) }
69
+ describe '#and' do
70
+ it { pending; fail }
71
+ end
72
+
73
+ #it { expect(subject).to respond_to(:or) }
74
+ describe '#or' do
75
+ it { pending; fail }
76
+ end
77
+
78
+ it { expect(subject).to respond_to(:skip) }
79
+ describe '#skip' do
80
+ it { expect(subject.skip(5)).to eq(subject) }
81
+ it 'properly formats query with skip specified' do
82
+ subject.skip(5)
83
+ expect(subject.to_s).to eq('Products?$skip=5')
84
+ end
85
+ end
86
+
87
+ it { expect(subject).to respond_to(:limit) }
88
+ describe '#limit' do
89
+
90
+ it { expect(subject.limit(5)).to eq(subject) }
91
+ it 'properly formats query with limit specified' do
92
+ subject.limit(5)
93
+ expect(subject.to_s).to eq('Products?$top=5')
94
+ end
95
+ end
96
+
97
+ it { expect(subject).to respond_to(:include_count) }
98
+ describe '#include_count' do
99
+ it { expect(subject.include_count).to eq(subject) }
100
+ it 'properly formats query with include_count specified' do
101
+ subject.include_count
102
+ expect(subject.to_s).to eq('Products?$count=true')
103
+ end
104
+ end
105
+
106
+ it { expect(subject).to respond_to(:select) }
107
+ describe '#select' do
108
+ it { expect(subject.select(:Name, :Price)).to eq(subject) }
109
+ it 'properly formats query with select operation specified' do
110
+ subject.select(:Name, :Price)
111
+ expect(subject.to_s).to eq('Products?$select=Name,Price')
112
+ end
113
+ end
114
+
115
+ it { expect(subject).to respond_to(:expand) }
116
+ describe '#expand' do
117
+ it { expect(subject.expand(:Supplier)).to eq(subject) }
118
+ it 'properly formats query with expand operation specified' do
119
+ subject.expand(:Supplier)
120
+ expect(subject.to_s).to eq('Products?$expand=Supplier')
121
+ end
122
+ end
123
+
124
+ it { expect(subject).to respond_to(:order_by) }
125
+ describe '#order_by' do
126
+ it { expect(subject.order_by(:Name, :Price)).to eq(subject) }
127
+ it 'properly formats query with orderby operation specified' do
128
+ subject.order_by(:Name, :Price)
129
+ expect(subject.to_s).to eq('Products?$orderby=Name,Price')
130
+ end
131
+ end
132
+
133
+ describe '#execute' do
134
+ it { expect(subject).to respond_to(:execute) }
135
+ it { expect(subject.execute).to be_a(FrOData::Service::Response) }
136
+ end
137
+
138
+ describe '#count' do
139
+ it { expect(subject).to respond_to(:count) }
140
+ it { expect(subject.count).to be_a(Integer) }
141
+ it { expect(subject.count).to eq(11) }
142
+
143
+ context 'with filters' do
144
+ let(:criteria) { subject[:Name].eq('Bread') }
145
+
146
+ it { expect(subject.where(criteria).count).to eq(1) }
147
+ end
148
+ end
149
+
150
+ describe '#empty?' do
151
+ it { expect(subject).to respond_to(:empty?) }
152
+ it { expect(subject.empty?).to eq(false) }
153
+
154
+ context 'with filters' do
155
+ let(:non_empty_criteria) { subject[:Name].eq('Bread') }
156
+ let(:empty_criteria) { subject[:Name].eq('NonExistent') }
157
+
158
+ it { expect(subject.where(non_empty_criteria).empty?).to eq(false) }
159
+ it { expect(subject.where(empty_criteria).empty?).to eq(true) }
160
+ end
161
+ end
162
+
163
+ describe '#in_batches' do
164
+ it { expect(subject).to respond_to(:in_batches) }
165
+ it 'returns FrOData::Entities in batches of specified size' do
166
+ batch_count = entity_count = 0
167
+
168
+ subject.in_batches(of: 5) do |batch|
169
+ expect(batch).to be_a(FrOData::Service::Response)
170
+ expect(batch.count).to eq(5) unless batch_count == 2
171
+
172
+ batch.each do |entity|
173
+ expect(entity).to be_a(FrOData::Entity)
174
+ expect(entity.type).to eq('ODataDemo.Product')
175
+ entity_count += 1
176
+ end
177
+
178
+ batch_count += 1
179
+ end
180
+
181
+ expect(batch_count).to eq(3)
182
+ expect(entity_count).to eq(11)
183
+ end
184
+
185
+ describe '#each' do
186
+ it 'returns FrOData::Entities' do
187
+ entity_count = 0
188
+
189
+ subject.in_batches(of: 5).each do |entity|
190
+ expect(entity).to be_a(FrOData::Entity)
191
+ expect(entity.type).to eq('ODataDemo.Product')
192
+ entity_count += 1
193
+ end
194
+
195
+ expect(entity_count).to eq(11)
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+
3
+ describe FrOData::Schema::ComplexType, vcr: {cassette_name: 'schema/complex_type_specs'} do
4
+ before(:example) do
5
+ FrOData::Service.new('http://services.odata.org/V4/OData/OData.svc', name: 'ODataDemo')
6
+ end
7
+
8
+ let(:service) { FrOData::ServiceRegistry['ODataDemo'] }
9
+
10
+ let(:address) { {
11
+ 'Street' => '123 Main St',
12
+ 'City' => 'Huntington Beach',
13
+ 'State' => 'CA',
14
+ 'ZipCode' => '92648',
15
+ 'Country' => 'USA'
16
+ } }
17
+
18
+ let(:complex_type) { service.complex_types['ODataDemo.Address'] }
19
+ let(:subject) { complex_type.property_class.new('Address', nil) }
20
+
21
+ describe 'is properly parsed from service metadata' do
22
+ it { expect(complex_type.name).to eq('Address') }
23
+ it { expect(complex_type.namespace).to eq('ODataDemo') }
24
+ it { expect(complex_type.type).to eq('ODataDemo.Address') }
25
+ it { expect(complex_type.property_names).to eq(%w{Street City State ZipCode Country}) }
26
+ end
27
+
28
+ # Check property instance inheritance hierarchy
29
+ it { expect(subject).to be_a(FrOData::Property) }
30
+ it { expect(subject).to be_a(FrOData::Properties::Complex) }
31
+
32
+ it { expect(subject).to respond_to(:name) }
33
+ it { expect(subject).to respond_to(:type) }
34
+ it { expect(subject).to respond_to(:property_names) }
35
+ it { expect(subject).to respond_to(:[]) }
36
+ it { expect(subject).to respond_to(:[]=) }
37
+
38
+ it { expect(subject[ 'Street']).to be_nil }
39
+ it { expect(subject[ 'City']).to be_nil }
40
+ it { expect(subject[ 'State']).to be_nil }
41
+ it { expect(subject['ZipCode']).to be_nil }
42
+ it { expect(subject['Country']).to be_nil }
43
+
44
+ describe '#[]=' do
45
+ before do
46
+ address.each { |key, val| subject[key] = val }
47
+ end
48
+
49
+ it { expect(subject.value).to eq(address) }
50
+
51
+ it { expect(subject[ 'Street']).to eq(address[ 'Street']) }
52
+ it { expect(subject[ 'City']).to eq(address[ 'City']) }
53
+ it { expect(subject[ 'State']).to eq(address[ 'State']) }
54
+ it { expect(subject['ZipCode']).to eq(address['ZipCode']) }
55
+ it { expect(subject['Country']).to eq(address['Country']) }
56
+ end
57
+
58
+ describe '#value=' do
59
+ before { subject.value = address }
60
+
61
+ it { expect(subject.value).to eq(address) }
62
+
63
+ it { expect(subject[ 'Street']).to eq(address[ 'Street']) }
64
+ it { expect(subject[ 'City']).to eq(address[ 'City']) }
65
+ it { expect(subject[ 'State']).to eq(address[ 'State']) }
66
+ it { expect(subject['ZipCode']).to eq(address['ZipCode']) }
67
+ it { expect(subject['Country']).to eq(address['Country']) }
68
+ end
69
+
70
+ describe '#to_xml' do
71
+ let(:builder) do
72
+ Nokogiri::XML::Builder.new do |xml|
73
+ xml.entry(
74
+ 'xmlns' => 'http://www.w3.org/2005/Atom',
75
+ 'xmlns:data' => 'http://docs.oasis-open.org/odata/ns/data',
76
+ 'xmlns:metadata' => 'http://docs.oasis-open.org/odata/ns/metadata',
77
+ ) do
78
+ subject.to_xml(xml)
79
+ end
80
+ end
81
+ end
82
+ let(:xml) { Nokogiri::XML(builder.to_xml) }
83
+
84
+ before(:each) do
85
+ subject.value = address
86
+ xml.remove_namespaces!
87
+ end
88
+
89
+ it { expect(xml.xpath("/entry/Address[@type='ODataDemo.Address']").count).to eq(1) }
90
+ it { expect(xml.xpath('/entry/Address/Street').count).to eq(1) }
91
+ it { expect(xml.xpath('/entry/Address/City').count).to eq(1) }
92
+ it { expect(xml.xpath('/entry/Address/State').count).to eq(1) }
93
+ it { expect(xml.xpath('/entry/Address/ZipCode').count).to eq(1) }
94
+ it { expect(xml.xpath('/entry/Address/Country').count).to eq(1) }
95
+ end
96
+ end