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.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/Appraisals +15 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +161 -0
- data/README.md +4 -0
- data/Rakefile +28 -0
- data/copy_tuner_client.gemspec +33 -0
- data/features/rails.feature +270 -0
- data/features/step_definitions/copycopter_server_steps.rb +64 -0
- data/features/step_definitions/rails_steps.rb +172 -0
- data/features/support/env.rb +11 -0
- data/features/support/rails_server.rb +124 -0
- data/gemfiles/2.3.gemfile +7 -0
- data/gemfiles/2.3.gemfile.lock +105 -0
- data/gemfiles/3.0.gemfile +7 -0
- data/gemfiles/3.0.gemfile.lock +147 -0
- data/gemfiles/3.1.gemfile +11 -0
- data/gemfiles/3.1.gemfile.lock +191 -0
- data/init.rb +1 -0
- data/lib/copy_tuner_client/cache.rb +144 -0
- data/lib/copy_tuner_client/client.rb +136 -0
- data/lib/copy_tuner_client/configuration.rb +224 -0
- data/lib/copy_tuner_client/errors.rb +12 -0
- data/lib/copy_tuner_client/i18n_backend.rb +92 -0
- data/lib/copy_tuner_client/poller.rb +44 -0
- data/lib/copy_tuner_client/prefixed_logger.rb +45 -0
- data/lib/copy_tuner_client/process_guard.rb +92 -0
- data/lib/copy_tuner_client/rails.rb +21 -0
- data/lib/copy_tuner_client/railtie.rb +12 -0
- data/lib/copy_tuner_client/request_sync.rb +39 -0
- data/lib/copy_tuner_client/version.rb +7 -0
- data/lib/copy_tuner_client.rb +75 -0
- data/lib/tasks/copy_tuner_client_tasks.rake +20 -0
- data/spec/copy_tuner_client/cache_spec.rb +273 -0
- data/spec/copy_tuner_client/client_spec.rb +236 -0
- data/spec/copy_tuner_client/configuration_spec.rb +305 -0
- data/spec/copy_tuner_client/i18n_backend_spec.rb +157 -0
- data/spec/copy_tuner_client/poller_spec.rb +108 -0
- data/spec/copy_tuner_client/prefixed_logger_spec.rb +37 -0
- data/spec/copy_tuner_client/process_guard_spec.rb +118 -0
- data/spec/copy_tuner_client/request_sync_spec.rb +47 -0
- data/spec/copy_tuner_client_spec.rb +19 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/client_spec_helpers.rb +8 -0
- data/spec/support/defines_constants.rb +44 -0
- data/spec/support/fake_client.rb +53 -0
- data/spec/support/fake_copy_tuner_app.rb +175 -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_resque_job.rb +18 -0
- data/spec/support/fake_unicorn.rb +13 -0
- data/spec/support/middleware_stack.rb +13 -0
- data/spec/support/writing_cache.rb +17 -0
- data/tmp/projects.json +1 -0
- metadata +389 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CopyTunerClient do
|
4
|
+
|
5
|
+
before do
|
6
|
+
CopyTunerClient.configuration.stubs(:cache => 'cache',
|
7
|
+
:client => 'client')
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'delegates cache to the configuration object' do
|
11
|
+
CopyTunerClient.cache.should == 'cache'
|
12
|
+
CopyTunerClient.configuration.should have_received(:cache).once
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'delegates client to the configuration object' do
|
16
|
+
CopyTunerClient.client.should == 'client'
|
17
|
+
CopyTunerClient.configuration.should have_received(:client).once
|
18
|
+
end
|
19
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec'
|
3
|
+
require 'bourne'
|
4
|
+
require 'sham_rack'
|
5
|
+
require 'webmock/rspec'
|
6
|
+
|
7
|
+
PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
8
|
+
|
9
|
+
$LOAD_PATH << File.join(PROJECT_ROOT, 'lib')
|
10
|
+
|
11
|
+
require 'copy_tuner_client'
|
12
|
+
|
13
|
+
Dir.glob(File.join(PROJECT_ROOT, 'spec', 'support', '**', '*.rb')).each do |file|
|
14
|
+
require(file)
|
15
|
+
end
|
16
|
+
|
17
|
+
WebMock.disable_net_connect!
|
18
|
+
ShamRack.mount FakeCopyTunerApp.new, 'copy-tuner.com', 80
|
19
|
+
|
20
|
+
RSpec.configure do |config|
|
21
|
+
config.include ClientSpecHelpers
|
22
|
+
config.include WebMock::API
|
23
|
+
config.mock_with :mocha
|
24
|
+
|
25
|
+
config.before do
|
26
|
+
FakeCopyTunerApp.reset
|
27
|
+
reset_config
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module DefinesConstants
|
2
|
+
def self.included(example_group)
|
3
|
+
super
|
4
|
+
example_group.class_eval do
|
5
|
+
before { @defined_constants = [] }
|
6
|
+
after { undefine_constants }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def define_class(class_name, base = Object, &block)
|
11
|
+
class_name = class_name.to_s.camelize
|
12
|
+
klass = Class.new(base)
|
13
|
+
define_constant(class_name, klass)
|
14
|
+
klass.class_eval(&block) if block_given?
|
15
|
+
klass
|
16
|
+
end
|
17
|
+
|
18
|
+
def define_constant(path, value)
|
19
|
+
parse_constant(path) do |parent, name|
|
20
|
+
parent.const_set(name, value)
|
21
|
+
end
|
22
|
+
|
23
|
+
@defined_constants << path
|
24
|
+
value
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_constant(path)
|
28
|
+
parent_names = path.split('::')
|
29
|
+
name = parent_names.pop
|
30
|
+
parent = parent_names.inject(Object) do |ref, child_name|
|
31
|
+
ref.const_get(child_name)
|
32
|
+
end
|
33
|
+
yield(parent, name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def undefine_constants
|
37
|
+
@defined_constants.reverse.each do |path|
|
38
|
+
parse_constant(path) do |parent, name|
|
39
|
+
parent.send(:remove_const, name)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class FakeClient
|
2
|
+
def initialize
|
3
|
+
@data = {}
|
4
|
+
@uploaded = {}
|
5
|
+
@uploads = 0
|
6
|
+
@downloads = 0
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :uploaded, :uploads, :downloads
|
10
|
+
attr_accessor :delay, :error
|
11
|
+
|
12
|
+
def []=(key, value)
|
13
|
+
@data[key] = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def download
|
17
|
+
wait_for_delay
|
18
|
+
raise_error_if_present
|
19
|
+
@downloads += 1
|
20
|
+
yield @data.dup
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def upload(data)
|
25
|
+
wait_for_delay
|
26
|
+
raise_error_if_present
|
27
|
+
@uploaded.update(data)
|
28
|
+
@uploads += 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def uploaded?
|
32
|
+
@uploads > 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def downloaded?
|
36
|
+
@downloads > 0
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def wait_for_delay
|
42
|
+
if delay
|
43
|
+
sleep delay
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def raise_error_if_present
|
48
|
+
if error
|
49
|
+
raise error
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'json'
|
3
|
+
require 'thin'
|
4
|
+
|
5
|
+
class FakeCopyTunerApp < Sinatra::Base
|
6
|
+
disable :show_exceptions
|
7
|
+
|
8
|
+
def self.start
|
9
|
+
Thread.new do
|
10
|
+
if ENV['DEBUG']
|
11
|
+
Thin::Logging.debug = true
|
12
|
+
else
|
13
|
+
Thin::Logging.silent = true
|
14
|
+
end
|
15
|
+
|
16
|
+
Rack::Handler::Thin.run self, :Port => port
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.port
|
21
|
+
(ENV['COPY_TUNER_PORT'] || 3002).to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.add_project(api_key)
|
25
|
+
Project.create api_key
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.reset
|
29
|
+
Project.delete_all
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.project(api_key)
|
33
|
+
Project.find api_key
|
34
|
+
end
|
35
|
+
|
36
|
+
def with_project(api_key)
|
37
|
+
if api_key == 'raise_error'
|
38
|
+
halt 500, { :error => 'Blah ha' }.to_json
|
39
|
+
elsif project = Project.find(api_key)
|
40
|
+
yield project
|
41
|
+
else
|
42
|
+
halt 404, { :error => 'No such project' }.to_json
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
get '/api/v2/projects/:api_key/published_blurbs' do |api_key|
|
47
|
+
with_project(api_key) do |project|
|
48
|
+
etag project.etag
|
49
|
+
project.published.to_json
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
get '/api/v2/projects/:api_key/draft_blurbs' do |api_key|
|
54
|
+
with_project(api_key) do |project|
|
55
|
+
etag project.etag
|
56
|
+
project.draft.to_json
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
post '/api/v2/projects/:api_key/draft_blurbs' do |api_key|
|
61
|
+
with_project(api_key) do |project|
|
62
|
+
with_json_data do |data|
|
63
|
+
project.update 'draft' => data
|
64
|
+
201
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def with_json_data
|
70
|
+
if request.content_type == 'application/json'
|
71
|
+
yield JSON.parse(request.body.read)
|
72
|
+
else
|
73
|
+
406
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
post '/api/v2/projects/:api_key/deploys' do |api_key|
|
78
|
+
with_project(api_key) do |project|
|
79
|
+
project.deploy
|
80
|
+
201
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class Project
|
85
|
+
attr_reader :draft, :published, :api_key
|
86
|
+
|
87
|
+
def initialize(attrs)
|
88
|
+
@api_key = attrs['api_key']
|
89
|
+
@draft = attrs['draft'] || {}
|
90
|
+
@etag = attrs['etag'] || 1
|
91
|
+
@published = attrs['published'] || {}
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_hash
|
95
|
+
{
|
96
|
+
'api_key' => @api_key,
|
97
|
+
'etag' => @etag,
|
98
|
+
'draft' => @draft,
|
99
|
+
'published' => @published
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def update(attrs)
|
104
|
+
if attrs['draft']
|
105
|
+
@draft.update attrs['draft']
|
106
|
+
end
|
107
|
+
|
108
|
+
if attrs['published']
|
109
|
+
@published.update attrs['published']
|
110
|
+
end
|
111
|
+
|
112
|
+
@etag += 1
|
113
|
+
self.class.save self
|
114
|
+
end
|
115
|
+
|
116
|
+
def reload
|
117
|
+
self.class.find api_key
|
118
|
+
end
|
119
|
+
|
120
|
+
def deploy
|
121
|
+
@published.update @draft
|
122
|
+
self.class.save self
|
123
|
+
end
|
124
|
+
|
125
|
+
def etag
|
126
|
+
@etag.to_s
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.create(api_key)
|
130
|
+
project = Project.new('api_key' => api_key)
|
131
|
+
save project
|
132
|
+
project
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.find(api_key)
|
136
|
+
open_project_data do |data|
|
137
|
+
if project_hash = data[api_key]
|
138
|
+
Project.new project_hash.dup
|
139
|
+
else
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.delete_all
|
146
|
+
open_project_data do |data|
|
147
|
+
data.clear
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.save(project)
|
152
|
+
open_project_data do |data|
|
153
|
+
data[project.api_key] = project.to_hash
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.open_project_data
|
158
|
+
project_file = File.expand_path('/../../../tmp/projects.json', __FILE__)
|
159
|
+
|
160
|
+
if File.exist? project_file
|
161
|
+
data = JSON.parse(IO.read(project_file))
|
162
|
+
else
|
163
|
+
data = {}
|
164
|
+
end
|
165
|
+
|
166
|
+
result = yield(data)
|
167
|
+
|
168
|
+
File.open(project_file, 'w') do |file|
|
169
|
+
file.write data.to_json
|
170
|
+
end
|
171
|
+
|
172
|
+
result
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class FakeHtmlSafeString < String
|
2
|
+
def initialize(*args)
|
3
|
+
super(*args)
|
4
|
+
@html_safe = false
|
5
|
+
end
|
6
|
+
|
7
|
+
def html_safe
|
8
|
+
dup.html_safe!
|
9
|
+
end
|
10
|
+
|
11
|
+
def html_safe!
|
12
|
+
@html_safe = true
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def html_safe?
|
17
|
+
@html_safe
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
class FakeLogger
|
2
|
+
def initialize
|
3
|
+
@entries = {
|
4
|
+
:info => [],
|
5
|
+
:debug => [],
|
6
|
+
:warn => [],
|
7
|
+
:error => [],
|
8
|
+
:fatal => [],
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def info(message = nil, &block)
|
13
|
+
log(:info, message, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def debug(message = nil, &block)
|
17
|
+
log(:debug, message, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def warn(message = nil, &block)
|
21
|
+
log(:warn, message, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def error(message = nil, &block)
|
25
|
+
log(:error, message, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def fatal(message = nil, &block)
|
29
|
+
log(:fatal, message, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def log(severity, message = nil, &block)
|
33
|
+
message ||= block.call
|
34
|
+
@entries[severity] << message
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_entry?(level, expected_entry)
|
38
|
+
@entries[level].any? { |actual_entry| actual_entry.include?(expected_entry) }
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :entries
|
42
|
+
end
|
43
|
+
|
44
|
+
RSpec::Matchers.define :have_entry do |severity, entry|
|
45
|
+
match do |logger|
|
46
|
+
@logger = logger
|
47
|
+
logger.has_entry?(severity, entry)
|
48
|
+
end
|
49
|
+
|
50
|
+
failure_message_for_should do
|
51
|
+
"Expected #{severity}(#{entry.inspect}); got entries:\n\n#{entries}"
|
52
|
+
end
|
53
|
+
|
54
|
+
failure_message_for_should_not do
|
55
|
+
"Unexpected #{severity}(#{entry.inspect}); got entries:\n\n#{entries}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def entries
|
59
|
+
lines = @logger.entries.inject([]) do |result, (severity, entries)|
|
60
|
+
if entries.empty?
|
61
|
+
result
|
62
|
+
else
|
63
|
+
result << "#{severity}:\n#{entries.join("\n")}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
lines.join("\n\n")
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class FakePassenger
|
2
|
+
def initialize
|
3
|
+
@handlers = {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def on_event(name, &handler)
|
7
|
+
@handlers[name] ||= []
|
8
|
+
@handlers[name] << handler
|
9
|
+
end
|
10
|
+
|
11
|
+
def call_event(name, *args)
|
12
|
+
if @handlers[name]
|
13
|
+
@handlers[name].each do |handler|
|
14
|
+
handler.call(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def become_master
|
20
|
+
$0 = "PassengerApplicationSpawner"
|
21
|
+
end
|
22
|
+
|
23
|
+
def spawn
|
24
|
+
$0 = "PassengerFork"
|
25
|
+
call_event(:starting_worker_process, true)
|
26
|
+
end
|
27
|
+
end
|