papermill-agent 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -4,8 +4,7 @@ gem 'rest-client', '~>1.6.1'
4
4
 
5
5
  group :test do
6
6
  gem 'fakeweb', '>=1.3.0'
7
- gem 'autotest', '>=4.2.2'
7
+ gem 'autotest', '4.4.5'
8
8
  gem 'sinatra', '1.1.0'
9
- gem 'rails', '~> 3.0.0'
10
9
  gem 'rspec', '>=2.0.1'
11
10
  end
data/Rakefile CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'rspec/core/rake_task'
2
2
  task :default => :spec
3
3
 
4
- Rspec::Core::RakeTask.new do |t|
5
- end
4
+ RSpec::Core::RakeTask.new
@@ -2,31 +2,34 @@ require 'singleton'
2
2
  require 'timeout'
3
3
  require 'json'
4
4
  require 'restclient'
5
- require 'yaml'
6
5
 
7
6
  module Papermill
8
7
 
9
8
  class Agent
10
9
  include Singleton
11
-
12
- # papermill endpoint which will receive client requests
13
- API_ENDPOINT = 'http://api.papermillapp.com'
14
10
 
15
11
  # send new request data every 10 seconds
16
12
  UPDATE_INTERVAL = 10
17
13
 
18
- attr_reader :last_sent, :mutex, :config
14
+ attr_reader :last_sent, :mutex, :config, :logger
15
+
16
+ # request timeout threshold for sending data to papermill
17
+ @@request_timeout = 5
18
+ def self.request_timeout
19
+ @@request_timeout
20
+ end
19
21
 
20
22
  def start
21
23
  @last_sent = Time.now
22
24
  @mutex = Mutex.new
23
- @config = YAML.load_file('config/papermill.yml')
25
+ @config = Configurator.new
26
+ @logger = Logger.new
27
+ logger.info "\n\n=============== Starting papermill-agent at #{Time.now} in process #{Process.pid}"
24
28
  Thread.abort_on_exception = true
25
29
 
26
30
  @worker_thread = Thread.new do
27
31
  loop do
28
32
  if time_since_last_sent > UPDATE_INTERVAL
29
- p "sending #{Storage.store.count} requests to papermill..."
30
33
  send_data_to_papermill
31
34
  @last_sent = Time.now
32
35
  end
@@ -36,38 +39,45 @@ module Papermill
36
39
  end
37
40
  end
38
41
 
39
- def time_since_last_sent
40
- Time.now - last_sent
41
- end
42
-
43
- def seconds_until_next_run
44
- UPDATE_INTERVAL - time_since_last_sent
45
- end
46
-
47
42
  def send_data_to_papermill
48
43
  begin
49
- Timeout.timeout(9) { do_request unless Storage.store.empty? }
50
- rescue Timeout::Error
51
- p 'timeout error'
44
+ Timeout.timeout(config.setting('request_timout') || self.class.request_timeout) do
45
+ do_request if !Storage.empty? && config.live_mode
46
+ end
47
+ rescue Timeout::Error => e
48
+ logger.log_exception(e)
52
49
  end
53
50
  end
54
51
 
55
52
  def do_request
56
53
  begin
57
- RestClient.post API_ENDPOINT, { :api_key => config['token'], :payload => jsonify_payload }
58
- rescue RestClient::Exception, Errno::ECONNREFUSED
59
- p 'transmission error ocurred...'
54
+ logger.info("#{Time.now}: Sending #{Storage.size} requests to papermill")
55
+ RestClient.post config.endpoint, { :token => config.token, :payload => jsonify_payload }
56
+ Storage.clear
57
+ rescue RestClient::Exception, Errno::ECONNREFUSED, SocketError => e
58
+ logger.log_exception e
60
59
  end
61
60
  end
62
61
 
62
+ def time_since_last_sent
63
+ Time.now - last_sent
64
+ end
65
+
66
+ def seconds_until_next_run
67
+ UPDATE_INTERVAL - time_since_last_sent
68
+ end
69
+
63
70
  # Return a json version of the storage array.
64
71
  # Also, we'll clear out the storage here
65
72
  #
66
73
  # TODO: move this to the Storage class.
67
74
  def jsonify_payload
68
75
  mutex.synchronize do
69
- json_data = JSON.generate(Storage.store.flatten)
70
- Storage.clear
76
+ begin
77
+ json_data = Storage.store.flatten.to_json
78
+ rescue => e
79
+ logger.log_exception(e)
80
+ end
71
81
  return json_data
72
82
  end
73
83
  end
@@ -0,0 +1,63 @@
1
+ require 'yaml'
2
+
3
+ module Papermill
4
+
5
+ class Configurator
6
+ attr_writer :environment
7
+
8
+ def initialize(config_source = 'config/papermill.yml')
9
+ load_config(config_source)
10
+ end
11
+
12
+ def load_config(config_source)
13
+ @config = case
14
+ when config_source.class == String
15
+ YAML.load_file(config_source)
16
+ when config_source.class == Hash
17
+ config_source
18
+ end
19
+ end
20
+
21
+ def token
22
+ config['token']
23
+ end
24
+
25
+ def endpoint
26
+ if setting('endpoint') && environment != 'production'
27
+ config[environment]['endpoint']
28
+ else
29
+ API_ENDPOINT
30
+ end
31
+ end
32
+
33
+ # returns the value of a setting for the current environment or nil,
34
+ # if it does not exist.
35
+ def setting(name)
36
+ config[environment] && config[environment][name]
37
+ end
38
+
39
+ def live_mode
40
+ if has_config_for_environment?('live_mode')
41
+ setting('live_mode')
42
+ else
43
+ # default to true for production environments, false otherwise
44
+ environment == 'production' ? true : false
45
+ end
46
+ end
47
+
48
+ def environment
49
+ @environment ||= (defined?(Rails) && Rails.env) || ENV['RACK_ENV'] || 'development'
50
+ end
51
+
52
+ private
53
+
54
+ # holds the internal config hash
55
+ def config
56
+ @config
57
+ end
58
+
59
+ def has_config_for_environment?(var)
60
+ config[environment] && config[environment].has_key?(var)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,14 @@
1
+ require 'logger'
2
+
3
+ module Papermill
4
+ class Logger < ::Logger
5
+ def initialize(io_object = 'log/papermill.log')
6
+ FileUtils.mkdir('log') unless Dir.exists?('log')
7
+ super(io_object)
8
+ end
9
+
10
+ def log_exception(exception)
11
+ error(exception.message + exception.backtrace.join("\n"))
12
+ end
13
+ end
14
+ end
@@ -3,13 +3,20 @@ module Papermill
3
3
  module ResponseAdapters
4
4
 
5
5
  class Base
6
+ ENV_CAPTURE_FIELDS = [
7
+ 'PATH_INFO', 'QUERY_STRING', 'REMOTE_ADDR', 'REMOTE_HOST', 'REQUEST_METHOD',
8
+ 'REQUEST_URI', 'SCRIPT_NAME', 'SERVER_NAME', 'SERVER_SOFTWARE', 'HTTP_HOST',
9
+ 'HTTP_ACCEPT', 'HTTP_USER_AGENT', 'REQUEST_PATH', 'rack.url_scheme',
10
+ 'HTTP_REFERER'
11
+ ]
12
+
6
13
  attr_reader :status, :headers, :response, :env
7
- def initialize(*args)
8
- @status, @headers, @response, @env = args.flatten
14
+ def initialize(status, headers, response, env = {})
15
+ @status, @headers, @response, @env = status, headers, response, env
9
16
  end
10
17
 
11
18
  def parse
12
- parsed_response = { :headers => headers.merge(env), :status => status }
19
+ parsed_response = { :headers => extract_env_info, :status => status }
13
20
  # if @status != 304 && @response #&& !@response.body.is_a?(Proc)
14
21
  # parsed_response.merge!(prepare_extra_fields)
15
22
  # end
@@ -19,8 +26,21 @@ module Papermill
19
26
 
20
27
  private
21
28
  def additional_response_data
22
- {:request_time => Time.now}
29
+ {'request_time' => Time.now}
23
30
  end
31
+
32
+ def extract_env_info
33
+ result_hash = env.select { |key, value| ENV_CAPTURE_FIELDS.include?(key) }
34
+
35
+ # rename rack.* keys b/c mongo does not allow key names containing a .
36
+ rack_keys = ENV_CAPTURE_FIELDS.select { |key| key =~ /^rack\./ }
37
+ rack_keys.each do |key|
38
+ new_key = key.gsub(/^rack\./, '').upcase
39
+ result_hash[new_key] = result_hash.delete(key)
40
+ end
41
+ result_hash
42
+ end
43
+
24
44
  end
25
45
 
26
46
  end
@@ -2,22 +2,18 @@
2
2
  module Papermill
3
3
 
4
4
  class ResponseParser
5
- # ENV_CAPTURE_FIELDS = [
6
- # 'PATH_INFO', 'QUERY_STRING', 'REMOTE_ADDR', 'REMOTE_HOST', 'REQUEST_METHOD',
7
- # 'REQUEST_URI', 'SCRIPT_NAME', 'SERVER_NAME', 'SERVER_SOFTWARE', 'HTTP_HOST',
8
- # 'HTTP_ACCEPT', 'HTTP_USER_AGENT', 'REQUEST_PATH'
9
- # ]
10
-
11
- def self.parse(status, headers, response, env = {})
12
- klass = if defined?(Rails)
5
+ def self.parse_klass
6
+ @klass ||= if defined?(Rails)
13
7
  ResponseAdapters::Rails
14
8
  elsif defined?(Sinatra)
15
9
  ResponseAdapters::Sinatra
16
10
  else
17
11
  ResponseAdapters::Base
18
12
  end
13
+ end
19
14
 
20
- klass.new(status, headers, response, env).parse
15
+ def self.parse(status, headers, response, env = {})
16
+ parse_klass.new(status, headers, response, env).parse
21
17
  end
22
18
  end
23
19
 
@@ -19,6 +19,14 @@ module Papermill
19
19
  def clear
20
20
  store.clear
21
21
  end
22
+
23
+ def size
24
+ store.size
25
+ end
26
+
27
+ def empty?
28
+ store.empty?
29
+ end
22
30
  end
23
31
  end
24
32
 
@@ -2,8 +2,13 @@
2
2
  $:.unshift File.dirname(File.expand_path(__FILE__))
3
3
 
4
4
  module Papermill
5
+ # papermill endpoint which will receive client requests
6
+ API_ENDPOINT = 'http://api.papermillapp.com'
7
+
5
8
  autoload :Agent, 'papermill-agent/agent'
6
9
  autoload :Collector, 'papermill-agent/rack/collector'
10
+ autoload :Configurator, 'papermill-agent/configurator'
11
+ autoload :Logger, 'papermill-agent/logger'
7
12
  autoload :ResponseParser, 'papermill-agent/response_parser'
8
13
  autoload :Storage, 'papermill-agent/storage'
9
14
 
data/spec/agent_spec.rb CHANGED
@@ -4,19 +4,19 @@ module Papermill
4
4
 
5
5
  describe 'the agent' do
6
6
  it 'has a mutex' do
7
- Agent.instance.mutex.should be_instance_of Mutex
7
+ agent.mutex.should be_instance_of Mutex
8
8
  end
9
9
 
10
10
  context 'when a new process is started' do
11
11
  context 'when an agent process does not already exist' do
12
12
  it 'should create a new agent process' do
13
- Papermill::Agent.instance.should be_instance_of Papermill::Agent
13
+ Agent.instance.should be_instance_of Papermill::Agent
14
14
  end
15
15
  end
16
16
 
17
17
  context 'when an agent process already exists' do
18
18
  it 'should only allow one agent instance to exist' do
19
- Papermill::Agent.instance.should eql Papermill::Agent.instance
19
+ Agent.instance.should eql Papermill::Agent.instance
20
20
  end
21
21
  end
22
22
  end
@@ -29,62 +29,208 @@ module Papermill
29
29
  end
30
30
  end
31
31
 
32
+ context 'when in a production environment' do
33
+ before { agent.config.stub(:environment => 'production') }
34
+
35
+ context 'and the storage is empty' do
36
+ before { Storage.clear }
37
+
38
+ it 'does not send anything to papermill' do
39
+ agent.should_not_receive(:do_request)
40
+ agent.send_data_to_papermill
41
+ end
42
+ end
43
+
44
+ context 'and the storage is not empty' do
45
+ before { Storage.store << ['an item'] }
46
+
47
+ it 'sends data to papermill' do
48
+ agent.should_receive(:do_request)
49
+ agent.send_data_to_papermill
50
+ end
51
+ end
52
+ end
53
+
54
+ context 'when in a non-production environment' do
55
+ before { agent.config.environment = 'non-production' }
56
+
57
+ context 'and there is no config over-ride to log requests in that environment' do
58
+ context 'and the storage is not empty' do
59
+ before { Storage.store << [{:headers => {'Content-Type' => 'text/html'}, :status => 200}] }
60
+
61
+ it 'does not send data to papermill' do
62
+ agent.should_not_receive(:do_request)
63
+ agent.send_data_to_papermill
64
+ end
65
+ end
66
+
67
+ context 'and the storage is empty' do
68
+ before { Storage.clear }
69
+
70
+ it 'does not send data to papermill' do
71
+ agent.should_not_receive(:do_request)
72
+ agent.send_data_to_papermill
73
+ end
74
+ end
75
+ end
76
+
77
+ context 'and there is a config over-ride present for that environment' do
78
+ before { agent.config.stub!(:live_mode => true) }
79
+
80
+ context 'when the storage is empty' do
81
+ before { Storage.clear }
82
+
83
+ it 'sends data to papermill' do
84
+ agent.should_not_receive(:do_request)
85
+ agent.send_data_to_papermill
86
+ end
87
+ end
88
+
89
+ context 'when the storage is not empty' do
90
+ before { Storage.store << [{:headers => {'Content-Type' => 'text/html'}, :status => 200}] }
91
+
92
+ it 'sends data to papermill' do
93
+ agent.should_receive(:do_request)
94
+ agent.send_data_to_papermill
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ context 'when in a production environment' do
101
+ before { agent.config.environment = 'production' }
102
+
103
+ context 'by default' do
104
+ context 'when requests have been logged' do
105
+ before { Storage.store << [{:headers => {'Content-Type' => 'text/html'}, :status => 200}] }
106
+
107
+ it 'sends request data to the papermill api' do
108
+ agent.should_receive(:do_request)
109
+ agent.send_data_to_papermill
110
+ end
111
+ end
112
+
113
+ context 'when no requests have been logged' do
114
+ before { Storage.clear }
115
+
116
+ it 'does not send data to papermill' do
117
+ agent.should_not_receive(:do_request)
118
+ agent.send_data_to_papermill
119
+ end
120
+ end
121
+ end
122
+
123
+ context 'if a config over-ride is specified to not send data to papermill' do
124
+ before { agent.config.stub!(:live_mode => false) }
125
+
126
+ context 'when requests have been logged' do
127
+ before { Storage.store << [{:headers => {'Content-Type' => 'text/html'}, :status => 200}] }
128
+
129
+ it 'does not send data to papermill' do
130
+ agent.should_not_receive(:do_request)
131
+ agent.send_data_to_papermill
132
+ end
133
+ end
134
+
135
+ context 'when no requests have been logged' do
136
+ before { Storage.clear }
137
+
138
+ it 'does not send data to papermill' do
139
+ agent.should_not_receive(:do_request)
140
+ agent.send_data_to_papermill
141
+ end
142
+ end
143
+ end
144
+ end
145
+
32
146
  end
33
147
 
34
148
  describe 'the api endpoint' do
35
- subject { Agent::API_ENDPOINT }
149
+ subject { Papermill::API_ENDPOINT }
36
150
  it { should == 'http://api.papermillapp.com' }
37
151
  end
38
152
 
153
+ describe 'request timeout interval' do
154
+ it 'defaults to 5 seconds' do
155
+ Agent.request_timeout.should == 5
156
+ end
157
+ end
158
+
39
159
  describe 'determining the time since the last time data was sent to papermill' do
40
160
  before do
41
- Agent.instance.stub!(:last_sent => Time.mktime(2010, 11, 11, 0, 0, 0))
161
+ agent.stub!(:last_sent => Time.mktime(2010, 11, 11, 0, 0, 0))
42
162
  Time.stub!(:now => Time.mktime(2010, 11, 11, 0, 0, 9))
43
163
  end
44
164
 
45
165
  context 'time since the last run' do
46
166
  it 'subtracts the current time from @last_sent' do
47
- Agent.instance.time_since_last_sent.should == 9
167
+ agent.time_since_last_sent.should == 9
48
168
  end
49
169
  end
50
170
 
51
171
  context 'the time until the next run' do
52
172
  it 'time until next = interval - time elapsed since last' do
53
- Agent.instance.seconds_until_next_run.should == 1
173
+ agent.seconds_until_next_run.should == 1
54
174
  end
55
175
  end
56
-
57
176
  end
58
177
 
59
178
  describe 'configuration' do
60
179
  it 'provides access to the api token' do
61
- Agent.instance.config['token'].should == 'api-key'
180
+ agent.config.token.should == 'api-key'
62
181
  end
63
182
  end
64
183
 
65
- describe 'sending data to papermill' do
184
+ describe 'given a logged request' do
66
185
  before do
67
186
  Storage.store << [{:headers => {'Content-Type' => 'text/html'}, :status => 200}]
68
187
  end
69
188
 
70
- it 'jsonifies the payload data' do
71
- Agent.instance.jsonify_payload.should == '[{"headers":{"Content-Type":"text/html"},"status":200}]'
189
+ describe 'serializing the payload' do
190
+ it 'jsonifies the payload data' do
191
+ agent.jsonify_payload.should == '[{"headers":{"Content-Type":"text/html"},"status":200}]'
192
+ end
72
193
  end
73
194
 
74
- it 'empties the store' do
75
- Agent.instance.jsonify_payload
76
- Storage.store.should be_empty
195
+ describe 'sending data to papermill' do
196
+ before { agent.config.stub!(:environment => 'production') }
197
+
198
+ it 'empties the store' do
199
+ agent.send_data_to_papermill
200
+ Storage.store.should be_empty
201
+ end
202
+
203
+ it 'sends a request to the papermill api endpoint' do
204
+ RestClient.should_receive(:post).with(Papermill::API_ENDPOINT, :token => 'api-key', :payload => '[{"headers":{"Content-Type":"text/html"},"status":200}]')
205
+ agent.send_data_to_papermill
206
+ end
207
+
208
+ it 'does not send anything if no requests have been stored' do
209
+ Storage.clear
210
+ Agent.should_not_receive(:do_request)
211
+ agent.send_data_to_papermill
212
+ end
213
+
214
+ it 'logs a message to the logger' do
215
+ agent.logger.should_receive(:info).with(/Sending 1 requests to papermill/)
216
+ agent.send_data_to_papermill
217
+ end
77
218
  end
78
219
 
79
- it 'sends a request to the papermill api endpoint' do
80
- RestClient.should_receive(:post).with(Agent::API_ENDPOINT, :api_key => 'api-key', :payload => '[{"headers":{"Content-Type":"text/html"},"status":200}]')
81
- Agent.instance.send_data_to_papermill
220
+ end
221
+
222
+ describe 'the logger' do
223
+ before { agent.start }
224
+
225
+ it 'has a logger' do
226
+ agent.logger.should be_instance_of Papermill::Logger
82
227
  end
228
+ end
83
229
 
84
- it 'should not send anything if no requests have been stored' do
85
- Storage.clear
86
- Agent.should_not_receive(:do_request)
87
- Agent.instance.send_data_to_papermill
230
+ describe 'starting the agent' do
231
+ it 'loads the configurator' do
232
+ agent.start
233
+ agent.config.should be_instance_of Configurator
88
234
  end
89
235
  end
90
236
 
@@ -0,0 +1,180 @@
1
+ require 'spec_helper'
2
+
3
+ module Papermill
4
+
5
+ describe 'loading the configurator' do
6
+ context 'when a string is passed in' do
7
+ it 'attempts to load the yaml file by that name' do
8
+ YAML.should_receive(:load_file).with('filename')
9
+ Configurator.new('filename')
10
+ end
11
+ end
12
+
13
+ context 'when a hash is passed in' do
14
+ it 'uses the hash as the config object' do
15
+ Configurator.new(mock_config_hash).token.should == mock_config_hash['token']
16
+ end
17
+ end
18
+
19
+ it 'loads the papermill yaml config file' do
20
+ Configurator.new
21
+ end
22
+ end
23
+
24
+ describe 'given a configurator instance' do
25
+ let(:configurator) do
26
+ YAML.stub!(:load_file).and_return({'token' => 'token'})
27
+ Configurator.new
28
+ end
29
+
30
+ describe 'the token' do
31
+ it 'should return the token from the config' do
32
+ configurator.token.should == 'token'
33
+ end
34
+ end
35
+
36
+ describe 'configuring the api endpoint' do
37
+ let(:configurator) { Configurator.new(
38
+ mock_config_hash.merge(
39
+ 'test' => {'endpoint' => 'another endpoint'},
40
+ 'production' => {'endpoint' => 'fake production endpoint'})
41
+ )
42
+ }
43
+
44
+ context 'for non-production environments' do
45
+ it 'allows the user to over-ride the endpoint' do
46
+ configurator.stub!(:environment => 'test')
47
+ configurator.endpoint.should == 'another endpoint'
48
+ end
49
+
50
+ it 'defaults to the api endpoint if nothing else has been specified' do
51
+ configurator.stub!(:environment => 'an undefined env')
52
+ configurator.endpoint.should == Papermill::API_ENDPOINT
53
+ end
54
+ end
55
+
56
+ context 'for production environments' do
57
+ before { configurator.stub!(:environment => 'production') }
58
+
59
+ it "always sets the endpoint to papermillapp's servers" do
60
+ configurator.endpoint.should == Papermill::API_ENDPOINT
61
+ end
62
+
63
+ it 'should be the fake endpoint' do
64
+ configurator.endpoint.should_not == 'fake production endpoint'
65
+ end
66
+ end
67
+ end
68
+
69
+ describe 'determining the environment' do
70
+ let(:configurator) { Configurator.new(mock_config_hash) }
71
+
72
+ context 'when in a Rails app' do
73
+ before do
74
+ Papermill.class_eval do
75
+ # create a fake rails class
76
+ class Rails
77
+ def self.env; 'rails environment'; end
78
+ end
79
+ end
80
+ end
81
+ # remove the fake rails class
82
+ after { Papermill.class_eval { remove_const(:Rails) } }
83
+
84
+ it 'get the environment from rails' do
85
+ configurator.environment.should == 'rails environment'
86
+ end
87
+ end
88
+
89
+ context 'when in a rack app' do
90
+ before do
91
+ ENV.stub!(:[]).with('RACK_ENV').and_return('rack env')
92
+ end
93
+
94
+ it "gets the environment from ENV['RACK_ENV']" do
95
+ configurator.environment.should == 'rack env'
96
+ end
97
+ end
98
+
99
+ context 'not in rails or a rack app' do
100
+ before { ENV['RACK_ENV'] = nil }
101
+ after { ENV['RACK_ENV'] = 'test' }
102
+
103
+ it 'defaults the environment to development' do
104
+ configurator.environment.should == 'development'
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ describe 'setting the environment' do
111
+ let(:configurator) { Configurator.new }
112
+ before { configurator.environment = 'some environment' }
113
+
114
+ it 'allows manual setting of the envionment' do
115
+ configurator.environment.should == 'some environment'
116
+ end
117
+ end
118
+
119
+ describe 'determining whether or not to send data to papermill' do
120
+ context 'for a production environment' do
121
+ let(:config) { Configurator.new }
122
+ before { config.environment = 'production' }
123
+
124
+ context 'by default' do
125
+ it 'is true' do
126
+ config.live_mode.should == true
127
+ end
128
+ end
129
+
130
+ context 'when a config override is specified with a value of false' do
131
+ let(:config) { Configurator.new({'production' => {'live_mode' => false}}) }
132
+
133
+ it 'does not send data to papermill' do
134
+ config.environment.should == 'production'
135
+ config.live_mode.should == false
136
+ end
137
+ end
138
+ end
139
+
140
+ context 'for a non-production environment' do
141
+ context 'by default' do
142
+ let(:config) { Configurator.new }
143
+ before { config.environment = 'non-production' }
144
+
145
+ it 'is false' do
146
+ config.live_mode.should == false
147
+ end
148
+ end
149
+
150
+ context 'when an environment override is specified' do
151
+ let(:config) { Configurator.new('non-production' => {'live_mode' => true}) }
152
+ before { config.environment = 'non-production' }
153
+
154
+ it 'uses that setting' do
155
+ config.live_mode.should == true
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ describe 'retrieving a setting for the current environment' do
162
+ context 'for a given environment' do
163
+ let(:config) { Configurator.new({'production' => {'my key' => 'my val'}}) }
164
+ before { config.environment = 'production' }
165
+
166
+ context 'for a setting that has a value' do
167
+ it 'returns the value of the setting identified by the name' do
168
+ config.setting('my key').should == 'my val'
169
+ end
170
+ end
171
+
172
+ context 'for a setting that does not have a value' do
173
+ it 'returns nil' do
174
+ config.setting('does not exist').should == nil
175
+ end
176
+ end
177
+ end
178
+
179
+ end
180
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ module Papermill
4
+ describe 'the papermill logger' do
5
+ it 'inherits from the ruby std lib logger' do
6
+ ::Papermill::Logger.superclass.should == ::Logger
7
+ end
8
+ end
9
+
10
+ describe 'instantiating the log file' do
11
+ context 'when the log directory does not exist' do
12
+ it 'should not raise a file system error' do
13
+ lambda {
14
+ Logger.new
15
+ }.should_not raise_error(Errno::ENOENT)
16
+ end
17
+
18
+ it 'creates the log file' do
19
+ Logger.new
20
+ File.exists?('log/papermill.log').should be_true
21
+ end
22
+ end
23
+ end
24
+
25
+ describe '#log_error' do
26
+ let(:logger) { Logger.new($stdout) }
27
+ let(:exception) { mock('Exception', :message => 'msg', :backtrace => ['...']) }
28
+
29
+ it 'records the message and the backtrace of the exception' do
30
+ logger.should_receive(:error)
31
+ exception.should_receive(:message)
32
+ exception.should_receive(:backtrace)
33
+
34
+ logger.log_exception(exception)
35
+ end
36
+ end
37
+ end
@@ -4,15 +4,18 @@ require 'sinatra'
4
4
 
5
5
  module Papermill
6
6
 
7
- describe 'collecting statistics via the middleware layer' do
8
- before do
9
- @app = Sinatra.new Sinatra::Application do
7
+ describe 'collecting statistics with Collector' do
8
+ def app
9
+ @app ||= Sinatra.new Sinatra::Application do
10
10
  get '/' do
11
11
  '<html><body>Hello, world!</body></html>'
12
12
  end
13
+
14
+ use Papermill::Collector
13
15
  end
14
- @app.use Papermill::Collector
16
+ end
15
17
 
18
+ before do
16
19
  request_headers = {'HTTP_USER_AGENT' => 'agent', 'REMOTE_ADDR' => '0.0.0.0',
17
20
  'QUERY_STRING' => 'q=search',
18
21
  'SCRIPT_NAME' => 'my-script',
@@ -20,11 +23,17 @@ module Papermill
20
23
  'HTTP_HOST' => 'localhost',
21
24
  'REQUEST_URI' => 'i-requested-this',
22
25
  'REMOTE_HOST' => '123.345.34.2',
23
- 'SERVER_SOFTWARE' => 'apache'
26
+ 'SERVER_SOFTWARE' => 'apache',
27
+ 'HTTP_REFERER' => 'previous url'
24
28
  }
25
29
 
26
30
  Time.stub(:now => Time.utc(2010, 01, 01))
27
- @status, @headers, @body = @app.call(Rack::MockRequest.env_for('/', request_headers))
31
+ @status, @headers, @body = app.call(Rack::MockRequest.env_for('/', request_headers))
32
+ end
33
+
34
+ context 'for a 304 response' do
35
+ it 'records the status code'
36
+ it 'doesnt attempt to save any headers'
28
37
  end
29
38
 
30
39
  def last_store
@@ -42,7 +51,7 @@ module Papermill
42
51
  it 'records request duration'
43
52
 
44
53
  it 'records the request time' do
45
- last_store[:request_time].should == Time.utc(2010, 01, 01)
54
+ last_store['request_time'].should == Time.utc(2010, 01, 01)
46
55
  end
47
56
 
48
57
  it 'records the path info' do
@@ -93,8 +102,16 @@ module Papermill
93
102
  headers['REMOTE_HOST'].should == '123.345.34.2'
94
103
  end
95
104
 
105
+ it 'records the http referrer' do
106
+ headers['HTTP_REFERER'].should == 'previous url'
107
+ end
108
+
109
+ it 'does not save any keys with a period in the name' do
110
+ headers.keys.detect { |k| k =~ /\./ }.should == nil
111
+ end
112
+
96
113
  it 'records the url scheme' do
97
- headers['rack.url_scheme'].should == 'http'
114
+ headers['URL_SCHEME'].should == 'http'
98
115
  end
99
116
 
100
117
  context 'for a 304 response' do
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ module Papermill
4
+ describe 'initializing the Base parser' do
5
+ context 'with no environment' do
6
+ let(:base) { ResponseAdapters::Base.new(200, {}, lambda {}) }
7
+
8
+ it 'sets it to an empty hash' do
9
+ base.env.should == {}
10
+ end
11
+ end
12
+ end
13
+ end
@@ -14,12 +14,14 @@ module Papermill
14
14
  end
15
15
 
16
16
  context 'in a rails app' do
17
- class Rails
17
+ before do
18
+ # self.class.module_eval { remove_const(:Sinatra) if defined?(Sinatra) }
18
19
  end
19
20
 
20
21
  it 'uses the rails adapter' do
21
- ResponseAdapters::Rails.should_receive(:new).and_return(mock('object', :parse => nil))
22
- ResponseParser.parse(200, {}, [], {})
22
+ class Rails
23
+ end
24
+ ResponseParser.parse_klass.should == ResponseAdapters::Rails
23
25
  end
24
26
  end
25
27
  end
data/spec/spec_helper.rb CHANGED
@@ -12,8 +12,18 @@ require 'papermill-agent'
12
12
  require 'fakeweb'
13
13
 
14
14
  FakeWeb.allow_net_connect = false
15
+ FakeWeb.register_uri(:post, Papermill::API_ENDPOINT, {})
15
16
 
16
17
  RSpec.configure do |config|
18
+ def mock_config_hash
19
+ {'token' => 'mock-token'}
20
+ end
21
+
22
+ def agent
23
+ Papermill::Agent.instance
24
+ end
25
+
26
+ config.mock_with :rspec
17
27
  config.before(:each) do
18
28
  # clear the storage before each example
19
29
  Papermill::Storage.clear
data/spec/storage_spec.rb CHANGED
@@ -2,6 +2,22 @@ require 'spec_helper'
2
2
 
3
3
  module Papermill
4
4
 
5
+ describe 'determining if the storage has items in it' do
6
+ subject { Storage.empty? }
7
+
8
+ context 'when a request has been added to the storage' do
9
+ before { Storage.store << ['something worth saving'] }
10
+
11
+ it { should == false }
12
+ end
13
+
14
+ context 'when no requests have been added to the storage' do
15
+ before { Storage.clear }
16
+
17
+ it { should == true }
18
+ end
19
+ end
20
+
5
21
  describe 'adding an item to the local data storage' do
6
22
  it 'acts like an array' do
7
23
  Storage.store.should respond_to(:<<)
@@ -19,6 +35,17 @@ module Papermill
19
35
  end
20
36
  end
21
37
 
38
+ describe 'determine the number of records stored' do
39
+ before do
40
+ Storage.store << 'record 1'
41
+ Storage.store << 'record 2'
42
+ end
43
+
44
+ it 'should be 2' do
45
+ Storage.size.should == 2
46
+ end
47
+ end
48
+
22
49
  describe 'emptying the cache' do
23
50
  before { Storage.clear }
24
51
 
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 1
9
- version: 0.0.1
8
+ - 2
9
+ version: 0.0.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Alex Sharp
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-11-19 00:00:00 -08:00
17
+ date: 2011-01-02 00:00:00 -06:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -44,6 +44,8 @@ extra_rdoc_files:
44
44
  - doc/ideas.md
45
45
  files:
46
46
  - lib/papermill-agent/agent.rb
47
+ - lib/papermill-agent/configurator.rb
48
+ - lib/papermill-agent/logger.rb
47
49
  - lib/papermill-agent/rack/collector.rb
48
50
  - lib/papermill-agent/response_adapters/base.rb
49
51
  - lib/papermill-agent/response_adapters/rails.rb
@@ -58,9 +60,12 @@ files:
58
60
  - Rakefile
59
61
  - doc/ideas.md
60
62
  - spec/agent_spec.rb
63
+ - spec/configurator_spec.rb
61
64
  - spec/integration/rails2_spec.rb
62
65
  - spec/integration/rails3_spec.rb
66
+ - spec/logger_spec.rb
63
67
  - spec/rack/collector_spec.rb
68
+ - spec/response_adapters/base_spec.rb
64
69
  - spec/response_parser_spec.rb
65
70
  - spec/spec_helper.rb
66
71
  - spec/storage_spec.rb
@@ -98,9 +103,12 @@ specification_version: 3
98
103
  summary: The client agent for papermillapp.com
99
104
  test_files:
100
105
  - spec/agent_spec.rb
106
+ - spec/configurator_spec.rb
101
107
  - spec/integration/rails2_spec.rb
102
108
  - spec/integration/rails3_spec.rb
109
+ - spec/logger_spec.rb
103
110
  - spec/rack/collector_spec.rb
111
+ - spec/response_adapters/base_spec.rb
104
112
  - spec/response_parser_spec.rb
105
113
  - spec/spec_helper.rb
106
114
  - spec/storage_spec.rb