frodo 0.10.0

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 (153) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +2 -0
  3. data/.circleci/config.yml +54 -0
  4. data/.gitignore +24 -0
  5. data/.gitlab-ci.yml +9 -0
  6. data/.rspec +2 -0
  7. data/.ruby-gemset +1 -0
  8. data/.ruby-version +1 -0
  9. data/.travis.yml +75 -0
  10. data/CHANGELOG.md +163 -0
  11. data/Gemfile +4 -0
  12. data/LICENSE.txt +23 -0
  13. data/README.md +479 -0
  14. data/Rakefile +7 -0
  15. data/TODO.md +55 -0
  16. data/frodo.gemspec +39 -0
  17. data/images/frodo.jpg +0 -0
  18. data/lib/frodo/abstract_client.rb +11 -0
  19. data/lib/frodo/client.rb +6 -0
  20. data/lib/frodo/concerns/api.rb +292 -0
  21. data/lib/frodo/concerns/authentication.rb +32 -0
  22. data/lib/frodo/concerns/base.rb +84 -0
  23. data/lib/frodo/concerns/caching.rb +26 -0
  24. data/lib/frodo/concerns/connection.rb +79 -0
  25. data/lib/frodo/concerns/verbs.rb +68 -0
  26. data/lib/frodo/config.rb +143 -0
  27. data/lib/frodo/entity.rb +335 -0
  28. data/lib/frodo/entity_container.rb +75 -0
  29. data/lib/frodo/entity_set.rb +131 -0
  30. data/lib/frodo/errors.rb +70 -0
  31. data/lib/frodo/middleware/authentication/token.rb +13 -0
  32. data/lib/frodo/middleware/authentication.rb +87 -0
  33. data/lib/frodo/middleware/authorization.rb +18 -0
  34. data/lib/frodo/middleware/caching.rb +30 -0
  35. data/lib/frodo/middleware/custom_headers.rb +14 -0
  36. data/lib/frodo/middleware/gzip.rb +33 -0
  37. data/lib/frodo/middleware/instance_url.rb +20 -0
  38. data/lib/frodo/middleware/logger.rb +42 -0
  39. data/lib/frodo/middleware/multipart.rb +64 -0
  40. data/lib/frodo/middleware/odata_headers.rb +13 -0
  41. data/lib/frodo/middleware/raise_error.rb +47 -0
  42. data/lib/frodo/middleware.rb +33 -0
  43. data/lib/frodo/navigation_property/proxy.rb +80 -0
  44. data/lib/frodo/navigation_property.rb +29 -0
  45. data/lib/frodo/properties/binary.rb +50 -0
  46. data/lib/frodo/properties/boolean.rb +37 -0
  47. data/lib/frodo/properties/collection.rb +50 -0
  48. data/lib/frodo/properties/complex.rb +114 -0
  49. data/lib/frodo/properties/date.rb +27 -0
  50. data/lib/frodo/properties/date_time.rb +83 -0
  51. data/lib/frodo/properties/date_time_offset.rb +17 -0
  52. data/lib/frodo/properties/decimal.rb +54 -0
  53. data/lib/frodo/properties/enum.rb +62 -0
  54. data/lib/frodo/properties/float.rb +67 -0
  55. data/lib/frodo/properties/geography/base.rb +162 -0
  56. data/lib/frodo/properties/geography/line_string.rb +33 -0
  57. data/lib/frodo/properties/geography/point.rb +31 -0
  58. data/lib/frodo/properties/geography/polygon.rb +38 -0
  59. data/lib/frodo/properties/geography.rb +13 -0
  60. data/lib/frodo/properties/guid.rb +17 -0
  61. data/lib/frodo/properties/integer.rb +107 -0
  62. data/lib/frodo/properties/number.rb +14 -0
  63. data/lib/frodo/properties/string.rb +72 -0
  64. data/lib/frodo/properties/time.rb +40 -0
  65. data/lib/frodo/properties/time_of_day.rb +27 -0
  66. data/lib/frodo/properties.rb +32 -0
  67. data/lib/frodo/property.rb +139 -0
  68. data/lib/frodo/property_registry.rb +41 -0
  69. data/lib/frodo/query/criteria/comparison_operators.rb +49 -0
  70. data/lib/frodo/query/criteria/date_functions.rb +61 -0
  71. data/lib/frodo/query/criteria/geography_functions.rb +21 -0
  72. data/lib/frodo/query/criteria/lambda_operators.rb +27 -0
  73. data/lib/frodo/query/criteria/string_functions.rb +40 -0
  74. data/lib/frodo/query/criteria.rb +92 -0
  75. data/lib/frodo/query/in_batches.rb +58 -0
  76. data/lib/frodo/query.rb +221 -0
  77. data/lib/frodo/railtie.rb +19 -0
  78. data/lib/frodo/schema/complex_type.rb +79 -0
  79. data/lib/frodo/schema/enum_type.rb +95 -0
  80. data/lib/frodo/schema.rb +164 -0
  81. data/lib/frodo/service.rb +199 -0
  82. data/lib/frodo/service_registry.rb +52 -0
  83. data/lib/frodo/version.rb +3 -0
  84. data/lib/frodo.rb +67 -0
  85. data/spec/fixtures/auth_success_response.json +11 -0
  86. data/spec/fixtures/error.json +11 -0
  87. data/spec/fixtures/files/entity_to_xml.xml +18 -0
  88. data/spec/fixtures/files/error.xml +5 -0
  89. data/spec/fixtures/files/metadata.xml +150 -0
  90. data/spec/fixtures/files/metadata_with_error.xml +157 -0
  91. data/spec/fixtures/files/product_0.json +10 -0
  92. data/spec/fixtures/files/product_0.xml +28 -0
  93. data/spec/fixtures/files/products.json +106 -0
  94. data/spec/fixtures/files/products.xml +308 -0
  95. data/spec/fixtures/files/supplier_0.json +26 -0
  96. data/spec/fixtures/files/supplier_0.xml +32 -0
  97. data/spec/fixtures/leads.json +923 -0
  98. data/spec/fixtures/refresh_error_response.json +8 -0
  99. data/spec/frodo/abstract_client_spec.rb +13 -0
  100. data/spec/frodo/client_spec.rb +57 -0
  101. data/spec/frodo/concerns/authentication_spec.rb +79 -0
  102. data/spec/frodo/concerns/base_spec.rb +68 -0
  103. data/spec/frodo/concerns/caching_spec.rb +40 -0
  104. data/spec/frodo/concerns/connection_spec.rb +65 -0
  105. data/spec/frodo/config_spec.rb +127 -0
  106. data/spec/frodo/entity/shared_examples.rb +83 -0
  107. data/spec/frodo/entity_container_spec.rb +38 -0
  108. data/spec/frodo/entity_set_spec.rb +169 -0
  109. data/spec/frodo/entity_spec.rb +153 -0
  110. data/spec/frodo/errors_spec.rb +48 -0
  111. data/spec/frodo/middleware/authentication/token_spec.rb +87 -0
  112. data/spec/frodo/middleware/authentication_spec.rb +83 -0
  113. data/spec/frodo/middleware/authorization_spec.rb +17 -0
  114. data/spec/frodo/middleware/custom_headers_spec.rb +21 -0
  115. data/spec/frodo/middleware/gzip_spec.rb +68 -0
  116. data/spec/frodo/middleware/instance_url_spec.rb +27 -0
  117. data/spec/frodo/middleware/logger_spec.rb +21 -0
  118. data/spec/frodo/middleware/odata_headers_spec.rb +15 -0
  119. data/spec/frodo/middleware/raise_error_spec.rb +66 -0
  120. data/spec/frodo/navigation_property/proxy_spec.rb +46 -0
  121. data/spec/frodo/navigation_property_spec.rb +55 -0
  122. data/spec/frodo/properties/binary_spec.rb +50 -0
  123. data/spec/frodo/properties/boolean_spec.rb +72 -0
  124. data/spec/frodo/properties/collection_spec.rb +44 -0
  125. data/spec/frodo/properties/date_spec.rb +23 -0
  126. data/spec/frodo/properties/date_time_offset_spec.rb +30 -0
  127. data/spec/frodo/properties/date_time_spec.rb +23 -0
  128. data/spec/frodo/properties/decimal_spec.rb +50 -0
  129. data/spec/frodo/properties/float_spec.rb +45 -0
  130. data/spec/frodo/properties/geography/line_string_spec.rb +33 -0
  131. data/spec/frodo/properties/geography/point_spec.rb +29 -0
  132. data/spec/frodo/properties/geography/polygon_spec.rb +55 -0
  133. data/spec/frodo/properties/geography/shared_examples.rb +72 -0
  134. data/spec/frodo/properties/guid_spec.rb +17 -0
  135. data/spec/frodo/properties/integer_spec.rb +58 -0
  136. data/spec/frodo/properties/string_spec.rb +46 -0
  137. data/spec/frodo/properties/time_of_day_spec.rb +23 -0
  138. data/spec/frodo/properties/time_spec.rb +15 -0
  139. data/spec/frodo/property_registry_spec.rb +16 -0
  140. data/spec/frodo/property_spec.rb +71 -0
  141. data/spec/frodo/query/criteria_spec.rb +229 -0
  142. data/spec/frodo/query_spec.rb +156 -0
  143. data/spec/frodo/schema/complex_type_spec.rb +97 -0
  144. data/spec/frodo/schema/enum_type_spec.rb +112 -0
  145. data/spec/frodo/schema_spec.rb +113 -0
  146. data/spec/frodo/service_registry_spec.rb +19 -0
  147. data/spec/frodo/service_spec.rb +153 -0
  148. data/spec/frodo/usage_example_spec.rb +161 -0
  149. data/spec/spec_helper.rb +35 -0
  150. data/spec/support/coverage.rb +2 -0
  151. data/spec/support/fixture_helpers.rb +14 -0
  152. data/spec/support/middleware.rb +19 -0
  153. metadata +479 -0
@@ -0,0 +1,156 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frodo::Query, vcr: {cassette_name: 'query_specs'} do
4
+ before(:example) do
5
+ Frodo::Service.new('http://services.odata.org/V4/OData/OData.svc', name: 'ODataDemo', metadata_file: metadata_file)
6
+ end
7
+ let(:metadata_file) { 'spec/fixtures/files/metadata.xml' }
8
+ let(:subject) { Frodo::Query.new(entity_set) }
9
+ let(:entity_set) { Frodo::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
+ describe '#to_s' do
19
+ it { expect(subject).to respond_to(:to_s) }
20
+ it { expect(subject.to_s).to eq('Products')}
21
+ end
22
+
23
+ describe '#[]' do
24
+ it { expect(subject).to respond_to(:[]) }
25
+ it { expect(subject[:Name]).to be_a(Frodo::Query::Criteria) }
26
+ it { expect(subject[:Name].property).to be_a(Frodo::Property) }
27
+ end
28
+
29
+ describe '#find' do
30
+ let(:product) { subject.find(0) }
31
+
32
+ it { expect(subject).to respond_to(:find) }
33
+
34
+ it 'generate the query string to find an entity by its ID' do
35
+ expect(product).to eq("Products(0)")
36
+ end
37
+
38
+ it 'allows selecting specific fields only' do
39
+ product_with_name_only = subject.select('Name').find(0)
40
+ expect(product_with_name_only).to eq("Products(0)?$select=Name")
41
+ end
42
+ end
43
+
44
+ describe '#where' do
45
+ let(:criteria) { subject[:Name].eq('Bread') }
46
+ let(:params) {{ '$filter' => "Name eq 'Bread'" }}
47
+ let(:query_string) { "Products?$filter=Name eq 'Bread'" }
48
+
49
+ it { expect(subject).to respond_to(:where) }
50
+ it { expect(subject.where(criteria)).to eq(subject) }
51
+ it { expect(subject.where(criteria).params).to eq(params) }
52
+ it { expect(subject.where(criteria).to_s).to eq(query_string) }
53
+ end
54
+
55
+ describe '#search' do
56
+ let(:term) { '"mountain bike"' }
57
+ let(:params) {{ '$search' => '"mountain bike"' }}
58
+ let(:query_string) { 'Products?$search="mountain bike"' }
59
+
60
+ it { expect(subject).to respond_to(:search) }
61
+ it { expect(subject.search(term)).to eq(subject) }
62
+ it { expect(subject.search(term).params).to eq(params) }
63
+ it { expect(subject.search(term).to_s).to eq(query_string) }
64
+
65
+ describe 'with multiple terms' do
66
+ let(:params) {{ '$search' => '"mountain bike" AND NOT clothing' }}
67
+ let(:query_string) { 'Products?$search="mountain bike" AND NOT clothing' }
68
+
69
+ it { expect(subject.search(term).search('NOT clothing').params).to eq(params) }
70
+ it { expect(subject.search(term).search('NOT clothing').to_s).to eq(query_string) }
71
+ end
72
+ end
73
+
74
+ #it { expect(subject).to respond_to(:and) }
75
+ describe '#and' do
76
+ it { pending; fail }
77
+ end
78
+
79
+ #it { expect(subject).to respond_to(:or) }
80
+ describe '#or' do
81
+ it { pending; fail }
82
+ end
83
+
84
+ describe '#skip' do
85
+ it { expect(subject).to respond_to(:skip) }
86
+ it { expect(subject.skip(5)).to eq(subject) }
87
+ it 'properly formats query with skip specified' do
88
+ subject.skip(5)
89
+ expect(subject.params).to eq('$skip' => 5)
90
+ expect(subject.to_s).to eq('Products?$skip=5')
91
+ end
92
+ end
93
+
94
+ describe '#limit' do
95
+ it { expect(subject).to respond_to(:limit) }
96
+ it { expect(subject.limit(5)).to eq(subject) }
97
+ it 'properly formats query with limit specified' do
98
+ subject.limit(5)
99
+ expect(subject.params).to eq('$top' => 5)
100
+ expect(subject.to_s).to eq('Products?$top=5')
101
+ end
102
+ end
103
+
104
+ describe '#include_count' do
105
+ it { expect(subject).to respond_to(:include_count) }
106
+ it { expect(subject.include_count).to eq(subject) }
107
+ it 'properly formats query with include_count specified' do
108
+ subject.include_count
109
+ expect(subject.params).to eq('$count' => 'true')
110
+ expect(subject.to_s).to eq('Products?$count=true')
111
+ end
112
+ end
113
+
114
+ describe '#select' do
115
+ it { expect(subject).to respond_to(:select) }
116
+ it { expect(subject.select(:Name, :Price)).to eq(subject) }
117
+ it 'properly formats query with select operation specified' do
118
+ subject.select(:Name, :Price)
119
+ expect(subject.params).to eq('$select' => 'Name,Price')
120
+ expect(subject.to_s).to eq('Products?$select=Name,Price')
121
+ end
122
+ end
123
+
124
+ describe '#expand' do
125
+ it { expect(subject).to respond_to(:expand) }
126
+ it { expect(subject.expand(:Supplier)).to eq(subject) }
127
+ it 'properly formats query with expand operation specified' do
128
+ subject.expand(:Supplier)
129
+ expect(subject.params).to eq('$expand' => 'Supplier')
130
+ expect(subject.to_s).to eq('Products?$expand=Supplier')
131
+ end
132
+ end
133
+
134
+ describe '#order_by' do
135
+ it { expect(subject).to respond_to(:order_by) }
136
+ it { expect(subject.order_by(:Name, :Price)).to eq(subject) }
137
+ it 'properly formats query with orderby operation specified' do
138
+ subject.order_by(:Name, :Price)
139
+ expect(subject.params).to eq('$orderby' => 'Name,Price')
140
+ expect(subject.to_s).to eq('Products?$orderby=Name,Price')
141
+ end
142
+ end
143
+
144
+ describe '#count' do
145
+ it { expect(subject).to respond_to(:count) }
146
+ it { expect(subject.count).to eq("Products/$count") }
147
+
148
+ # FIXME: Should we support that?
149
+ # context 'with filters' do
150
+ # let(:criteria) { subject[:Name].eq('Bread') }
151
+
152
+ # it { expect(subject.where(criteria).count).to eq(1) }
153
+ # end
154
+ end
155
+
156
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frodo::Schema::ComplexType, vcr: {cassette_name: 'schema/complex_type_specs'} do
4
+ before(:example) do
5
+ Frodo::Service.new('http://services.odata.org/V4/OData/OData.svc', name: 'ODataDemo', metadata_file: metadata_file)
6
+ end
7
+
8
+ let(:metadata_file) { 'spec/fixtures/files/metadata.xml' }
9
+ let(:service) { Frodo::ServiceRegistry['ODataDemo'] }
10
+
11
+ let(:address) { {
12
+ 'Street' => '123 Main St',
13
+ 'City' => 'Huntington Beach',
14
+ 'State' => 'CA',
15
+ 'ZipCode' => '92648',
16
+ 'Country' => 'USA'
17
+ } }
18
+
19
+ let(:complex_type) { service.complex_types['ODataDemo.Address'] }
20
+ let(:subject) { complex_type.property_class.new('Address', nil) }
21
+
22
+ describe 'is properly parsed from service metadata' do
23
+ it { expect(complex_type.name).to eq('Address') }
24
+ it { expect(complex_type.namespace).to eq('ODataDemo') }
25
+ it { expect(complex_type.type).to eq('ODataDemo.Address') }
26
+ it { expect(complex_type.property_names).to eq(%w{Street City State ZipCode Country}) }
27
+ end
28
+
29
+ # Check property instance inheritance hierarchy
30
+ it { expect(subject).to be_a(Frodo::Property) }
31
+ it { expect(subject).to be_a(Frodo::Properties::Complex) }
32
+
33
+ it { expect(subject).to respond_to(:name) }
34
+ it { expect(subject).to respond_to(:type) }
35
+ it { expect(subject).to respond_to(:property_names) }
36
+ it { expect(subject).to respond_to(:[]) }
37
+ it { expect(subject).to respond_to(:[]=) }
38
+
39
+ it { expect(subject[ 'Street']).to be_nil }
40
+ it { expect(subject[ 'City']).to be_nil }
41
+ it { expect(subject[ 'State']).to be_nil }
42
+ it { expect(subject['ZipCode']).to be_nil }
43
+ it { expect(subject['Country']).to be_nil }
44
+
45
+ describe '#[]=' do
46
+ before do
47
+ address.each { |key, val| subject[key] = val }
48
+ end
49
+
50
+ it { expect(subject.value).to eq(address) }
51
+
52
+ it { expect(subject[ 'Street']).to eq(address[ 'Street']) }
53
+ it { expect(subject[ 'City']).to eq(address[ 'City']) }
54
+ it { expect(subject[ 'State']).to eq(address[ 'State']) }
55
+ it { expect(subject['ZipCode']).to eq(address['ZipCode']) }
56
+ it { expect(subject['Country']).to eq(address['Country']) }
57
+ end
58
+
59
+ describe '#value=' do
60
+ before { subject.value = address }
61
+
62
+ it { expect(subject.value).to eq(address) }
63
+
64
+ it { expect(subject[ 'Street']).to eq(address[ 'Street']) }
65
+ it { expect(subject[ 'City']).to eq(address[ 'City']) }
66
+ it { expect(subject[ 'State']).to eq(address[ 'State']) }
67
+ it { expect(subject['ZipCode']).to eq(address['ZipCode']) }
68
+ it { expect(subject['Country']).to eq(address['Country']) }
69
+ end
70
+
71
+ describe '#to_xml' do
72
+ let(:builder) do
73
+ Nokogiri::XML::Builder.new do |xml|
74
+ xml.entry(
75
+ 'xmlns' => 'http://www.w3.org/2005/Atom',
76
+ 'xmlns:data' => 'http://docs.oasis-open.org/odata/ns/data',
77
+ 'xmlns:metadata' => 'http://docs.oasis-open.org/odata/ns/metadata',
78
+ ) do
79
+ subject.to_xml(xml)
80
+ end
81
+ end
82
+ end
83
+ let(:xml) { Nokogiri::XML(builder.to_xml) }
84
+
85
+ before(:each) do
86
+ subject.value = address
87
+ xml.remove_namespaces!
88
+ end
89
+
90
+ it { expect(xml.xpath("/entry/Address[@type='ODataDemo.Address']").count).to eq(1) }
91
+ it { expect(xml.xpath('/entry/Address/Street').count).to eq(1) }
92
+ it { expect(xml.xpath('/entry/Address/City').count).to eq(1) }
93
+ it { expect(xml.xpath('/entry/Address/State').count).to eq(1) }
94
+ it { expect(xml.xpath('/entry/Address/ZipCode').count).to eq(1) }
95
+ it { expect(xml.xpath('/entry/Address/Country').count).to eq(1) }
96
+ end
97
+ end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frodo::Schema::EnumType, vcr: {cassette_name: 'schema/enum_type_specs'} do
4
+ before(:example) do
5
+ Frodo::Service.new('http://services.odata.org/V4/OData/OData.svc', name: 'ODataDemo', metadata_file: metadata_file)
6
+ end
7
+
8
+ let(:metadata_file) { 'spec/fixtures/files/metadata.xml' }
9
+ let(:service) { Frodo::ServiceRegistry['ODataDemo'] }
10
+
11
+ let(:enum_type) { service.enum_types['ODataDemo.ProductStatus'] }
12
+ let(:subject) { enum_type.property_class.new('ProductStatus', nil) }
13
+
14
+ describe 'is properly parsed from service metadata' do
15
+ it { expect(enum_type.name).to eq('ProductStatus') }
16
+ it { expect(enum_type.namespace).to eq('ODataDemo') }
17
+ it { expect(enum_type.type).to eq('ODataDemo.ProductStatus') }
18
+ it { expect(enum_type.is_flags?).to eq(false) }
19
+ it { expect(enum_type.underlying_type).to eq('Edm.Byte') }
20
+ it { expect(enum_type.members.values).to eq(%w{Available LowStock Backordered Discontinued}) }
21
+ end
22
+
23
+ # Check property instance inheritance hierarchy
24
+ it { expect(subject).to be_a(Frodo::Property) }
25
+ it { expect(subject).to be_a(Frodo::Properties::Enum) }
26
+
27
+ it { expect(subject).to respond_to(:name) }
28
+ it { expect(subject).to respond_to(:type) }
29
+ it { expect(subject).to respond_to(:members) }
30
+
31
+ describe '#value=' do
32
+ it 'allows setting a valid value' do
33
+ subject.value = 'Available'
34
+ expect(subject.value).to eq('Available')
35
+ end
36
+
37
+ it 'does not allow setting an invalid value' do
38
+ expect {
39
+ subject.value = 'Invalid'
40
+ }.to raise_error(ArgumentError)
41
+ end
42
+
43
+ it 'allows setting by numeric value' do
44
+ expect {
45
+ subject.value = 1
46
+ }.not_to raise_error
47
+ expect(subject.value).to eq('LowStock')
48
+ end
49
+
50
+ context 'when `IsFlags` is false' do
51
+ it 'does not allow setting multiple values' do
52
+ expect {
53
+ subject.value = 'Available, Backordered'
54
+ }.to raise_error(ArgumentError)
55
+ end
56
+ end
57
+
58
+ context 'when `IsFlags` is true' do
59
+ before do
60
+ subject.define_singleton_method(:is_flags?) { true }
61
+ end
62
+
63
+ it 'allows setting multiple values' do
64
+ subject.value = 'Available, Backordered'
65
+ expect(subject.value).to eq(%w[Available Backordered])
66
+ end
67
+
68
+ it 'does not allow setting invalid values' do
69
+ expect {
70
+ subject.value = 'Available, Invalid'
71
+ }.to raise_error(ArgumentError)
72
+ end
73
+
74
+ it 'allows setting by numeric value' do
75
+ expect {
76
+ subject.value = '0, 1'
77
+ }.not_to raise_error
78
+ expect(subject.value).to eq(%w[Available LowStock])
79
+ end
80
+ end
81
+ end
82
+
83
+ describe 'lenient validation' do
84
+ let(:subject) do
85
+ enum_type.property_class.new('ProductStatus', nil, strict: false)
86
+ end
87
+
88
+ describe '#value=' do
89
+ it 'ignores invalid values' do
90
+ expect {
91
+ subject.value = 'Invalid'
92
+ }.not_to raise_error
93
+
94
+ expect(subject.value).to be_nil
95
+ end
96
+
97
+ context 'when `IsFlags` is true' do
98
+ before do
99
+ subject.define_singleton_method(:is_flags?) { true }
100
+ end
101
+
102
+ it 'ignores invalid values' do
103
+ expect {
104
+ subject.value = 'Available, Invalid'
105
+ }.not_to raise_error
106
+
107
+ expect(subject.value).to eq(['Available'])
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frodo::Schema do
4
+ let(:subject) { Frodo::Schema.new(schema_xml, service) }
5
+ let(:service) do
6
+ Frodo::Service.new('http://services.odata.org/V4/OData/OData.svc', metadata_file: metadata_file)
7
+ end
8
+ let(:metadata_file) { 'spec/fixtures/files/metadata.xml' }
9
+ let(:schema_xml) { service.metadata.xpath('//Schema').first }
10
+
11
+ let(:entity_types) { %w{Product FeaturedProduct ProductDetail Category Supplier Person Customer Employee PersonDetail Advertisement} }
12
+ let(:complex_types) { %w{Address} }
13
+ let(:enum_types) { %w{ProductStatus} }
14
+
15
+ describe '#namespace' do
16
+ it { expect(subject).to respond_to(:namespace) }
17
+ it "returns the schema's namespace attribute" do
18
+ expect(subject.namespace).to eq('ODataDemo')
19
+ end
20
+ end
21
+
22
+ describe '#actions' do
23
+ # TODO add a action definition to metadata
24
+ it { expect(subject).to respond_to(:actions) }
25
+ it { expect(subject.actions.size).to eq(0) }
26
+ end
27
+
28
+ describe '#annotations' do
29
+ # TBD
30
+ end
31
+
32
+ describe '#complex_types' do
33
+ it { expect(subject).to respond_to(:complex_types) }
34
+ it { expect(subject.complex_types.size).to eq(1) }
35
+ it { expect(subject.complex_types.keys).to eq(complex_types) }
36
+ end
37
+
38
+ describe '#entity_types' do
39
+ it { expect(subject).to respond_to(:entity_types) }
40
+ it { expect(subject.entity_types.size).to eq(10) }
41
+ it { expect(subject.entity_types).to eq(entity_types) }
42
+ end
43
+
44
+ describe '#enum_types' do
45
+ it { expect(subject).to respond_to(:enum_types) }
46
+ it { expect(subject.enum_types.size).to eq(1) }
47
+ it { expect(subject.enum_types.keys).to eq(enum_types)}
48
+ end
49
+
50
+ describe '#functions' do
51
+ # TODO add a function definition to metadata
52
+ it { expect(subject).to respond_to(:functions) }
53
+ it { expect(subject.functions.size).to eq(0) }
54
+ end
55
+
56
+ describe '#terms' do
57
+ # TBD
58
+ end
59
+
60
+ describe '#type_definitions' do
61
+ # TODO add a type definition to metadata
62
+ it { expect(subject).to respond_to(:type_definitions) }
63
+ it { expect(subject.type_definitions.size).to eq(0) }
64
+ end
65
+
66
+ describe '#navigation_properties' do
67
+ it { expect(subject).to respond_to(:navigation_properties) }
68
+ it { expect(subject.navigation_properties['Product'].size).to eq(3) }
69
+ it { expect(subject.navigation_properties['Product'].values).to all(be_a(Frodo::NavigationProperty)) }
70
+ end
71
+
72
+ describe '#get_property_type' do
73
+ it { expect(subject).to respond_to(:get_property_type) }
74
+ it { expect(subject.get_property_type('Product', 'ID')).to eq('Edm.Int32') }
75
+ it { expect(subject.get_property_type('Product', 'ProductStatus')).to eq('ODataDemo.ProductStatus') }
76
+ end
77
+
78
+ describe '#primary_key_for' do
79
+ it { expect(subject).to respond_to(:primary_key_for) }
80
+ it { expect(subject.primary_key_for('Product')).to eq('ID') }
81
+ end
82
+
83
+ describe '#properties_for_entity' do
84
+ let(:metadata_file) { 'spec/fixtures/files/metadata_with_error.xml' }
85
+ it { expect(subject).to respond_to(:properties_for_entity) }
86
+ it { expect(subject.properties_for_entity('Product').keys).to eq(%w[
87
+ ID
88
+ Name
89
+ Description
90
+ ReleaseDate
91
+ DiscontinuedDate
92
+ Rating
93
+ Price
94
+ ProductStatus
95
+ ]) }
96
+ it { expect(subject.properties_for_entity('Product').values).to all(be_a(Frodo::Property)) }
97
+ it { expect(subject.properties_for_entity('FeaturedProduct').keys).to eq(%w[
98
+ ID
99
+ Name
100
+ Description
101
+ ReleaseDate
102
+ DiscontinuedDate
103
+ Rating
104
+ Price
105
+ ProductStatus
106
+ ]) }
107
+ it 'has error message containing type name' do
108
+ expect {
109
+ subject.properties_for_entity('Error')
110
+ }.to raise_error(/Does.Not.Exist/)
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frodo::ServiceRegistry, vcr: {cassette_name: 'service_registry_specs'} do
4
+ let(:subject) { Frodo::ServiceRegistry }
5
+ let(:sample_service) { Frodo::Service.new('http://services.odata.org/V4/OData/OData.svc', name: 'demoService', metadata_file: metadata_file) }
6
+ let(:metadata_file) { 'spec/fixtures/files/metadata.xml' }
7
+
8
+ it { expect(subject).to respond_to(:add) }
9
+ it { expect(subject).to respond_to(:[]) }
10
+
11
+ describe '#add' do
12
+ before(:example) do
13
+ subject.add(sample_service)
14
+ end
15
+
16
+ it { expect(subject['demoService']).to eq(sample_service) }
17
+ it { expect(subject['http://services.odata.org/V4/OData/OData.svc']).to eq(sample_service) }
18
+ end
19
+ end
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frodo::Service do
4
+ let(:service_url) { 'http://services.odata.org/V4/OData/OData.svc' }
5
+ let(:metadata_file) { 'spec/fixtures/files/metadata.xml' }
6
+ let(:subject) { Frodo::Service.new(service_url, name: 'ODataDemo', metadata_file: metadata_file) }
7
+
8
+ describe '.new' do
9
+ it 'adds itself to Frodo::ServiceRegistry on creation' do
10
+ expect(Frodo::ServiceRegistry['ODataDemo']).to be_nil
11
+ expect(Frodo::ServiceRegistry[service_url]).to be_nil
12
+
13
+ subject
14
+
15
+ expect(Frodo::ServiceRegistry['ODataDemo']).to eq(subject)
16
+ expect(Frodo::ServiceRegistry[service_url]).to eq(subject)
17
+ end
18
+
19
+ it 'registers custom types on creation' do
20
+ expect(Frodo::PropertyRegistry['ODataDemo.Address']).to be_a(Class)
21
+ expect(Frodo::PropertyRegistry['ODataDemo.ProductStatus']).to be_a(Class)
22
+ end
23
+
24
+ it 'allows logger to be set via option' do
25
+ logger = Logger.new(STDERR).tap { |l| l.level = Logger::ERROR }
26
+ service = Frodo::Service.new(service_url, metadata_file: metadata_file, logger: logger)
27
+ expect(service.logger).to eq(logger)
28
+ end
29
+ end
30
+
31
+ describe '#logger' do
32
+ let(:subject) { Frodo::Service.new(service_url, name: 'ODataDemo', logger: logger, metadata_file: metadata_file) }
33
+ let(:logger) { Logger.new(STDERR).tap { |l| l.level = Logger::ERROR } }
34
+
35
+ it 'returns the logger used by the service' do
36
+ expect(subject.logger).to be_a(Logger)
37
+ end
38
+
39
+ it 'returns the default logger if none was set' do
40
+ expect(subject.logger.level).to eq(Logger::ERROR)
41
+ end
42
+
43
+ it 'uses Rails logger if available' do
44
+ stub_const 'Rails', Class.new { def self.logger; end }
45
+ allow(Rails).to receive(:logger).and_return(logger)
46
+ expect(subject.logger).to eq(logger)
47
+ end
48
+ end
49
+
50
+ describe '#service_url' do
51
+ it { expect(subject).to respond_to(:service_url) }
52
+ it { expect(subject.service_url).to eq(service_url) }
53
+ end
54
+
55
+ describe '#schemas' do
56
+ it { expect(subject).to respond_to(:schemas) }
57
+ it { expect(subject.schemas.keys).to eq(['ODataDemo']) }
58
+ it { expect(subject.schemas.values).to all(be_a(Frodo::Schema)) }
59
+ it {
60
+ subject.schemas.each do |namespace, schema|
61
+ expect(schema.namespace).to eq(namespace)
62
+ end
63
+ }
64
+ end
65
+
66
+ describe '#entity_types' do
67
+ it { expect(subject).to respond_to(:entity_types) }
68
+ it { expect(subject.entity_types.size).to eq(10) }
69
+ it { expect(subject.entity_types).to eq(%w[
70
+ ODataDemo.Product
71
+ ODataDemo.FeaturedProduct
72
+ ODataDemo.ProductDetail
73
+ ODataDemo.Category
74
+ ODataDemo.Supplier
75
+ ODataDemo.Person
76
+ ODataDemo.Customer
77
+ ODataDemo.Employee
78
+ ODataDemo.PersonDetail
79
+ ODataDemo.Advertisement
80
+ ]) }
81
+ end
82
+
83
+ describe '#entity_sets' do
84
+ it { expect(subject).to respond_to(:entity_sets) }
85
+ it { expect(subject.entity_sets.size).to eq(7) }
86
+ it { expect(subject.entity_sets.keys).to eq(%w[
87
+ Products
88
+ ProductDetails
89
+ Categories
90
+ Suppliers
91
+ Persons
92
+ PersonDetails
93
+ Advertisements
94
+ ]) }
95
+ it { expect(subject.entity_sets.values).to eq(%w[
96
+ ODataDemo.Product
97
+ ODataDemo.ProductDetail
98
+ ODataDemo.Category
99
+ ODataDemo.Supplier
100
+ ODataDemo.Person
101
+ ODataDemo.PersonDetail
102
+ ODataDemo.Advertisement
103
+ ]) }
104
+ end
105
+
106
+ describe '#complex_types' do
107
+ it { expect(subject).to respond_to(:complex_types) }
108
+ it { expect(subject.complex_types.size).to eq(1) }
109
+ it { expect(subject.complex_types.keys).to eq(['ODataDemo.Address']) }
110
+ end
111
+
112
+ describe '#enum_types' do
113
+ it { expect(subject).to respond_to(:enum_types) }
114
+ it { expect(subject.enum_types.size).to eq(1) }
115
+ it { expect(subject.enum_types.keys).to eq(['ODataDemo.ProductStatus'])}
116
+ end
117
+
118
+ describe '#namespace' do
119
+ it { expect(subject.namespace).to eq('ODataDemo') }
120
+ end
121
+
122
+ describe '#[]' do
123
+ let(:entity_sets) { subject.entity_sets.keys.map { |name| subject[name] } }
124
+ it { expect(entity_sets).to all(be_a(Frodo::EntitySet)) }
125
+ it { expect {subject['Nonexistant']}.to raise_error(ArgumentError) }
126
+ end
127
+
128
+ describe '#get_property_type' do
129
+ it { expect(subject).to respond_to(:get_property_type) }
130
+ it { expect(subject.get_property_type('ODataDemo.Product', 'ID')).to eq('Edm.Int32') }
131
+ it { expect(subject.get_property_type('ODataDemo.Product', 'ProductStatus')).to eq('ODataDemo.ProductStatus') }
132
+ end
133
+
134
+ describe '#primary_key_for' do
135
+ it { expect(subject).to respond_to(:primary_key_for) }
136
+ it { expect(subject.primary_key_for('ODataDemo.Product')).to eq('ID') }
137
+ end
138
+
139
+ describe '#properties_for_entity' do
140
+ it { expect(subject).to respond_to(:properties_for_entity) }
141
+ it { expect(subject.properties_for_entity('ODataDemo.Product').keys).to eq(%w[
142
+ ID
143
+ Name
144
+ Description
145
+ ReleaseDate
146
+ DiscontinuedDate
147
+ Rating
148
+ Price
149
+ ProductStatus
150
+ ]) }
151
+ it { expect(subject.properties_for_entity('ODataDemo.Product').values).to all(be_a(Frodo::Property)) }
152
+ end
153
+ end