copy_tuner_client 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|