vra-restapi 1.5.2

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.
@@ -0,0 +1,42 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ module Vra
20
+ class Resources
21
+ attr_reader :client
22
+
23
+ def initialize(client)
24
+ @client = client
25
+ end
26
+
27
+ def all_resources
28
+ resources = []
29
+
30
+ items = client.http_get_paginated_array!('/catalog-service/api/consumer/resources')
31
+ items.each do |item|
32
+ resources << Vra::Resource.new(client, data: item)
33
+ end
34
+
35
+ resources
36
+ end
37
+
38
+ def by_id(id)
39
+ Vra::Resource.new(client, id: id)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,21 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ module Vra
20
+ VERSION = '1.5.2'.freeze
21
+ end
@@ -0,0 +1,98 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+
21
+ describe Vra::CatalogRequest do
22
+ let(:client) do
23
+ Vra::Client.new(username: 'user@corp.local',
24
+ password: 'password',
25
+ tenant: 'tenant',
26
+ base_url: 'https://vra.corp.local')
27
+ end
28
+
29
+ let(:catalog_id) { '9e98042e-5443-4082-afd5-ab5a32939bbc' }
30
+
31
+ let(:catalog_item_payload) do
32
+ {
33
+ '@type' => 'CatalogItem',
34
+ 'id' => '9e98042e-5443-4082-afd5-ab5a32939bbc',
35
+ 'version' => 2,
36
+ 'name' => 'CentOS 6.6',
37
+ 'description' => 'Blueprint for deploying a CentOS Linux development server',
38
+ 'status' => 'PUBLISHED',
39
+ 'statusName' => 'Published',
40
+ 'organization' => {
41
+ 'tenantRef' => 'vsphere.local',
42
+ 'tenantLabel' => 'vsphere.local',
43
+ 'subtenantRef' => '962ab3f9-858c-4483-a49f-fa97392c314b',
44
+ 'subtenantLabel' => 'catalog_subtenant'
45
+ },
46
+ 'providerBinding' => {
47
+ 'bindingId' => '33af5413-4f20-4b3b-8268-32edad434dfb',
48
+ 'providerRef' => {
49
+ 'id' => 'c3b2bc30-47b0-454f-b57d-df02a7356fe6',
50
+ 'label' => 'iaas-service'
51
+ }
52
+ }
53
+ }
54
+ end
55
+
56
+ describe '#initialize' do
57
+ it 'raises an error if no ID or catalog item data have been provided' do
58
+ expect { Vra::CatalogItem.new }.to raise_error(ArgumentError)
59
+ end
60
+
61
+ it 'raises an error if an ID and catalog item data have both been provided' do
62
+ expect { Vra::CatalogItem.new(id: 123, data: 'foo') }.to raise_error(ArgumentError)
63
+ end
64
+
65
+ context 'when an ID is provided' do
66
+ it 'fetches the catalog_item record' do
67
+ catalog_item = Vra::CatalogItem.allocate
68
+ expect(catalog_item).to receive(:fetch_catalog_item)
69
+ catalog_item.send(:initialize, client, id: catalog_id)
70
+ end
71
+ end
72
+
73
+ context 'when catalog item data is provided' do
74
+ it 'populates the ID correctly' do
75
+ catalog_item = Vra::Resource.new(client, data: catalog_item_payload)
76
+ expect(catalog_item.id).to eq catalog_id
77
+ end
78
+ end
79
+ end
80
+
81
+ describe '#fetch_catalog_item' do
82
+ context 'when the catalog item exists' do
83
+ let(:response) { double('response', code: 200, body: catalog_item_payload.to_json) }
84
+
85
+ it 'calls http_get against the catalog_service' do
86
+ expect(client).to receive(:http_get).with('/catalog-service/api/consumer/catalogItems/catalog-12345').and_return(response)
87
+ Vra::CatalogItem.new(client, id: 'catalog-12345')
88
+ end
89
+ end
90
+
91
+ context 'when the catalog item does not exist' do
92
+ it 'raises an exception' do
93
+ allow(client).to receive(:http_get).with('/catalog-service/api/consumer/catalogItems/catalog-12345').and_raise(Vra::Exception::HTTPNotFound)
94
+ expect { Vra::CatalogItem.new(client, id: 'catalog-12345') }.to raise_error(Vra::Exception::NotFound)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,124 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+
21
+ describe Vra::CatalogRequest do
22
+ before(:each) do
23
+ catalog_item = double('catalog_item')
24
+ allow(catalog_item).to receive(:blueprint_id).and_return('catalog_blueprint')
25
+ allow(catalog_item).to receive(:tenant_id).and_return('catalog_tenant')
26
+ allow(catalog_item).to receive(:subtenant_id).and_return('catalog_subtenant')
27
+ allow(Vra::CatalogItem).to receive(:new).and_return(catalog_item)
28
+ end
29
+
30
+ let(:client) do
31
+ Vra::Client.new(username: 'user@corp.local',
32
+ password: 'password',
33
+ tenant: 'tenant',
34
+ base_url: 'https://vra.corp.local')
35
+ end
36
+
37
+ context 'when no subtenant ID is provided' do
38
+ let(:request) do
39
+ client.catalog.request('catalog-12345',
40
+ cpus: 2,
41
+ memory: 1024,
42
+ lease_days: 15,
43
+ requested_for: 'tester@corp.local',
44
+ notes: 'test notes')
45
+ end
46
+
47
+ it 'uses the subtenant ID from the catalog item' do
48
+ expect(request.subtenant_id).to eq 'catalog_subtenant'
49
+ end
50
+ end
51
+
52
+ context 'when subtenant is provided, and all shared tests' do
53
+ let(:request) do
54
+ client.catalog.request('catalog-12345',
55
+ cpus: 2,
56
+ memory: 1024,
57
+ lease_days: 15,
58
+ requested_for: 'tester@corp.local',
59
+ notes: 'test notes',
60
+ subtenant_id: 'user_subtenant')
61
+ end
62
+
63
+ describe '#initialize' do
64
+ it 'sets the appropriate instance vars' do
65
+ expect(request.catalog_id).to eq 'catalog-12345'
66
+ expect(request.cpus).to eq 2
67
+ expect(request.memory).to eq 1024
68
+ expect(request.lease_days).to eq 15
69
+ expect(request.requested_for).to eq 'tester@corp.local'
70
+ expect(request.notes).to eq 'test notes'
71
+ expect(request.subtenant_id).to eq 'user_subtenant'
72
+ end
73
+ end
74
+
75
+ describe '#validate_params!' do
76
+ context 'when all required params are provided' do
77
+ it 'does not raise an exception' do
78
+ expect { request.validate_params! }.to_not raise_error
79
+ end
80
+ end
81
+
82
+ context 'when a required parameter is not provided' do
83
+ it 'raises an exception' do
84
+ request.cpus = nil
85
+ expect { request.validate_params! }.to raise_error(ArgumentError)
86
+ end
87
+ end
88
+ end
89
+
90
+ describe '#request_payload' do
91
+ it 'properly handles additional parameters' do
92
+ request.set_parameter('param1', 'string', 'my string')
93
+ request.set_parameter('param2', 'integer', '2468')
94
+
95
+ payload = request.request_payload
96
+ param1 = payload['requestData']['entries'].find { |x| x['key'] == 'param1' }
97
+ param2 = payload['requestData']['entries'].find { |x| x['key'] == 'param2' }
98
+
99
+ expect(param1).to be_a(Hash)
100
+ expect(param2).to be_a(Hash)
101
+ expect(param1['value']['value']).to eq 'my string'
102
+ expect(param2['value']['value']).to eq 2468
103
+ end
104
+ end
105
+
106
+ describe '#submit' do
107
+ before do
108
+ allow(request).to receive(:request_payload).and_return({})
109
+ response = double('response', code: 200, headers: { location: '/requests/request-12345' })
110
+ allow(client).to receive(:http_post).with('/catalog-service/api/consumer/requests', '{}').and_return(response)
111
+ end
112
+
113
+ it 'calls http_post' do
114
+ expect(client).to receive(:http_post).with('/catalog-service/api/consumer/requests', '{}')
115
+
116
+ request.submit
117
+ end
118
+
119
+ it 'returns a Vra::Request object' do
120
+ expect(request.submit).to be_an_instance_of(Vra::Request)
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,124 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+
21
+ describe Vra::Catalog do
22
+ let(:client) do
23
+ Vra::Client.new(username: 'user@corp.local',
24
+ password: 'password',
25
+ tenant: 'tenant',
26
+ base_url: 'https://vra.corp.local')
27
+ end
28
+
29
+ let(:catalog_item) do
30
+ {
31
+ '@type' => 'CatalogItem',
32
+ 'id' => 'a9cd6148-6e0b-4a80-ac47-f5255c52b43d',
33
+ 'version' => 2,
34
+ 'name' => 'CentOS 6.6',
35
+ 'description' => 'Blueprint for deploying a CentOS Linux development server',
36
+ 'status' => 'PUBLISHED',
37
+ 'statusName' => 'Published',
38
+ 'organization' => {
39
+ 'tenantRef' => 'vsphere.local',
40
+ 'tenantLabel' => 'vsphere.local',
41
+ 'subtenantRef' => nil,
42
+ 'subtenantLabel' => nil
43
+ },
44
+ 'providerBinding' => {
45
+ 'bindingId' => '33af5413-4f20-4b3b-8268-32edad434dfb',
46
+ 'providerRef' => {
47
+ 'id' => 'c3b2bc30-47b0-454f-b57d-df02a7356fe6',
48
+ 'label' => 'iaas-service'
49
+ }
50
+ }
51
+ }
52
+ end
53
+
54
+ let(:entitled_catalog_item) do
55
+ {
56
+ '@type' => 'ConsumerEntitledCatalogItem',
57
+ 'catalogItem' => {
58
+ 'id' => 'd29efd6b-3cd6-4f8d-b1d8-da4ddd4e52b1',
59
+ 'version' => 2,
60
+ 'name' => 'WindowsServer2012',
61
+ 'description' => 'Windows Server 2012 with the latest updates and patches.',
62
+ 'status' => 'PUBLISHED',
63
+ 'statusName' => 'Published',
64
+ 'organization' => {
65
+ 'tenantRef' => 'vsphere.local',
66
+ 'tenantLabel' => 'vsphere.local',
67
+ 'subtenantRef' => nil,
68
+ 'subtenantLabel' => nil
69
+ },
70
+ 'providerBinding' => {
71
+ 'bindingId' => '59fd02a1-acca-4918-9d3d-2298d310caef',
72
+ 'providerRef' => {
73
+ 'id' => 'c3b2bc30-47b0-454f-b57d-df02a7356fe6',
74
+ 'label' => 'iaas-service'
75
+ }
76
+ }
77
+ }
78
+ }
79
+ end
80
+
81
+ describe '#all_items' do
82
+ it 'calls the catalogItems endpoint' do
83
+ expect(client).to receive(:http_get_paginated_array!).with('/catalog-service/api/consumer/catalogItems')
84
+ .and_return([ catalog_item ])
85
+
86
+ client.catalog.all_items
87
+ end
88
+
89
+ it 'returns a Vra::CatalogItem object' do
90
+ allow(client).to receive(:http_get_paginated_array!).with('/catalog-service/api/consumer/catalogItems')
91
+ .and_return([ catalog_item ])
92
+
93
+ items = client.catalog.all_items
94
+
95
+ expect(items.first).to be_an_instance_of(Vra::CatalogItem)
96
+ end
97
+ end
98
+
99
+ describe '#entitled_items' do
100
+ it 'calls the entitledCatalogItems endpoint' do
101
+ expect(client).to receive(:http_get_paginated_array!).with('/catalog-service/api/consumer/entitledCatalogItems')
102
+ .and_return([ entitled_catalog_item ])
103
+
104
+ client.catalog.entitled_items
105
+ end
106
+
107
+ it 'returns a Vra::CatalogItem object' do
108
+ allow(client).to receive(:http_get_paginated_array!).with('/catalog-service/api/consumer/entitledCatalogItems')
109
+ .and_return([ entitled_catalog_item ])
110
+
111
+ items = client.catalog.entitled_items
112
+
113
+ expect(items.first).to be_an_instance_of(Vra::CatalogItem)
114
+ end
115
+ end
116
+
117
+ describe '#request' do
118
+ it 'returns a new Vra::CatalogRequest object' do
119
+ allow(Vra::CatalogItem).to receive(:new)
120
+ request = client.catalog.request('blueprint-1', cpus: 2)
121
+ expect(request).to be_an_instance_of(Vra::CatalogRequest)
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,481 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+
21
+ describe Vra::Client do
22
+ let(:client) do
23
+ Vra::Client.new(username: 'user@corp.local',
24
+ password: 'password',
25
+ tenant: 'tenant',
26
+ base_url: 'https://vra.corp.local')
27
+ end
28
+
29
+ describe '#initialize' do
30
+ it 'calls validate_client_options!' do
31
+ client = Vra::Client.allocate
32
+ expect(client).to receive(:validate_client_options!)
33
+ client.send(:initialize, username: 'user@corp.local',
34
+ password: 'password',
35
+ tenant: 'tenant',
36
+ base_url: 'https://vra.corp.local')
37
+ end
38
+ end
39
+
40
+ describe '#bearer_token_request_body' do
41
+ it 'gets the correct password from the PasswordMasker object' do
42
+ expect(client.bearer_token_request_body['password']).to eq('password')
43
+ end
44
+ end
45
+
46
+ describe '#request_headers' do
47
+ context 'when bearer token exists' do
48
+ it 'has an Authorization header' do
49
+ client.bearer_token = '12345'
50
+ expect(client.request_headers.key?('Authorization')).to be true
51
+ end
52
+ end
53
+
54
+ context 'when bearer token does not exist' do
55
+ it 'has an Authorization header' do
56
+ expect(client.request_headers.key?('Authorization')).to be false
57
+ end
58
+ end
59
+ end
60
+
61
+ describe '#authorize!' do
62
+ context 'when a token is not authorized or no token exists' do
63
+ it 'generates a token successfully' do
64
+ allow(client).to receive(:authorized?).twice.and_return(false, true)
65
+ expect(client).to receive(:generate_bearer_token)
66
+
67
+ client.authorize!
68
+ end
69
+
70
+ it 'raises an exception if token generation fails' do
71
+ allow(client).to receive(:authorized?).and_return(false)
72
+ allow(client).to receive(:generate_bearer_token).and_raise(Vra::Exception::Unauthorized)
73
+
74
+ expect { client.authorize! }.to raise_error(Vra::Exception::Unauthorized)
75
+ end
76
+
77
+ it 'raises an exception if a generated token is unauthorized' do
78
+ allow(client).to receive(:authorized).twice.and_return(false, false)
79
+ allow(client).to receive(:generate_bearer_token)
80
+
81
+ expect { client.authorize! }.to raise_error(Vra::Exception::Unauthorized)
82
+ end
83
+ end
84
+
85
+ context 'when a token is authorized' do
86
+ it 'does not generate a new token' do
87
+ allow(client).to receive(:authorized?).and_return(true)
88
+ expect(client).to_not receive(:generate_bearer_token)
89
+
90
+ client.authorize!
91
+ end
92
+ end
93
+ end
94
+
95
+ describe '#authorized?' do
96
+ context 'when token does not exist' do
97
+ it 'returns false' do
98
+ expect(client.authorized?).to be false
99
+ end
100
+ end
101
+
102
+ context 'when token exists' do
103
+ before(:each) do
104
+ client.bearer_token = '12345'
105
+ end
106
+
107
+ url = '/identity/api/tokens/12345'
108
+
109
+ it 'returns true if the token validates successfully' do
110
+ response = double('response')
111
+ allow(response).to receive(:code).and_return(204)
112
+ allow(client).to receive(:http_head).with(url, :skip_auth).and_return(response)
113
+
114
+ expect(client.authorized?).to be true
115
+ end
116
+
117
+ it 'returns false if the token validates unsuccessfully' do
118
+ response = double('response')
119
+ allow(response).to receive(:code).and_return(500)
120
+ allow(client).to receive(:http_head).with(url, :skip_auth).and_return(response)
121
+
122
+ expect(client.authorized?).to be false
123
+ end
124
+ end
125
+ end
126
+
127
+ describe '#generate_bearer_token' do
128
+ payload = {
129
+ 'username' => 'user@corp.local',
130
+ 'password' => 'password',
131
+ 'tenant' => 'tenant'
132
+ }.to_json
133
+
134
+ it 'posts to the tokens API endpoint' do
135
+ response = double('response')
136
+ allow(response).to receive(:code).and_return(200)
137
+ allow(response).to receive(:body).and_return('{"id":"12345"}')
138
+ expect(client).to receive(:http_post).with('/identity/api/tokens',
139
+ payload,
140
+ :skip_auth).and_return(response)
141
+
142
+ client.generate_bearer_token
143
+ end
144
+
145
+ context 'when token is generated successfully' do
146
+ it 'sets the token' do
147
+ response = double('response')
148
+ allow(response).to receive(:code).and_return(200)
149
+ allow(response).to receive(:body).and_return('{"id":"12345"}')
150
+ allow(client).to receive(:http_post).with('/identity/api/tokens',
151
+ payload,
152
+ :skip_auth).and_return(response)
153
+
154
+ client.generate_bearer_token
155
+
156
+ expect(client.bearer_token).to eq '12345'
157
+ end
158
+ end
159
+
160
+ context 'when token is not generated successfully' do
161
+ it 'raises an exception' do
162
+ response = double('response')
163
+ allow(response).to receive(:code).and_return(500)
164
+ allow(response).to receive(:body).and_return('error string')
165
+ allow(client).to receive(:http_post).with('/identity/api/tokens',
166
+ payload,
167
+ :skip_auth)
168
+ .and_return(response)
169
+
170
+ expect { client.generate_bearer_token }.to raise_error(Vra::Exception::Unauthorized)
171
+ end
172
+ end
173
+ end
174
+
175
+ describe '#full_url' do
176
+ it 'returns a properly formatted url' do
177
+ expect(client.full_url('/mypath')).to eq 'https://vra.corp.local/mypath'
178
+ end
179
+ end
180
+
181
+ describe '#http_head' do
182
+ context 'when skip_auth is nil' do
183
+ it 'authorizes before proceeding' do
184
+ response = double('response')
185
+ allow(RestClient::Request).to receive(:execute).and_return(response)
186
+ expect(client).to receive(:authorize!)
187
+
188
+ client.http_head('/test')
189
+ end
190
+ end
191
+
192
+ context 'when skip_auth is not nil' do
193
+ it 'does not authorize before proceeding' do
194
+ response = double('response')
195
+ allow(RestClient::Request).to receive(:execute).and_return(response)
196
+ expect(client).to_not receive(:authorize!)
197
+
198
+ client.http_head('/test', :skip_auth)
199
+ end
200
+ end
201
+
202
+ it 'calls RestClient::Request#execute' do
203
+ response = double('response')
204
+ path = '/test'
205
+ full_url = 'https://vra.corp.local/test'
206
+ headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' }
207
+ verify_ssl = true
208
+
209
+ allow(client).to receive(:authorize!)
210
+ expect(RestClient::Request).to receive(:execute).with(method: :head,
211
+ url: full_url,
212
+ headers: headers,
213
+ verify_ssl: verify_ssl)
214
+ .and_return(response)
215
+
216
+ client.http_head(path)
217
+ end
218
+ end
219
+
220
+ describe '#http_get' do
221
+ context 'when skip_auth is nil' do
222
+ it 'authorizes before proceeding' do
223
+ response = double('response')
224
+ allow(RestClient::Request).to receive(:execute).and_return(response)
225
+ expect(client).to receive(:authorize!)
226
+
227
+ client.http_get('/test')
228
+ end
229
+ end
230
+
231
+ context 'when skip_auth is not nil' do
232
+ it 'does not authorize before proceeding' do
233
+ response = double('response')
234
+ allow(RestClient::Request).to receive(:execute).and_return(response)
235
+ expect(client).to_not receive(:authorize!)
236
+
237
+ client.http_get('/test', :skip_auth)
238
+ end
239
+ end
240
+
241
+ it 'calls RestClient::Request#execute' do
242
+ response = double('response')
243
+ path = '/test'
244
+ full_url = 'https://vra.corp.local/test'
245
+ headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' }
246
+ verify_ssl = true
247
+
248
+ allow(client).to receive(:authorize!)
249
+ expect(RestClient::Request).to receive(:execute).with(method: :get,
250
+ url: full_url,
251
+ headers: headers,
252
+ verify_ssl: verify_ssl)
253
+ .and_return(response)
254
+
255
+ client.http_get(path)
256
+ end
257
+
258
+ it 'calls raise_http_exception upon a RestClient error' do
259
+ allow(client).to receive(:authorize!)
260
+ allow(RestClient::Request).to receive(:execute).and_raise(RestClient::ResourceNotFound)
261
+ expect(client).to receive(:raise_http_exception)
262
+
263
+ client.http_get('/404')
264
+ end
265
+ end
266
+
267
+ describe '#http_get!' do
268
+ it 'returns the response body' do
269
+ response = double('response', body: 'body text')
270
+ allow(client).to receive(:http_get).with('/test').and_return(response)
271
+
272
+ expect(client.http_get!('/test')).to eq 'body text'
273
+ end
274
+ end
275
+
276
+ describe '#http_get_paginated_array!' do
277
+ it 'allows a limit override' do
278
+ client.page_size = 10
279
+ expect(client).to receive(:http_get!)
280
+ .with('/test?limit=10&page=1')
281
+ .and_return({ 'content' => [], 'metadata' => { 'totalPages' => 1 } }.to_json)
282
+
283
+ client.http_get_paginated_array!('/test')
284
+ end
285
+
286
+ it 'only calls http_get! once when total pages is 0 (no items)' do
287
+ expect(client).to receive(:http_get!)
288
+ .once
289
+ .with('/test?limit=20&page=1')
290
+ .and_return({ 'content' => [], 'metadata' => { 'totalPages' => 0 } }.to_json)
291
+
292
+ client.http_get_paginated_array!('/test')
293
+ end
294
+
295
+ it 'only calls http_get! once when total pages is 1' do
296
+ expect(client).to receive(:http_get!)
297
+ .once
298
+ .with('/test?limit=20&page=1')
299
+ .and_return({ 'content' => [], 'metadata' => { 'totalPages' => 1 } }.to_json)
300
+
301
+ client.http_get_paginated_array!('/test')
302
+ end
303
+
304
+ it 'calls http_get! 3 times if there are 3 pages of response' do
305
+ expect(client).to receive(:http_get!)
306
+ .with('/test?limit=20&page=1')
307
+ .and_return({ 'content' => [], 'metadata' => { 'totalPages' => 3 } }.to_json)
308
+ expect(client).to receive(:http_get!)
309
+ .with('/test?limit=20&page=2')
310
+ .and_return({ 'content' => [], 'metadata' => { 'totalPages' => 3 } }.to_json)
311
+ expect(client).to receive(:http_get!)
312
+ .with('/test?limit=20&page=3')
313
+ .and_return({ 'content' => [], 'metadata' => { 'totalPages' => 3 } }.to_json)
314
+
315
+ client.http_get_paginated_array!('/test')
316
+ end
317
+
318
+ it 'raises an exception if duplicate items are returned by the API' do
319
+ allow(client).to receive(:http_get!)
320
+ .with('/test?limit=20&page=1')
321
+ .and_return({ 'content' => [ 1, 2, 3, 1 ], 'metadata' => { 'totalPages' => 1 } }.to_json)
322
+
323
+ expect { client.http_get_paginated_array!('/test') }.to raise_error(Vra::Exception::DuplicateItemsDetected)
324
+ end
325
+ end
326
+
327
+ describe '#http_post' do
328
+ context 'when skip_auth is nil' do
329
+ it 'authorizes before proceeding' do
330
+ response = double('response')
331
+ allow(RestClient::Request).to receive(:execute).and_return(response)
332
+ expect(client).to receive(:authorize!)
333
+
334
+ client.http_post('/test', 'some payload')
335
+ end
336
+ end
337
+
338
+ context 'when skip_auth is not nil' do
339
+ it 'does not authorize before proceeding' do
340
+ response = double('response')
341
+ allow(RestClient::Request).to receive(:execute).and_return(response)
342
+ expect(client).to_not receive(:authorize!)
343
+
344
+ client.http_post('/test', 'some payload', :skip_auth)
345
+ end
346
+ end
347
+
348
+ it 'calls RestClient::Request#execute' do
349
+ response = double('response')
350
+ path = '/test'
351
+ full_url = 'https://vra.corp.local/test'
352
+ headers = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' }
353
+ payload = 'some payload'
354
+ verify_ssl = true
355
+
356
+ allow(client).to receive(:authorize!)
357
+ expect(RestClient::Request).to receive(:execute).with(method: :post,
358
+ url: full_url,
359
+ headers: headers,
360
+ payload: payload,
361
+ verify_ssl: verify_ssl)
362
+ .and_return(response)
363
+
364
+ client.http_post(path, payload)
365
+ end
366
+
367
+ it 'calls raise_http_exception upon a RestClient error' do
368
+ allow(client).to receive(:authorize!)
369
+ allow(RestClient::Request).to receive(:execute).and_raise(RestClient::ResourceNotFound)
370
+ expect(client).to receive(:raise_http_exception)
371
+
372
+ client.http_post('/404', 'test payload')
373
+ end
374
+ end
375
+
376
+ describe '#http_post!' do
377
+ it 'returns the response body' do
378
+ response = double('response', body: 'body text')
379
+ allow(client).to receive(:http_post).with('/test', 'test payload').and_return(response)
380
+
381
+ expect(client.http_post!('/test', 'test payload')).to eq 'body text'
382
+ end
383
+ end
384
+
385
+ describe '#raise_http_exception' do
386
+ context 'when a 404 is received' do
387
+ let(:exception) do
388
+ double('RestClient::ResourceNotFound',
389
+ http_code: 404,
390
+ message: 'Not Found',
391
+ response: '404 Not Found')
392
+ end
393
+
394
+ it 'raises a Vra::Exception::HTTPNotFound exception' do
395
+ expect { client.raise_http_exception(exception, '/test') }.to raise_error(Vra::Exception::HTTPNotFound)
396
+ end
397
+ end
398
+
399
+ context 'when an unspecified http error is received' do
400
+ let(:exception) do
401
+ double('RestClient::BadRequest',
402
+ http_code: 400,
403
+ message: 'Bad Request',
404
+ response: '400 Bad Request')
405
+ end
406
+
407
+ it 'raises a Vra::Exception::HTTPError exception' do
408
+ expect { client.raise_http_exception(exception, '/test') }.to raise_error(Vra::Exception::HTTPError)
409
+ end
410
+ end
411
+ end
412
+
413
+ describe '#validate_client_options!' do
414
+ context 'when all required options are supplied' do
415
+ it 'does not raise an exception' do
416
+ expect { client.validate_client_options! }.not_to raise_error
417
+ end
418
+ end
419
+
420
+ context 'when username is missing' do
421
+ let(:client) do
422
+ Vra::Client.new(password: 'password',
423
+ tenant: 'tenant',
424
+ base_url: 'https://vra.corp.local')
425
+ end
426
+
427
+ it 'raises an exception' do
428
+ expect { client.validate_client_options! }.to raise_error(ArgumentError)
429
+ end
430
+ end
431
+
432
+ context 'when password is missing' do
433
+ let(:client) do
434
+ Vra::Client.new(username: 'username',
435
+ tenant: 'tenant',
436
+ base_url: 'https://vra.corp.local')
437
+ end
438
+
439
+ it 'raises an exception' do
440
+ expect { client.validate_client_options! }.to raise_error(ArgumentError)
441
+ end
442
+ end
443
+
444
+ context 'when tenant is missing' do
445
+ let(:client) do
446
+ Vra::Client.new(username: 'username',
447
+ password: 'password',
448
+ base_url: 'https://vra.corp.local')
449
+ end
450
+
451
+ it 'raises an exception' do
452
+ expect { client.validate_client_options! }.to raise_error(ArgumentError)
453
+ end
454
+ end
455
+
456
+ context 'when base URL is missing' do
457
+ let(:client) do
458
+ Vra::Client.new(username: 'username',
459
+ password: 'password',
460
+ tenant: 'tenant')
461
+ end
462
+
463
+ it 'raises an exception' do
464
+ expect { client.validate_client_options! }.to raise_error(ArgumentError)
465
+ end
466
+ end
467
+
468
+ context 'when base URL is not a valid HTTP URL' do
469
+ let(:client) do
470
+ Vra::Client.new(username: 'username',
471
+ password: 'password',
472
+ tenant: 'tenant',
473
+ base_url: 'something-that-is-not-a-HTTP-URI')
474
+ end
475
+
476
+ it 'raises an exception' do
477
+ expect { client.validate_client_options! }.to raise_error(ArgumentError)
478
+ end
479
+ end
480
+ end
481
+ end