copy_tuner_client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +9 -0
  4. data/Appraisals +15 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +161 -0
  7. data/README.md +4 -0
  8. data/Rakefile +28 -0
  9. data/copy_tuner_client.gemspec +33 -0
  10. data/features/rails.feature +270 -0
  11. data/features/step_definitions/copycopter_server_steps.rb +64 -0
  12. data/features/step_definitions/rails_steps.rb +172 -0
  13. data/features/support/env.rb +11 -0
  14. data/features/support/rails_server.rb +124 -0
  15. data/gemfiles/2.3.gemfile +7 -0
  16. data/gemfiles/2.3.gemfile.lock +105 -0
  17. data/gemfiles/3.0.gemfile +7 -0
  18. data/gemfiles/3.0.gemfile.lock +147 -0
  19. data/gemfiles/3.1.gemfile +11 -0
  20. data/gemfiles/3.1.gemfile.lock +191 -0
  21. data/init.rb +1 -0
  22. data/lib/copy_tuner_client/cache.rb +144 -0
  23. data/lib/copy_tuner_client/client.rb +136 -0
  24. data/lib/copy_tuner_client/configuration.rb +224 -0
  25. data/lib/copy_tuner_client/errors.rb +12 -0
  26. data/lib/copy_tuner_client/i18n_backend.rb +92 -0
  27. data/lib/copy_tuner_client/poller.rb +44 -0
  28. data/lib/copy_tuner_client/prefixed_logger.rb +45 -0
  29. data/lib/copy_tuner_client/process_guard.rb +92 -0
  30. data/lib/copy_tuner_client/rails.rb +21 -0
  31. data/lib/copy_tuner_client/railtie.rb +12 -0
  32. data/lib/copy_tuner_client/request_sync.rb +39 -0
  33. data/lib/copy_tuner_client/version.rb +7 -0
  34. data/lib/copy_tuner_client.rb +75 -0
  35. data/lib/tasks/copy_tuner_client_tasks.rake +20 -0
  36. data/spec/copy_tuner_client/cache_spec.rb +273 -0
  37. data/spec/copy_tuner_client/client_spec.rb +236 -0
  38. data/spec/copy_tuner_client/configuration_spec.rb +305 -0
  39. data/spec/copy_tuner_client/i18n_backend_spec.rb +157 -0
  40. data/spec/copy_tuner_client/poller_spec.rb +108 -0
  41. data/spec/copy_tuner_client/prefixed_logger_spec.rb +37 -0
  42. data/spec/copy_tuner_client/process_guard_spec.rb +118 -0
  43. data/spec/copy_tuner_client/request_sync_spec.rb +47 -0
  44. data/spec/copy_tuner_client_spec.rb +19 -0
  45. data/spec/spec_helper.rb +29 -0
  46. data/spec/support/client_spec_helpers.rb +8 -0
  47. data/spec/support/defines_constants.rb +44 -0
  48. data/spec/support/fake_client.rb +53 -0
  49. data/spec/support/fake_copy_tuner_app.rb +175 -0
  50. data/spec/support/fake_html_safe_string.rb +20 -0
  51. data/spec/support/fake_logger.rb +68 -0
  52. data/spec/support/fake_passenger.rb +27 -0
  53. data/spec/support/fake_resque_job.rb +18 -0
  54. data/spec/support/fake_unicorn.rb +13 -0
  55. data/spec/support/middleware_stack.rb +13 -0
  56. data/spec/support/writing_cache.rb +17 -0
  57. data/tmp/projects.json +1 -0
  58. metadata +389 -0
@@ -0,0 +1,236 @@
1
+ require 'spec_helper'
2
+
3
+ describe CopyTunerClient do
4
+ def build_client(config = {})
5
+ config[:logger] ||= FakeLogger.new
6
+ default_config = CopyTunerClient::Configuration.new.to_hash
7
+ CopyTunerClient::Client.new(default_config.update(config))
8
+ end
9
+
10
+ def add_project
11
+ api_key = 'xyz123'
12
+ FakeCopyTunerApp.add_project(api_key)
13
+ end
14
+
15
+ def build_client_with_project(config = {})
16
+ project = add_project
17
+ config[:api_key] = project.api_key
18
+ build_client(config)
19
+ end
20
+
21
+ describe 'opening a connection' do
22
+ let(:config) { CopyTunerClient::Configuration.new }
23
+ let(:http) { Net::HTTP.new(config.host, config.port) }
24
+
25
+ before do
26
+ Net::HTTP.stubs(:new => http)
27
+ end
28
+
29
+ it 'should timeout when connecting' do
30
+ project = add_project
31
+ client = build_client(:api_key => project.api_key, :http_open_timeout => 4)
32
+ client.download { |ignore| }
33
+ http.open_timeout.should == 4
34
+ end
35
+
36
+ it 'should timeout when reading' do
37
+ project = add_project
38
+ client = build_client(:api_key => project.api_key, :http_read_timeout => 4)
39
+ client.download { |ignore| }
40
+ http.read_timeout.should == 4
41
+ end
42
+
43
+ it 'uses verified ssl when secure' do
44
+ project = add_project
45
+ client = build_client(:api_key => project.api_key, :secure => true)
46
+ client.download { |ignore| }
47
+ http.use_ssl?.should == true
48
+ http.verify_mode.should == OpenSSL::SSL::VERIFY_PEER
49
+ end
50
+
51
+ it 'does not use ssl when insecure' do
52
+ project = add_project
53
+ client = build_client(:api_key => project.api_key, :secure => false)
54
+ client.download { |ignore| }
55
+ http.use_ssl?.should == false
56
+ end
57
+
58
+ it 'wraps HTTP errors with ConnectionError' do
59
+ errors = [
60
+ Timeout::Error.new,
61
+ Errno::EINVAL.new,
62
+ Errno::ECONNRESET.new,
63
+ EOFError.new,
64
+ Net::HTTPBadResponse.new,
65
+ Net::HTTPHeaderSyntaxError.new,
66
+ Net::ProtocolError.new,
67
+ SocketError.new,
68
+ OpenSSL::SSL::SSLError.new,
69
+ Errno::ECONNREFUSED.new
70
+ ]
71
+
72
+ errors.each do |original_error|
73
+ http.stubs(:request).raises(original_error)
74
+ client = build_client_with_project
75
+ expect { client.download { |ignore| } }.
76
+ to raise_error(CopyTunerClient::ConnectionError) { |error|
77
+ error.message.
78
+ should == "#{original_error.class.name}: #{original_error.message}"
79
+ }
80
+ end
81
+ end
82
+
83
+ it 'handles 500 errors from downloads with ConnectionError' do
84
+ client = build_client(:api_key => 'raise_error')
85
+ expect { client.download { |ignore| } }.
86
+ to raise_error(CopyTunerClient::ConnectionError)
87
+ end
88
+
89
+ it 'handles 500 errors from uploads with ConnectionError' do
90
+ client = build_client(:api_key => 'raise_error')
91
+ expect { client.upload({}) }.to raise_error(CopyTunerClient::ConnectionError)
92
+ end
93
+
94
+ it 'handles 404 errors from downloads with ConnectionError' do
95
+ client = build_client(:api_key => 'bogus')
96
+ expect { client.download { |ignore| } }.
97
+ to raise_error(CopyTunerClient::InvalidApiKey)
98
+ end
99
+
100
+ it 'handles 404 errors from uploads with ConnectionError' do
101
+ client = build_client(:api_key => 'bogus')
102
+ expect { client.upload({}) }.to raise_error(CopyTunerClient::InvalidApiKey)
103
+ end
104
+ end
105
+
106
+ it 'downloads published blurbs for an existing project' do
107
+ project = add_project
108
+ project.update({
109
+ 'draft' => {
110
+ 'key.one' => 'unexpected one',
111
+ 'key.three' => 'unexpected three'
112
+ },
113
+ 'published' => {
114
+ 'key.one' => 'expected one',
115
+ 'key.two' => 'expected two'
116
+ }
117
+ })
118
+ client = build_client(:api_key => project.api_key, :public => true)
119
+ blurbs = nil
120
+
121
+ client.download { |yielded| blurbs = yielded }
122
+
123
+ blurbs.should == {
124
+ 'key.one' => 'expected one',
125
+ 'key.two' => 'expected two'
126
+ }
127
+ end
128
+
129
+ it 'logs that it performed a download' do
130
+ logger = FakeLogger.new
131
+ client = build_client_with_project(:logger => logger)
132
+ client.download { |ignore| }
133
+ logger.should have_entry(:info, 'Downloaded translations')
134
+ end
135
+
136
+ it 'downloads draft blurbs for an existing project' do
137
+ project = add_project
138
+ project.update({
139
+ 'draft' => {
140
+ 'key.one' => 'expected one',
141
+ 'key.two' => 'expected two'
142
+ },
143
+ 'published' => {
144
+ 'key.one' => 'unexpected one',
145
+ 'key.three' => 'unexpected three'
146
+ }
147
+ })
148
+ client = build_client(:api_key => project.api_key, :public => false)
149
+ blurbs = nil
150
+
151
+ client.download { |yielded| blurbs = yielded }
152
+
153
+ blurbs.should == {
154
+ 'key.one' => 'expected one',
155
+ 'key.two' => 'expected two'
156
+ }
157
+ end
158
+
159
+ it "handles a 304 response when downloading" do
160
+ project = add_project
161
+ project.update('draft' => { 'key.one' => "expected one" })
162
+ logger = FakeLogger.new
163
+ client = build_client(:api_key => project.api_key,
164
+ :public => false,
165
+ :logger => logger)
166
+ yields = 0
167
+
168
+ 2.times do
169
+ client.download { |ignore| yields += 1 }
170
+ end
171
+
172
+ yields.should == 1
173
+ logger.should have_entry(:info, "No new translations")
174
+ end
175
+
176
+ it "uploads defaults for missing blurbs in an existing project" do
177
+ project = add_project
178
+
179
+ blurbs = {
180
+ 'key.one' => 'expected one',
181
+ 'key.two' => 'expected two'
182
+ }
183
+
184
+ client = build_client(:api_key => project.api_key, :public => true)
185
+ client.upload(blurbs)
186
+
187
+ project.reload.draft.should == blurbs
188
+ end
189
+
190
+ it "logs that it performed an upload" do
191
+ logger = FakeLogger.new
192
+ client = build_client_with_project(:logger => logger)
193
+ client.upload({})
194
+ logger.should have_entry(:info, "Uploaded missing translations")
195
+ end
196
+
197
+ it "deploys from the top-level constant" do
198
+ client = build_client
199
+ CopyTunerClient.configure do |config|
200
+ config.client = client
201
+ end
202
+ client.stubs(:deploy)
203
+
204
+ CopyTunerClient.deploy
205
+
206
+ client.should have_received(:deploy)
207
+ end
208
+
209
+ it "deploys" do
210
+ project = add_project
211
+ project.update({
212
+ 'draft' => {
213
+ 'key.one' => "expected one",
214
+ 'key.two' => "expected two"
215
+ },
216
+ 'published' => {
217
+ 'key.one' => "unexpected one",
218
+ 'key.two' => "unexpected one",
219
+ }
220
+ })
221
+ logger = FakeLogger.new
222
+ client = build_client(:api_key => project.api_key, :logger => logger)
223
+
224
+ client.deploy
225
+
226
+ project.reload.published.should == {
227
+ 'key.one' => "expected one",
228
+ 'key.two' => "expected two"
229
+ }
230
+ logger.should have_entry(:info, "Deployed")
231
+ end
232
+
233
+ it "handles deploy errors" do
234
+ expect { build_client.deploy }.to raise_error(CopyTunerClient::InvalidApiKey)
235
+ end
236
+ end
@@ -0,0 +1,305 @@
1
+ require 'spec_helper'
2
+
3
+ describe CopyTunerClient::Configuration do
4
+ RSpec::Matchers.define :have_config_option do |option|
5
+ match do |config|
6
+ config.should respond_to(option)
7
+
8
+ if instance_variables.include?(:'@default')
9
+ config.send(option).should == @default
10
+ end
11
+
12
+ if @overridable
13
+ value = 'a value'
14
+ config.send(:"#{option}=", value)
15
+ config.send(option).should == value
16
+ end
17
+ end
18
+
19
+ chain :default do |default|
20
+ @default = default
21
+ end
22
+
23
+ chain :overridable do
24
+ @overridable = true
25
+ end
26
+ end
27
+
28
+ it { should have_config_option(:proxy_host).overridable.default(nil) }
29
+ it { should have_config_option(:proxy_port).overridable.default(nil) }
30
+ it { should have_config_option(:proxy_user).overridable.default(nil) }
31
+ it { should have_config_option(:proxy_pass).overridable.default(nil) }
32
+ it { should have_config_option(:environment_name).overridable.default(nil) }
33
+ it { should have_config_option(:client_version).overridable.default(CopyTunerClient::VERSION) }
34
+ it { should have_config_option(:client_name).overridable.default('CopyTuner Client') }
35
+ it { should have_config_option(:client_url).overridable.default('https://rubygems.org/gems/copy_tuner_client') }
36
+ it { should have_config_option(:secure).overridable.default(false) }
37
+ it { should have_config_option(:host).overridable.default('copy-tuner.com') }
38
+ it { should have_config_option(:http_open_timeout).overridable.default(2) }
39
+ it { should have_config_option(:http_read_timeout).overridable.default(5) }
40
+ it { should have_config_option(:port).overridable }
41
+ it { should have_config_option(:development_environments).overridable }
42
+ it { should have_config_option(:api_key).overridable }
43
+ it { should have_config_option(:polling_delay).overridable.default(300) }
44
+ it { should have_config_option(:framework).overridable }
45
+ it { should have_config_option(:middleware).overridable }
46
+ it { should have_config_option(:client).overridable }
47
+ it { should have_config_option(:cache).overridable }
48
+
49
+ it 'should provide default values for secure connections' do
50
+ config = CopyTunerClient::Configuration.new
51
+ config.secure = true
52
+ config.port.should == 443
53
+ config.protocol.should == 'https'
54
+ end
55
+
56
+ it 'should provide default values for insecure connections' do
57
+ config = CopyTunerClient::Configuration.new
58
+ config.secure = false
59
+ config.port.should == 80
60
+ config.protocol.should == 'http'
61
+ end
62
+
63
+ it 'should not cache inferred ports' do
64
+ config = CopyTunerClient::Configuration.new
65
+ config.secure = false
66
+ config.port
67
+ config.secure = true
68
+ config.port.should == 443
69
+ end
70
+
71
+ it 'should act like a hash' do
72
+ config = CopyTunerClient::Configuration.new
73
+ hash = config.to_hash
74
+
75
+ [:api_key, :environment_name, :host, :http_open_timeout,
76
+ :http_read_timeout, :client_name, :client_url, :client_version, :port,
77
+ :protocol, :proxy_host, :proxy_pass, :proxy_port, :proxy_user, :secure,
78
+ :development_environments, :logger, :framework, :ca_file].each do |option|
79
+ hash[option].should == config[option]
80
+ end
81
+
82
+ hash[:public].should == config.public?
83
+ end
84
+
85
+ it 'should be mergable' do
86
+ config = CopyTunerClient::Configuration.new
87
+ hash = config.to_hash
88
+ config.merge(:key => 'value').should == hash.merge(:key => 'value')
89
+ end
90
+
91
+ it 'should use development and staging as development environments by default' do
92
+ config = CopyTunerClient::Configuration.new
93
+ config.development_environments.should =~ %w(development staging)
94
+ end
95
+
96
+ it 'should use test and cucumber as test environments by default' do
97
+ config = CopyTunerClient::Configuration.new
98
+ config.test_environments.should =~ %w(test cucumber)
99
+ end
100
+
101
+ it 'should be test in a test environment' do
102
+ config = CopyTunerClient::Configuration.new
103
+ config.test_environments = %w(test)
104
+ config.environment_name = 'test'
105
+ config.should be_test
106
+ end
107
+
108
+ it 'should be public in a public environment' do
109
+ config = CopyTunerClient::Configuration.new
110
+ config.development_environments = %w(development)
111
+ config.environment_name = 'production'
112
+ config.should be_public
113
+ config.should_not be_development
114
+ end
115
+
116
+ it 'should be development in a development environment' do
117
+ config = CopyTunerClient::Configuration.new
118
+ config.development_environments = %w(staging)
119
+ config.environment_name = 'staging'
120
+ config.should be_development
121
+ config.should_not be_public
122
+ end
123
+
124
+ it 'should be public without an environment name' do
125
+ config = CopyTunerClient::Configuration.new
126
+ config.should be_public
127
+ end
128
+
129
+ it 'should yield and save a configuration when configuring' do
130
+ yielded_configuration = nil
131
+
132
+ CopyTunerClient.configure(false) do |config|
133
+ yielded_configuration = config
134
+ end
135
+
136
+ yielded_configuration.should be_kind_of(CopyTunerClient::Configuration)
137
+ CopyTunerClient.configuration.should == yielded_configuration
138
+ end
139
+
140
+ it 'does not apply the configuration when asked not to' do
141
+ logger = FakeLogger.new
142
+ CopyTunerClient.configure(false) { |config| config.logger = logger }
143
+ CopyTunerClient.configuration.should_not be_applied
144
+ logger.entries[:info].should be_empty
145
+ end
146
+
147
+ it 'should not remove existing config options when configuring twice' do
148
+ first_config = nil
149
+
150
+ CopyTunerClient.configure(false) do |config|
151
+ first_config = config
152
+ end
153
+
154
+ CopyTunerClient.configure(false) do |config|
155
+ config.should == first_config
156
+ end
157
+ end
158
+
159
+ it 'starts out unapplied' do
160
+ CopyTunerClient::Configuration.new.should_not be_applied
161
+ end
162
+
163
+ it 'logs to $stdout by default' do
164
+ logger = FakeLogger.new
165
+ Logger.stubs :new => logger
166
+ config = CopyTunerClient::Configuration.new
167
+ Logger.should have_received(:new).with($stdout)
168
+ config.logger.original_logger.should == logger
169
+ end
170
+
171
+ it 'generates environment info without a framework' do
172
+ subject.environment_name = 'production'
173
+ subject.environment_info.should == "[Ruby: #{RUBY_VERSION}] [Env: production]"
174
+ end
175
+
176
+ it 'generates environment info with a framework' do
177
+ subject.environment_name = 'production'
178
+ subject.framework = 'Sinatra: 1.0.0'
179
+ subject.environment_info.
180
+ should == "[Ruby: #{RUBY_VERSION}] [Sinatra: 1.0.0] [Env: production]"
181
+ end
182
+
183
+ it 'prefixes log entries' do
184
+ logger = FakeLogger.new
185
+ config = CopyTunerClient::Configuration.new
186
+
187
+ config.logger = logger
188
+
189
+ prefixed_logger = config.logger
190
+ prefixed_logger.should be_a(CopyTunerClient::PrefixedLogger)
191
+ prefixed_logger.original_logger.should == logger
192
+ end
193
+ end
194
+
195
+ share_examples_for 'applied configuration' do
196
+ subject { CopyTunerClient::Configuration.new }
197
+ let(:backend) { stub('i18n-backend') }
198
+ let(:cache) { stub('cache') }
199
+ let(:client) { stub('client') }
200
+ let(:logger) { FakeLogger.new }
201
+ let(:poller) { stub('poller') }
202
+ let(:process_guard) { stub('process_guard', :start => nil) }
203
+
204
+ before do
205
+ CopyTunerClient::I18nBackend.stubs :new => backend
206
+ CopyTunerClient::Client.stubs :new => client
207
+ CopyTunerClient::Cache.stubs :new => cache
208
+ CopyTunerClient::Poller.stubs :new => poller
209
+ CopyTunerClient::ProcessGuard.stubs :new => process_guard
210
+ subject.logger = logger
211
+ apply
212
+ end
213
+
214
+ it { should be_applied }
215
+
216
+ it 'builds and assigns an I18n backend' do
217
+ CopyTunerClient::I18nBackend.should have_received(:new).with(cache)
218
+ I18n.backend.should == backend
219
+ end
220
+
221
+ it 'builds and assigns a poller' do
222
+ CopyTunerClient::Poller.should have_received(:new).with(cache, subject.to_hash)
223
+ end
224
+
225
+ it 'builds a process guard' do
226
+ CopyTunerClient::ProcessGuard.should have_received(:new).
227
+ with(cache, poller, subject.to_hash)
228
+ end
229
+
230
+ it 'logs that it is ready' do
231
+ logger.should have_entry(:info, "Client #{CopyTunerClient::VERSION} ready")
232
+ end
233
+
234
+ it 'logs environment info' do
235
+ logger.should have_entry(:info, "Environment Info: #{subject.environment_info}")
236
+ end
237
+ end
238
+
239
+ describe CopyTunerClient::Configuration, 'applied when testing' do
240
+ it_should_behave_like 'applied configuration' do
241
+ it 'does not start the process guard' do
242
+ process_guard.should have_received(:start).never
243
+ end
244
+ end
245
+
246
+ def apply
247
+ subject.environment_name = 'test'
248
+ subject.apply
249
+ end
250
+ end
251
+
252
+ describe CopyTunerClient::Configuration, 'applied when not testing' do
253
+ it_should_behave_like 'applied configuration' do
254
+ it 'starts the process guard' do
255
+ process_guard.should have_received(:start)
256
+ end
257
+ end
258
+
259
+ def apply
260
+ subject.environment_name = 'development'
261
+ subject.apply
262
+ end
263
+ end
264
+
265
+ describe CopyTunerClient::Configuration, 'applied when developing with middleware' do
266
+ it_should_behave_like 'applied configuration' do
267
+ it 'adds the sync middleware' do
268
+ middleware.should include(CopyTunerClient::RequestSync)
269
+ end
270
+ end
271
+
272
+ let(:middleware) { MiddlewareStack.new }
273
+
274
+ def apply
275
+ subject.middleware = middleware
276
+ subject.environment_name = 'development'
277
+ subject.apply
278
+ end
279
+ end
280
+
281
+ describe CopyTunerClient::Configuration, 'applied when developing without middleware' do
282
+ it_should_behave_like 'applied configuration'
283
+
284
+ def apply
285
+ subject.middleware = nil
286
+ subject.environment_name = 'development'
287
+ subject.apply
288
+ end
289
+ end
290
+
291
+ describe CopyTunerClient::Configuration, 'applied with middleware when not developing' do
292
+ it_should_behave_like 'applied configuration'
293
+
294
+ let(:middleware) { MiddlewareStack.new }
295
+
296
+ def apply
297
+ subject.middleware = middleware
298
+ subject.environment_name = 'test'
299
+ subject.apply
300
+ end
301
+
302
+ it 'does not add the sync middleware' do
303
+ middleware.should_not include(CopyTunerClient::RequestSync)
304
+ end
305
+ end