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,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
@@ -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,8 @@
1
+ module ClientSpecHelpers
2
+ def reset_config
3
+ CopyTunerClient.configuration = nil
4
+ CopyTunerClient.configure(false) do |config|
5
+ config.api_key = 'abc123'
6
+ end
7
+ end
8
+ 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