frodo 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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