web_fetch 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WebFetch
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
@@ -0,0 +1,64 @@
1
+ .gitignore
2
+ .rspec
3
+ .rubocop.yml
4
+ .ruby-version
5
+ .strong_versions.yml
6
+ Gemfile
7
+ LICENSE
8
+ Makefile
9
+ README.md
10
+ Rakefile
11
+ TODO
12
+ bin/rspec
13
+ bin/rubocop
14
+ bin/strong_versions
15
+ bin/web_fetch_control
16
+ bin/web_fetch_server
17
+ config/locales/en.yml
18
+ doc/examples/blocking_requests.rb
19
+ doc/examples/non_blocking_requests.rb
20
+ doc/examples/use_uid_for_request.rb
21
+ doc/web_fetch_architecture.png
22
+ docker/Dockerfile
23
+ lib/web_fetch.rb
24
+ lib/web_fetch/client.rb
25
+ lib/web_fetch/concerns/client_http.rb
26
+ lib/web_fetch/concerns/http_helpers.rb
27
+ lib/web_fetch/concerns/validatable.rb
28
+ lib/web_fetch/errors.rb
29
+ lib/web_fetch/gatherer.rb
30
+ lib/web_fetch/helpers.rb
31
+ lib/web_fetch/logger.rb
32
+ lib/web_fetch/promise.rb
33
+ lib/web_fetch/request.rb
34
+ lib/web_fetch/resources.rb
35
+ lib/web_fetch/response.rb
36
+ lib/web_fetch/retriever.rb
37
+ lib/web_fetch/router.rb
38
+ lib/web_fetch/server.rb
39
+ lib/web_fetch/storage.rb
40
+ lib/web_fetch/storage/memcached.rb
41
+ lib/web_fetch/storage/memory.rb
42
+ lib/web_fetch/storage/redis.rb
43
+ lib/web_fetch/version.rb
44
+ manifest
45
+ spec/client_spec.rb
46
+ spec/concerns/validatable_spec.rb
47
+ spec/features/http_fetching_spec.rb
48
+ spec/gatherer_spec.rb
49
+ spec/helpers_spec.rb
50
+ spec/i18n_spec.rb
51
+ spec/promise_spec.rb
52
+ spec/request_spec.rb
53
+ spec/resources_spec.rb
54
+ spec/response_spec.rb
55
+ spec/retriever_spec.rb
56
+ spec/router_spec.rb
57
+ spec/server_spec.rb
58
+ spec/spec_helper.rb
59
+ spec/storage/memcached_spec.rb
60
+ spec/storage/memory_spec.rb
61
+ spec/storage/redis_spec.rb
62
+ spec/storage/shared_examples.rb
63
+ swagger.yaml
64
+ web_fetch.gemspec
@@ -65,13 +65,10 @@ describe WebFetch::Client do
65
65
 
66
66
  it { is_expected.to be_a WebFetch::Response }
67
67
 
68
- context 'no matching request found' do
69
- subject { proc { client.fetch('not-found') } }
70
- it { is_expected.to raise_error WebFetch::RequestNotFoundError }
68
+ context 'pending' do
69
+ subject { client.fetch('not-ready-yet') }
70
+ it { is_expected.to be_pending }
71
71
  end
72
-
73
- # Tested more extensively in supporting methods #retrieve_by_uid and
74
- # #find_by_uid below
75
72
  end
76
73
 
77
74
  describe '#retrieve_by_uid' do
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  describe WebFetch::Gatherer do
4
- let(:server) { WebFetch::MockServer.new }
4
+ let(:storage) { double }
5
5
 
6
6
  let(:valid_params) do
7
7
  { requests: [
@@ -11,31 +11,33 @@ describe WebFetch::Gatherer do
11
11
  end
12
12
 
13
13
  it 'is initialisable with params' do
14
- expect(described_class.new(server, valid_params)).to be_a described_class
14
+ expect(described_class.new(storage, valid_params)).to be_a described_class
15
15
  end
16
16
 
17
17
  describe 'validation' do
18
18
  context 'invalid' do
19
19
  it 'is invalid if `requests` parameter is not passed' do
20
- gatherer = described_class.new(server, {})
20
+ gatherer = described_class.new(storage, {})
21
21
  expect(gatherer.valid?).to be false
22
22
  expect(gatherer.errors).to include I18n.t(:requests_missing)
23
23
  end
24
24
 
25
25
  it 'is invalid if `requests` is not an array parameter' do
26
- gatherer = described_class.new(server, requests: 'hello')
26
+ gatherer = described_class.new(storage, requests: 'hello')
27
27
  expect(gatherer.valid?).to be false
28
28
  expect(gatherer.errors).to include I18n.t(:requests_not_array)
29
29
  end
30
30
 
31
31
  it 'is invalid if `requests` is an empty array' do
32
- gatherer = described_class.new(server, requests: [])
32
+ gatherer = described_class.new(storage, requests: [])
33
33
  expect(gatherer.valid?).to be false
34
34
  expect(gatherer.errors).to include I18n.t(:requests_empty)
35
35
  end
36
36
 
37
37
  it 'is invalid if `url` missing from any requests' do
38
- gatherer = described_class.new(server, requests: [{ url: 'hello' }, {}])
38
+ gatherer = described_class.new(
39
+ storage, requests: [{ url: 'hello' }, {}]
40
+ )
39
41
  expect(gatherer.valid?).to be false
40
42
  expect(gatherer.errors).to include I18n.t(:missing_url)
41
43
  end
@@ -43,14 +45,29 @@ describe WebFetch::Gatherer do
43
45
 
44
46
  context 'valid' do
45
47
  it 'is valid when passed valid params' do
46
- expect(described_class.new(server, valid_params).valid?).to be true
48
+ expect(described_class.new(storage, valid_params).valid?).to be true
47
49
  end
48
50
  end
49
51
  end
50
52
 
51
53
  describe '#start' do
54
+ let(:http) do
55
+ double(new: double(public_send: double(callback: nil, errback: nil)))
56
+ end
57
+
58
+ let(:logger) { double(debug: nil) }
59
+
60
+ before do
61
+ stub_request(:get, 'http://remotehost:8089/').to_return(status: 200)
62
+ stub_request(:get, 'http://blah/').to_return(status: 200)
63
+ stub_request(:put, 'http://blah/').to_return(status: 200)
64
+ stub_request(:get, 'http://hello/').to_return(status: 200)
65
+ end
66
+
52
67
  it 'returns a hash containing sha1 hashes of requests' do
53
- response = described_class.new(server, valid_params).start
68
+ deferred = double(callback: nil, errback: nil)
69
+ http = double(new: double(public_send: deferred))
70
+ response = described_class.new(storage, valid_params, logger, http).start
54
71
  hash = Digest::SHA1.new.digest(JSON.dump(valid_params[:requests].first))
55
72
  expect(response[:requests].first[:hash]).to eql Digest.hexencode(hash)
56
73
  end
@@ -68,14 +85,16 @@ describe WebFetch::Gatherer do
68
85
  headers: { 'Content-Type' => 'hello' },
69
86
  method: 'PUT' }
70
87
  responses = [req1, req2, req3, req4, req5].map do |req|
71
- described_class.new(server, requests: [req], _server: server).start
88
+ described_class.new(
89
+ storage, { requests: [req] }, logger, http
90
+ ).start
72
91
  end
73
92
  hashes = responses.map { |res| res[:requests].first[:hash] }
74
93
  expect(hashes.uniq.length).to eql 5
75
94
  end
76
95
 
77
96
  it 'returns a hash containing unique IDs for requests' do
78
- response = described_class.new(server, valid_params).start
97
+ response = described_class.new(storage, valid_params, logger, http).start
79
98
  uid1 = response[:requests][0][:uid]
80
99
  uid2 = response[:requests][1][:uid]
81
100
  expect(uid1).to_not eql uid2
@@ -85,9 +104,8 @@ describe WebFetch::Gatherer do
85
104
  it 'is included in response' do
86
105
  # Ensure that the requester can embed their own identifiers to link to
87
106
  # the uid of the delegated request
88
- params = { requests: [url: '-', bob: 'hello'],
89
- _server: server }
90
- response = described_class.new(server, params).start
107
+ params = { requests: [url: 'http://blah/', bob: 'hello'] }
108
+ response = described_class.new(storage, params, logger, http).start
91
109
  expect(response[:requests].first[:request][:bob]).to eql 'hello'
92
110
  end
93
111
 
@@ -95,14 +113,13 @@ describe WebFetch::Gatherer do
95
113
  # Ensure that only pertinent values are used to compute hash (i.e.
96
114
  # adding auxiliary data will still allow retrieval by hash for
97
115
  # otherwise duplicate requests
98
- params1 = { requests: [url: 'http://blah', bob: 'hello'],
99
- _server: server }
100
- response1 = described_class.new(server, params1).start
101
-
102
- params2 = { requests: [url: 'http://blah', not_bob: 'good bye'],
103
- _server: server }
104
- response2 = described_class.new(server, params2).start
105
- expect(response1[:requests][0][:hash]).to eql response2[:requests][0][:hash]
116
+ params1 = { requests: [url: 'http://blah', bob: 'hello'] }
117
+ response1 = described_class.new(storage, params1, logger, http).start
118
+
119
+ params2 = { requests: [url: 'http://blah', not_bob: 'good bye'] }
120
+ response2 = described_class.new(storage, params2, logger, http).start
121
+ expect(response1[:requests][0][:hash])
122
+ .to eql response2[:requests][0][:hash]
106
123
  end
107
124
  end
108
125
  end
@@ -9,21 +9,28 @@ RSpec.describe WebFetch::Promise do
9
9
  let(:find_url) { "http://#{client.host}:#{client.port}/find/#{uid}" }
10
10
 
11
11
  let(:client_success) do
12
- double(fetch: double('success',
13
- complete?: true, pending?: false, success?: true, error: nil
14
- ))
12
+ double(
13
+ fetch: double('success',
14
+ complete?: true,
15
+ pending?: false,
16
+ success?: true,
17
+ error: nil)
18
+ )
15
19
  end
16
20
 
17
21
  let(:client_failure) do
18
- double(fetch: double('failure',
19
- complete?: true, pending?: false, success?: false, error: 'foo'
20
- ))
22
+ double(
23
+ fetch: double('failure',
24
+ complete?: true,
25
+ pending?: false,
26
+ success?: false,
27
+ error: 'foo')
28
+ )
21
29
  end
22
30
 
23
31
  let(:client_pending) do
24
32
  double(fetch: double('pending',
25
- complete?: false, pending?: true, error: nil
26
- ))
33
+ complete?: false, pending?: true, error: nil))
27
34
  end
28
35
 
29
36
  let(:client_not_started) do
@@ -36,8 +43,30 @@ RSpec.describe WebFetch::Promise do
36
43
 
37
44
  describe '#fetch' do
38
45
  before do
39
- stub_request(:get, retrieve_url).to_return(body: { request: {} }.to_json)
40
- stub_request(:get, find_url).to_return(body: { request: {} }.to_json)
46
+ stub_request(:get, retrieve_url)
47
+ .to_return(
48
+ body: {
49
+ request: {
50
+ uid: 123,
51
+ request: {},
52
+ response: {
53
+ success: true, status: 200, body: 'abc123', headers: {}
54
+ }
55
+ }
56
+ }.to_json
57
+ )
58
+
59
+ stub_request(:get, find_url).to_return(
60
+ body: {
61
+ request: {
62
+ uid: 123,
63
+ request: {},
64
+ response: {
65
+ success: true, status: 200, body: 'abc123', headers: {}
66
+ }
67
+ }
68
+ }.to_json
69
+ )
41
70
  end
42
71
 
43
72
  subject { promise.fetch(fetch_options) }
@@ -63,7 +92,16 @@ RSpec.describe WebFetch::Promise do
63
92
  stub_request(:get, retrieve_url)
64
93
  .to_return(
65
94
  body: {
66
- response: { success: true, body: 'abc123' }, request: {}
95
+ request: {
96
+ uid: 123,
97
+ request: {},
98
+ response: {
99
+ success: true,
100
+ status: 200,
101
+ body: Base64.encode64('abc123'),
102
+ headers: {}
103
+ }
104
+ }
67
105
  }.to_json
68
106
  )
69
107
  promise.fetch
@@ -6,11 +6,16 @@ describe WebFetch::Resources do
6
6
  describe '.root' do
7
7
  it 'responds with application name' do
8
8
  expect(described_class.root(nil, nil))
9
- .to eql(status: 200, payload: { application: 'WebFetch' })
9
+ .to eql(
10
+ status: 200,
11
+ command: 'root',
12
+ payload: { application: 'WebFetch' }
13
+ )
10
14
  end
11
15
  end
12
16
 
13
17
  describe '.gather' do
18
+ before { stub_request(:any, 'http://google.com').to_return(status: 200) }
14
19
  let(:response) do
15
20
  described_class.gather(server, requests: [{ url: 'http://google.com' }])
16
21
  end
@@ -25,34 +30,26 @@ describe WebFetch::Resources do
25
30
  end
26
31
 
27
32
  describe '.retrieve' do
28
- it 'gives 404 not found when unrecognised uid requested' do
33
+ it 'gives pending when unrecognised uid requested' do
29
34
  response = described_class.retrieve(server, uid: '123')
30
- expect(response[:status]).to eql 404
31
- error = response[:payload][:error]
32
- expect(error).to eql I18n.t(:uid_not_found)
35
+ expect(response[:request][:pending]).to be true
33
36
  end
34
37
 
35
- it 'gives 404 not found when unrecognised hash requested' do
38
+ it 'gives pending when unrecognised hash requested' do
36
39
  response = described_class.retrieve(server, hash: 'abc')
37
- expect(response[:status]).to eql 404
38
- error = response[:payload][:error]
39
- expect(error).to eql I18n.t(:hash_not_found)
40
+ expect(response[:request][:pending]).to be true
40
41
  end
41
42
  end
42
43
 
43
44
  describe '.find' do
44
- it 'gives 404 not found when unrecognised uid requested' do
45
+ it 'gives pending when unrecognised uid requested' do
45
46
  response = described_class.find(server, uid: '123')
46
- expect(response[:status]).to eql 404
47
- error = response[:payload][:error]
48
- expect(error).to eql I18n.t(:uid_not_found)
47
+ expect(response[:request][:pending]).to be true
49
48
  end
50
49
 
51
- it 'gives 404 not found when unrecognised hash requested' do
50
+ it 'gives pending when unrecognised hash requested' do
52
51
  response = described_class.find(server, hash: 'abc')
53
- expect(response[:status]).to eql 404
54
- error = response[:payload][:error]
55
- expect(error).to eql I18n.t(:hash_not_found)
52
+ expect(response[:request][:pending]).to be true
56
53
  end
57
54
  end
58
55
  end
@@ -3,15 +3,19 @@
3
3
  RSpec.describe WebFetch::Response do
4
4
  let(:response) do
5
5
  described_class.new(
6
- body: 'abc123',
7
- headers: { 'Foo' => 'Bar' },
8
- status: 200,
9
- pending: false,
10
- success: false,
11
- error: 'foo error happened',
12
- request: { url: 'http://blah/' },
13
- uid: 'uid123',
14
- response_time: 123.45
6
+ request: {
7
+ response: {
8
+ body: Base64.encode64('abc123'),
9
+ headers: { 'Foo' => 'Bar' },
10
+ error: 'foo error happened',
11
+ status: 200,
12
+ success: false
13
+ },
14
+ pending: false,
15
+ request: { url: 'http://blah/' },
16
+ uid: 'uid123',
17
+ response_time: 123.45
18
+ }
15
19
  )
16
20
  end
17
21
 
@@ -1,23 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  describe WebFetch::Retriever do
4
- let(:server) { WebFetch::MockServer.new }
4
+ let(:storage) { double(fetch: nil, store: nil) }
5
5
  let(:valid_params) { { uid: 'abc123' } }
6
6
 
7
7
  it 'is initialisable with params' do
8
- expect(described_class.new(server, valid_params, {}))
8
+ expect(described_class.new(storage, valid_params, {}))
9
9
  .to be_a described_class
10
10
  end
11
11
 
12
12
  describe 'validation' do
13
13
  context 'valid' do
14
14
  it 'is valid when `uid` given' do
15
- retriever = described_class.new(server, { uid: 'abc123' }, {})
15
+ retriever = described_class.new(storage, { uid: 'abc123' }, {})
16
16
  expect(retriever.valid?).to be true
17
17
  end
18
18
 
19
19
  it 'is valid when `hash` given' do
20
- retriever = described_class.new(server, { hash: 'def456' }, {})
20
+ retriever = described_class.new(storage, { hash: 'def456' }, {})
21
21
  expect(retriever.valid?).to be true
22
22
  end
23
23
  end
@@ -25,14 +25,14 @@ describe WebFetch::Retriever do
25
25
  context 'invalid' do
26
26
  it 'is invalid if both `hash` and `uid` given' do
27
27
  retriever = described_class.new(
28
- server, { hash: 'def456', uid: 'abc123' }, {}
28
+ storage, { hash: 'def456', uid: 'abc123' }, {}
29
29
  )
30
30
  expect(retriever.valid?).to be false
31
31
  expect(retriever.errors).to include I18n.t(:hash_or_uid_but_not_both)
32
32
  end
33
33
 
34
34
  it 'is invalid if neither `hash` nor `uid` given' do
35
- retriever = described_class.new(server, {}, {})
35
+ retriever = described_class.new(storage, {}, {})
36
36
  expect(retriever.valid?).to be false
37
37
  expect(retriever.errors).to include I18n.t(:missing_hash_and_uid)
38
38
  end
@@ -40,14 +40,14 @@ describe WebFetch::Retriever do
40
40
  end
41
41
 
42
42
  describe '#find' do
43
- it 'returns `nil` when given uid has not been requested' do
44
- retriever = described_class.new(server, { uid: 'nope' }, {})
45
- expect(retriever.find).to be_nil
43
+ it 'returns "pending" when given uid has not been requested' do
44
+ retriever = described_class.new(storage, { uid: 'nope' }, {})
45
+ expect(retriever.find[:pending]).to be true
46
46
  end
47
47
 
48
- it 'returns `nil` when given hash has not been requested' do
49
- retriever = described_class.new(server, { hash: 'also nope' }, {})
50
- expect(retriever.find).to be_nil
48
+ it 'returns "pending" when given hash has not been requested' do
49
+ retriever = described_class.new(storage, { hash: 'also nope' }, {})
50
+ expect(retriever.find[:pending]).to be true
51
51
  end
52
52
 
53
53
  it 'returns payload when request has been retrieved' do
@@ -58,14 +58,13 @@ describe WebFetch::Retriever do
58
58
  url = 'http://blah.blah/success'
59
59
  stub_request(:any, url)
60
60
 
61
- gatherer = WebFetch::Gatherer.new(server, requests: [{ url: url }])
61
+ gatherer = WebFetch::Gatherer.new(storage, requests: [{ url: url }])
62
62
  response = gatherer.start
63
63
  uid = response[:requests].first[:uid]
64
- expect(server).to receive(:storage)
65
- .and_return(uid => { body: 'fake body' })
64
+ allow(storage).to receive(:fetch).and_return('test')
66
65
 
67
- retriever = described_class.new(server, { uid: uid }, {})
68
- expect(retriever.find[:body]).to eql 'fake body'
66
+ retriever = described_class.new(storage, { uid: uid }, {})
67
+ expect(retriever.find).to eql 'test'
69
68
  end
70
69
  end
71
70
  end