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.
- checksums.yaml +7 -0
- data/.autotest +2 -0
- data/.circleci/config.yml +54 -0
- data/.gitignore +24 -0
- data/.gitlab-ci.yml +9 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +75 -0
- data/CHANGELOG.md +163 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +23 -0
- data/README.md +479 -0
- data/Rakefile +7 -0
- data/TODO.md +55 -0
- data/frodo.gemspec +39 -0
- data/images/frodo.jpg +0 -0
- data/lib/frodo/abstract_client.rb +11 -0
- data/lib/frodo/client.rb +6 -0
- data/lib/frodo/concerns/api.rb +292 -0
- data/lib/frodo/concerns/authentication.rb +32 -0
- data/lib/frodo/concerns/base.rb +84 -0
- data/lib/frodo/concerns/caching.rb +26 -0
- data/lib/frodo/concerns/connection.rb +79 -0
- data/lib/frodo/concerns/verbs.rb +68 -0
- data/lib/frodo/config.rb +143 -0
- data/lib/frodo/entity.rb +335 -0
- data/lib/frodo/entity_container.rb +75 -0
- data/lib/frodo/entity_set.rb +131 -0
- data/lib/frodo/errors.rb +70 -0
- data/lib/frodo/middleware/authentication/token.rb +13 -0
- data/lib/frodo/middleware/authentication.rb +87 -0
- data/lib/frodo/middleware/authorization.rb +18 -0
- data/lib/frodo/middleware/caching.rb +30 -0
- data/lib/frodo/middleware/custom_headers.rb +14 -0
- data/lib/frodo/middleware/gzip.rb +33 -0
- data/lib/frodo/middleware/instance_url.rb +20 -0
- data/lib/frodo/middleware/logger.rb +42 -0
- data/lib/frodo/middleware/multipart.rb +64 -0
- data/lib/frodo/middleware/odata_headers.rb +13 -0
- data/lib/frodo/middleware/raise_error.rb +47 -0
- data/lib/frodo/middleware.rb +33 -0
- data/lib/frodo/navigation_property/proxy.rb +80 -0
- data/lib/frodo/navigation_property.rb +29 -0
- data/lib/frodo/properties/binary.rb +50 -0
- data/lib/frodo/properties/boolean.rb +37 -0
- data/lib/frodo/properties/collection.rb +50 -0
- data/lib/frodo/properties/complex.rb +114 -0
- data/lib/frodo/properties/date.rb +27 -0
- data/lib/frodo/properties/date_time.rb +83 -0
- data/lib/frodo/properties/date_time_offset.rb +17 -0
- data/lib/frodo/properties/decimal.rb +54 -0
- data/lib/frodo/properties/enum.rb +62 -0
- data/lib/frodo/properties/float.rb +67 -0
- data/lib/frodo/properties/geography/base.rb +162 -0
- data/lib/frodo/properties/geography/line_string.rb +33 -0
- data/lib/frodo/properties/geography/point.rb +31 -0
- data/lib/frodo/properties/geography/polygon.rb +38 -0
- data/lib/frodo/properties/geography.rb +13 -0
- data/lib/frodo/properties/guid.rb +17 -0
- data/lib/frodo/properties/integer.rb +107 -0
- data/lib/frodo/properties/number.rb +14 -0
- data/lib/frodo/properties/string.rb +72 -0
- data/lib/frodo/properties/time.rb +40 -0
- data/lib/frodo/properties/time_of_day.rb +27 -0
- data/lib/frodo/properties.rb +32 -0
- data/lib/frodo/property.rb +139 -0
- data/lib/frodo/property_registry.rb +41 -0
- data/lib/frodo/query/criteria/comparison_operators.rb +49 -0
- data/lib/frodo/query/criteria/date_functions.rb +61 -0
- data/lib/frodo/query/criteria/geography_functions.rb +21 -0
- data/lib/frodo/query/criteria/lambda_operators.rb +27 -0
- data/lib/frodo/query/criteria/string_functions.rb +40 -0
- data/lib/frodo/query/criteria.rb +92 -0
- data/lib/frodo/query/in_batches.rb +58 -0
- data/lib/frodo/query.rb +221 -0
- data/lib/frodo/railtie.rb +19 -0
- data/lib/frodo/schema/complex_type.rb +79 -0
- data/lib/frodo/schema/enum_type.rb +95 -0
- data/lib/frodo/schema.rb +164 -0
- data/lib/frodo/service.rb +199 -0
- data/lib/frodo/service_registry.rb +52 -0
- data/lib/frodo/version.rb +3 -0
- data/lib/frodo.rb +67 -0
- data/spec/fixtures/auth_success_response.json +11 -0
- data/spec/fixtures/error.json +11 -0
- data/spec/fixtures/files/entity_to_xml.xml +18 -0
- data/spec/fixtures/files/error.xml +5 -0
- data/spec/fixtures/files/metadata.xml +150 -0
- data/spec/fixtures/files/metadata_with_error.xml +157 -0
- data/spec/fixtures/files/product_0.json +10 -0
- data/spec/fixtures/files/product_0.xml +28 -0
- data/spec/fixtures/files/products.json +106 -0
- data/spec/fixtures/files/products.xml +308 -0
- data/spec/fixtures/files/supplier_0.json +26 -0
- data/spec/fixtures/files/supplier_0.xml +32 -0
- data/spec/fixtures/leads.json +923 -0
- data/spec/fixtures/refresh_error_response.json +8 -0
- data/spec/frodo/abstract_client_spec.rb +13 -0
- data/spec/frodo/client_spec.rb +57 -0
- data/spec/frodo/concerns/authentication_spec.rb +79 -0
- data/spec/frodo/concerns/base_spec.rb +68 -0
- data/spec/frodo/concerns/caching_spec.rb +40 -0
- data/spec/frodo/concerns/connection_spec.rb +65 -0
- data/spec/frodo/config_spec.rb +127 -0
- data/spec/frodo/entity/shared_examples.rb +83 -0
- data/spec/frodo/entity_container_spec.rb +38 -0
- data/spec/frodo/entity_set_spec.rb +169 -0
- data/spec/frodo/entity_spec.rb +153 -0
- data/spec/frodo/errors_spec.rb +48 -0
- data/spec/frodo/middleware/authentication/token_spec.rb +87 -0
- data/spec/frodo/middleware/authentication_spec.rb +83 -0
- data/spec/frodo/middleware/authorization_spec.rb +17 -0
- data/spec/frodo/middleware/custom_headers_spec.rb +21 -0
- data/spec/frodo/middleware/gzip_spec.rb +68 -0
- data/spec/frodo/middleware/instance_url_spec.rb +27 -0
- data/spec/frodo/middleware/logger_spec.rb +21 -0
- data/spec/frodo/middleware/odata_headers_spec.rb +15 -0
- data/spec/frodo/middleware/raise_error_spec.rb +66 -0
- data/spec/frodo/navigation_property/proxy_spec.rb +46 -0
- data/spec/frodo/navigation_property_spec.rb +55 -0
- data/spec/frodo/properties/binary_spec.rb +50 -0
- data/spec/frodo/properties/boolean_spec.rb +72 -0
- data/spec/frodo/properties/collection_spec.rb +44 -0
- data/spec/frodo/properties/date_spec.rb +23 -0
- data/spec/frodo/properties/date_time_offset_spec.rb +30 -0
- data/spec/frodo/properties/date_time_spec.rb +23 -0
- data/spec/frodo/properties/decimal_spec.rb +50 -0
- data/spec/frodo/properties/float_spec.rb +45 -0
- data/spec/frodo/properties/geography/line_string_spec.rb +33 -0
- data/spec/frodo/properties/geography/point_spec.rb +29 -0
- data/spec/frodo/properties/geography/polygon_spec.rb +55 -0
- data/spec/frodo/properties/geography/shared_examples.rb +72 -0
- data/spec/frodo/properties/guid_spec.rb +17 -0
- data/spec/frodo/properties/integer_spec.rb +58 -0
- data/spec/frodo/properties/string_spec.rb +46 -0
- data/spec/frodo/properties/time_of_day_spec.rb +23 -0
- data/spec/frodo/properties/time_spec.rb +15 -0
- data/spec/frodo/property_registry_spec.rb +16 -0
- data/spec/frodo/property_spec.rb +71 -0
- data/spec/frodo/query/criteria_spec.rb +229 -0
- data/spec/frodo/query_spec.rb +156 -0
- data/spec/frodo/schema/complex_type_spec.rb +97 -0
- data/spec/frodo/schema/enum_type_spec.rb +112 -0
- data/spec/frodo/schema_spec.rb +113 -0
- data/spec/frodo/service_registry_spec.rb +19 -0
- data/spec/frodo/service_spec.rb +153 -0
- data/spec/frodo/usage_example_spec.rb +161 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/support/coverage.rb +2 -0
- data/spec/support/fixture_helpers.rb +14 -0
- data/spec/support/middleware.rb +19 -0
- metadata +479 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Frodo::EntitySet, vcr: {cassette_name: 'entity_set_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(:subject) { Frodo::EntitySet.new(options) }
|
|
10
|
+
let(:options) { {
|
|
11
|
+
container: 'DemoService', namespace: 'ODataDemo', name: 'Products',
|
|
12
|
+
type: 'ODataDemo.Product', service_name: 'ODataDemo'
|
|
13
|
+
} }
|
|
14
|
+
|
|
15
|
+
it { expect(subject).to respond_to(:name) }
|
|
16
|
+
it { expect(subject).to respond_to(:type) }
|
|
17
|
+
it { expect(subject).to respond_to(:container) }
|
|
18
|
+
it { expect(subject).to respond_to(:namespace) }
|
|
19
|
+
it { expect(subject).to respond_to(:service_name) }
|
|
20
|
+
it { expect(subject).to respond_to(:new_entity) }
|
|
21
|
+
# it { expect(subject).to respond_to(:[]) }
|
|
22
|
+
# it { expect(subject).to respond_to(:<<) }
|
|
23
|
+
|
|
24
|
+
it { expect(subject.name).to eq('Products') }
|
|
25
|
+
it { expect(subject.container).to eq('DemoService') }
|
|
26
|
+
it { expect(subject.namespace).to eq('ODataDemo') }
|
|
27
|
+
it { expect(subject.service_name).to eq('ODataDemo') }
|
|
28
|
+
it { expect(subject.type).to eq('ODataDemo.Product') }
|
|
29
|
+
|
|
30
|
+
# describe '#each' do
|
|
31
|
+
# it { expect(subject).to respond_to(:each) }
|
|
32
|
+
# it { expect(lambda {
|
|
33
|
+
# @counter = 0
|
|
34
|
+
# subject.each {|entity| @counter += 1}
|
|
35
|
+
# @counter
|
|
36
|
+
# }.call).to eq(11) }
|
|
37
|
+
# it { expect(lambda {
|
|
38
|
+
# @entities = []
|
|
39
|
+
# subject.each {|entity| @entities << entity}
|
|
40
|
+
# @entities
|
|
41
|
+
# }.call.shuffle.first).to be_a(Frodo::Entity) }
|
|
42
|
+
# end
|
|
43
|
+
|
|
44
|
+
# describe '#first' do
|
|
45
|
+
# it { expect(subject).to respond_to(:first) }
|
|
46
|
+
|
|
47
|
+
# describe 'retrieving a single entity' do
|
|
48
|
+
# it { expect(subject.first).to be_a(Frodo::Entity) }
|
|
49
|
+
# it { expect(subject.first['ID']).to eq(0) }
|
|
50
|
+
# end
|
|
51
|
+
|
|
52
|
+
# describe 'retrieving multiple entities' do
|
|
53
|
+
# it { expect(subject.first(5)).to be_a(Array) }
|
|
54
|
+
# it { expect(subject.first(5).length).to eq(5) }
|
|
55
|
+
# it do
|
|
56
|
+
# subject.first(5).each do |entity|
|
|
57
|
+
# expect(entity).to be_a(Frodo::Entity)
|
|
58
|
+
# end
|
|
59
|
+
# end
|
|
60
|
+
# end
|
|
61
|
+
# end
|
|
62
|
+
|
|
63
|
+
# describe '#count' do
|
|
64
|
+
# it { expect(subject).to respond_to(:count) }
|
|
65
|
+
# it { expect(subject.count).to eq(11) }
|
|
66
|
+
# end
|
|
67
|
+
|
|
68
|
+
describe '#query' do
|
|
69
|
+
it { expect(subject).to respond_to(:query) }
|
|
70
|
+
it { expect(subject.query).to be_a(Frodo::Query) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe '#new_entity' do
|
|
74
|
+
let(:new_entity) { subject.new_entity(properties) }
|
|
75
|
+
let(:release_date) { DateTime.new(2014,7,5) }
|
|
76
|
+
let(:properties) { {
|
|
77
|
+
Name: 'Widget',
|
|
78
|
+
Description: 'Just a simple widget',
|
|
79
|
+
ReleaseDate: release_date,
|
|
80
|
+
DiscontinuedDate: nil,
|
|
81
|
+
Rating: 4,
|
|
82
|
+
Price: 3.5
|
|
83
|
+
} }
|
|
84
|
+
|
|
85
|
+
it { expect(new_entity.entity_set).to eq(subject) }
|
|
86
|
+
it { expect(new_entity['ID']).to be_nil }
|
|
87
|
+
it { expect(new_entity['Name']).to eq('Widget') }
|
|
88
|
+
it { expect(new_entity['Description']).to eq('Just a simple widget') }
|
|
89
|
+
it { expect(new_entity['ReleaseDate']).to eq(release_date) }
|
|
90
|
+
it { expect(new_entity['DiscontinuedDate']).to be_nil }
|
|
91
|
+
it { expect(new_entity['Rating']).to eq(4) }
|
|
92
|
+
it { expect(new_entity['Price']).to eq(3.5) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# describe '#[]' do
|
|
96
|
+
# let(:existing_entity) { subject[0] }
|
|
97
|
+
# let(:nonexistant_entity) { subject[99] }
|
|
98
|
+
|
|
99
|
+
# it 'finds an entity by its primary key' do
|
|
100
|
+
# expect(existing_entity).to be_a(Frodo::Entity)
|
|
101
|
+
# expect(existing_entity['ID']).to eq(0)
|
|
102
|
+
# end
|
|
103
|
+
|
|
104
|
+
# it 'raises an error when no entity was found' do
|
|
105
|
+
# expect { nonexistant_entity }.to raise_error(Frodo::Errors::NotFound)
|
|
106
|
+
# end
|
|
107
|
+
|
|
108
|
+
# describe 'eager loading' do
|
|
109
|
+
# it 'works with a single property' do
|
|
110
|
+
# product_with_categories = subject[1, expand: 'Categories']
|
|
111
|
+
|
|
112
|
+
# expect(product_with_categories['Categories']).to eq([
|
|
113
|
+
# { "ID" => 0, "Name" => "Food" },
|
|
114
|
+
# { "ID" => 1, "Name" => "Beverages" }
|
|
115
|
+
# ])
|
|
116
|
+
# end
|
|
117
|
+
|
|
118
|
+
# it 'works with multiple properties' do
|
|
119
|
+
# product_with_details = subject[1, expand: %w[Categories Supplier]]
|
|
120
|
+
|
|
121
|
+
# expect(product_with_details['Supplier']).to include('Name' => 'Exotic Liquids')
|
|
122
|
+
# expect(product_with_details['Categories']).to be_a(Array)
|
|
123
|
+
# end
|
|
124
|
+
|
|
125
|
+
# it 'works with special shortcut for all properties' do
|
|
126
|
+
# product_with_all_details = subject[1, expand: :all]
|
|
127
|
+
|
|
128
|
+
# expect(product_with_all_details['Supplier']).to include('Name' => 'Exotic Liquids')
|
|
129
|
+
# expect(product_with_all_details['Categories']).to be_a(Array)
|
|
130
|
+
# expect(product_with_all_details['ProductDetail']).to include('Details' => 'Details of product 1')
|
|
131
|
+
# end
|
|
132
|
+
# end
|
|
133
|
+
# end
|
|
134
|
+
|
|
135
|
+
# describe '#<<' do
|
|
136
|
+
# let(:new_entity) { subject.new_entity(properties) }
|
|
137
|
+
# let(:bad_entity) { subject.new_entity }
|
|
138
|
+
# let(:existing_entity) { subject.first }
|
|
139
|
+
# let(:properties) { {
|
|
140
|
+
# Name: 'Widget',
|
|
141
|
+
# Description: 'Just a simple widget',
|
|
142
|
+
# ReleaseDate: DateTime.now.new_offset(0),
|
|
143
|
+
# DiscontinuedDate: nil,
|
|
144
|
+
# Rating: 4,
|
|
145
|
+
# Price: 3.5
|
|
146
|
+
# } }
|
|
147
|
+
|
|
148
|
+
# xdescribe 'with an existing entity', vcr: {cassette_name: 'entity_set_specs/existing_entry'} do
|
|
149
|
+
# before(:each) do
|
|
150
|
+
# subject << existing_entity
|
|
151
|
+
# end
|
|
152
|
+
|
|
153
|
+
# it { expect(existing_entity.any_errors?).to eq(false) }
|
|
154
|
+
# end
|
|
155
|
+
|
|
156
|
+
# xdescribe 'with a new entity', vcr: {cassette_name: 'entity_set_specs/new_entry'} do
|
|
157
|
+
# it do
|
|
158
|
+
# expect(new_entity['ID']).to be_nil
|
|
159
|
+
# expect {subject << new_entity}.to_not raise_error
|
|
160
|
+
# expect(new_entity['ID']).to_not be_nil
|
|
161
|
+
# expect(new_entity['ID']).to eq(9999)
|
|
162
|
+
# end
|
|
163
|
+
# end
|
|
164
|
+
|
|
165
|
+
# xdescribe 'with a bad entity', vcr: {cassette_name: 'entity_set_specs/bad_entry'} do
|
|
166
|
+
# it { expect{subject << bad_entity}.to raise_error }
|
|
167
|
+
# end
|
|
168
|
+
# end
|
|
169
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require_relative 'entity/shared_examples'
|
|
3
|
+
|
|
4
|
+
describe Frodo::Entity, vcr: {cassette_name: 'entity_specs'} do
|
|
5
|
+
before(:example) do
|
|
6
|
+
Frodo::Service.new('http://services.odata.org/V4/OData/OData.svc', name: 'ODataDemo', metadata_file: metadata_file)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
let(:metadata_file) { 'spec/fixtures/files/metadata.xml' }
|
|
10
|
+
let(:subject) { Frodo::Entity.new(options) }
|
|
11
|
+
let(:options) { {
|
|
12
|
+
type: 'ODataDemo.Product',
|
|
13
|
+
namespace: 'ODataDemo',
|
|
14
|
+
service_name: 'ODataDemo'
|
|
15
|
+
} }
|
|
16
|
+
|
|
17
|
+
it { expect(subject).to respond_to(:name, :type, :namespace, :service_name) }
|
|
18
|
+
|
|
19
|
+
it { expect(subject.name).to eq('Product') }
|
|
20
|
+
it { expect(subject.type).to eq('ODataDemo.Product') }
|
|
21
|
+
it { expect(subject.namespace).to eq('ODataDemo') }
|
|
22
|
+
it { expect(subject.service_name).to eq('ODataDemo') }
|
|
23
|
+
|
|
24
|
+
describe '.with_properties' do
|
|
25
|
+
let(:subject) { Frodo::Entity.with_properties(properties, options) }
|
|
26
|
+
let(:properties) { {
|
|
27
|
+
"ID" => 0,
|
|
28
|
+
"Name" => "Bread",
|
|
29
|
+
"Description" => "Whole grain bread",
|
|
30
|
+
"ReleaseDate" => "1992-01-01T00:00:00Z",
|
|
31
|
+
"DiscontinuedDate" => nil,
|
|
32
|
+
"Rating" => 4,
|
|
33
|
+
"Price" => 2.5
|
|
34
|
+
} }
|
|
35
|
+
let(:entity_set) {
|
|
36
|
+
Frodo::EntitySet.new(
|
|
37
|
+
container: 'DemoService',
|
|
38
|
+
namespace: 'ODataDemo',
|
|
39
|
+
name: 'Products',
|
|
40
|
+
type: 'Product',
|
|
41
|
+
service_name: 'ODataDemo')
|
|
42
|
+
}
|
|
43
|
+
let(:options) { {
|
|
44
|
+
type: 'ODataDemo.Product',
|
|
45
|
+
namespace: 'ODataDemo',
|
|
46
|
+
service_name: 'ODataDemo',
|
|
47
|
+
entity_set: entity_set
|
|
48
|
+
} }
|
|
49
|
+
|
|
50
|
+
it_behaves_like 'a valid product'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe '.from_xml' do
|
|
54
|
+
let(:subject) { Frodo::Entity.from_xml(product_xml, options) }
|
|
55
|
+
let(:product_xml) {
|
|
56
|
+
document = ::Nokogiri::XML(File.open('spec/fixtures/files/product_0.xml'))
|
|
57
|
+
document.remove_namespaces!
|
|
58
|
+
document.xpath('//entry').first
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
it { expect(Frodo::Entity).to respond_to(:from_xml) }
|
|
62
|
+
|
|
63
|
+
it_behaves_like 'a valid product'
|
|
64
|
+
|
|
65
|
+
context 'with a complex type property' do
|
|
66
|
+
let(:options) { {
|
|
67
|
+
type: 'ODataDemo.Supplier',
|
|
68
|
+
namespace: 'ODataDemo',
|
|
69
|
+
service_name: 'ODataDemo'
|
|
70
|
+
} }
|
|
71
|
+
|
|
72
|
+
let(:subject) { Frodo::Entity.from_xml(supplier_xml, options) }
|
|
73
|
+
let(:supplier_xml) {
|
|
74
|
+
document = ::Nokogiri::XML(File.open('spec/fixtures/files/supplier_0.xml'))
|
|
75
|
+
document.remove_namespaces!
|
|
76
|
+
document.xpath('//entry').first
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
it_behaves_like 'a valid supplier'
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe '#to_xml' do
|
|
84
|
+
let(:subject) { Frodo::Entity.with_properties(properties, options) }
|
|
85
|
+
let(:properties) { {
|
|
86
|
+
"ID" => 0,
|
|
87
|
+
"Name" => "Bread",
|
|
88
|
+
"Description" => "Whole grain bread",
|
|
89
|
+
"ReleaseDate" => "1992-01-01T00:00:00Z",
|
|
90
|
+
"DiscontinuedDate" => nil,
|
|
91
|
+
"Rating" => 4,
|
|
92
|
+
"Price" => 2.5
|
|
93
|
+
} }
|
|
94
|
+
let(:options) { {
|
|
95
|
+
type: 'ODataDemo.Product',
|
|
96
|
+
namespace: 'ODataDemo',
|
|
97
|
+
service_name: 'ODataDemo'
|
|
98
|
+
} }
|
|
99
|
+
let(:product_xml) {
|
|
100
|
+
File.read('spec/fixtures/files/entity_to_xml.xml')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# TODO: parse the XML and veryify property values instead?
|
|
104
|
+
# TODO: explicitly assert namespace URIs?
|
|
105
|
+
it { expect(subject.to_xml).to eq(product_xml) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
describe '.from_json' do
|
|
109
|
+
let(:subject) { Frodo::Entity.from_json(product_json, options) }
|
|
110
|
+
let(:product_json) {
|
|
111
|
+
File.read('spec/fixtures/files/product_0.json')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
it { expect(Frodo::Entity).to respond_to(:from_json) }
|
|
115
|
+
it_behaves_like 'a valid product'
|
|
116
|
+
|
|
117
|
+
context 'with a complex type property' do
|
|
118
|
+
let(:options) { {
|
|
119
|
+
type: 'ODataDemo.Supplier',
|
|
120
|
+
namespace: 'ODataDemo',
|
|
121
|
+
service_name: 'ODataDemo'
|
|
122
|
+
} }
|
|
123
|
+
|
|
124
|
+
let(:subject) { Frodo::Entity.from_json(supplier_json, options) }
|
|
125
|
+
let(:supplier_json) {
|
|
126
|
+
File.read('spec/fixtures/files/supplier_0.json')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
it_behaves_like 'a valid supplier'
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe '#to_json' do
|
|
134
|
+
let(:subject) { Frodo::Entity.with_properties(properties, options) }
|
|
135
|
+
let(:properties) { {
|
|
136
|
+
"ID" => 0,
|
|
137
|
+
"Name" => "Bread",
|
|
138
|
+
"Description" => "Whole grain bread",
|
|
139
|
+
"ReleaseDate" => "1992-01-01T00:00:00Z",
|
|
140
|
+
"DiscontinuedDate" => nil,
|
|
141
|
+
"Rating" => 4,
|
|
142
|
+
"Price" => 2.5,
|
|
143
|
+
"ProductStatus" => nil
|
|
144
|
+
} }
|
|
145
|
+
let(:options) { {
|
|
146
|
+
type: 'ODataDemo.Product',
|
|
147
|
+
namespace: 'ODataDemo',
|
|
148
|
+
service_name: 'ODataDemo'
|
|
149
|
+
} }
|
|
150
|
+
|
|
151
|
+
it { expect(subject.to_json).to eq(properties.to_json) }
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Frodo::RequestError do
|
|
4
|
+
subject { Frodo::RequestError.new(response, 'The server made a boo-boo.') }
|
|
5
|
+
let(:response) { instance_double('Faraday::Response', status: 400) }
|
|
6
|
+
|
|
7
|
+
describe '#http_status' do
|
|
8
|
+
it 'returns the status code' do
|
|
9
|
+
expect(subject.http_status).to eq(response.status)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe '#response' do
|
|
14
|
+
it 'returns the response' do
|
|
15
|
+
expect(subject.response).to eq(response)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe '#message' do
|
|
20
|
+
it 'returns the error message' do
|
|
21
|
+
expect(subject.message).to eq('The server made a boo-boo.')
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe Frodo::Errors::InternalServerError do
|
|
27
|
+
let(:response) { instance_double('Faraday::Response', status: 500) }
|
|
28
|
+
|
|
29
|
+
context 'with custom error message' do
|
|
30
|
+
subject { Frodo::Errors::InternalServerError.new(response, 'The server made a boo-boo.')}
|
|
31
|
+
|
|
32
|
+
describe '#message' do
|
|
33
|
+
it 'combines default message with custom message' do
|
|
34
|
+
expect(subject.message).to eq('500 Internal Server Error: The server made a boo-boo.')
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context 'without custom error message' do
|
|
40
|
+
subject { Frodo::Errors::InternalServerError.new(response) }
|
|
41
|
+
|
|
42
|
+
describe '#message' do
|
|
43
|
+
it 'returns the default message' do
|
|
44
|
+
expect(subject.message).to eq('500 Internal Server Error')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'webmock/rspec'
|
|
5
|
+
|
|
6
|
+
describe Frodo::Middleware::Authentication::Token do
|
|
7
|
+
include WebMock::API
|
|
8
|
+
|
|
9
|
+
describe 'authentication middleware' do
|
|
10
|
+
let(:options) do
|
|
11
|
+
{ refresh_token: 'refresh_token',
|
|
12
|
+
client_id: 'client_id',
|
|
13
|
+
client_secret: 'client_secret',
|
|
14
|
+
adapter: :net_http,
|
|
15
|
+
host: 'login.window.net'
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
let(:success_request) do
|
|
20
|
+
stub_request(:post, "https://login.window.net/common/oauth2/token").with(
|
|
21
|
+
body: "grant_type=refresh_token&refresh_token=refresh_token&" \
|
|
22
|
+
"client_id=client_id&client_secret=client_secret"
|
|
23
|
+
).to_return(status: 200, body: fixture("auth_success_response"))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
let(:fail_request) do
|
|
27
|
+
stub_request(:post, "https://login.window.net/common/oauth2/token").with(
|
|
28
|
+
body: "grant_type=refresh_token&refresh_token=refresh_token&" \
|
|
29
|
+
"client_id=client_id&client_secret=client_secret"
|
|
30
|
+
).to_return(status: 400, body: fixture("refresh_error_response"))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '.authenticate!' do
|
|
34
|
+
context 'when successful' do
|
|
35
|
+
let!(:request) { success_request }
|
|
36
|
+
|
|
37
|
+
describe '@options' do
|
|
38
|
+
subject { options }
|
|
39
|
+
|
|
40
|
+
before do
|
|
41
|
+
middleware.authenticate!
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it { expect(subject[:host]).to eq 'login.window.net' }
|
|
45
|
+
|
|
46
|
+
it { expect(subject[:oauth_token]).to eq "gfEzf4azkWZMTjlay7usiSWhc0eOLNkKMw" }
|
|
47
|
+
|
|
48
|
+
it { expect(subject[:refresh_token]).to eq "QswqIkdHSdbyvbDFuLwHNAoU1QgAA" }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context 'when an authentication_callback is specified' do
|
|
52
|
+
before(:each) do
|
|
53
|
+
options.merge!(authentication_callback: auth_callback)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'calls the authentication callback with the response body' do
|
|
57
|
+
expect(auth_callback).to receive(:call)
|
|
58
|
+
middleware.authenticate!
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
context 'when unsuccessful' do
|
|
64
|
+
let!(:request) { fail_request }
|
|
65
|
+
|
|
66
|
+
it 'raises an exception' do
|
|
67
|
+
expect {
|
|
68
|
+
middleware.authenticate!
|
|
69
|
+
}.to raise_error Frodo::AuthenticationError
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context 'when an authentication_callback is specified' do
|
|
73
|
+
before(:each) do
|
|
74
|
+
options.merge!(authentication_callback: auth_callback)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'does not call the authentication callback' do
|
|
78
|
+
expect(auth_callback).to_not receive(:call)
|
|
79
|
+
expect do
|
|
80
|
+
middleware.authenticate!
|
|
81
|
+
end.to raise_error
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Frodo::Middleware::Authentication do
|
|
6
|
+
let(:options) do
|
|
7
|
+
{ host: 'login.windows.net',
|
|
8
|
+
proxy_uri: 'https://not-a-real-site.com',
|
|
9
|
+
authentication_retries: retries,
|
|
10
|
+
adapter: :net_http,
|
|
11
|
+
ssl: { version: :TLSv1_2 } }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe '.authenticate!' do
|
|
15
|
+
subject { lambda { middleware.authenticate! } }
|
|
16
|
+
it { should raise_error NotImplementedError }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe '.call' do
|
|
20
|
+
subject { lambda { middleware.call(env) } }
|
|
21
|
+
|
|
22
|
+
context 'when successfull' do
|
|
23
|
+
before do
|
|
24
|
+
expect(app).to receive(:call).once
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it { should_not raise_error }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context 'when an exception is thrown' do
|
|
31
|
+
before do
|
|
32
|
+
expect(middleware).to receive(:authenticate!)
|
|
33
|
+
expect(app).to receive(:call).once.
|
|
34
|
+
and_raise(Frodo::UnauthorizedError.new('something bad'))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it { should raise_error Frodo::UnauthorizedError }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe '.connection' do
|
|
42
|
+
subject(:connection) { middleware.connection }
|
|
43
|
+
|
|
44
|
+
it { expect(subject.url_prefix).to eq(URI.parse('https://login.windows.net')) }
|
|
45
|
+
|
|
46
|
+
it "should have a proxy URI" do
|
|
47
|
+
expect(connection.proxy[:uri]).to eq(URI.parse('https://not-a-real-site.com'))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe '.builder' do
|
|
51
|
+
subject(:builder) { connection.builder }
|
|
52
|
+
|
|
53
|
+
context 'with logging disabled' do
|
|
54
|
+
before do
|
|
55
|
+
expect(Frodo).to receive(:log?).and_return(false)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it { expect(subject.handlers).to include FaradayMiddleware::ParseJson, Faraday::Adapter::NetHttp }
|
|
59
|
+
it { expect(subject.handlers).not_to include Frodo::Middleware::Logger }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context 'with logging enabled' do
|
|
63
|
+
before do
|
|
64
|
+
expect(Frodo).to receive(:log?).and_return(true)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it { expect(subject.handlers).to include FaradayMiddleware::ParseJson, Faraday::Adapter::NetHttp, Frodo::Middleware::Logger }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
context 'with specified adapter' do
|
|
71
|
+
before do
|
|
72
|
+
options[:adapter] = :typhoeus
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it { expect(subject.handlers).to include FaradayMiddleware::ParseJson, Faraday::Adapter::Typhoeus }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "should have SSL config set" do
|
|
80
|
+
expect(connection.ssl[:version]).to eq(:TLSv1_2)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Frodo::Middleware::Authorization do
|
|
6
|
+
let(:options) { { oauth_token: 'token' } }
|
|
7
|
+
|
|
8
|
+
describe '.call' do
|
|
9
|
+
subject { middleware }
|
|
10
|
+
|
|
11
|
+
before do
|
|
12
|
+
subject.call(env)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it { expect(env[:request_headers]['Authorization']).to eq 'Bearer token' }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Frodo::Middleware::CustomHeaders do
|
|
6
|
+
describe '.call' do
|
|
7
|
+
subject { lambda { middleware.call(env) } }
|
|
8
|
+
|
|
9
|
+
context 'when :request_headers are a Hash' do
|
|
10
|
+
let(:options) { { request_headers: { 'x-test-header' => 'Test Value' } } }
|
|
11
|
+
|
|
12
|
+
it { should change { env[:request_headers]['x-test-header'] }.to eq 'Test Value' }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
context 'when :request_headers are not a Hash' do
|
|
16
|
+
let(:options) { { request_headers: 'bad header' } }
|
|
17
|
+
|
|
18
|
+
it { should_not(change { env[:request_headers] }) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Frodo::Middleware::Gzip do
|
|
6
|
+
let(:options) { { oauth_token: 'token' } }
|
|
7
|
+
|
|
8
|
+
# Return a gzipped string.
|
|
9
|
+
def gzip(str)
|
|
10
|
+
StringIO.new.tap do |io|
|
|
11
|
+
gz = Zlib::GzipWriter.new(io)
|
|
12
|
+
gz.write(str)
|
|
13
|
+
gz.close
|
|
14
|
+
end.string
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '.call' do
|
|
18
|
+
subject { lambda { middleware.call(env) } }
|
|
19
|
+
|
|
20
|
+
before do
|
|
21
|
+
expect(app).to receive(:on_complete) { middleware.on_complete(env) }
|
|
22
|
+
expect(app).to receive(:call) do
|
|
23
|
+
env[:body] = gzip fixture('leads')
|
|
24
|
+
env[:response_headers]['Content-Encoding'] = 'gzip'
|
|
25
|
+
app
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'decompresses the body' do
|
|
30
|
+
should change { env[:body] }.to(fixture('leads'))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'when :compress is false' do
|
|
34
|
+
it { should_not(change { env[:request_headers]['Accept-Encoding'] }) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context 'when :compress is true' do
|
|
38
|
+
before do
|
|
39
|
+
options[:compress] = true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it { should(change { env[:request_headers]['Accept-Encoding'] }.to('gzip')) }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe '.decompress' do
|
|
47
|
+
let(:body) { gzip fixture('leads') }
|
|
48
|
+
|
|
49
|
+
subject { middleware.decompress(body) }
|
|
50
|
+
it { should eq fixture('leads') }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe '.gzipped?' do
|
|
54
|
+
subject { middleware.gzipped?(env) }
|
|
55
|
+
|
|
56
|
+
context 'when gzipped' do
|
|
57
|
+
before do
|
|
58
|
+
env[:response_headers]['Content-Encoding'] = 'gzip'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it { should be_truthy }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
context 'when not gzipped' do
|
|
65
|
+
it { should be_falsey }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Frodo::Middleware::InstanceURL do
|
|
6
|
+
describe '.call' do
|
|
7
|
+
subject { lambda { middleware.call(nil) } }
|
|
8
|
+
let(:connection) { double("connection") }
|
|
9
|
+
|
|
10
|
+
context 'when the instance url is not set' do
|
|
11
|
+
before do
|
|
12
|
+
allow(client).to receive_message_chain :connection, url_prefix: URI.parse('http:/')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it { should raise_error Frodo::UnauthorizedError }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
context 'when the instance url is set' do
|
|
19
|
+
before do
|
|
20
|
+
allow(client).to receive_message_chain :connection, url_prefix: URI.parse('http://foobar.com/')
|
|
21
|
+
expect(app).to receive(:call).once
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it { should_not raise_error }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|