gini-api 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,412 @@
1
+ require 'spec_helper'
2
+
3
+ describe Gini::Api::OAuth do
4
+
5
+ let(:user) { 'user@gini.net' }
6
+ let(:pass) { 'secret' }
7
+ let(:auth_code) { '1234567890' }
8
+ let(:state) { '1234567890' }
9
+ let(:code) { 'abcdefghij'}
10
+ let(:redirect) { 'http://localhost' }
11
+ let(:status) { 303 }
12
+ let(:token_status) { 200 }
13
+ let(:token_body) { 'client_id=cid&client_secret=sec&code=1234567890&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost' }
14
+ let(:header) { { 'location' => "https://api.gini.net?code=#{code}&state=#{state}" } }
15
+ let(:oauth_site) { 'https://user.gini.net' }
16
+ let(:authorize_uri) { "#{oauth_site}/authorize?client_id=cid&redirect_uri=#{redirect}&response_type=code&state=#{state}" }
17
+ let(:api) do
18
+ double('API',
19
+ client_id: 'cid',
20
+ client_secret: 'sec',
21
+ oauth_site: oauth_site,
22
+ oauth_redirect: redirect
23
+ )
24
+ end
25
+
26
+ describe '#new' do
27
+
28
+ context 'login with username/password' do
29
+
30
+ before do
31
+ allow(SecureRandom).to \
32
+ receive(:hex) { state }
33
+
34
+ stub_request(:post,
35
+ authorize_uri
36
+ ).to_return(
37
+ status: status,
38
+ headers: header,
39
+ body: {}
40
+ )
41
+
42
+ stub_request(:post,
43
+ "#{oauth_site}/token"
44
+ ).to_return(
45
+ status: token_status,
46
+ headers: {
47
+ 'content-type' => 'application/json'
48
+ },
49
+ body: {
50
+ access_token: '123-456',
51
+ token_type: 'bearer',
52
+ expires_in: 300,
53
+ refresh_token: '987-654'
54
+ }.to_json
55
+ )
56
+ end
57
+
58
+ subject(:oauth) do
59
+ Gini::Api::OAuth.new(api,
60
+ username: user,
61
+ password: pass
62
+ )
63
+ end
64
+
65
+ it { should respond_to(:login_with_credentials) }
66
+ it { should respond_to(:exchange_code_for_token) }
67
+ it { should respond_to(:destroy) }
68
+
69
+ it 'does set token' do
70
+ expect(oauth.token.token).to eql('123-456')
71
+ end
72
+
73
+ context 'with invalid credentials' do
74
+
75
+ let(:status) { 500 }
76
+
77
+ it do
78
+ expect {
79
+ Gini::Api::OAuth.new(
80
+ api,
81
+ username: user,
82
+ password: pass
83
+ ) }.to raise_error(Gini::Api::OAuthError, /Failed to acquire auth_code/)
84
+ end
85
+
86
+ end
87
+
88
+ context 'with non-redirect status code' do
89
+
90
+ let(:status) { 200 }
91
+
92
+ it do
93
+ expect {
94
+ Gini::Api::OAuth.new(
95
+ api,
96
+ username: user,
97
+ password: pass
98
+ ) }.to raise_error(Gini::Api::OAuthError, /API login failed/)
99
+ end
100
+ end
101
+
102
+ context 'with invalid location header' do
103
+
104
+ let(:header) { { location: 'https://api.gini.net' } }
105
+
106
+ it do
107
+ expect {
108
+ Gini::Api::OAuth.new(
109
+ api,
110
+ username: user,
111
+ password: pass
112
+ ) }.to raise_error(Gini::Api::OAuthError, /Failed to parse location header/)
113
+ end
114
+
115
+ end
116
+
117
+ context 'with CSRF token mismatch' do
118
+
119
+ let(:header) { { location: "https://rspec.gini.net?code=#{code}&state=hacked"} }
120
+
121
+ it do
122
+ expect {
123
+ Gini::Api::OAuth.new(
124
+ api,
125
+ username: user,
126
+ password: pass
127
+ ) }.to raise_error(Gini::Api::OAuthError, /CSRF token mismatch detected/)
128
+ end
129
+
130
+ end
131
+
132
+ context 'without code' do
133
+
134
+ let(:header) { { location: "https://api.gini.net?state=#{state}"} }
135
+
136
+ it do
137
+ expect {
138
+ Gini::Api::OAuth.new(
139
+ api,
140
+ username: user,
141
+ password: pass
142
+ ) }.to raise_error(Gini::Api::OAuthError, /Failed to extract code from location/)
143
+ end
144
+
145
+ end
146
+
147
+ context 'with invalid client credentials' do
148
+
149
+ let(:token_status) { 401 }
150
+
151
+ it do
152
+ expect {
153
+ Gini::Api::OAuth.new(
154
+ api,
155
+ username: user,
156
+ password: pass
157
+ ) }.to raise_error(Gini::Api::OAuthError, /Failed to exchange auth_code/)
158
+ end
159
+
160
+ end
161
+
162
+ end
163
+
164
+ context 'login with auth_code' do
165
+
166
+ before do
167
+ stub_request(:post,
168
+ "#{oauth_site}/token"
169
+ ).with(
170
+ body: 'client_id=cid&client_secret=sec&code=1234567890&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost'
171
+ ).to_return(
172
+ status: token_status,
173
+ headers: {
174
+ 'content-type' => 'application/json'
175
+ },
176
+ body: {
177
+ access_token: '123-456',
178
+ token_type: 'bearer',
179
+ expires_in: 300,
180
+ refresh_token: '987-654'
181
+ }.to_json
182
+ )
183
+ end
184
+
185
+ subject(:oauth) { Gini::Api::OAuth.new(api, auth_code: auth_code) }
186
+
187
+ it 'does set token' do
188
+ expect(oauth.token.token).to eql('123-456')
189
+ end
190
+
191
+ context 'overrides #refresh!' do
192
+
193
+ it do
194
+ expect(oauth.token).to respond_to(:refresh!)
195
+ end
196
+
197
+ end
198
+
199
+ context 'overrides #request' do
200
+
201
+ it do
202
+ expect(oauth.token).to respond_to(:request)
203
+ end
204
+
205
+ end
206
+
207
+ end
208
+
209
+ end
210
+
211
+ describe '#destroy' do
212
+
213
+ let(:status) { 204 }
214
+ let(:refresh_token) { false }
215
+
216
+ before do
217
+ stub_request(:post,
218
+ "#{oauth_site}/token"
219
+ ).with(
220
+ body: 'client_id=cid&client_secret=sec&code=1234567890&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost'
221
+ ).to_return(
222
+ status: token_status,
223
+ headers: {
224
+ 'content-type' => 'application/json'
225
+ },
226
+ body: {
227
+ access_token: '123-456',
228
+ token_type: 'bearer',
229
+ expires_in: 300,
230
+ refresh_token: '987-654'
231
+ }.to_json
232
+ )
233
+
234
+ stub_request(
235
+ :delete,
236
+ %r{/accessToken/123-456}
237
+ ).to_return(status: status)
238
+
239
+ oauth.token.stub(:refresh_token).and_return(refresh_token)
240
+ end
241
+
242
+ subject(:oauth) { Gini::Api::OAuth.new(api, auth_code: auth_code) }
243
+
244
+ context 'with refresh token' do
245
+
246
+ let(:refresh_token) { true }
247
+
248
+ it 'does a refresh first' do
249
+ expect(oauth.token).to receive(:refresh_token)
250
+ expect(oauth.token).to receive(:refresh!)
251
+ expect(oauth.destroy).to be_nil
252
+ end
253
+
254
+ end
255
+
256
+ context 'without refresh token' do
257
+
258
+ it 'destroys token directly' do
259
+ expect(oauth.token).to receive(:refresh_token)
260
+ expect(oauth.token).not_to receive(:refresh!)
261
+ expect(oauth.destroy).to be_nil
262
+ end
263
+ end
264
+
265
+ context 'with invalid token' do
266
+
267
+ let(:status) { 404 }
268
+
269
+ it do
270
+ expect{oauth.destroy}.to raise_error Gini::Api::OAuthError, /Failed to destroy token/
271
+ end
272
+ end
273
+
274
+ context 'with unexpected http status code' do
275
+
276
+ let(:status) { 200 }
277
+
278
+ it do
279
+ expect{oauth.destroy}.to raise_error Gini::Api::OAuthError, /Failed to destroy token/
280
+ end
281
+ end
282
+ end
283
+
284
+ describe 'overridden AccessToken#refresh!' do
285
+
286
+ before do
287
+ stub_request(:post,
288
+ "#{oauth_site}/token"
289
+ ).with(
290
+ body: 'client_id=cid&client_secret=sec&code=1234567890&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost'
291
+ ).to_return(
292
+ status: token_status,
293
+ headers: {
294
+ 'content-type' => 'application/json'
295
+ },
296
+ body: {
297
+ access_token: '123-456',
298
+ token_type: 'bearer',
299
+ expires_in: 300,
300
+ refresh_token: '987-654'
301
+ }.to_json
302
+ )
303
+ end
304
+
305
+ subject(:oauth) { Gini::Api::OAuth.new(api, auth_code: auth_code) }
306
+
307
+
308
+ it do
309
+ expect(oauth.token.token).to eql('123-456')
310
+
311
+ stub_request(
312
+ :post,
313
+ "#{oauth_site}/token"
314
+ ).to_return(
315
+ status: 200,
316
+ headers: {
317
+ 'content-type' => 'application/json'
318
+ },
319
+ body: {
320
+ access_token: '42-42-42',
321
+ token_type: 'bearer',
322
+ expires_in: 300,
323
+ refresh_token: '987-654'
324
+ }.to_json
325
+ )
326
+
327
+ oauth.token.refresh!
328
+ expect(oauth.token.token).to eql('42-42-42')
329
+ end
330
+
331
+ end
332
+
333
+ describe 'overridden AccessToken#request' do
334
+
335
+ before do
336
+ stub_request(:post,
337
+ "#{oauth_site}/token"
338
+ ).with(
339
+ body: 'client_id=cid&client_secret=sec&code=1234567890&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost'
340
+ ).to_return(
341
+ status: token_status,
342
+ headers: {
343
+ 'content-type' => 'application/json'
344
+ },
345
+ body: {
346
+ access_token: '123-456',
347
+ token_type: 'bearer',
348
+ expires_in: 300,
349
+ refresh_token: '987-654'
350
+ }.to_json
351
+ )
352
+ end
353
+
354
+ subject(:oauth) { Gini::Api::OAuth.new(api, auth_code: auth_code) }
355
+
356
+
357
+ context 'with expired token' do
358
+
359
+ it 'does call refresh!' do
360
+ stub_request(:get, "https://user.gini.net/a")
361
+ stub_request(
362
+ :post,
363
+ "#{oauth_site}/token"
364
+ ).to_return(
365
+ status: 200,
366
+ headers: {
367
+ 'content-type' => 'application/json'
368
+ },
369
+ body: {
370
+ access_token: '42-42-42',
371
+ token_type: 'bearer',
372
+ expires_in: -10,
373
+ refresh_token: '987-654'
374
+ }.to_json
375
+ )
376
+
377
+ expect(oauth.token).to receive(:refresh!)
378
+ oauth.token.request(:get, '/a')
379
+ end
380
+
381
+ end
382
+
383
+ context 'with valid token' do
384
+
385
+ it 'does not call refresh!' do
386
+
387
+ stub_request(:get, "https://user.gini.net/a")
388
+ stub_request(
389
+ :post,
390
+ "#{oauth_site}/token"
391
+ ).to_return(
392
+ status: 200,
393
+ headers: {
394
+ 'content-type' => 'application/json'
395
+ },
396
+ body: {
397
+ access_token: '42-42-42',
398
+ token_type: 'bearer',
399
+ expires_in: 300,
400
+ refresh_token: '987-654'
401
+ }.to_json
402
+ )
403
+
404
+ expect(oauth.token).not_to receive(:refresh!)
405
+ oauth.token.request(:get, '/a')
406
+ end
407
+
408
+ end
409
+
410
+ end
411
+
412
+ end
@@ -0,0 +1,124 @@
1
+ require 'spec_helper'
2
+
3
+ # Disable coverage for integration tests
4
+ ENV['COVERAGE'] = nil
5
+
6
+ # Cancel integration test when mandatory env vars are missing
7
+ ['GINI_API_USER', 'GINI_API_PASS', 'GINI_CLIENT_SECRET'].each do |m|
8
+ fail "Unset ENV variable #{m}. Tests aborted" unless ENV.has_key?(m)
9
+ end
10
+
11
+ # Let's make some REAL requests
12
+ WebMock.allow_net_connect!
13
+
14
+ describe 'Gini::Api integration test' do
15
+
16
+ before :all do
17
+ @user = ENV['GINI_API_USER']
18
+ @pass = ENV['GINI_API_PASS']
19
+ @testdoc = "#{File.dirname(__FILE__)}/files/test.pdf"
20
+ @api = Gini::Api::Client.new(
21
+ client_id: ENV['GINI_CLIENT_ID'],
22
+ client_secret: ENV['GINI_CLIENT_SECRET'],
23
+ oauth_site: 'https://user.gini.net/',
24
+ api_uri: 'https://api.gini.net',
25
+ log: Logger.new('/dev/null')
26
+ )
27
+ end
28
+
29
+ context 'OAuth' do
30
+
31
+ it '#login sets token' do
32
+ @api.login(username: @user, password: @pass)
33
+ expect(@api.token.token).to match(/\w+-\w+/)
34
+ expect(@api.token.expired?).to be_false
35
+ end
36
+
37
+ it '#logout destroys token' do
38
+ expect(@api.token.get("/accessToken/#{@api.token.token}").status).to eql(200)
39
+ @api.logout
40
+ expect { @api.token.get("/accessToken/#{@api.token.token}") }.to raise_error(OAuth2::Error)
41
+ end
42
+
43
+ end
44
+
45
+ context 'document' do
46
+
47
+ before do
48
+ @api.login(username: @user, password: @pass)
49
+ @doc = @api.upload(@testdoc)
50
+ end
51
+
52
+ it '#upload returns Gini::Api::Doucment' do
53
+ @api.logout
54
+ expect(@doc.id).to match(/\w+-\w+/)
55
+ @api.delete(@doc.id)
56
+ end
57
+
58
+ it '#get returns Gini::Api::Doucment' do
59
+ doc_get = @api.get(@doc.id)
60
+ expect(@doc.id).to eql(doc_get.id)
61
+ @api.delete(@doc.id)
62
+ @api.logout
63
+ end
64
+
65
+ it '#list returns Gini::Api::DocumentSet' do
66
+ list = @api.list
67
+ expect(list).to be_a(Gini::Api::DocumentSet)
68
+ expect(list.total).to eql(1)
69
+ expect(list.documents[0]).to be_a(Gini::Api::Document)
70
+ @api.delete(@doc.id)
71
+ @api.logout
72
+ end
73
+
74
+ it '#delete returns true' do
75
+ expect(@api.delete(@doc.id)).to be_true
76
+ expect { @api.get(@doc.id) }.to raise_error(Gini::Api::RequestError)
77
+ @api.logout
78
+ end
79
+
80
+ context 'data' do
81
+
82
+ after do
83
+ @api.delete(@doc.id)
84
+ @api.logout
85
+ end
86
+
87
+ context 'extractions' do
88
+
89
+ subject(:extractions) { @doc.extractions }
90
+
91
+ it '#extractions populates instance vars' do
92
+ expect(extractions).to be_kind_of(Gini::Api::Document::Extractions)
93
+ expect(extractions.candidates).to be_kind_of(Hash)
94
+ expect(extractions).to respond_to(:amountToPay)
95
+ expect(extractions[:amountToPay]).to eql('1039.87:EUR')
96
+ end
97
+
98
+ it '#[] returns data' do
99
+ expect(extractions[:docType]).to eql('Invoice')
100
+ end
101
+
102
+ end
103
+
104
+ context 'layout' do
105
+
106
+ subject(:layout) { @doc.layout }
107
+
108
+ it '#to_xml returns XML string' do
109
+ expect(layout).to be_kind_of(Gini::Api::Document::Layout)
110
+ expect(layout.to_xml).to match(/xml/)
111
+ end
112
+
113
+ it '#to_json returns JSON string' do
114
+ expect(layout).to be_kind_of(Gini::Api::Document::Layout)
115
+ expect(layout.to_json).to satisfy { |json| JSON.parse(json).is_a?(Hash) }
116
+ end
117
+
118
+ end
119
+
120
+ end
121
+
122
+ end
123
+
124
+ end