jmongo 1.0.3 → 1.1.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 (96) hide show
  1. data/Gemfile +8 -0
  2. data/Gemfile.lock +43 -0
  3. data/Rakefile +72 -0
  4. data/jmongo.gemspec +84 -6
  5. data/lib/jmongo.rb +6 -14
  6. data/lib/jmongo/collection.rb +196 -114
  7. data/lib/jmongo/connection.rb +39 -13
  8. data/lib/jmongo/cursor.rb +161 -63
  9. data/lib/jmongo/db.rb +119 -30
  10. data/lib/jmongo/exceptions.rb +39 -0
  11. data/lib/jmongo/mongo-2.6.5.gb1.jar +0 -0
  12. data/lib/jmongo/mongo/bson.rb +130 -0
  13. data/lib/jmongo/mongo/collection.rb +185 -0
  14. data/lib/jmongo/mongo/connection.rb +45 -0
  15. data/lib/jmongo/mongo/db.rb +31 -0
  16. data/lib/jmongo/mongo/jmongo.rb +44 -0
  17. data/lib/jmongo/mongo/mongo.rb +98 -0
  18. data/lib/jmongo/mongo/ruby_ext.rb +38 -0
  19. data/lib/jmongo/mongo/utils.rb +136 -0
  20. data/lib/jmongo/version.rb +1 -1
  21. data/test-results.txt +98 -0
  22. data/test/auxillary/1.4_features.rb +166 -0
  23. data/test/auxillary/authentication_test.rb +68 -0
  24. data/test/auxillary/autoreconnect_test.rb +41 -0
  25. data/test/auxillary/fork_test.rb +30 -0
  26. data/test/auxillary/repl_set_auth_test.rb +58 -0
  27. data/test/auxillary/slave_connection_test.rb +36 -0
  28. data/test/auxillary/threaded_authentication_test.rb +101 -0
  29. data/test/bson/binary_test.rb +15 -0
  30. data/test/bson/bson_test.rb +657 -0
  31. data/test/bson/byte_buffer_test.rb +208 -0
  32. data/test/bson/hash_with_indifferent_access_test.rb +38 -0
  33. data/test/bson/json_test.rb +17 -0
  34. data/test/bson/object_id_test.rb +138 -0
  35. data/test/bson/ordered_hash_test.rb +245 -0
  36. data/test/bson/test_helper.rb +46 -0
  37. data/test/bson/timestamp_test.rb +46 -0
  38. data/test/collection_test.rb +933 -0
  39. data/test/connection_test.rb +325 -0
  40. data/test/conversions_test.rb +121 -0
  41. data/test/cursor_fail_test.rb +75 -0
  42. data/test/cursor_message_test.rb +43 -0
  43. data/test/cursor_test.rb +547 -0
  44. data/test/data/empty_data +0 -0
  45. data/test/data/sample_data +0 -0
  46. data/test/data/sample_file.pdf +0 -0
  47. data/test/data/small_data.txt +1 -0
  48. data/test/db_api_test.rb +739 -0
  49. data/test/db_connection_test.rb +15 -0
  50. data/test/db_test.rb +325 -0
  51. data/test/grid_file_system_test.rb +260 -0
  52. data/test/grid_io_test.rb +210 -0
  53. data/test/grid_test.rb +259 -0
  54. data/test/load/thin/config.ru +6 -0
  55. data/test/load/thin/config.yml.template +6 -0
  56. data/test/load/thin/load.rb +24 -0
  57. data/test/load/unicorn/config.ru +6 -0
  58. data/test/load/unicorn/load.rb +23 -0
  59. data/test/load/unicorn/unicorn.rb.template +29 -0
  60. data/test/replica_sets/connect_test.rb +111 -0
  61. data/test/replica_sets/connection_string_test.rb +29 -0
  62. data/test/replica_sets/count_test.rb +36 -0
  63. data/test/replica_sets/insert_test.rb +54 -0
  64. data/test/replica_sets/pooled_insert_test.rb +58 -0
  65. data/test/replica_sets/query_secondaries.rb +109 -0
  66. data/test/replica_sets/query_test.rb +52 -0
  67. data/test/replica_sets/read_preference_test.rb +43 -0
  68. data/test/replica_sets/refresh_test.rb +123 -0
  69. data/test/replica_sets/replication_ack_test.rb +71 -0
  70. data/test/replica_sets/rs_test_helper.rb +27 -0
  71. data/test/safe_test.rb +68 -0
  72. data/test/support/hash_with_indifferent_access.rb +186 -0
  73. data/test/support/keys.rb +45 -0
  74. data/test/support_test.rb +19 -0
  75. data/test/test_helper.rb +111 -0
  76. data/test/threading/threading_with_large_pool_test.rb +90 -0
  77. data/test/threading_test.rb +88 -0
  78. data/test/tools/auth_repl_set_manager.rb +14 -0
  79. data/test/tools/keyfile.txt +1 -0
  80. data/test/tools/repl_set_manager.rb +377 -0
  81. data/test/unit/collection_test.rb +128 -0
  82. data/test/unit/connection_test.rb +85 -0
  83. data/test/unit/cursor_test.rb +127 -0
  84. data/test/unit/db_test.rb +96 -0
  85. data/test/unit/grid_test.rb +51 -0
  86. data/test/unit/node_test.rb +73 -0
  87. data/test/unit/pool_manager_test.rb +47 -0
  88. data/test/unit/pool_test.rb +9 -0
  89. data/test/unit/read_test.rb +101 -0
  90. data/test/unit/safe_test.rb +125 -0
  91. data/test/uri_test.rb +92 -0
  92. metadata +170 -99
  93. data/lib/jmongo/ajrb.rb +0 -189
  94. data/lib/jmongo/jmongo_jext.rb +0 -302
  95. data/lib/jmongo/mongo-2.6.3.jar +0 -0
  96. data/lib/jmongo/utils.rb +0 -61
@@ -0,0 +1,68 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'mongo'
3
+ require 'test/unit'
4
+ require './test/test_helper'
5
+
6
+ # NOTE: This test requires bouncing the server.
7
+ # It also requires that a user exists on the admin database.
8
+ class AuthenticationTest < Test::Unit::TestCase
9
+ include Mongo
10
+
11
+ def setup
12
+ @conn = Mongo::Connection.new
13
+ @db1 = @conn.db('mongo-ruby-test-auth1')
14
+ @db2 = @conn.db('mongo-ruby-test-auth2')
15
+ @admin = @conn.db('admin')
16
+ end
17
+
18
+ def teardown
19
+ @db1.authenticate('user1', 'secret')
20
+ @db2.authenticate('user2', 'secret')
21
+ @conn.drop_database('mongo-ruby-test-auth1')
22
+ @conn.drop_database('mongo-ruby-test-auth2')
23
+ end
24
+
25
+ def test_authenticate
26
+ @admin.authenticate('bob', 'secret')
27
+ @db1.add_user('user1', 'secret')
28
+ @db2.add_user('user2', 'secret')
29
+ @admin.logout
30
+
31
+ assert_raise Mongo::OperationFailure do
32
+ @db1['stuff'].insert({:a => 2}, :safe => true)
33
+ end
34
+
35
+ assert_raise Mongo::OperationFailure do
36
+ @db2['stuff'].insert({:a => 2}, :safe => true)
37
+ end
38
+
39
+ @db1.authenticate('user1', 'secret')
40
+ @db2.authenticate('user2', 'secret')
41
+
42
+ assert @db1['stuff'].insert({:a => 2}, :safe => true)
43
+ assert @db2['stuff'].insert({:a => 2}, :safe => true)
44
+
45
+ puts "Please bounce the server."
46
+ gets
47
+
48
+ # Here we reconnect.
49
+ begin
50
+ @db1['stuff'].find.to_a
51
+ rescue Mongo::ConnectionFailure
52
+ end
53
+
54
+ assert @db1['stuff'].insert({:a => 2}, :safe => true)
55
+ assert @db2['stuff'].insert({:a => 2}, :safe => true)
56
+
57
+ @db1.logout
58
+ assert_raise Mongo::OperationFailure do
59
+ @db1['stuff'].insert({:a => 2}, :safe => true)
60
+ end
61
+
62
+ @db2.logout
63
+ assert_raise Mongo::OperationFailure do
64
+ assert @db2['stuff'].insert({:a => 2}, :safe => true)
65
+ end
66
+ end
67
+
68
+ end
@@ -0,0 +1,41 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'mongo'
3
+ require 'test/unit'
4
+ require './test/test_helper'
5
+
6
+ # NOTE: This test requires bouncing the server
7
+ class AutoreconnectTest < Test::Unit::TestCase
8
+ include Mongo
9
+
10
+ def setup
11
+ @conn = Mongo::Connection.new
12
+ @db = @conn.db('mongo-ruby-test')
13
+ @db.drop_collection("test-connect")
14
+ @coll = @db.collection("test-connect")
15
+ end
16
+
17
+ def test_query
18
+ @coll.save({:a => 20})
19
+ @coll.save({:a => 30})
20
+ @coll.save({:a => 40})
21
+ results = []
22
+ @coll.find.each {|r| results << r}
23
+ [20, 30, 40].each do |a|
24
+ assert results.any? {|r| r['a'] == a}, "Could not find record for a => #{a}"
25
+ end
26
+
27
+ puts "Please disconnect and then reconnect the current master."
28
+ gets
29
+
30
+ begin
31
+ @coll.find.to_a
32
+ rescue Mongo::ConnectionFailure
33
+ end
34
+
35
+ results = []
36
+ @coll.find.each {|r| results << r}
37
+ [20, 30, 40].each do |a|
38
+ assert results.any? {|r| r['a'] == a}, "Could not find record for a => #{a}"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'mongo'
3
+ require 'test/unit'
4
+ require './test/test_helper'
5
+
6
+ class ForkTest < Test::Unit::TestCase
7
+ include Mongo
8
+
9
+ def setup
10
+ @conn = standard_connection
11
+ end
12
+
13
+ def test_fork
14
+ # Now insert some data
15
+ 10.times do |n|
16
+ @conn[MONGO_TEST_DB]['nums'].insert({:a => n})
17
+ end
18
+
19
+ # Now fork. You'll almost always see an exception here.
20
+ if !Kernel.fork
21
+ 10.times do
22
+ assert @conn[MONGO_TEST_DB]['nums'].find_one
23
+ end
24
+ else
25
+ 10.times do
26
+ assert @conn[MONGO_TEST_DB]['nums'].find_one
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,58 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require './test/test_helper'
3
+ require './test/tools/auth_repl_set_manager'
4
+
5
+ class AuthTest < Test::Unit::TestCase
6
+ include Mongo
7
+
8
+ def setup
9
+ @manager = AuthReplSetManager.new(:start_port => 40000)
10
+ @manager.start_set
11
+ end
12
+
13
+ def teardown
14
+ @manager.cleanup_set
15
+ end
16
+
17
+ def test_repl_set_auth
18
+ @conn = ReplSetConnection.new([@manager.host, @manager.ports[0]], [@manager.host, @manager.ports[1]],
19
+ [@manager.host, @manager.ports[2]], :name => @manager.name)
20
+
21
+ # Add an admin user
22
+ @conn['admin'].add_user("me", "secret")
23
+
24
+ # Ensure that insert fails
25
+ assert_raise_error Mongo::OperationFailure, "unauthorized" do
26
+ @conn['foo']['stuff'].insert({:a => 2}, :safe => {:w => 3})
27
+ end
28
+
29
+ # Then authenticate
30
+ assert @conn['admin'].authenticate("me", "secret")
31
+
32
+ # Insert should succeed now
33
+ assert @conn['foo']['stuff'].insert({:a => 2}, :safe => {:w => 3})
34
+
35
+ # So should a query
36
+ assert @conn['foo']['stuff'].find_one
37
+
38
+ # But not when we logout
39
+ @conn['admin'].logout
40
+
41
+ assert_raise_error Mongo::OperationFailure, "unauthorized" do
42
+ @conn['foo']['stuff'].find_one
43
+ end
44
+
45
+ # Same should apply to a random secondary
46
+ @slave1 = Connection.new(@conn.secondary_pools[0].host,
47
+ @conn.secondary_pools[0].port, :slave_ok => true)
48
+
49
+ # Find should fail
50
+ assert_raise_error Mongo::OperationFailure, "unauthorized" do
51
+ @slave1['foo']['stuff'].find_one
52
+ end
53
+
54
+ # But not when authenticated
55
+ @slave1['admin'].authenticate("me", "secret")
56
+ assert @slave1['foo']['stuff'].find_one
57
+ end
58
+ end
@@ -0,0 +1,36 @@
1
+ require './test/test_helper'
2
+
3
+ # NOTE: these tests are run only if we can connect to a single MongoDB in slave mode.
4
+ class SlaveConnectionTest < Test::Unit::TestCase
5
+ include Mongo
6
+
7
+ def self.connect_to_slave
8
+ @@host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
9
+ @@port = ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT
10
+ conn = Connection.new(@@host, @@port, :slave_ok => true)
11
+ response = conn['admin'].command(:ismaster => 1)
12
+ Mongo::Support.ok?(response) && response['ismaster'] != 1
13
+ end
14
+
15
+ if self.connect_to_slave
16
+ puts "Connected to slave; running slave tests."
17
+
18
+ def test_connect_to_slave
19
+ assert_raise Mongo::ConnectionFailure do
20
+ @db = Connection.new(@@host, @@port, :slave_ok => false).db('ruby-mongo-demo')
21
+ end
22
+ end
23
+
24
+ def test_slave_ok_sent_to_queries
25
+ @con = Connection.new(@@host, @@port, :slave_ok => true)
26
+ assert_equal true, @con.slave_ok?
27
+ end
28
+ else
29
+ puts "Not connected to slave; skipping slave connection tests."
30
+
31
+ def test_slave_ok_false_on_queries
32
+ @conn = Connection.new(@@host, @@port)
33
+ assert !@conn.slave_ok?
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,101 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'mongo'
3
+ require 'thread'
4
+ require 'test/unit'
5
+ require './test/test_helper'
6
+
7
+ # NOTE: This test requires bouncing the server.
8
+ # It also requires that a user exists on the admin database.
9
+ class AuthenticationTest < Test::Unit::TestCase
10
+ include Mongo
11
+
12
+ def setup
13
+ @conn = standard_connection(:pool_size => 10)
14
+ @db1 = @conn.db('mongo-ruby-test-auth1')
15
+ @db2 = @conn.db('mongo-ruby-test-auth2')
16
+ @admin = @conn.db('admin')
17
+ end
18
+
19
+ def teardown
20
+ @db1.authenticate('user1', 'secret')
21
+ @db2.authenticate('user2', 'secret')
22
+ @conn.drop_database('mongo-ruby-test-auth1')
23
+ @conn.drop_database('mongo-ruby-test-auth2')
24
+ end
25
+
26
+ def threaded_exec
27
+ threads = []
28
+
29
+ 100.times do
30
+ threads << Thread.new do
31
+ yield
32
+ end
33
+ end
34
+
35
+ 100.times do |n|
36
+ threads[n].join
37
+ end
38
+ end
39
+
40
+ def test_authenticate
41
+ @admin.authenticate('bob', 'secret')
42
+ @db1.add_user('user1', 'secret')
43
+ @db2.add_user('user2', 'secret')
44
+ @admin.logout
45
+
46
+ threaded_exec do
47
+ assert_raise Mongo::OperationFailure do
48
+ @db1['stuff'].insert({:a => 2}, :safe => true)
49
+ end
50
+ end
51
+
52
+ threaded_exec do
53
+ assert_raise Mongo::OperationFailure do
54
+ @db2['stuff'].insert({:a => 2}, :safe => true)
55
+ end
56
+ end
57
+
58
+ @db1.authenticate('user1', 'secret')
59
+ @db2.authenticate('user2', 'secret')
60
+
61
+ threaded_exec do
62
+ assert @db1['stuff'].insert({:a => 2}, :safe => true)
63
+ end
64
+
65
+ threaded_exec do
66
+ assert @db2['stuff'].insert({:a => 2}, :safe => true)
67
+ end
68
+
69
+ puts "Please bounce the server."
70
+ gets
71
+
72
+ # Here we reconnect.
73
+ begin
74
+ @db1['stuff'].find.to_a
75
+ rescue Mongo::ConnectionFailure
76
+ end
77
+
78
+ threaded_exec do
79
+ assert @db1['stuff'].insert({:a => 2}, :safe => true)
80
+ end
81
+
82
+ threaded_exec do
83
+ assert @db2['stuff'].insert({:a => 2}, :safe => true)
84
+ end
85
+
86
+ @db1.logout
87
+ threaded_exec do
88
+ assert_raise Mongo::OperationFailure do
89
+ @db1['stuff'].insert({:a => 2}, :safe => true)
90
+ end
91
+ end
92
+
93
+ @db2.logout
94
+ threaded_exec do
95
+ assert_raise Mongo::OperationFailure do
96
+ assert @db2['stuff'].insert({:a => 2}, :safe => true)
97
+ end
98
+ end
99
+ end
100
+
101
+ end
@@ -0,0 +1,15 @@
1
+ # encoding:utf-8
2
+ require './test/bson/test_helper'
3
+
4
+ class BinaryTest < Test::Unit::TestCase
5
+ context "Inspecting" do
6
+ setup do
7
+ @data = ("THIS IS BINARY " * 50).unpack("c*")
8
+ end
9
+
10
+ should "not display actual data" do
11
+ binary = BSON::Binary.new(@data)
12
+ assert_equal "<BSON::Binary:#{binary.object_id}>", binary.inspect
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,657 @@
1
+ # encoding:utf-8
2
+ require './test/bson/test_helper'
3
+ require 'set'
4
+
5
+ if RUBY_VERSION < '1.9'
6
+ require 'complex'
7
+ require 'rational'
8
+ end
9
+ require 'bigdecimal'
10
+
11
+ begin
12
+ require 'tzinfo'
13
+ require 'active_support/core_ext'
14
+ Time.zone = "Pacific Time (US & Canada)"
15
+ Zone = Time.zone.now
16
+ rescue LoadError
17
+ warn 'Mocking time with zone'
18
+ module ActiveSupport
19
+ class TimeWithZone
20
+ def initialize(utc_time, zone)
21
+ end
22
+ end
23
+ end
24
+ Zone = ActiveSupport::TimeWithZone.new(Time.now.utc, 'EST')
25
+ end
26
+
27
+ class BSONTest < Test::Unit::TestCase
28
+
29
+ include BSON
30
+
31
+ def setup
32
+ @encoder = BSON::BSON_CODER
33
+ end
34
+
35
+ def assert_doc_pass(doc, options={})
36
+ bson = @encoder.serialize(doc)
37
+ if options[:debug]
38
+ puts "DEBUGGING DOC:"
39
+ p bson.to_a
40
+ puts "DESERIALIZES TO:"
41
+ end
42
+ assert_equal @encoder.serialize(doc).to_a, bson.to_a
43
+ assert_equal doc, @encoder.deserialize(bson)
44
+ end
45
+
46
+ def test_require_hash
47
+ assert_raise_error InvalidDocument, "takes a Hash" do
48
+ BSON.serialize('foo')
49
+ end
50
+
51
+ assert_raise_error InvalidDocument, "takes a Hash" do
52
+ BSON.serialize(Object.new)
53
+ end
54
+
55
+ assert_raise_error InvalidDocument, "takes a Hash" do
56
+ BSON.serialize(Set.new)
57
+ end
58
+ end
59
+
60
+ def test_string
61
+ doc = {'doc' => 'hello, world'}
62
+ assert_doc_pass(doc)
63
+ end
64
+
65
+ def test_valid_utf8_string
66
+ doc = {'doc' => 'aé'}
67
+ assert_doc_pass(doc)
68
+ end
69
+
70
+ def test_valid_utf8_key
71
+ doc = {'aé' => 'hello'}
72
+ assert_doc_pass(doc)
73
+ end
74
+
75
+ def test_limit_max_bson_size
76
+ doc = {'name' => 'a' * BSON_CODER.max_bson_size}
77
+ assert_raise InvalidDocument do
78
+ assert @encoder.serialize(doc)
79
+ end
80
+ end
81
+
82
+ def test_max_bson_size
83
+ assert BSON_CODER.max_bson_size >= BSON::DEFAULT_MAX_BSON_SIZE
84
+ end
85
+
86
+ def test_update_max_bson_size
87
+ require 'ostruct'
88
+ mock_conn = OpenStruct.new
89
+ size = 7 * 1024 * 1024
90
+ mock_conn.max_bson_size = size
91
+ assert_equal size, BSON_CODER.update_max_bson_size(mock_conn)
92
+ assert_equal size, BSON_CODER.max_bson_size
93
+ end
94
+
95
+ def test_round_trip
96
+ doc = {'doc' => 123}
97
+ @encoder.deserialize(@encoder.serialize(doc))
98
+ end
99
+
100
+ # In 1.8 we test that other string encodings raise an exception.
101
+ # In 1.9 we test that they get auto-converted.
102
+ if RUBY_VERSION < '1.9'
103
+ if ! RUBY_PLATFORM =~ /java/
104
+ require 'iconv'
105
+ def test_non_utf8_string
106
+ string = Iconv.conv('iso-8859-1', 'utf-8', 'aé')
107
+ doc = {'doc' => string}
108
+ assert_doc_pass(doc)
109
+ assert_raise InvalidStringEncoding do
110
+ @encoder.serialize(doc)
111
+ end
112
+ end
113
+
114
+ def test_non_utf8_key
115
+ key = Iconv.conv('iso-8859-1', 'utf-8', 'aé')
116
+ doc = {key => 'hello'}
117
+ assert_raise InvalidStringEncoding do
118
+ @encoder.serialize(doc)
119
+ end
120
+ end
121
+ end
122
+ else
123
+ def test_non_utf8_string
124
+ assert_raise BSON::InvalidStringEncoding do
125
+ BSON::BSON_CODER.serialize({'str' => 'aé'.encode('iso-8859-1')})
126
+ end
127
+ end
128
+
129
+ def test_invalid_utf8_string
130
+ str = "123\xD9"
131
+ assert !str.valid_encoding?
132
+ assert_raise BSON::InvalidStringEncoding do
133
+ BSON::BSON_CODER.serialize({'str' => str})
134
+ end
135
+ end
136
+
137
+ def test_non_utf8_key
138
+ assert_raise BSON::InvalidStringEncoding do
139
+ BSON::BSON_CODER.serialize({'aé'.encode('iso-8859-1') => 'hello'})
140
+ end
141
+ end
142
+
143
+ # Based on a test from sqlite3-ruby
144
+ def test_default_internal_is_honored
145
+ before_enc = Encoding.default_internal
146
+
147
+ str = "壁に耳あり、障子に目あり"
148
+ bson = BSON::BSON_CODER.serialize("x" => str)
149
+
150
+ silently { Encoding.default_internal = 'EUC-JP' }
151
+ out = BSON::BSON_CODER.deserialize(bson)["x"]
152
+
153
+ assert_equal Encoding.default_internal, out.encoding
154
+ assert_equal str.encode('EUC-JP'), out
155
+ assert_equal str, out.encode(str.encoding)
156
+ ensure
157
+ silently { Encoding.default_internal = before_enc }
158
+ end
159
+ end
160
+
161
+ def test_code
162
+ doc = {'$where' => Code.new('this.a.b < this.b')}
163
+ assert_doc_pass(doc)
164
+ end
165
+
166
+ def test_code_with_symbol
167
+ assert_raise_error ArgumentError, "BSON::Code must be in the form of a String" do
168
+ Code.new(:fubar)
169
+ end
170
+ end
171
+
172
+ def test_code_with_scope
173
+ doc = {'$where' => Code.new('this.a.b < this.b', {'foo' => 1})}
174
+ assert_doc_pass(doc)
175
+ end
176
+
177
+ def test_double
178
+ doc = {'doc' => 41.25}
179
+ assert_doc_pass(doc)
180
+ end
181
+
182
+ def test_int
183
+ doc = {'doc' => 42}
184
+ assert_doc_pass(doc)
185
+
186
+ doc = {"doc" => -5600}
187
+ assert_doc_pass(doc)
188
+
189
+ doc = {"doc" => 2147483647}
190
+ assert_doc_pass(doc)
191
+
192
+ doc = {"doc" => -2147483648}
193
+ assert_doc_pass(doc)
194
+ end
195
+
196
+ def test_ordered_hash
197
+ doc = BSON::OrderedHash.new
198
+ doc["b"] = 1
199
+ doc["a"] = 2
200
+ doc["c"] = 3
201
+ doc["d"] = 4
202
+ assert_doc_pass(doc)
203
+ end
204
+
205
+ def test_object
206
+ doc = {'doc' => {'age' => 42, 'name' => 'Spongebob', 'shoe_size' => 9.5}}
207
+ assert_doc_pass(doc)
208
+ end
209
+
210
+ def test_embedded_document_with_nil
211
+ doc = {'doc' => {'age' => 42, 'name' => nil, 'shoe_size' => 9.5}}
212
+ assert_doc_pass(doc)
213
+ end
214
+
215
+ def test_embedded_document_with_date
216
+ doc = {'doc' => {'age' => 42, 'date' => Time.now.utc, 'shoe_size' => 9.5}}
217
+ bson = @encoder.serialize(doc)
218
+ doc2 = @encoder.deserialize(bson)
219
+ assert doc['doc']
220
+ assert_equal 42, doc['doc']['age']
221
+ assert_equal 9.5, doc['doc']['shoe_size']
222
+ assert_in_delta Time.now, doc['doc']['date'], 1
223
+ end
224
+
225
+ def test_oid
226
+ doc = {'doc' => ObjectId.new}
227
+ assert_doc_pass(doc)
228
+ end
229
+
230
+ def test_array
231
+ doc = {'doc' => [1, 2, 'a', 'b']}
232
+ assert_doc_pass(doc)
233
+ end
234
+
235
+ def test_array_keys
236
+ doc = {'doc' => [1, 2, 'a', 'b']}
237
+ bson = @encoder.serialize(doc).to_a
238
+ assert_equal 48, bson[14]
239
+ assert_equal 49, bson[21]
240
+ assert_equal 50, bson[28]
241
+ assert_equal 51, bson[37]
242
+ end
243
+
244
+ def test_regex
245
+ doc = {'doc' => /foobar/i}
246
+ assert_doc_pass(doc)
247
+ end
248
+
249
+ def test_regex_multiline
250
+ doc = {'doc' => /foobar/m}
251
+ assert_doc_pass(doc)
252
+ end
253
+
254
+ def test_boolean
255
+ doc = {'doc' => true}
256
+ assert_doc_pass(doc)
257
+ end
258
+
259
+ def test_date
260
+ doc = {'date' => Time.now}
261
+ bson = @encoder.serialize(doc)
262
+ doc2 = @encoder.deserialize(bson)
263
+ # Mongo only stores up to the millisecond
264
+ assert_in_delta doc['date'], doc2['date'], 0.001
265
+ end
266
+
267
+ def test_date_in_array
268
+ doc = {'date' => [Time.now.utc]}
269
+ bson = @encoder.serialize(doc)
270
+ doc2 = @encoder.deserialize(bson)
271
+ end
272
+
273
+ def test_date_returns_as_utc
274
+ doc = {'date' => Time.now.utc}
275
+ bson = @encoder.serialize(doc)
276
+ doc2 = @encoder.deserialize(bson)
277
+ assert doc2['date'].utc?
278
+ end
279
+
280
+ def test_date_before_epoch
281
+ begin
282
+ doc = {'date' => Time.utc(1600)}
283
+ bson = @encoder.serialize(doc)
284
+ doc2 = @encoder.deserialize(bson)
285
+ # Mongo only stores up to the millisecond
286
+ assert_in_delta doc['date'], doc2['date'], 2
287
+ rescue ArgumentError
288
+ # some versions of Ruby won't let you create pre-epoch Time instances
289
+ #
290
+ # TODO figure out how that will work if somebady has saved data
291
+ # w/ early dates already and is just querying for it.
292
+ end
293
+ end
294
+
295
+ def test_exeption_on_using_unsupported_date_class
296
+ [DateTime.now, Date.today, Zone].each do |invalid_date|
297
+ doc = {:date => invalid_date}
298
+ begin
299
+ bson = BSON::BSON_CODER.serialize(doc)
300
+ rescue => e
301
+ ensure
302
+ if !invalid_date.is_a? Time
303
+ assert_equal InvalidDocument, e.class
304
+ assert_match(/UTC Time/, e.message)
305
+ end
306
+ end
307
+ end
308
+ end
309
+
310
+ def test_dbref
311
+ oid = ObjectId.new
312
+ doc = {}
313
+ doc['dbref'] = DBRef.new('namespace', oid)
314
+ bson = @encoder.serialize(doc)
315
+ doc2 = @encoder.deserialize(bson)
316
+
317
+ # Java doesn't deserialize to DBRefs
318
+ if RUBY_PLATFORM =~ /java/
319
+ assert_equal 'namespace', doc2['dbref']['$ns']
320
+ assert_equal oid, doc2['dbref']['$id']
321
+ else
322
+ assert_equal 'namespace', doc2['dbref'].namespace
323
+ assert_equal oid, doc2['dbref'].object_id
324
+ end
325
+ end
326
+
327
+ def test_symbol
328
+ doc = {'sym' => :foo}
329
+ bson = @encoder.serialize(doc)
330
+ doc2 = @encoder.deserialize(bson)
331
+ assert_equal :foo, doc2['sym']
332
+ end
333
+
334
+ def test_binary
335
+ bin = Binary.new
336
+ 'binstring'.each_byte { |b| bin.put(b) }
337
+
338
+ doc = {'bin' => bin}
339
+ bson = @encoder.serialize(doc)
340
+ doc2 = @encoder.deserialize(bson)
341
+ bin2 = doc2['bin']
342
+ assert_kind_of Binary, bin2
343
+ assert_equal 'binstring', bin2.to_s
344
+ assert_equal Binary::SUBTYPE_SIMPLE, bin2.subtype
345
+ end
346
+
347
+ def test_binary_with_deprecated_subtype
348
+ bin = Binary.new
349
+ 'binstring'.each_byte { |b| bin.put(b) }
350
+ bin.subtype = Binary::SUBTYPE_BYTES
351
+
352
+ doc = {'bin' => bin}
353
+ bson = @encoder.serialize(doc)
354
+ doc2 = @encoder.deserialize(bson)
355
+ bin2 = doc2['bin']
356
+ assert_kind_of Binary, bin2
357
+ assert_equal 'binstring', bin2.to_s
358
+ assert_equal Binary::SUBTYPE_BYTES, bin2.subtype
359
+ end
360
+
361
+ def test_binary_with_string
362
+ b = Binary.new('somebinarystring')
363
+ doc = {'bin' => b}
364
+ bson = @encoder.serialize(doc)
365
+ doc2 = @encoder.deserialize(bson)
366
+ bin2 = doc2['bin']
367
+ assert_kind_of Binary, bin2
368
+ assert_equal 'somebinarystring', bin2.to_s
369
+ assert_equal Binary::SUBTYPE_SIMPLE, bin2.subtype
370
+ end
371
+
372
+ def test_binary_type
373
+ bin = Binary.new([1, 2, 3, 4, 5], Binary::SUBTYPE_USER_DEFINED)
374
+
375
+ doc = {'bin' => bin}
376
+ bson = @encoder.serialize(doc)
377
+ doc2 = @encoder.deserialize(bson)
378
+ bin2 = doc2['bin']
379
+ assert_kind_of Binary, bin2
380
+ assert_equal [1, 2, 3, 4, 5], bin2.to_a
381
+ assert_equal Binary::SUBTYPE_USER_DEFINED, bin2.subtype
382
+ end
383
+
384
+ # Java doesn't support binary subtype 0 yet
385
+ if !(RUBY_PLATFORM =~ /java/)
386
+ def test_binary_subtype_0
387
+ bin = Binary.new([1, 2, 3, 4, 5], Binary::SUBTYPE_SIMPLE)
388
+
389
+ doc = {'bin' => bin}
390
+ bson = @encoder.serialize(doc)
391
+ doc2 = @encoder.deserialize(bson)
392
+ bin2 = doc2['bin']
393
+ assert_kind_of Binary, bin2
394
+ assert_equal [1, 2, 3, 4, 5], bin2.to_a
395
+ assert_equal Binary::SUBTYPE_SIMPLE, bin2.subtype
396
+ end
397
+ end
398
+
399
+ def test_binary_byte_buffer
400
+ bb = Binary.new
401
+ 5.times { |i| bb.put(i + 1) }
402
+
403
+ doc = {'bin' => bb}
404
+ bson = @encoder.serialize(doc)
405
+ doc2 = @encoder.deserialize(bson)
406
+ bin2 = doc2['bin']
407
+ assert_kind_of Binary, bin2
408
+ assert_equal [1, 2, 3, 4, 5], bin2.to_a
409
+ assert_equal Binary::SUBTYPE_SIMPLE, bin2.subtype
410
+ end
411
+
412
+ def test_put_id_first
413
+ val = BSON::OrderedHash.new
414
+ val['not_id'] = 1
415
+ val['_id'] = 2
416
+ roundtrip = @encoder.deserialize(@encoder.serialize(val, false, true).to_s)
417
+ assert_kind_of BSON::OrderedHash, roundtrip
418
+ assert_equal '_id', roundtrip.keys.first
419
+
420
+ val = {'a' => 'foo', 'b' => 'bar', :_id => 42, 'z' => 'hello'}
421
+ roundtrip = @encoder.deserialize(@encoder.serialize(val, false, true).to_s)
422
+ assert_kind_of BSON::OrderedHash, roundtrip
423
+ assert_equal '_id', roundtrip.keys.first
424
+ end
425
+
426
+ def test_nil_id
427
+ doc = {"_id" => nil}
428
+ assert_doc_pass(doc)
429
+ end
430
+
431
+ if !(RUBY_PLATFORM =~ /java/)
432
+ def test_timestamp
433
+ val = {"test" => [4, 20]}
434
+ result = @encoder.deserialize([0x13, 0x00, 0x00, 0x00,
435
+ 0x11, 0x74, 0x65, 0x73,
436
+ 0x74, 0x00, 0x04, 0x00,
437
+ 0x00, 0x00, 0x14, 0x00,
438
+ 0x00, 0x00, 0x00])
439
+
440
+ silently do
441
+ assert_equal 4, result["test"][0]
442
+ assert_equal 20, result["test"][1]
443
+ end
444
+ end
445
+ end
446
+
447
+ def test_timestamp_type
448
+ ts = Timestamp.new(5000, 100)
449
+ doc = {:ts => ts}
450
+ bson = @encoder.serialize(doc)
451
+ assert_equal ts, @encoder.deserialize(bson)["ts"]
452
+ end
453
+
454
+ def test_overflow
455
+ doc = {"x" => 2**75}
456
+ assert_raise RangeError do
457
+ bson = @encoder.serialize(doc)
458
+ end
459
+
460
+ doc = {"x" => 9223372036854775}
461
+ assert_doc_pass(doc)
462
+
463
+ doc = {"x" => 9223372036854775807}
464
+ assert_doc_pass(doc)
465
+
466
+ doc["x"] = doc["x"] + 1
467
+ assert_raise RangeError do
468
+ bson = @encoder.serialize(doc)
469
+ end
470
+
471
+ doc = {"x" => -9223372036854775}
472
+ assert_doc_pass(doc)
473
+
474
+ doc = {"x" => -9223372036854775808}
475
+ assert_doc_pass(doc)
476
+
477
+ doc["x"] = doc["x"] - 1
478
+ assert_raise RangeError do
479
+ bson = BSON::BSON_CODER.serialize(doc)
480
+ end
481
+ end
482
+
483
+ def test_invalid_numeric_types
484
+ [BigDecimal.new("1.0"), Complex(0, 1), Rational(2, 3)].each do |type|
485
+ doc = {"x" => type}
486
+ begin
487
+ @encoder.serialize(doc)
488
+ rescue => e
489
+ ensure
490
+ assert_equal InvalidDocument, e.class
491
+ assert_match(/Cannot serialize/, e.message)
492
+ end
493
+ end
494
+ end
495
+
496
+ def test_do_not_change_original_object
497
+ val = BSON::OrderedHash.new
498
+ val['not_id'] = 1
499
+ val['_id'] = 2
500
+ assert val.keys.include?('_id')
501
+ @encoder.serialize(val)
502
+ assert val.keys.include?('_id')
503
+
504
+ val = {'a' => 'foo', 'b' => 'bar', :_id => 42, 'z' => 'hello'}
505
+ assert val.keys.include?(:_id)
506
+ @encoder.serialize(val)
507
+ assert val.keys.include?(:_id)
508
+ end
509
+
510
+ # note we only test for _id here because in the general case we will
511
+ # write duplicates for :key and "key". _id is a special case because
512
+ # we call has_key? to check for it's existence rather than just iterating
513
+ # over it like we do for the rest of the keys. thus, things like
514
+ # HashWithIndifferentAccess can cause problems for _id but not for other
515
+ # keys. rather than require rails to test with HWIA directly, we do this
516
+ # somewhat hacky test.
517
+ #
518
+ # Note that the driver only eliminates duplicate ids when move_id is true.
519
+ def test_no_duplicate_id
520
+ dup = {"_id" => "foo", :_id => "foo"}
521
+ one = {"_id" => "foo"}
522
+
523
+ assert_equal @encoder.serialize(one, false, true).to_a, @encoder.serialize(dup, false, true).to_a
524
+ end
525
+
526
+ def test_duplicate_keys
527
+ #dup = {"_foo" => "foo", :_foo => "foo"}
528
+ #one = {"_foo" => "foo"}
529
+
530
+ #assert_equal @encoder.serialize(one).to_a, @encoder.serialize(dup).to_a
531
+ warn "Pending test for duplicate keys"
532
+ end
533
+
534
+ def test_no_duplicate_id_when_moving_id
535
+ dup = {"_id" => "foo", :_id => "foo"}
536
+ one = {:_id => "foo"}
537
+
538
+ assert_equal @encoder.serialize(one, false, true).to_s, @encoder.serialize(dup, false, true).to_s
539
+ end
540
+
541
+ def test_null_character
542
+ doc = {"a" => "\x00"}
543
+
544
+ assert_doc_pass(doc)
545
+
546
+ assert_raise InvalidDocument do
547
+ @encoder.serialize({"\x00" => "a"})
548
+ end
549
+
550
+ assert_raise InvalidDocument do
551
+ @encoder.serialize({"a" => (Regexp.compile "ab\x00c")})
552
+ end
553
+ end
554
+
555
+ def test_max_key
556
+ doc = {"a" => MaxKey.new}
557
+ assert_doc_pass(doc)
558
+ end
559
+
560
+ def test_min_key
561
+ doc = {"a" => MinKey.new}
562
+ assert_doc_pass(doc)
563
+ end
564
+
565
+ def test_invalid_object
566
+ o = Object.new
567
+ assert_raise InvalidDocument do
568
+ @encoder.serialize({:foo => o})
569
+ end
570
+
571
+ assert_raise InvalidDocument do
572
+ @encoder.serialize({:foo => Date.today})
573
+ end
574
+ end
575
+
576
+ def test_move_id
577
+ a = BSON::OrderedHash.new
578
+ a['text'] = 'abc'
579
+ a['key'] = 'abc'
580
+ a['_id'] = 1
581
+
582
+
583
+ assert_equal ")\000\000\000\020_id\000\001\000\000\000\002text" +
584
+ "\000\004\000\000\000abc\000\002key\000\004\000\000\000abc\000\000",
585
+ @encoder.serialize(a, false, true).to_s
586
+
587
+ assert_equal ")\000\000\000\002text\000\004\000\000\000abc\000\002key" +
588
+ "\000\004\000\000\000abc\000\020_id\000\001\000\000\000\000",
589
+ @encoder.serialize(a, false, false).to_s
590
+ end
591
+
592
+ def test_move_id_with_nested_doc
593
+ b = BSON::OrderedHash.new
594
+ b['text'] = 'abc'
595
+ b['_id'] = 2
596
+ c = BSON::OrderedHash.new
597
+ c['text'] = 'abc'
598
+ c['hash'] = b
599
+ c['_id'] = 3
600
+ assert_equal ">\000\000\000\020_id\000\003\000\000\000\002text" +
601
+ "\000\004\000\000\000abc\000\003hash\000\034\000\000" +
602
+ "\000\002text\000\004\000\000\000abc\000\020_id\000\002\000\000\000\000\000",
603
+ @encoder.serialize(c, false, true).to_s
604
+
605
+ # Java doesn't support this. Isn't actually necessary.
606
+ if !(RUBY_PLATFORM =~ /java/)
607
+ assert_equal ">\000\000\000\002text\000\004\000\000\000abc\000\003hash" +
608
+ "\000\034\000\000\000\002text\000\004\000\000\000abc\000\020_id" +
609
+ "\000\002\000\000\000\000\020_id\000\003\000\000\000\000",
610
+ @encoder.serialize(c, false, false).to_s
611
+ end
612
+ end
613
+
614
+ def test_invalid_key_names
615
+ assert @encoder.serialize({"hello" => "world"}, true)
616
+ assert @encoder.serialize({"hello" => {"hello" => "world"}}, true)
617
+
618
+ assert @encoder.serialize({"he$llo" => "world"}, true)
619
+ assert @encoder.serialize({"hello" => {"hell$o" => "world"}}, true)
620
+
621
+ assert_raise BSON::InvalidDocument do
622
+ @encoder.serialize({"he\0llo" => "world"}, true)
623
+ end
624
+
625
+ assert_raise BSON::InvalidKeyName do
626
+ @encoder.serialize({"$hello" => "world"}, true)
627
+ end
628
+
629
+ assert_raise BSON::InvalidKeyName do
630
+ @encoder.serialize({"hello" => {"$hello" => "world"}}, true)
631
+ end
632
+
633
+ assert_raise BSON::InvalidKeyName do
634
+ @encoder.serialize({".hello" => "world"}, true)
635
+ end
636
+
637
+ assert_raise BSON::InvalidKeyName do
638
+ @encoder.serialize({"hello" => {".hello" => "world"}}, true)
639
+ end
640
+
641
+ assert_raise BSON::InvalidKeyName do
642
+ @encoder.serialize({"hello." => "world"}, true)
643
+ end
644
+
645
+ assert_raise BSON::InvalidKeyName do
646
+ @encoder.serialize({"hello" => {"hello." => "world"}}, true)
647
+ end
648
+
649
+ assert_raise BSON::InvalidKeyName do
650
+ @encoder.serialize({"hel.lo" => "world"}, true)
651
+ end
652
+
653
+ assert_raise BSON::InvalidKeyName do
654
+ @encoder.serialize({"hello" => {"hel.lo" => "world"}}, true)
655
+ end
656
+ end
657
+ end