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,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