right_support 2.6.17 → 2.7.0

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