alephant-broker 1.2.1 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,18 +1,18 @@
1
1
  require 'alephant/logger'
2
2
  require 'alephant/broker/component'
3
3
 
4
-
5
4
  module Alephant
6
5
  module Broker
7
6
  module Request
8
7
  class Batch
9
8
  include Logger
10
9
 
11
- attr_reader :batch_id, :components
10
+ attr_reader :batch_id, :components, :load_strategy
12
11
 
13
- def initialize(env)
12
+ def initialize(component_factory, env)
14
13
  logger.debug("Request::Batch#initialize(#{env.settings})")
15
14
 
15
+ @component_factory = component_factory
16
16
  @batch_id = env.data['batch_id']
17
17
  @components = components_for env
18
18
 
@@ -23,14 +23,13 @@ module Alephant
23
23
 
24
24
  def components_for(env)
25
25
  env.data['components'].map do |c|
26
- Component.new(
26
+ @component_factory.create(
27
27
  c['component'],
28
28
  batch_id,
29
29
  c['options']
30
30
  )
31
31
  end
32
32
  end
33
-
34
33
  end
35
34
  end
36
35
  end
@@ -1,26 +1,31 @@
1
1
  require 'alephant/broker/request'
2
+ require 'alephant/broker/component_factory'
2
3
 
3
- module Alephant::Broker::Request
4
- class Factory
4
+ module Alephant
5
+ module Broker
6
+ module Request
7
+ class Factory
8
+ def self.request_type_from(env)
9
+ env.path.split('/')[1]
10
+ end
5
11
 
6
- def self.request_type_from(env)
7
- env.path.split('/')[1]
8
- end
12
+ def self.request_for(load_strategy, env)
13
+ component_factory = ComponentFactory.new load_strategy
9
14
 
10
- def self.request_for(env)
11
- case request_type_from(env)
12
- when 'multi'
13
- Multi.new(env)
14
- when 'component'
15
- Asset.new(env)
16
- when 'components'
17
- Batch.new(env)
18
- when 'status'
19
- Status.new
20
- else
21
- NotFound.new
15
+ case request_type_from(env)
16
+ when 'multi'
17
+ Multi.new(env)
18
+ when 'component'
19
+ Asset.new(component_factory, env)
20
+ when 'components'
21
+ Batch.new(component_factory, env)
22
+ when 'status'
23
+ Status.new
24
+ else
25
+ NotFound.new
26
+ end
27
+ end
22
28
  end
23
29
  end
24
30
  end
25
31
  end
26
-
@@ -4,6 +4,7 @@ require 'alephant/broker/request'
4
4
  require 'alephant/broker/response'
5
5
  require 'alephant/broker/request/factory'
6
6
  require 'alephant/broker/response/factory'
7
+ require 'alephant/broker/errors/content_not_found'
7
8
 
8
9
  module Alephant
9
10
  module Broker
@@ -11,23 +12,17 @@ module Alephant
11
12
  class Handler
12
13
  extend Logger
13
14
 
14
- def self.request_for(env)
15
- Request::Factory.request_for env
15
+ def self.request_for(load_strategy, env)
16
+ Request::Factory.request_for(load_strategy, env)
16
17
  end
17
18
 
18
19
  def self.response_for(request)
19
20
  Response::Factory.response_for request
20
21
  end
21
22
 
22
- def self.process(env)
23
- begin
24
- response_for request_for(env)
25
- rescue Exception => e
26
- logger.warn("Broker.requestHandler.process: Exception raised (#{e.message}, #{e.backtrace.join('\n')})")
27
- Response::Factory.error
28
- end
23
+ def self.process(load_strategy, env)
24
+ response_for request_for(load_strategy, env)
29
25
  end
30
-
31
26
  end
32
27
  end
33
28
  end
@@ -1,4 +1,3 @@
1
- require 'alephant/broker/errors/invalid_cache_key'
2
1
  require 'alephant/logger'
3
2
 
4
3
  module Alephant
@@ -7,23 +6,31 @@ module Alephant
7
6
  class Asset < Base
8
7
  include Logger
9
8
 
10
- attr_reader :component
11
-
12
9
  def initialize(component)
13
10
  @component = component
14
- super()
11
+ super component.status
15
12
  end
16
13
 
17
14
  def setup
18
- loaded_content = load(component)
15
+ @headers = @component.headers
16
+ @content = @component.content
17
+ log if @component.is_a? Component
18
+ end
19
+
20
+ private
19
21
 
20
- @content = loaded_content[:body]
21
- @content_type = loaded_content[:content_type]
22
- @status = loaded_content[:status]
23
- @sequence = component.version.nil? ? 'not available' : component.version
24
- @cached = component.cached
22
+ def batched
23
+ @component.batch_id.nil? ? '' : 'batched'
25
24
  end
26
25
 
26
+ def details
27
+ c = @component
28
+ "#{c.id}/#{c.opts_hash}/#{c.headers} #{batched} #{c.options}"
29
+ end
30
+
31
+ def log
32
+ logger.info "Broker: Component loaded! #{details} (200)"
33
+ end
27
34
  end
28
35
  end
29
36
  end
@@ -1,3 +1,4 @@
1
+ require 'alephant/broker/errors/invalid_cache_key'
1
2
  require 'aws-sdk'
2
3
  require 'ostruct'
3
4
 
@@ -5,8 +6,7 @@ module Alephant
5
6
  module Broker
6
7
  module Response
7
8
  class Base
8
- attr_reader :headers
9
- attr_accessor :status, :content, :content_type, :version, :sequence, :cached
9
+ attr_reader :content, :headers, :status
10
10
 
11
11
  STATUS_CODE_MAPPING = {
12
12
  200 => 'ok',
@@ -15,64 +15,16 @@ module Alephant
15
15
  }
16
16
 
17
17
  def initialize(status = 200, content_type = "text/html")
18
- @headers = {}
19
- @sequence = 'not available'
20
- @version = Broker.config.fetch('elasticache_cache_version', 'not available').to_s
21
- @cached = false
22
- @content_type = content_type
23
- @status = status
24
- @content = STATUS_CODE_MAPPING[status]
18
+ @content = STATUS_CODE_MAPPING[status]
19
+ @headers = { "Content-Type" => content_type }
20
+ @status = status
25
21
 
26
22
  setup
27
23
  end
28
24
 
29
- def to_h
30
- {
31
- :status => @status,
32
- :content => @content,
33
- :content_type => @content_type,
34
- :version => @version,
35
- :sequence => @sequence,
36
- :cached => @cached
37
- }
38
- end
39
-
40
25
  protected
41
26
 
42
27
  def setup; end
43
-
44
- def load(component)
45
- begin
46
-
47
- data = OpenStruct.new(:status => 200, :content_type => content_type)
48
- component.load
49
-
50
- data.content_type = component.content_type
51
- data.body = component.content.force_encoding('UTF-8')
52
- rescue AWS::S3::Errors::NoSuchKey, InvalidCacheKey => e
53
- data.body = "Not found"
54
- data.status = 404
55
- rescue StandardError => e
56
- data.body = "#{error_for(e)}"
57
- data.status = 500
58
- end
59
-
60
- log(component, data.status, e)
61
- data.marshal_dump
62
- end
63
-
64
- def log(c, status, e = nil)
65
- logger.info("Broker: Component loaded: #{details_for(c)} (#{status}) #{error_for(e)}")
66
- end
67
-
68
- def details_for(c)
69
- "#{c.id}/#{c.opts_hash}/#{c.version} #{c.batch_id.nil? ? '' : "batched"} (#{c.options})"
70
- end
71
-
72
- def error_for(e)
73
- e.nil? ? nil : "#{e.message}\n#{e.backtrace.join('\n')}"
74
- end
75
-
76
28
  end
77
29
  end
78
30
  end
@@ -1,5 +1,5 @@
1
1
  require 'alephant/logger'
2
- require 'peach'
2
+ require 'pmap'
3
3
 
4
4
  module Alephant
5
5
  module Broker
@@ -18,22 +18,25 @@ module Alephant
18
18
 
19
19
  def setup
20
20
  @content = JSON.generate({
21
- "batch_id" => batch_id,
22
- "components" => json
21
+ 'batch_id' => batch_id,
22
+ 'components' => json
23
23
  })
24
24
  end
25
25
 
26
26
  private
27
27
 
28
28
  def json
29
- logger.info("Broker: Batch load started (#{batch_id})")
30
- result = components.pmap do | component |
29
+ logger.info "Broker: Batch load started (#{batch_id})"
30
+ result = components.pmap do |component|
31
31
  {
32
- 'component' => component.id,
33
- 'options' => component.options
34
- }.merge load(component)
32
+ 'component' => component.id,
33
+ 'options' => component.options,
34
+ 'status' => component.status,
35
+ 'content_type' => component.content_type,
36
+ 'body' => component.content
37
+ }
35
38
  end
36
- logger.info("Broker: Batch load done (#{batch_id})")
39
+ logger.info "Broker: Batch load done (#{batch_id})"
37
40
 
38
41
  result
39
42
  end
@@ -22,6 +22,10 @@ module Alephant
22
22
  def self.error
23
23
  ServerError.new
24
24
  end
25
+
26
+ def self.not_found
27
+ NotFound.new
28
+ end
25
29
  end
26
30
  end
27
31
  end
@@ -1,5 +1,5 @@
1
1
  module Alephant
2
2
  module Broker
3
- VERSION = "1.2.1"
3
+ VERSION = "1.3.1"
4
4
  end
5
5
  end
@@ -0,0 +1 @@
1
+ {"batch_id":"baz","components":[{"component":"ni_council_results_table"},{"component":"ni_council_results_table"}]}
@@ -0,0 +1 @@
1
+ {"batch_id":"baz","components":[{"component":"ni_council_results_table","options":{},"status":200,"content_type":"foo/bar","body":"Test"},{"component":"ni_council_results_table","options":{},"status":200,"content_type":"foo/bar","body":"Test"}]}
@@ -0,0 +1 @@
1
+ require_relative '../spec_helper'
data/spec/rack_spec.rb CHANGED
@@ -1,23 +1,37 @@
1
- ENV['RACK_ENV'] = 'test'
2
-
3
1
  require 'spec_helper'
4
- require 'rack/test'
5
- require 'alephant/broker'
6
-
7
- RSpec.configure do |conf|
8
- conf.include Rack::Test::Methods
9
- end
10
2
 
11
- describe 'Broker Rack Application' do
12
- before do
13
- cache_hash = {
3
+ describe Alephant::Broker::Application do
4
+ include Rack::Test::Methods
5
+
6
+ let(:app) do
7
+ described_class.new(
8
+ Alephant::Broker::LoadStrategy::S3.new,
9
+ {
10
+ :lookup_table_name => 'test_table',
11
+ :bucket_id => 'test_bucket',
12
+ :path => 'bucket_path'
13
+ }
14
+ )
15
+ end
16
+ let(:cache_hash) do
17
+ {
14
18
  :content_type => 'test/content',
15
19
  :content => 'Test'
16
20
  }
21
+ end
22
+ let(:sequencer_double) do
23
+ instance_double(
24
+ 'Alephant::Sequencer::Sequencer',
25
+ :get_last_seen => '111'
26
+ )
27
+ end
28
+
29
+ before do
30
+ allow_any_instance_of(Logger).to receive(:info)
31
+ allow_any_instance_of(Logger).to receive(:debug)
17
32
 
18
33
  allow_any_instance_of(Alephant::Broker::Cache::Client)
19
- .to receive(:get)
20
- .and_return(cache_hash)
34
+ .to receive(:get).and_return(cache_hash)
21
35
 
22
36
  allow_any_instance_of(Alephant::Broker::Component)
23
37
  .to receive_messages(
@@ -27,97 +41,84 @@ describe 'Broker Rack Application' do
27
41
  )
28
42
 
29
43
  allow_any_instance_of(Alephant::Broker::Response::Asset)
30
- .to receive(:status)
31
- .and_return(200)
32
- end
33
-
34
- def app
35
- Alephant::Broker::Application.new({
36
- :lookup_table_name => 'test_table',
37
- :bucket_id => 'test_bucket',
38
- :path => 'bucket_path'
39
- })
40
- end
41
-
42
- it 'Tests status page' do
43
- get '/status'
44
- expect(last_response).to be_ok
45
- expect(last_response.body).to eq('ok')
46
- end
47
-
48
- it "Tests not found page" do
49
- get '/some/non-existent-page'
50
- expect(last_response.status).to eq(404)
51
- expect(last_response.body).to eq('Not found')
52
- end
53
-
54
- it "Test asset data is returned" do
55
- get '/component/test_component'
56
-
57
- expect(last_response).to be_ok
58
- expect(last_response.body).to eq('Test')
59
- end
44
+ .to receive(:status).and_return(200)
60
45
 
61
- it "Tests query string parameters are passed correctly to lookup" do
62
- get '/component/test_component?variant=test_variant'
63
-
64
- expect(last_response).to be_ok
65
- expect(last_response.body).to eq('Test')
46
+ allow(Alephant::Sequencer).to receive(:create) { sequencer_double }
66
47
  end
67
48
 
68
- it "Tests 404 when lookup doesn't return a valid location" do
69
- allow_any_instance_of(Alephant::Broker::Response::Asset)
70
- .to receive(:status)
71
- .and_return(404)
72
-
73
- get '/component/test_component'
74
-
75
- expect(last_response.status).to eq(404)
49
+ describe 'Status endpoint `/status`' do
50
+ before { get '/status' }
51
+ specify { expect(last_response.status).to eql 200 }
52
+ specify { expect(last_response.body).to eql 'ok' }
76
53
  end
77
54
 
78
- it "Tests 500 when exception is raised in application" do
79
- allow_any_instance_of(Alephant::Broker::Response::Asset)
80
- .to receive(:status)
81
- .and_return(500)
82
-
83
- get '/component/test_component'
84
-
85
- expect(last_response.status).to eq(500)
55
+ describe '404 endpoint `/banana`' do
56
+ before { get '/banana' }
57
+ specify { expect(last_response.status).to eql 404 }
58
+ specify { expect(last_response.body).to eq 'Not found' }
86
59
  end
87
60
 
88
- it "Test batch asset data is returned" do
89
- json = '{"batch_id":"baz","components":[{"component":"ni_council_results_table"},{"component":"ni_council_results_table"}]}'
90
- compiled_json = '{"batch_id":"baz","components":[{"component":"ni_council_results_table","options":{},"status":200,"content_type":"foo/bar","body":"Test"},{"component":"ni_council_results_table","options":{},"status":200,"content_type":"foo/bar","body":"Test"}]}'
91
-
92
- post '/components/batch', json, "CONTENT_TYPE" => "application/json"
93
-
94
- expect(last_response).to be_ok
95
- expect(last_response.body).to eq(compiled_json)
61
+ describe 'Component endpoint `/component/...`' do
62
+ let(:batch_json) do
63
+ IO.read("#{File.dirname(__FILE__)}/fixtures/json/batch.json").strip
64
+ end
65
+ let(:batch_compiled_json) do
66
+ IO.read("#{File.dirname(__FILE__)}/fixtures/json/batch_compiled.json").strip
67
+ end
68
+
69
+ context 'for a valid component ID' do
70
+ before { get '/component/test_component' }
71
+ specify { expect(last_response.status).to eql 200 }
72
+ specify { expect(last_response.body).to eql 'Test' }
73
+ end
74
+
75
+ context 'for valid URL parameters in request' do
76
+ before { get '/component/test_component?variant=test_variant' }
77
+ specify { expect(last_response.status).to eq 200 }
78
+ specify { expect(last_response.body).to eq 'Test' }
79
+ end
80
+
81
+ context 'when using valid batch asset data' do
82
+ before { post '/components/batch', batch_json, 'CONTENT_TYPE' => 'application/json' }
83
+ specify { expect(last_response.status).to eql 200 }
84
+ specify { expect(JSON.parse last_response.body).to eq JSON.parse(batch_compiled_json) }
85
+ end
96
86
  end
97
87
 
98
- it "Should handle old cache data gracefully" do
99
- lookup_location_double = double('Alephant::Lookup::Location', :location => 'test/location')
100
- lookup_helper_double = double('Alephant::Lookup::LookupHelper', :read => lookup_location_double)
101
-
102
- cache_double = double('Alephant::Broker::Cache::Client', :set => { :content_type => 'test/html', :content => '<p>Some data</p>' }, :get => '<p>Some data</p>')
103
- s3_cache_double = double('Alephant::Cache', :get => 'test_content')
104
-
105
- allow(Alephant::Lookup)
106
- .to receive(:create)
107
- .and_return(lookup_helper_double)
108
-
109
- allow(Alephant::Broker::Cache::Client)
110
- .to receive(:new)
111
- .and_return(cache_double)
112
-
113
- allow(Alephant::Cache)
114
- .to receive(:new)
115
- .and_return(s3_cache_double)
116
-
117
- expect(cache_double).to receive(:set).once
118
-
119
- get '/component/test_component'
120
-
88
+ describe 'Cached data' do
89
+ let(:cache_double) do
90
+ instance_double(
91
+ 'Alephant::Broker::Cache::Client',
92
+ :set => {
93
+ :content_type => 'test/html',
94
+ :content => '<p>Some data</p>'
95
+ },
96
+ :get => '<p>Some data</p>'
97
+ )
98
+ end
99
+ let(:lookup_location_double) do
100
+ instance_double('Alephant::Lookup::Location', location: 'test/location')
101
+ end
102
+ let(:lookup_helper_double) do
103
+ instance_double('Alephant::Lookup::LookupHelper', read: lookup_location_double)
104
+ end
105
+ let(:s3_cache_double) do
106
+ instance_double(
107
+ 'Alephant::Cache',
108
+ :get => 'test_content'
109
+ )
110
+ end
111
+
112
+ context 'which is old' do
113
+ before do
114
+ allow(Alephant::Lookup).to receive(:create) { lookup_helper_double }
115
+ allow(Alephant::Broker::Cache::Client).to receive(:new) { cache_double }
116
+ allow(Alephant::Cache).to receive(:new) { s3_cache_double }
117
+ end
118
+ it 'should update the cache (call `.set`)' do
119
+ expect(cache_double).to receive(:set).once
120
+ end
121
+ after { get '/component/test_component' }
122
+ end
121
123
  end
122
-
123
124
  end