right_support 2.6.17 → 2.7.0

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 (83) hide show
  1. data/.rspec +4 -0
  2. data/CHANGELOG.rdoc +37 -0
  3. data/Gemfile +29 -0
  4. data/Gemfile.lock +111 -0
  5. data/README.rdoc +2 -0
  6. data/Rakefile +62 -0
  7. data/VERSION +1 -0
  8. data/features/balancer_error_handling.feature +34 -0
  9. data/features/balancer_health_check.feature +33 -0
  10. data/features/continuous_integration.feature +51 -0
  11. data/features/continuous_integration_cucumber.feature +28 -0
  12. data/features/continuous_integration_rspec1.feature +28 -0
  13. data/features/continuous_integration_rspec2.feature +28 -0
  14. data/features/http_client_timeout.feature +19 -0
  15. data/features/serialization.feature +95 -0
  16. data/features/step_definitions/http_client_steps.rb +27 -0
  17. data/features/step_definitions/request_balancer_steps.rb +93 -0
  18. data/features/step_definitions/ruby_steps.rb +176 -0
  19. data/features/step_definitions/serialization_steps.rb +96 -0
  20. data/features/step_definitions/server_steps.rb +134 -0
  21. data/features/support/env.rb +138 -0
  22. data/features/support/file_utils_bundler_mixin.rb +45 -0
  23. data/lib/right_support/ci/java_cucumber_formatter.rb +22 -8
  24. data/lib/right_support/ci/java_spec_formatter.rb +26 -8
  25. data/lib/right_support/ci/rake_task.rb +3 -0
  26. data/lib/right_support/ci.rb +24 -0
  27. data/lib/right_support/crypto/signed_hash.rb +22 -0
  28. data/lib/right_support/data/serializer.rb +24 -2
  29. data/lib/right_support/net/address_helper.rb +20 -8
  30. data/lib/right_support/net/dns.rb +20 -8
  31. data/lib/right_support/net/http_client.rb +22 -0
  32. data/lib/right_support/net/request_balancer.rb +27 -21
  33. data/lib/right_support/net/s3_helper.rb +20 -8
  34. data/lib/right_support/net/ssl/open_ssl_patch.rb +22 -0
  35. data/lib/right_support/net/ssl.rb +20 -8
  36. data/lib/right_support/ruby/easy_singleton.rb +22 -0
  37. data/lib/right_support/ruby/object_extensions.rb +22 -0
  38. data/lib/right_support/ruby/string_extensions.rb +1 -1
  39. data/lib/right_support.rb +13 -10
  40. data/right_support.gemspec +180 -18
  41. data/right_support.rconf +8 -0
  42. data/spec/config/feature_set_spec.rb +83 -0
  43. data/spec/crypto/signed_hash_spec.rb +60 -0
  44. data/spec/data/hash_tools_spec.rb +471 -0
  45. data/spec/data/uuid_spec.rb +45 -0
  46. data/spec/db/cassandra_model_part1_spec.rb +84 -0
  47. data/spec/db/cassandra_model_part2_spec.rb +73 -0
  48. data/spec/db/cassandra_model_spec.rb +359 -0
  49. data/spec/fixtures/encrypted_priv_rsa.pem +30 -0
  50. data/spec/fixtures/good_priv_dsa.pem +12 -0
  51. data/spec/fixtures/good_priv_rsa.pem +15 -0
  52. data/spec/fixtures/good_pub_dsa.ssh +1 -0
  53. data/spec/fixtures/good_pub_rsa.pem +5 -0
  54. data/spec/fixtures/good_pub_rsa.ssh +1 -0
  55. data/spec/log/exception_logger_spec.rb +76 -0
  56. data/spec/log/filter_logger_spec.rb +8 -0
  57. data/spec/log/mixin_spec.rb +62 -0
  58. data/spec/log/multiplexer_spec.rb +54 -0
  59. data/spec/log/null_logger_spec.rb +36 -0
  60. data/spec/log/system_logger_spec.rb +92 -0
  61. data/spec/net/address_helper_spec.rb +57 -0
  62. data/spec/net/balancing/health_check_spec.rb +382 -0
  63. data/spec/net/balancing/round_robin_spec.rb +15 -0
  64. data/spec/net/balancing/sticky_policy_spec.rb +92 -0
  65. data/spec/net/dns_spec.rb +152 -0
  66. data/spec/net/http_client_spec.rb +171 -0
  67. data/spec/net/request_balancer_spec.rb +579 -0
  68. data/spec/net/s3_helper_spec.rb +160 -0
  69. data/spec/net/ssl_spec.rb +42 -0
  70. data/spec/net/string_encoder_spec.rb +58 -0
  71. data/spec/rack/log_setter_spec.rb +5 -0
  72. data/spec/rack/request_logger_spec.rb +68 -0
  73. data/spec/rack/request_tracker_spec.rb +5 -0
  74. data/spec/ruby/easy_singleton_spec.rb +72 -0
  75. data/spec/ruby/object_extensions_spec.rb +27 -0
  76. data/spec/ruby/string_extensions_spec.rb +98 -0
  77. data/spec/spec_helper.rb +181 -0
  78. data/spec/stats/activity_spec.rb +193 -0
  79. data/spec/stats/exceptions_spec.rb +123 -0
  80. data/spec/stats/helpers_spec.rb +603 -0
  81. data/spec/validation/openssl_spec.rb +37 -0
  82. data/spec/validation/ssh_spec.rb +39 -0
  83. metadata +218 -19
@@ -0,0 +1,160 @@
1
+ require "spec_helper"
2
+
3
+ describe RightSupport::Net::S3Helper do
4
+
5
+ def digest
6
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, passphrase, @valid_params[:data])
7
+ end
8
+
9
+ def ciphertext
10
+ "\203\346\271\273\323\377r\246\002\204\231\374h\327!\323"
11
+ end
12
+
13
+ def passphrase
14
+ "#{@valid_params[:key]}:key"
15
+ end
16
+
17
+ before(:each) do
18
+ @valid_params = { :key => "foo", :data => "bar" }
19
+
20
+ # Mock right_aws config & objects with reasonable default behavior
21
+ @s3_config = {'creds' =>
22
+ {'aws_access_key_id' => 'knock_knock',
23
+ 'aws_secret_access_key' => 'open_sesame'},
24
+ 'bucket_name' => 'my_own_personal_bucket',
25
+ 'master_secret' => 'if i told you i would have to kill you'}
26
+ @s3_class = flexmock("s3")
27
+ @s3_object = flexmock("s3_object")
28
+ @s3_bucket = flexmock("s3_bucket")
29
+ @s3_class.should_receive(:new).with(String, String, Hash).and_return(@s3_object)
30
+ @s3_object.should_receive(:bucket).with('my_own_personal_bucket').and_return(@s3_bucket).by_default
31
+ @s3_bucket.should_receive(:key).and_return(@s3_object).by_default
32
+ @s3_bucket.should_receive(:put).with(String, String, Hash).and_return(true).by_default
33
+
34
+ @s3_object.should_receive(:meta_headers).and_return({"digest" => digest})
35
+ @s3_object.should_receive(:data).and_return(ciphertext)
36
+
37
+ flexmock(RightSupport::Net::S3Helper).should_receive(:s3_enabled?).and_return(:true)
38
+ flexmock(RightSupport::Net::S3Helper).should_receive(:master_secret).and_return('key')
39
+
40
+ @encrypt = flexmock("encrypt")
41
+ @encrypt.should_receive(:encrypt).with(String, String, Hash).and_return(ciphertext)
42
+ @encrypt.should_receive(:encrypt).with(Hash).and_return(ciphertext)
43
+ @encrypt.should_receive(:decrypt).with(Hash).and_return(@valid_params[:data])
44
+ end
45
+
46
+ context :init do
47
+ context "when all params are valid" do
48
+ it "succeeds" do
49
+ RightSupport::Net::S3Helper.init(@s3_config, @s3_class, @encrypt).should be_true
50
+ end
51
+ end
52
+
53
+ context "when bucket does not exist" do
54
+ before(:each) do
55
+ @bad_s3_config = @s3_config.clone
56
+ @bad_s3_config['bucket_name'] = "supercallifragilisticexpialidocious"
57
+ @s3_object.should_receive(:bucket).with("supercallifragilisticexpialidocious").and_return(nil)
58
+ end
59
+
60
+ it "raises BucketNotFound" do
61
+ lambda {
62
+ RightSupport::Net::S3Helper.init(@bad_s3_config, @s3_class, @encrypt)
63
+ }.should raise_error(RightSupport::Net::S3Helper::BucketNotFound)
64
+ end
65
+ end
66
+ end
67
+
68
+ context :get do
69
+ before(:each) do
70
+ RightSupport::Net::S3Helper.init(@s3_config, @s3_class, @encrypt)
71
+ end
72
+
73
+ context "with valid params" do
74
+ it 'returns plaintext' do
75
+ RightSupport::Net::S3Helper.get(@valid_params[:key]).should be_eql(@valid_params[:data])
76
+ end
77
+ end
78
+ end
79
+
80
+ context :post do
81
+ before(:each) do
82
+ RightSupport::Net::S3Helper.init(@s3_config, @s3_class, @encrypt)
83
+ end
84
+
85
+ context "with valid params" do
86
+ it 'encrypts and saves data' do
87
+ @s3_bucket.should_receive(:put).with(@valid_params[:key], ciphertext, {"digest" => digest}).and_return(true)
88
+ RightSupport::Net::S3Helper.post(@valid_params[:key], @valid_params[:data]).should be_true
89
+ end
90
+ end
91
+ end
92
+
93
+ context :health_check do
94
+ before(:each) do
95
+ RightSupport::Net::S3Helper.init(@s3_config, @s3_class, @encrypt)
96
+ end
97
+
98
+ context 'with invalid configuration' do
99
+ before :each do
100
+ flexmock(RightSupport::Net::S3Helper).should_receive(:config).and_return({'creds' => {'aws_access_key_id' => '@@AWS_ACCESS_KEY_ID@@', 'aws_secret_access_key' => '@@AWS_SECRET_ACCESS_KEY@@'}, 'bucket_name' => '@@s3_bucket_NAME@@', 'master_secret' => '@@MASTER_SECRET@@'})
101
+ end
102
+
103
+ it 'return error' do
104
+ RightSupport::Net::S3Helper.health_check.class.should == String
105
+ end
106
+ end
107
+
108
+ context 'with valid configuration but invalid s3 connection credentials' do
109
+ before :each do
110
+ flexmock(RightSupport::Net::S3Helper).should_receive(:config).and_return({'creds' => {'aws_access_key_id' => 'AWS_ACCESS_KEY_ID', 'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'}, 'bucket_name' => 'BUCKET_NAME', 'master_secret' => 'MASTER_SECRET'})
111
+ end
112
+
113
+ it 'returns error ' do
114
+ RightSupport::Net::S3Helper.health_check.class.should == String
115
+ end
116
+ end
117
+
118
+ context 'with valid configuration and s3 connection' do
119
+ before :each do
120
+ flexmock(RightSupport::Net::S3Helper).should_receive(:config).and_return({'creds' => {'aws_access_key_id' => 'dsfgdsfgdsfgdsfg', 'aws_secret_access_key' => 'sdghdhsdg'}, 'bucket_name' => 'dshdshdfgh', 'master_secret' => 'dshdshg'})
121
+ flexmock(RightSupport::Net::S3Helper).should_receive(:post).with('ping', 'heath check').and_return(true)
122
+ flexmock(RightSupport::Net::S3Helper).should_receive(:get).with('ping').and_return('heath check')
123
+ end
124
+
125
+ it 'returns ok' do
126
+ RightSupport::Net::S3Helper.health_check.should == true
127
+ end
128
+ end
129
+ end
130
+
131
+ context 'test/development environment' do
132
+
133
+ before :each do
134
+ @key1 = 'aws_access_key_id'
135
+ @key2 = 'aws_secret_access_key'
136
+ @opt_with_no_subdomains = {:no_subdomains => true}
137
+ @opt_without_subdoamins = {}
138
+ @config = {}
139
+ @config["creds"] = {}
140
+ @config["creds"]["aws_access_key_id"] = @key1
141
+ @config["creds"]["aws_secret_access_key"] = @key2
142
+ @s3_class = flexmock('Rightscale::S3')
143
+ flexmock(RightSupport::Net::S3Helper).should_receive(:bucket).and_return(true)
144
+ end
145
+
146
+ it 'works ok with :no_subdomains' do
147
+ @s3_class.should_receive(:new).with(@key1, @key2, @opt_with_no_subdomains)
148
+ RightSupport::Net::S3Helper.init(@config, @s3_class, @s3_class, :no_subdomains => true)
149
+ RightSupport::Net::S3Helper.s3
150
+ end
151
+
152
+ it 'works ok without subdomains' do
153
+ @s3_class.should_receive(:new).with(@key1, @key2, @opt_without_subdoamins)
154
+ RightSupport::Net::S3Helper.init(@config, @s3_class, @s3_class)
155
+ RightSupport::Net::S3Helper.s3
156
+ end
157
+
158
+ end
159
+
160
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe RightSupport::Net::SSL do
4
+ PATCH = RightSupport::Net::SSL::OpenSSLPatch
5
+
6
+ context :with_expected_hostname do
7
+ before(:all) do
8
+ PATCH.enable!
9
+ end
10
+
11
+ it 'works' do
12
+ OpenSSL::SSL.should respond_to(:verify_certificate_identity_without_hack)
13
+ PATCH.enabled?.should be_true
14
+
15
+ cert = flexmock('SSL certificate')
16
+
17
+ flexmock(OpenSSL::SSL).
18
+ should_receive(:verify_certificate_identity_without_hack).
19
+ with(cert, 'reposeX.rightscale.com').and_return(true)
20
+ RightSupport::Net::SSL.with_expected_hostname('reposeX.rightscale.com') do
21
+ OpenSSL::SSL.verify_certificate_identity(cert, '1.2.3.4').should be_true
22
+ end
23
+ end
24
+
25
+ context 'with disabled monkey-patch' do
26
+ before(:all) do
27
+ PATCH.disable!
28
+ end
29
+ it 'does not work' do
30
+ PATCH.enabled?.should be_false
31
+ cert = flexmock('SSL certificate')
32
+ flexmock(OpenSSL::SSL).
33
+ should_receive(:verify_certificate_identity_without_hack).
34
+ with(cert, '1.2.3.4').and_return(false)
35
+
36
+ RightSupport::Net::SSL.with_expected_hostname('reposeX.rightscale.com') do
37
+ OpenSSL::SSL.verify_certificate_identity(cert, '1.2.3.4').should be_false
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+ require 'cgi'
3
+
4
+ describe RightSupport::Net::StringEncoder do
5
+ #Alias for brevity's sake
6
+ ENCODINGS = RightSupport::Net::StringEncoder::ENCODINGS
7
+
8
+ before(:all) do
9
+ #Generate some random binary test vectors with varying
10
+ #lengths, 2-1024 bytes
11
+ @strings = []
12
+ (1..10).each do |i|
13
+ s = ''
14
+ i.times { s << rand(256) }
15
+
16
+ if RUBY_VERSION >= '1.9'
17
+ @strings << s.force_encoding(Encoding::ASCII_8BIT)
18
+ else
19
+ @strings << s
20
+ end
21
+ end
22
+ end
23
+
24
+ context :initialize do
25
+ it 'accepts a glob of encodings' do
26
+ obj = RightSupport::Net::StringEncoder.new(:base64, :url)
27
+ obj.decode(obj.encode('moo')).should == 'moo'
28
+ end
29
+
30
+ it 'accepts an Array of encodings' do
31
+ obj = RightSupport::Net::StringEncoder.new([:base64, :url])
32
+ obj.decode(obj.encode('moo')).should == 'moo'
33
+ end
34
+
35
+ context 'when unknown encodings are specified' do
36
+ it 'raises ArgumentError' do
37
+ lambda do
38
+ RightSupport::Net::StringEncoder.new(:xyzzy, :foobar)
39
+ end.should raise_error(ArgumentError)
40
+ end
41
+ end
42
+ end
43
+
44
+ #Ensure that encodings are symmetrical and commutative by testing round-trip
45
+ #for all combinations.
46
+ (1..ENCODINGS.length).each do |n|
47
+ context "when using any #{n} encoding#{n > 1 ? 's' : ''}" do
48
+ ENCODINGS.combination(n).each do |list|
49
+ it "round-trips #{list.join(', ')}" do
50
+ obj = RightSupport::Net::StringEncoder.new(*list)
51
+ @strings.each do |str|
52
+ obj.decode(obj.encode(str)).should == str
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe RightSupport::Rack::LogSetter do
4
+ it 'has test coverage'
5
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ describe RightSupport::Rack::RequestLogger do
4
+ class OhNoes < Exception; end
5
+
6
+ before(:each) do
7
+ @app = flexmock('Rack app')
8
+ @app.should_receive(:call).and_return([200, {}, 'body']).by_default
9
+ @logger = mock_logger
10
+ @env = {'rack.logger' => @logger}
11
+ @middleware = RightSupport::Rack::RequestLogger.new(@app)
12
+ end
13
+
14
+ context :initialize do
15
+ context 'without :logger option' do
16
+ it 'uses rack.logger' do
17
+ @logger.should_receive(:info)
18
+ @middleware.call(@env).should == [200, {}, 'body']
19
+ end
20
+ end
21
+ end
22
+
23
+ context :call do
24
+ context 'when the app raises an exception' do
25
+ before(:each) do
26
+ @app.should_receive(:call).and_raise(OhNoes)
27
+ end
28
+
29
+ it 'logs the exception' do
30
+ @logger.should_receive(:error)
31
+ lambda {
32
+ @middleware.call({})
33
+ }.should raise_error
34
+ end
35
+ end
36
+
37
+ context 'when Sinatra stores an exception' do
38
+ before(:each) do
39
+ @app.should_receive(:call).and_return([500, {}, 'body'])
40
+ @env['sinatra.error'] = OhNoes.new
41
+ end
42
+
43
+ it 'logs the exception' do
44
+ @logger.should_receive(:info)
45
+ @logger.should_receive(:error)
46
+ @middleware.call(@env)
47
+ end
48
+ end
49
+
50
+ context 'Shard ID logging' do
51
+ before(:each) do
52
+ @logger = mock_logger
53
+ end
54
+
55
+ it 'logs X-Shard header if it is present' do
56
+ @env['HTTP_X_SHARD'] = '9'
57
+ @logger.should_receive(:info).with(FlexMock.on { |arg| arg.should =~ /Shard: 9;/ } )
58
+ @middleware.send(:log_request_begin, @logger, @env)
59
+ end
60
+
61
+ it 'logs "default" if X-Shard header is absent' do
62
+ @logger.should_receive(:info).with(FlexMock.on { |arg| arg.should =~ /Shard: default;/ } )
63
+ @middleware.send(:log_request_begin, @logger, @env)
64
+ end
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe RightSupport::Rack::RequestTracker do
4
+ it 'has test coverage'
5
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ class Alice
4
+ include Singleton
5
+ include RightSupport::Ruby::EasySingleton
6
+
7
+ def alice?
8
+ true
9
+ end
10
+ end
11
+
12
+ class Bob
13
+ include RightSupport::Ruby::EasySingleton
14
+
15
+ def bob?
16
+ true
17
+ end
18
+ end
19
+
20
+ class Charlie
21
+ include RightSupport::Ruby::EasySingleton
22
+
23
+ def charlie?(&block)
24
+ block.call("this is charlie") if block
25
+ true
26
+ end
27
+
28
+ def horse(x,y,z)
29
+ yield("this is horse") if block_given?
30
+ x+y+z
31
+ end
32
+ end
33
+
34
+ describe RightSupport::Ruby::EasySingleton do
35
+ context 'when mixed into a base class' do
36
+ it 'ensures the base is already a Singleton' do
37
+ Alice.ancestors.should include(Singleton)
38
+ Bob.ancestors.should include(Singleton)
39
+ end
40
+
41
+ it 'adds a class-level method_missing' do
42
+ Alice.alice?.should be_true
43
+ Bob.bob?.should be_true
44
+
45
+ lambda { Alice.bob? }.should raise_error(NoMethodError)
46
+ end
47
+
48
+ it 'modifies class-level respond_to? to be truthful' do
49
+ Alice.respond_to?(:alice?).should be_true
50
+ Bob.respond_to?(:bob?).should be_true
51
+ Alice.respond_to?(:bob?).should be_false
52
+ Bob.respond_to?(:alice?).should be_false
53
+ end
54
+ end
55
+
56
+ context 'when proxying class-level method_missing to instance' do
57
+ it 'preserves parameters as passed' do
58
+ Charlie.horse(1,2,3).should == 6
59
+ end
60
+
61
+ it 'preserves block semantics' do
62
+ charlie = nil
63
+ horse = nil
64
+
65
+ Charlie.charlie? { |x| charlie = x }
66
+ charlie.should == 'this is charlie'
67
+
68
+ Charlie.horse(1,2,3) { |x| horse = x }
69
+ horse.should == 'this is horse'
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe RightSupport::Ruby::ObjectExtensions do
4
+ context :require_succeeds? do
5
+ it 'yields to its block when the require succeeds' do
6
+ @canary = false
7
+
8
+ # The 'set' source file ships with Ruby standard library and should
9
+ # always be available
10
+ if require_succeeds?('set')
11
+ @canary = true
12
+ end
13
+
14
+ @canary.should == true
15
+ end
16
+
17
+ it 'does not yield when require fails for any reason' do
18
+ @canary = false
19
+
20
+ if require_succeeds?('a_source_file_with_a_wholly_improbable_name')
21
+ @canary = true
22
+ end
23
+
24
+ @canary.should == false
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,98 @@
1
+ #
2
+ # Copyright (c) 2012 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require 'spec_helper'
24
+
25
+ describe RightSupport::Ruby::StringExtensions do
26
+
27
+ context ":snake_case" do
28
+
29
+ it "downcases single word" do
30
+ ["FOO", "Foo", "foo"].each do |w|
31
+ w.snake_case.should == "foo"
32
+ end
33
+ end
34
+
35
+ it "doesn't separate numbers from end of word" do
36
+ ["Foo1234", "foo1234"].each do |w|
37
+ w.snake_case.should == "foo1234"
38
+ end
39
+ end
40
+
41
+ it "doesn't separate numbers from word that starts with uppercase letter" do
42
+ "1234Foo".snake_case.should == "1234foo"
43
+ end
44
+
45
+ it "doesn't' separate numbers from word that starts with lowercase letter" do
46
+ "1234foo".snake_case.should == "1234foo"
47
+ end
48
+
49
+ it "downcases camel-cased words and connect with underscore" do
50
+ ["FooBar", "fooBar"].each do |w|
51
+ w.snake_case.should == "foo_bar"
52
+ end
53
+ end
54
+
55
+ it "starts new word with uppercase letter before lower case letter" do
56
+ ["FooBARBaz", "fooBARBaz"].each do |w|
57
+ w.snake_case.should == "foo_bar_baz"
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ context ":to_const_path" do
64
+
65
+ it "snakes-case the string" do
66
+ "Hello::World".to_const_path.should == "hello/world"
67
+ end
68
+
69
+ it "leaves (snake-cased) string without '::' unchanged" do
70
+ "hello".to_const_path.should == "hello"
71
+ end
72
+
73
+ it "replaces single '::' with '/'" do
74
+ "hello::world".to_const_path.should == "hello/world"
75
+ end
76
+
77
+ it "replaces multiple '::' with '/'" do
78
+ "hello::rightscale::world".to_const_path.should == "hello/rightscale/world"
79
+ end
80
+
81
+ end
82
+
83
+ context ':camelize' do
84
+
85
+ it 'camelizes the string' do
86
+ 'hello/world_hello'.camelize.should == 'Hello::WorldHello'
87
+ end
88
+
89
+ it 'camelizes strings with integers' do
90
+ '1hel2lo3/4wor5ld6_7hel8lo9'.camelize.should == '1hel2lo3::4wor5ld67hel8lo9'
91
+ end
92
+
93
+ it 'leaves camelized strings alone' do
94
+ '1Hel2lo3::4Wor5ld67Hel8lo9'.camelize.should == '1Hel2lo3::4Wor5ld67Hel8lo9'
95
+ end
96
+
97
+ end
98
+ end