copycopter_client 1.0.0.beta1
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.
- data/MIT-LICENSE +20 -0
- data/README.textile +71 -0
- data/Rakefile +38 -0
- data/features/rails.feature +267 -0
- data/features/step_definitions/copycopter_server_steps.rb +65 -0
- data/features/step_definitions/rails_steps.rb +134 -0
- data/features/support/env.rb +8 -0
- data/features/support/rails_server.rb +118 -0
- data/init.rb +2 -0
- data/lib/copycopter_client/client.rb +117 -0
- data/lib/copycopter_client/configuration.rb +197 -0
- data/lib/copycopter_client/errors.rb +13 -0
- data/lib/copycopter_client/helper.rb +40 -0
- data/lib/copycopter_client/i18n_backend.rb +100 -0
- data/lib/copycopter_client/prefixed_logger.rb +41 -0
- data/lib/copycopter_client/rails.rb +31 -0
- data/lib/copycopter_client/railtie.rb +13 -0
- data/lib/copycopter_client/sync.rb +145 -0
- data/lib/copycopter_client/version.rb +8 -0
- data/lib/copycopter_client.rb +58 -0
- data/lib/tasks/copycopter_client_tasks.rake +6 -0
- data/spec/copycopter_client/client_spec.rb +208 -0
- data/spec/copycopter_client/configuration_spec.rb +252 -0
- data/spec/copycopter_client/helper_spec.rb +86 -0
- data/spec/copycopter_client/i18n_backend_spec.rb +133 -0
- data/spec/copycopter_client/prefixed_logger_spec.rb +25 -0
- data/spec/copycopter_client/sync_spec.rb +295 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/client_spec_helpers.rb +9 -0
- data/spec/support/defines_constants.rb +38 -0
- data/spec/support/fake_client.rb +42 -0
- data/spec/support/fake_copycopter_app.rb +136 -0
- data/spec/support/fake_html_safe_string.rb +20 -0
- data/spec/support/fake_logger.rb +68 -0
- data/spec/support/fake_passenger.rb +27 -0
- data/spec/support/fake_unicorn.rb +14 -0
- metadata +121 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'copycopter_client/client'
|
3
|
+
|
4
|
+
module CopycopterClient
|
5
|
+
# Manages synchronization of copy between {I18nBackend} and {Client}. Acts
|
6
|
+
# like a Hash. Applications using the client will not need to interact with
|
7
|
+
# this class directly.
|
8
|
+
#
|
9
|
+
# Responsible for:
|
10
|
+
# * Starting and running the background polling thread
|
11
|
+
# * Locking down access to data used by both threads
|
12
|
+
class Sync
|
13
|
+
# Usually instantiated when {Configuration#apply} is invoked.
|
14
|
+
# @param client [Client] the client used to fetch and upload data
|
15
|
+
# @param options [Hash]
|
16
|
+
# @option options [Fixnum] :polling_delay the number of seconds in between each synchronization with the server
|
17
|
+
# @option options [Logger] :logger where errors should be logged
|
18
|
+
def initialize(client, options)
|
19
|
+
@client = client
|
20
|
+
@blurbs = {}
|
21
|
+
@polling_delay = options[:polling_delay]
|
22
|
+
@stop = false
|
23
|
+
@queued = {}
|
24
|
+
@mutex = Mutex.new
|
25
|
+
@logger = options[:logger]
|
26
|
+
@pending = false
|
27
|
+
end
|
28
|
+
|
29
|
+
# Starts the polling thread. The polling thread doesn't run in test environments.
|
30
|
+
#
|
31
|
+
# If this sync was created from a master spawner (as in the case for
|
32
|
+
# phusion passenger), it will instead register after fork hooks so that the
|
33
|
+
# poller starts in each spawned process.
|
34
|
+
def start
|
35
|
+
if spawner?
|
36
|
+
register_spawn_hooks
|
37
|
+
else
|
38
|
+
logger.info("Starting poller")
|
39
|
+
@pending = true
|
40
|
+
at_exit { sync }
|
41
|
+
Thread.new { poll }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Stops the polling thread after the next run.
|
46
|
+
def stop
|
47
|
+
@stop = true
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns content for the given blurb.
|
51
|
+
# @param key [String] the key of the desired blurb
|
52
|
+
# @return [String] the contents of the blurb
|
53
|
+
def [](key)
|
54
|
+
lock { @blurbs[key] }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Sets content for the given blurb. The content will be pushed to the
|
58
|
+
# server on the next poll.
|
59
|
+
# @param key [String] the key of the blurb to update
|
60
|
+
# @param value [String] the new contents of the blurb
|
61
|
+
def []=(key, value)
|
62
|
+
lock { @queued[key] = value }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Keys for all blurbs stored on the server.
|
66
|
+
# @return [Array<String>] keys
|
67
|
+
def keys
|
68
|
+
lock { @blurbs.keys }
|
69
|
+
end
|
70
|
+
|
71
|
+
# Waits until the first download has finished.
|
72
|
+
def wait_for_download
|
73
|
+
if @pending
|
74
|
+
logger.info("Waiting for first sync")
|
75
|
+
while @pending
|
76
|
+
sleep(0.1)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
attr_reader :client, :polling_delay, :logger
|
84
|
+
|
85
|
+
def poll
|
86
|
+
until @stop
|
87
|
+
sync
|
88
|
+
sleep(polling_delay)
|
89
|
+
end
|
90
|
+
rescue InvalidApiKey => error
|
91
|
+
logger.error(error.message)
|
92
|
+
end
|
93
|
+
|
94
|
+
def sync
|
95
|
+
begin
|
96
|
+
downloaded_blurbs = client.download
|
97
|
+
lock { @blurbs = downloaded_blurbs }
|
98
|
+
with_queued_changes do |queued|
|
99
|
+
client.upload(queued)
|
100
|
+
end
|
101
|
+
rescue ConnectionError => error
|
102
|
+
logger.error(error.message)
|
103
|
+
end
|
104
|
+
ensure
|
105
|
+
@pending = false
|
106
|
+
end
|
107
|
+
|
108
|
+
def with_queued_changes
|
109
|
+
changes_to_push = nil
|
110
|
+
lock do
|
111
|
+
unless @queued.empty?
|
112
|
+
changes_to_push = @queued
|
113
|
+
@queued = {}
|
114
|
+
end
|
115
|
+
end
|
116
|
+
yield(changes_to_push) if changes_to_push
|
117
|
+
end
|
118
|
+
|
119
|
+
def lock(&block)
|
120
|
+
@mutex.synchronize(&block)
|
121
|
+
end
|
122
|
+
|
123
|
+
def spawner?
|
124
|
+
$0.include?("ApplicationSpawner") || $0 =~ /unicorn.*master/
|
125
|
+
end
|
126
|
+
|
127
|
+
def register_spawn_hooks
|
128
|
+
if defined?(PhusionPassenger)
|
129
|
+
logger.info("Registered Phusion Passenger fork hook")
|
130
|
+
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
131
|
+
start
|
132
|
+
end
|
133
|
+
elsif defined?(Unicorn::HttpServer)
|
134
|
+
logger.info("Registered Unicorn fork hook")
|
135
|
+
Unicorn::HttpServer.class_eval do
|
136
|
+
alias_method :worker_loop_without_copycopter, :worker_loop
|
137
|
+
def worker_loop(worker)
|
138
|
+
CopycopterClient.start_sync
|
139
|
+
worker_loop_without_copycopter(worker)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'copycopter_client/version'
|
2
|
+
require 'copycopter_client/configuration'
|
3
|
+
|
4
|
+
# Top-level interface to the Copycopter client.
|
5
|
+
#
|
6
|
+
# Most applications should only need to use the {.configure}
|
7
|
+
# method, which will setup all the pieces and begin synchronization when
|
8
|
+
# appropriate.
|
9
|
+
module CopycopterClient
|
10
|
+
class << self
|
11
|
+
# @return [Client] instance used to communicate with the Copycopter server.
|
12
|
+
# This is set when {.configure} is called.
|
13
|
+
attr_accessor :client
|
14
|
+
|
15
|
+
# @return [Configuration] current client configuration
|
16
|
+
# Must act like a hash and return sensible values for all Copycopter
|
17
|
+
# configuration options. Usually set when {.configure} is called.
|
18
|
+
attr_accessor :configuration
|
19
|
+
|
20
|
+
# @return [Sync] instance used to synchronize changes.
|
21
|
+
# This is set when {.configure} is called.
|
22
|
+
attr_accessor :sync
|
23
|
+
end
|
24
|
+
|
25
|
+
# Issues a new deploy, marking all draft blurbs as published.
|
26
|
+
# This is called when the copycopter:deploy rake task is invoked.
|
27
|
+
def self.deploy
|
28
|
+
client.deploy
|
29
|
+
end
|
30
|
+
|
31
|
+
# Starts the polling process.
|
32
|
+
# This is called from Unicorn worker processes.
|
33
|
+
def self.start_sync
|
34
|
+
sync.start
|
35
|
+
end
|
36
|
+
|
37
|
+
# Call this method to modify defaults in your initializers.
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# CopycopterClient.configure do |config|
|
41
|
+
# config.api_key = '1234567890abcdef'
|
42
|
+
# config.secure = false
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# @param apply [Boolean] (internal) whether the configuration should be applied yet.
|
46
|
+
#
|
47
|
+
# @yield [Configuration] the configuration to be modified
|
48
|
+
def self.configure(apply = true)
|
49
|
+
self.configuration ||= Configuration.new
|
50
|
+
yield(configuration)
|
51
|
+
configuration.apply if apply
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if defined?(Rails)
|
56
|
+
require 'copycopter_client/rails'
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CopycopterClient do
|
4
|
+
def build_client(config = {})
|
5
|
+
config[:logger] ||= FakeLogger.new
|
6
|
+
default_config = CopycopterClient::Configuration.new.to_hash
|
7
|
+
CopycopterClient::Client.new(default_config.update(config))
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_project
|
11
|
+
api_key = 'xyz123'
|
12
|
+
FakeCopycopterApp.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) { CopycopterClient::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
|
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
|
40
|
+
http.read_timeout.should == 4
|
41
|
+
end
|
42
|
+
|
43
|
+
it "uses ssl when secure" do
|
44
|
+
project = add_project
|
45
|
+
client = build_client(:api_key => project.api_key, :secure => true)
|
46
|
+
client.download
|
47
|
+
http.use_ssl.should == true
|
48
|
+
end
|
49
|
+
|
50
|
+
it "doesn't use ssl when insecure" do
|
51
|
+
project = add_project
|
52
|
+
client = build_client(:api_key => project.api_key, :secure => false)
|
53
|
+
client.download
|
54
|
+
http.use_ssl.should == false
|
55
|
+
end
|
56
|
+
|
57
|
+
it "wraps HTTP errors with ConnectionError" do
|
58
|
+
errors = [
|
59
|
+
Timeout::Error.new,
|
60
|
+
Errno::EINVAL.new,
|
61
|
+
Errno::ECONNRESET.new,
|
62
|
+
EOFError.new,
|
63
|
+
Net::HTTPBadResponse.new,
|
64
|
+
Net::HTTPHeaderSyntaxError.new,
|
65
|
+
Net::ProtocolError.new
|
66
|
+
]
|
67
|
+
|
68
|
+
errors.each do |original_error|
|
69
|
+
http.stubs(:get).raises(original_error)
|
70
|
+
client = build_client_with_project
|
71
|
+
expect { client.download }.
|
72
|
+
to raise_error(CopycopterClient::ConnectionError) { |error|
|
73
|
+
error.message.
|
74
|
+
should == "#{original_error.class.name}: #{original_error.message}"
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "handles 500 errors from downloads with ConnectionError" do
|
80
|
+
client = build_client(:api_key => 'raise_error')
|
81
|
+
expect { client.download }.to raise_error(CopycopterClient::ConnectionError)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "handles 500 errors from uploads with ConnectionError" do
|
85
|
+
client = build_client(:api_key => 'raise_error')
|
86
|
+
expect { client.upload({}) }.to raise_error(CopycopterClient::ConnectionError)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "handles 404 errors from downloads with ConnectionError" do
|
90
|
+
client = build_client(:api_key => 'bogus')
|
91
|
+
expect { client.download }.to raise_error(CopycopterClient::InvalidApiKey)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "handles 404 errors from uploads with ConnectionError" do
|
95
|
+
client = build_client(:api_key => 'bogus')
|
96
|
+
expect { client.upload({}) }.to raise_error(CopycopterClient::InvalidApiKey)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it "downloads published blurbs for an existing project" do
|
101
|
+
project = add_project
|
102
|
+
project.update({
|
103
|
+
'draft' => {
|
104
|
+
'key.one' => "unexpected one",
|
105
|
+
'key.three' => "unexpected three"
|
106
|
+
},
|
107
|
+
'published' => {
|
108
|
+
'key.one' => "expected one",
|
109
|
+
'key.two' => "expected two"
|
110
|
+
}
|
111
|
+
})
|
112
|
+
|
113
|
+
blurbs = build_client(:api_key => project.api_key, :public => true).download
|
114
|
+
|
115
|
+
blurbs.should == {
|
116
|
+
'key.one' => 'expected one',
|
117
|
+
'key.two' => 'expected two'
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
it "logs that it performed a download" do
|
122
|
+
logger = FakeLogger.new
|
123
|
+
client = build_client_with_project(:logger => logger)
|
124
|
+
client.download
|
125
|
+
logger.should have_entry(:info, "Downloaded translations")
|
126
|
+
end
|
127
|
+
|
128
|
+
it "downloads draft blurbs for an existing project" do
|
129
|
+
project = add_project
|
130
|
+
project.update({
|
131
|
+
'draft' => {
|
132
|
+
'key.one' => "expected one",
|
133
|
+
'key.two' => "expected two"
|
134
|
+
},
|
135
|
+
'published' => {
|
136
|
+
'key.one' => "unexpected one",
|
137
|
+
'key.three' => "unexpected three"
|
138
|
+
}
|
139
|
+
})
|
140
|
+
|
141
|
+
blurbs = build_client(:api_key => project.api_key, :public => false).download
|
142
|
+
|
143
|
+
blurbs.should == {
|
144
|
+
'key.one' => 'expected one',
|
145
|
+
'key.two' => 'expected two'
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
it "uploads defaults for missing blurbs in an existing project" do
|
150
|
+
project = add_project
|
151
|
+
|
152
|
+
blurbs = {
|
153
|
+
'key.one' => 'expected one',
|
154
|
+
'key.two' => 'expected two'
|
155
|
+
}
|
156
|
+
|
157
|
+
client = build_client(:api_key => project.api_key, :public => true)
|
158
|
+
client.upload(blurbs)
|
159
|
+
|
160
|
+
project.reload.draft.should == blurbs
|
161
|
+
end
|
162
|
+
|
163
|
+
it "logs that it performed an upload" do
|
164
|
+
logger = FakeLogger.new
|
165
|
+
client = build_client_with_project(:logger => logger)
|
166
|
+
client.upload({})
|
167
|
+
logger.should have_entry(:info, "Uploaded missing translations")
|
168
|
+
end
|
169
|
+
|
170
|
+
it "deploys from the top-level constant" do
|
171
|
+
client = build_client
|
172
|
+
CopycopterClient.client = client
|
173
|
+
client.stubs(:deploy)
|
174
|
+
|
175
|
+
CopycopterClient.deploy
|
176
|
+
|
177
|
+
client.should have_received(:deploy)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "deploys" do
|
181
|
+
project = add_project
|
182
|
+
project.update({
|
183
|
+
'draft' => {
|
184
|
+
'key.one' => "expected one",
|
185
|
+
'key.two' => "expected two"
|
186
|
+
},
|
187
|
+
'published' => {
|
188
|
+
'key.one' => "unexpected one",
|
189
|
+
'key.two' => "unexpected one",
|
190
|
+
}
|
191
|
+
})
|
192
|
+
logger = FakeLogger.new
|
193
|
+
client = build_client(:api_key => project.api_key, :logger => logger)
|
194
|
+
|
195
|
+
client.deploy
|
196
|
+
|
197
|
+
project.reload.published.should == {
|
198
|
+
'key.one' => "expected one",
|
199
|
+
'key.two' => "expected two"
|
200
|
+
}
|
201
|
+
logger.should have_entry(:info, "Deployed")
|
202
|
+
end
|
203
|
+
|
204
|
+
it "handles deploy errors" do
|
205
|
+
expect { build_client.deploy }.to raise_error(CopycopterClient::InvalidApiKey)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
@@ -0,0 +1,252 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CopycopterClient::Configuration do
|
4
|
+
Spec::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(CopycopterClient::VERSION) }
|
34
|
+
it { should have_config_option(:client_name). overridable.default('Copycopter Client') }
|
35
|
+
it { should have_config_option(:client_url). overridable.default('http://copycopter.com') }
|
36
|
+
it { should have_config_option(:secure). overridable.default(false) }
|
37
|
+
it { should have_config_option(:host). overridable.default('copycopter.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(:fallback_backend). overridable }
|
46
|
+
|
47
|
+
it "should provide default values for secure connections" do
|
48
|
+
config = CopycopterClient::Configuration.new
|
49
|
+
config.secure = true
|
50
|
+
config.port.should == 443
|
51
|
+
config.protocol.should == 'https'
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should provide default values for insecure connections" do
|
55
|
+
config = CopycopterClient::Configuration.new
|
56
|
+
config.secure = false
|
57
|
+
config.port.should == 80
|
58
|
+
config.protocol.should == 'http'
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should not cache inferred ports" do
|
62
|
+
config = CopycopterClient::Configuration.new
|
63
|
+
config.secure = false
|
64
|
+
config.port
|
65
|
+
config.secure = true
|
66
|
+
config.port.should == 443
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should act like a hash" do
|
70
|
+
config = CopycopterClient::Configuration.new
|
71
|
+
hash = config.to_hash
|
72
|
+
[:api_key, :environment_name, :host, :http_open_timeout,
|
73
|
+
:http_read_timeout, :client_name, :client_url, :client_version, :port,
|
74
|
+
:protocol, :proxy_host, :proxy_pass, :proxy_port, :proxy_user, :secure,
|
75
|
+
:development_environments, :logger, :framework, :fallback_backend].each do |option|
|
76
|
+
hash[option].should == config[option]
|
77
|
+
end
|
78
|
+
hash[:public].should == config.public?
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should be mergable" do
|
82
|
+
config = CopycopterClient::Configuration.new
|
83
|
+
hash = config.to_hash
|
84
|
+
config.merge(:key => 'value').should == hash.merge(:key => 'value')
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should use development and staging as development environments by default" do
|
88
|
+
config = CopycopterClient::Configuration.new
|
89
|
+
config.development_environments.should =~ %w(development staging)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should use test and cucumber as test environments by default" do
|
93
|
+
config = CopycopterClient::Configuration.new
|
94
|
+
config.test_environments.should =~ %w(test cucumber)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should be test in a test environment" do
|
98
|
+
config = CopycopterClient::Configuration.new
|
99
|
+
config.test_environments = %w(test)
|
100
|
+
config.environment_name = 'test'
|
101
|
+
config.should be_test
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should be public in a public environment" do
|
105
|
+
config = CopycopterClient::Configuration.new
|
106
|
+
config.development_environments = %w(development)
|
107
|
+
config.environment_name = 'production'
|
108
|
+
config.should be_public
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should not be public in a development environment" do
|
112
|
+
config = CopycopterClient::Configuration.new
|
113
|
+
config.development_environments = %w(staging)
|
114
|
+
config.environment_name = 'staging'
|
115
|
+
config.should_not be_public
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should be public without an environment name" do
|
119
|
+
config = CopycopterClient::Configuration.new
|
120
|
+
config.should be_public
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should yield and save a configuration when configuring" do
|
124
|
+
yielded_configuration = nil
|
125
|
+
CopycopterClient.configure(false) do |config|
|
126
|
+
yielded_configuration = config
|
127
|
+
end
|
128
|
+
|
129
|
+
yielded_configuration.should be_kind_of(CopycopterClient::Configuration)
|
130
|
+
CopycopterClient.configuration.should == yielded_configuration
|
131
|
+
end
|
132
|
+
|
133
|
+
it "doesn't apply the configuration when asked not to" do
|
134
|
+
logger = FakeLogger.new
|
135
|
+
CopycopterClient.configure(false) { |config| config.logger = logger }
|
136
|
+
CopycopterClient.configuration.should_not be_applied
|
137
|
+
logger.entries[:info].should be_empty
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should not remove existing config options when configuring twice" do
|
141
|
+
first_config = nil
|
142
|
+
CopycopterClient.configure(false) do |config|
|
143
|
+
first_config = config
|
144
|
+
end
|
145
|
+
CopycopterClient.configure(false) do |config|
|
146
|
+
config.should == first_config
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
it "starts out unapplied" do
|
151
|
+
CopycopterClient::Configuration.new.should_not be_applied
|
152
|
+
end
|
153
|
+
|
154
|
+
it "logs to $stdout by default" do
|
155
|
+
logger = FakeLogger.new
|
156
|
+
Logger.stubs(:new => logger)
|
157
|
+
|
158
|
+
config = CopycopterClient::Configuration.new
|
159
|
+
Logger.should have_received(:new).with($stdout)
|
160
|
+
config.logger.original_logger.should == logger
|
161
|
+
end
|
162
|
+
|
163
|
+
it "generates environment info without a framework" do
|
164
|
+
subject.environment_name = 'production'
|
165
|
+
subject.environment_info.should == "[Ruby: #{RUBY_VERSION}] [Env: production]"
|
166
|
+
end
|
167
|
+
|
168
|
+
it "generates environment info with a framework" do
|
169
|
+
subject.environment_name = 'production'
|
170
|
+
subject.framework = 'Sinatra: 1.0.0'
|
171
|
+
subject.environment_info.
|
172
|
+
should == "[Ruby: #{RUBY_VERSION}] [Sinatra: 1.0.0] [Env: production]"
|
173
|
+
end
|
174
|
+
|
175
|
+
it "prefixes log entries" do
|
176
|
+
logger = FakeLogger.new
|
177
|
+
config = CopycopterClient::Configuration.new
|
178
|
+
|
179
|
+
config.logger = logger
|
180
|
+
|
181
|
+
prefixed_logger = config.logger
|
182
|
+
prefixed_logger.should be_a(CopycopterClient::PrefixedLogger)
|
183
|
+
prefixed_logger.original_logger.should == logger
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
share_examples_for "applied configuration" do
|
188
|
+
let(:backend) { stub('i18n-backend') }
|
189
|
+
let(:sync) { stub('sync', :start => nil) }
|
190
|
+
let(:client) { stub('client') }
|
191
|
+
let(:logger) { FakeLogger.new }
|
192
|
+
subject { CopycopterClient::Configuration.new }
|
193
|
+
|
194
|
+
before do
|
195
|
+
CopycopterClient::I18nBackend.stubs(:new => backend)
|
196
|
+
CopycopterClient::Client.stubs(:new => client)
|
197
|
+
CopycopterClient::Sync.stubs(:new => sync)
|
198
|
+
subject.logger = logger
|
199
|
+
end
|
200
|
+
|
201
|
+
it { should be_applied }
|
202
|
+
|
203
|
+
it "builds and assigns an I18n backend" do
|
204
|
+
CopycopterClient::Client.should have_received(:new).with(subject.to_hash)
|
205
|
+
CopycopterClient::Sync.should have_received(:new).with(client, subject.to_hash)
|
206
|
+
CopycopterClient::I18nBackend.should have_received(:new).with(sync, subject.to_hash)
|
207
|
+
I18n.backend.should == backend
|
208
|
+
end
|
209
|
+
|
210
|
+
it "logs that it's ready" do
|
211
|
+
logger.should have_entry(:info, "Client #{CopycopterClient::VERSION} ready")
|
212
|
+
end
|
213
|
+
|
214
|
+
it "logs environment info" do
|
215
|
+
logger.should have_entry(:info, "Environment Info: #{subject.environment_info}")
|
216
|
+
end
|
217
|
+
|
218
|
+
it "stores the client" do
|
219
|
+
CopycopterClient.client.should == client
|
220
|
+
end
|
221
|
+
|
222
|
+
it "stores the sync" do
|
223
|
+
CopycopterClient.sync.should == sync
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe CopycopterClient::Configuration, "applied when testing" do
|
228
|
+
it_should_behave_like "applied configuration"
|
229
|
+
|
230
|
+
before do
|
231
|
+
subject.environment_name = 'test'
|
232
|
+
subject.apply
|
233
|
+
end
|
234
|
+
|
235
|
+
it "doesn't start sync" do
|
236
|
+
sync.should have_received(:start).never
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe CopycopterClient::Configuration, "applied when not testing" do
|
241
|
+
it_should_behave_like "applied configuration"
|
242
|
+
|
243
|
+
before do
|
244
|
+
subject.environment_name = 'development'
|
245
|
+
subject.apply
|
246
|
+
end
|
247
|
+
|
248
|
+
it "starts sync" do
|
249
|
+
sync.should have_received(:start)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|