copy_tuner_client 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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