mongo 2.1.0.beta → 2.1.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (253) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Rakefile +2 -2
  5. data/lib/mongo.rb +2 -3
  6. data/lib/mongo/address.rb +7 -5
  7. data/lib/mongo/address/unix.rb +2 -2
  8. data/lib/mongo/auth/ldap/conversation.rb +6 -2
  9. data/lib/mongo/auth/scram/conversation.rb +8 -2
  10. data/lib/mongo/auth/user/view.rb +21 -0
  11. data/lib/mongo/bulk_write.rb +155 -23
  12. data/lib/mongo/bulk_write/combineable.rb +51 -0
  13. data/lib/mongo/bulk_write/ordered_combiner.rb +55 -0
  14. data/lib/mongo/bulk_write/result.rb +61 -8
  15. data/lib/mongo/bulk_write/result_combiner.rb +117 -0
  16. data/lib/mongo/bulk_write/transformable.rb +117 -0
  17. data/lib/mongo/bulk_write/unordered_combiner.rb +52 -0
  18. data/lib/mongo/bulk_write/validatable.rb +62 -0
  19. data/lib/mongo/client.rb +7 -3
  20. data/lib/mongo/cluster.rb +3 -3
  21. data/lib/mongo/cluster/topology/replica_set.rb +8 -6
  22. data/lib/mongo/cluster/topology/unknown.rb +5 -2
  23. data/lib/mongo/collection.rb +75 -4
  24. data/lib/mongo/collection/view.rb +1 -2
  25. data/lib/mongo/collection/view/aggregation.rb +13 -8
  26. data/lib/mongo/collection/view/immutable.rb +6 -6
  27. data/lib/mongo/collection/view/iterable.rb +13 -4
  28. data/lib/mongo/collection/view/map_reduce.rb +22 -17
  29. data/lib/mongo/collection/view/readable.rb +121 -70
  30. data/lib/mongo/cursor.rb +5 -1
  31. data/lib/mongo/database.rb +3 -3
  32. data/lib/mongo/database/view.rb +1 -1
  33. data/lib/mongo/error.rb +7 -0
  34. data/lib/mongo/{bulk_write/unordered_bulk_write.rb → error/closed_stream.rb} +12 -21
  35. data/lib/mongo/{bulk_write/ordered_bulk_write.rb → error/extra_file_chunk.rb} +13 -27
  36. data/lib/mongo/error/file_not_found.rb +37 -0
  37. data/lib/mongo/error/invalid_file.rb +2 -2
  38. data/lib/mongo/error/invalid_file_revision.rb +37 -0
  39. data/lib/mongo/error/invalid_uri.rb +5 -4
  40. data/lib/mongo/error/missing_file_chunk.rb +38 -0
  41. data/lib/mongo/error/operation_failure.rb +1 -1
  42. data/lib/mongo/error/unchangeable_collection_option.rb +38 -0
  43. data/lib/mongo/error/unexpected_chunk_length.rb +39 -0
  44. data/lib/mongo/grid.rb +2 -1
  45. data/lib/mongo/grid/file.rb +12 -9
  46. data/lib/mongo/grid/file/chunk.rb +6 -6
  47. data/lib/mongo/grid/file/{metadata.rb → info.rb} +41 -39
  48. data/lib/mongo/grid/fs_bucket.rb +441 -0
  49. data/lib/mongo/grid/stream.rb +64 -0
  50. data/lib/mongo/grid/stream/read.rb +208 -0
  51. data/lib/mongo/grid/stream/write.rb +187 -0
  52. data/lib/mongo/index/view.rb +1 -1
  53. data/lib/mongo/loggable.rb +34 -57
  54. data/lib/mongo/logger.rb +16 -78
  55. data/lib/mongo/monitoring.rb +1 -5
  56. data/lib/mongo/monitoring/command_log_subscriber.rb +35 -17
  57. data/lib/mongo/monitoring/event/command_succeeded.rb +20 -1
  58. data/lib/mongo/monitoring/publishable.rb +22 -12
  59. data/lib/mongo/operation.rb +3 -6
  60. data/lib/mongo/operation/commands.rb +24 -0
  61. data/lib/mongo/operation/{aggregate.rb → commands/aggregate.rb} +3 -41
  62. data/lib/mongo/operation/{aggregate → commands/aggregate}/result.rb +0 -0
  63. data/lib/mongo/operation/commands/collections_info.rb +66 -0
  64. data/lib/mongo/operation/{command.rb → commands/command.rb} +2 -18
  65. data/lib/mongo/operation/commands/indexes.rb +70 -0
  66. data/lib/mongo/operation/commands/list_collections.rb +54 -0
  67. data/lib/mongo/operation/commands/list_collections/result.rb +112 -0
  68. data/lib/mongo/operation/commands/list_indexes.rb +56 -0
  69. data/lib/mongo/operation/commands/list_indexes/result.rb +115 -0
  70. data/lib/mongo/operation/{map_reduce.rb → commands/map_reduce.rb} +3 -41
  71. data/lib/mongo/operation/{map_reduce → commands/map_reduce}/result.rb +0 -0
  72. data/lib/mongo/operation/{parallel_scan.rb → commands/parallel_scan.rb} +3 -23
  73. data/lib/mongo/operation/{parallel_scan → commands/parallel_scan}/result.rb +0 -0
  74. data/lib/mongo/operation/commands/user_query.rb +69 -0
  75. data/lib/mongo/operation/commands/users_info.rb +53 -0
  76. data/lib/mongo/operation/commands/users_info/result.rb +36 -0
  77. data/lib/mongo/operation/executable.rb +4 -68
  78. data/lib/mongo/operation/kill_cursors.rb +3 -3
  79. data/lib/mongo/operation/read.rb +0 -4
  80. data/lib/mongo/operation/read/get_more.rb +2 -22
  81. data/lib/mongo/operation/read/query.rb +2 -21
  82. data/lib/mongo/operation/{read_preferrable.rb → read_preference.rb} +3 -2
  83. data/lib/mongo/operation/specifiable.rb +24 -0
  84. data/lib/mongo/operation/write.rb +2 -0
  85. data/lib/mongo/operation/write/bulk.rb +6 -3
  86. data/lib/mongo/operation/write/bulk/bulkable.rb +82 -0
  87. data/lib/mongo/operation/write/bulk/delete.rb +71 -0
  88. data/lib/mongo/operation/write/bulk/delete/result.rb +74 -0
  89. data/lib/mongo/operation/write/bulk/insert.rb +96 -0
  90. data/lib/mongo/operation/write/bulk/insert/result.rb +129 -0
  91. data/lib/mongo/operation/write/bulk/legacy_mergable.rb +87 -0
  92. data/lib/mongo/operation/write/bulk/mergable.rb +71 -0
  93. data/lib/mongo/operation/write/bulk/update.rb +81 -0
  94. data/lib/mongo/operation/write/bulk/update/result.rb +174 -0
  95. data/lib/mongo/operation/write/command/create_index.rb +0 -1
  96. data/lib/mongo/operation/write/command/create_user.rb +0 -1
  97. data/lib/mongo/operation/write/command/delete.rb +0 -1
  98. data/lib/mongo/operation/write/command/drop_index.rb +0 -1
  99. data/lib/mongo/operation/write/command/insert.rb +0 -1
  100. data/lib/mongo/operation/write/command/remove_user.rb +0 -1
  101. data/lib/mongo/operation/write/command/update.rb +0 -1
  102. data/lib/mongo/operation/write/command/update_user.rb +0 -1
  103. data/lib/mongo/operation/write/command/writable.rb +13 -18
  104. data/lib/mongo/operation/write/create_index.rb +4 -27
  105. data/lib/mongo/operation/write/create_user.rb +4 -30
  106. data/lib/mongo/operation/write/delete.rb +5 -28
  107. data/lib/mongo/operation/write/drop_index.rb +3 -3
  108. data/lib/mongo/operation/write/gle.rb +48 -0
  109. data/lib/mongo/operation/write/idable.rb +5 -0
  110. data/lib/mongo/operation/write/insert.rb +2 -24
  111. data/lib/mongo/operation/write/remove_user.rb +4 -27
  112. data/lib/mongo/operation/write/update.rb +4 -32
  113. data/lib/mongo/operation/write/update_user.rb +4 -30
  114. data/lib/mongo/operation/write/write_command_enabled.rb +53 -0
  115. data/lib/mongo/options/mapper.rb +4 -2
  116. data/lib/mongo/protocol/delete.rb +68 -3
  117. data/lib/mongo/protocol/get_more.rb +54 -2
  118. data/lib/mongo/protocol/insert.rb +59 -1
  119. data/lib/mongo/protocol/kill_cursors.rb +53 -4
  120. data/lib/mongo/protocol/message.rb +12 -12
  121. data/lib/mongo/protocol/query.rb +139 -65
  122. data/lib/mongo/protocol/reply.rb +69 -1
  123. data/lib/mongo/protocol/update.rb +70 -1
  124. data/lib/mongo/server/connection.rb +11 -3
  125. data/lib/mongo/server/description.rb +29 -0
  126. data/lib/mongo/server/description/features.rb +2 -1
  127. data/lib/mongo/server/monitor.rb +2 -2
  128. data/lib/mongo/server_selector.rb +14 -10
  129. data/lib/mongo/server_selector/selectable.rb +24 -22
  130. data/lib/mongo/socket.rb +6 -3
  131. data/lib/mongo/socket/tcp.rb +2 -2
  132. data/lib/mongo/socket/unix.rb +5 -8
  133. data/lib/mongo/uri.rb +243 -139
  134. data/lib/mongo/version.rb +1 -1
  135. data/spec/mongo/address/unix_spec.rb +1 -1
  136. data/spec/mongo/address_spec.rb +25 -0
  137. data/spec/mongo/auth/ldap/conversation_spec.rb +43 -0
  138. data/spec/mongo/auth/user/view_spec.rb +26 -1
  139. data/spec/mongo/bulk_write/ordered_combiner_spec.rb +271 -0
  140. data/spec/mongo/bulk_write/unordered_combiner_spec.rb +239 -0
  141. data/spec/mongo/bulk_write_spec.rb +332 -166
  142. data/spec/mongo/client_spec.rb +25 -0
  143. data/spec/mongo/cluster/topology/replica_set_spec.rb +2 -0
  144. data/spec/mongo/collection/view/aggregation_spec.rb +65 -0
  145. data/spec/mongo/collection/view/immutable_spec.rb +103 -0
  146. data/spec/mongo/collection/view/map_reduce_spec.rb +98 -3
  147. data/spec/mongo/collection/view/readable_spec.rb +17 -30
  148. data/spec/mongo/collection/view_spec.rb +233 -7
  149. data/spec/mongo/collection_spec.rb +360 -18
  150. data/spec/mongo/command_monitoring_spec.rb +51 -0
  151. data/spec/mongo/connection_string_spec.rb +137 -0
  152. data/spec/mongo/database_spec.rb +27 -11
  153. data/spec/mongo/grid/file/chunk_spec.rb +5 -5
  154. data/spec/mongo/grid/file/{metadata_spec.rb → info_spec.rb} +29 -17
  155. data/spec/mongo/grid/file_spec.rb +8 -8
  156. data/spec/mongo/grid/fs_bucket_spec.rb +1020 -0
  157. data/spec/mongo/grid/stream/read_spec.rb +275 -0
  158. data/spec/mongo/grid/stream/write_spec.rb +440 -0
  159. data/spec/mongo/grid/stream_spec.rb +48 -0
  160. data/spec/mongo/gridfs_spec.rb +50 -0
  161. data/spec/mongo/logger_spec.rb +0 -40
  162. data/spec/mongo/monitoring/command_log_subscriber_spec.rb +76 -0
  163. data/spec/mongo/operation/{aggregate_spec.rb → commands/aggregate_spec.rb} +0 -42
  164. data/spec/mongo/operation/{read → commands}/collections_info_spec.rb +1 -1
  165. data/spec/mongo/operation/{command_spec.rb → commands/command_spec.rb} +0 -0
  166. data/spec/mongo/operation/{read → commands}/indexes_spec.rb +1 -1
  167. data/spec/mongo/operation/{map_reduce_spec.rb → commands/map_reduce_spec.rb} +0 -18
  168. data/spec/mongo/operation/kill_cursors_spec.rb +1 -1
  169. data/spec/mongo/operation/{read_preferrable_spec.rb → read_preference_spec.rb} +11 -11
  170. data/spec/mongo/operation/write/bulk/{bulk_delete_spec.rb → delete_spec.rb} +1 -12
  171. data/spec/mongo/operation/write/bulk/{bulk_insert_spec.rb → insert_spec.rb} +1 -12
  172. data/spec/mongo/operation/write/bulk/{bulk_update_spec.rb → update_spec.rb} +1 -12
  173. data/spec/mongo/operation/write/insert_spec.rb +0 -11
  174. data/spec/mongo/protocol/kill_cursors_spec.rb +5 -3
  175. data/spec/mongo/server/description_spec.rb +42 -0
  176. data/spec/mongo/server/monitor_spec.rb +21 -0
  177. data/spec/mongo/server_discovery_and_monitoring_spec.rb +1 -0
  178. data/spec/mongo/server_selection_spec.rb +3 -3
  179. data/spec/mongo/server_selector/nearest_spec.rb +34 -27
  180. data/spec/mongo/server_selector/primary_preferred_spec.rb +31 -30
  181. data/spec/mongo/server_selector/primary_spec.rb +14 -13
  182. data/spec/mongo/server_selector/secondary_preferred_spec.rb +27 -26
  183. data/spec/mongo/server_selector/secondary_spec.rb +23 -22
  184. data/spec/mongo/server_selector_spec.rb +87 -24
  185. data/spec/mongo/socket/unix_spec.rb +52 -0
  186. data/spec/mongo/uri_spec.rb +251 -39
  187. data/spec/spec_helper.rb +11 -4
  188. data/spec/support/authorization.rb +4 -5
  189. data/spec/support/command_monitoring.rb +365 -0
  190. data/spec/support/command_monitoring/bulkWrite.yml +73 -0
  191. data/spec/support/command_monitoring/command.yml +42 -0
  192. data/spec/support/command_monitoring/deleteMany.yml +55 -0
  193. data/spec/support/command_monitoring/deleteOne.yml +55 -0
  194. data/spec/support/command_monitoring/find.yml +219 -0
  195. data/spec/support/command_monitoring/insertMany.yml +81 -0
  196. data/spec/support/command_monitoring/insertOne.yml +51 -0
  197. data/spec/support/command_monitoring/updateMany.yml +67 -0
  198. data/spec/support/command_monitoring/updateOne.yml +95 -0
  199. data/spec/support/connection_string.rb +228 -0
  200. data/spec/support/connection_string_tests/invalid-uris.yml +193 -0
  201. data/spec/support/connection_string_tests/valid-auth.yml +256 -0
  202. data/spec/support/connection_string_tests/valid-host_identifiers.yml +121 -0
  203. data/spec/support/connection_string_tests/valid-options.yml +30 -0
  204. data/spec/support/connection_string_tests/valid-unix_socket-absolute.yml +197 -0
  205. data/spec/support/connection_string_tests/valid-unix_socket-relative.yml +213 -0
  206. data/spec/support/connection_string_tests/valid-warnings.yml +55 -0
  207. data/spec/support/crud.rb +3 -1
  208. data/spec/support/crud/read.rb +14 -10
  209. data/spec/support/crud/write.rb +36 -9
  210. data/spec/support/gridfs.rb +637 -0
  211. data/spec/support/gridfs_tests/delete.yml +157 -0
  212. data/spec/support/gridfs_tests/download.yml +210 -0
  213. data/spec/support/gridfs_tests/download_by_name.yml +113 -0
  214. data/spec/support/gridfs_tests/upload.yml +158 -0
  215. data/spec/support/sdam/rs/equal_electionids.yml +1 -2
  216. data/spec/support/sdam/rs/new_primary_new_electionid.yml +0 -3
  217. data/spec/support/sdam/rs/primary_mismatched_me.yml +37 -0
  218. data/spec/support/sdam/rs/primary_to_no_primary_mismatched_me.yml +75 -0
  219. data/spec/support/sdam/rs/secondary_mismatched_me.yml +37 -0
  220. data/spec/support/sdam/single/direct_connection_rsarbiter.yml +1 -1
  221. data/spec/support/sdam/single/direct_connection_rsprimary.yml +1 -1
  222. data/spec/support/sdam/single/direct_connection_rssecondary.yml +1 -1
  223. data/spec/support/sdam/single/direct_connection_slave.yml +1 -1
  224. data/spec/support/sdam/single/direct_connection_standalone.yml +1 -1
  225. data/spec/support/sdam/single/not_ok_response.yml +0 -1
  226. data/spec/support/server_discovery_and_monitoring.rb +3 -1
  227. data/spec/support/server_selection.rb +3 -1
  228. data/spec/support/shared/bulk_write.rb +192 -0
  229. data/spec/support/shared/server_selector.rb +21 -12
  230. metadata +147 -57
  231. metadata.gz.sig +0 -0
  232. data/lib/mongo/bulk_write/bulk_writable.rb +0 -252
  233. data/lib/mongo/bulk_write/deletable.rb +0 -57
  234. data/lib/mongo/bulk_write/insertable.rb +0 -49
  235. data/lib/mongo/bulk_write/replacable.rb +0 -58
  236. data/lib/mongo/bulk_write/updatable.rb +0 -69
  237. data/lib/mongo/grid/fs.rb +0 -146
  238. data/lib/mongo/operation/list_collections/result.rb +0 -114
  239. data/lib/mongo/operation/list_indexes/result.rb +0 -118
  240. data/lib/mongo/operation/read/collections_info.rb +0 -68
  241. data/lib/mongo/operation/read/indexes.rb +0 -69
  242. data/lib/mongo/operation/read/list_collections.rb +0 -76
  243. data/lib/mongo/operation/read/list_indexes.rb +0 -78
  244. data/lib/mongo/operation/write/bulk/bulk_delete.rb +0 -145
  245. data/lib/mongo/operation/write/bulk/bulk_delete/result.rb +0 -75
  246. data/lib/mongo/operation/write/bulk/bulk_insert.rb +0 -132
  247. data/lib/mongo/operation/write/bulk/bulk_insert/result.rb +0 -130
  248. data/lib/mongo/operation/write/bulk/bulk_mergable.rb +0 -67
  249. data/lib/mongo/operation/write/bulk/bulk_update.rb +0 -154
  250. data/lib/mongo/operation/write/bulk/bulk_update/result.rb +0 -174
  251. data/lib/mongo/operation/write/bulk/legacy_bulk_mergable.rb +0 -83
  252. data/spec/mongo/grid/fs_spec.rb +0 -160
  253. data/spec/mongo/loggable_spec.rb +0 -63
@@ -0,0 +1,637 @@
1
+ # Copyright (C) 2014-2015 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ # Matcher for determining whether the operation completed successfully.
17
+ #
18
+ # @since 2.1.0
19
+ RSpec::Matchers.define :completes_successfully do |test|
20
+
21
+ match do |actual|
22
+ actual == test.expected_result || test.expected_result.nil?
23
+ end
24
+ end
25
+
26
+ # Matcher for determining whether the actual chunks collection matches
27
+ # the expected chunks collection.
28
+ #
29
+ # @since 2.1.0
30
+ RSpec::Matchers.define :match_chunks_collection do |expected|
31
+
32
+ match do |actual|
33
+ return true if expected.nil?
34
+ if expected.find.to_a.empty?
35
+ actual.find.to_a.empty?
36
+ else
37
+ actual.find.all? do |doc|
38
+ if matching_doc = expected.find(files_id: doc['files_id'], n: doc['n']).first
39
+ matching_doc.all? do |k, v|
40
+ doc[k] == v || k == '_id'
41
+ end
42
+ else
43
+ false
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ # Matcher for determining whether the actual files collection matches
51
+ # the expected files collection.
52
+ #
53
+ # @since 2.1.0
54
+ RSpec::Matchers.define :match_files_collection do |expected|
55
+
56
+ match do |actual|
57
+ return true if expected.nil?
58
+ actual.find.all? do |doc|
59
+ if matching_doc = expected.find(_id: doc['_id']).first
60
+ matching_doc.all? do |k, v|
61
+ doc[k] == v
62
+ end
63
+ else
64
+ false
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ # Matcher for determining whether the operation raised the correct error.
71
+ #
72
+ # @since 2.1.0
73
+ RSpec::Matchers.define :match_error do |error|
74
+
75
+ match do |actual|
76
+ Mongo::GridFS::Test::ERROR_MAPPING[error] == actual.class
77
+ end
78
+ end
79
+
80
+
81
+ module Mongo
82
+ module GridFS
83
+
84
+ # Represents a GridFS specification test.
85
+ #
86
+ # @since 2.1.0
87
+ class Spec
88
+
89
+ # @return [ String ] description The spec description.
90
+ #
91
+ # @since 2.1.0
92
+ attr_reader :description
93
+
94
+ # Instantiate the new spec.
95
+ #
96
+ # @example Create the spec.
97
+ # Spec.new(file)
98
+ #
99
+ # @param [ String ] file The name of the file.
100
+ #
101
+ # @since 2.1.0
102
+ def initialize(file)
103
+ @spec = YAML.load(ERB.new(File.new(file).read).result)
104
+ @description = File.basename(file)
105
+ @data = @spec['data']
106
+ end
107
+
108
+ # Get a list of Tests for each test definition.
109
+ #
110
+ # @example Get the list of Tests.
111
+ # spec.tests
112
+ #
113
+ # @return [ Array<Test> ] The list of Tests.
114
+ #
115
+ # @since 2.1.0
116
+ def tests
117
+ @tests ||= @spec['tests'].collect do |test|
118
+ Test.new(@data, test)
119
+ end
120
+ end
121
+ end
122
+
123
+ # Contains shared helper functions for converting YAML test values to Ruby objects.
124
+ #
125
+ # @since 2.1.0
126
+ module Convertible
127
+
128
+ # Convert an integer to the corresponding CRUD method suffix.
129
+ #
130
+ # @param [ Integer ] int The limit.
131
+ #
132
+ # @return [ String ] The CRUD method suffix.
133
+ #
134
+ # @since 2.1.0
135
+ def limit(int)
136
+ int == 0 ? 'many' : 'one'
137
+ end
138
+
139
+ # Convert an id value to a BSON::ObjectId.
140
+ #
141
+ # @param [ Object ] v The value to convert.
142
+ # @param [ Hash ] opts The options.
143
+ #
144
+ # @option opts [ BSON::ObjectId ] :id The id override.
145
+ #
146
+ # @return [ BSON::ObjectId ] The object id.
147
+ #
148
+ # @since 2.1.0
149
+ def convert__id(v, opts = {})
150
+ to_oid(v, opts[:id])
151
+ end
152
+
153
+ # Convert a value to a date.
154
+ #
155
+ # @param [ Object ] v The value to convert.
156
+ # @param [ Hash ] opts The options.
157
+ #
158
+ # @return [ Time ] The upload date time value.
159
+ #
160
+ # @since 2.1.0
161
+ def convert_uploadDate(v, opts = {})
162
+ v.is_a?(Time) ? v : v['$date'] ? Time.parse(v['$date']) : upload_date
163
+ end
164
+
165
+ # Convert an file id value to a BSON::ObjectId.
166
+ #
167
+ # @param [ Object ] v The value to convert.
168
+ # @param [ Hash ] opts The options.
169
+ #
170
+ # @option opts [ BSON::ObjectId ] :id The id override.
171
+ #
172
+ # @return [ BSON::ObjectId ] The object id.
173
+ #
174
+ # @since 2.1.0
175
+ def convert_files_id(v, opts = {})
176
+ to_oid(v, opts[:files_id])
177
+ end
178
+
179
+ # Convert a value to BSON::Binary data.
180
+ #
181
+ # @param [ Object ] v The value to convert.
182
+ # @param [ Hash ] opts The options.
183
+ #
184
+ # @return [ BSON::Binary ] The converted data.
185
+ #
186
+ # @since 2.1.0
187
+ def convert_data(v, opts = {})
188
+ v.is_a?(BSON::Binary) ? v : BSON::Binary.new(to_hex(v['$hex'], opts), :generic)
189
+ end
190
+
191
+ # Transform documents to have the correct object types for serialization.
192
+ #
193
+ # @param [ Array<Hash> ] docs The documents to transform.
194
+ # @param [ Hash ] opts The options.
195
+ #
196
+ # @return [ Array<Hash> ] The transformed documents.
197
+ #
198
+ # @since 2.1.0
199
+ def transform_docs(docs, opts = {})
200
+ docs.collect do |doc|
201
+ doc.each do |k, v|
202
+ doc[k] = send("convert_#{k}", v, opts) if respond_to?("convert_#{k}")
203
+ end
204
+ doc
205
+ end
206
+ end
207
+
208
+ # Convert a string to a hex value.
209
+ #
210
+ # @param [ String ] string The value to convert.
211
+ # @param [ Hash ] opts The options.
212
+ #
213
+ # @return [ String ] The hex value.
214
+ #
215
+ # @since 2.1.0
216
+ def to_hex(string, opts = {})
217
+ [ string ].pack('H*')
218
+ end
219
+
220
+ # Convert an object id represented in json to a BSON::ObjectId.
221
+ # A new BSON::ObjectId is returned if the json document is empty.
222
+ #
223
+ # @param [ Object ] value The value to convert.
224
+ # @param [ Object ] id The id override.
225
+ #
226
+ # @return [ BSON::ObjectId ] The object id.
227
+ #
228
+ # @since 2.1.0
229
+ def to_oid(value, id = nil)
230
+ if id
231
+ id
232
+ elsif value.is_a?(BSON::ObjectId)
233
+ value
234
+ elsif value['$oid']
235
+ BSON::ObjectId.from_string(value['$oid'])
236
+ else
237
+ BSON::ObjectId.new
238
+ end
239
+ end
240
+
241
+ # Convert options.
242
+ #
243
+ # @return [ Hash ] The options.
244
+ #
245
+ # @since 2.1.0
246
+ def options
247
+ @act['arguments']['options'].reduce({}) do |opts, (k, v)|
248
+ opts.merge!(chunk_size: v) if k == "chunkSizeBytes"
249
+ opts.merge!(upload_date: upload_date)
250
+ opts.merge!(content_type: v) if k == "contentType"
251
+ opts.merge!(metadata: v) if k == "metadata"
252
+ opts
253
+ end
254
+ end
255
+ end
256
+
257
+ # Represents a single GridFS test.
258
+ #
259
+ # @since 2.1.0
260
+ class Test
261
+ include Convertible
262
+ extend Forwardable
263
+
264
+ def_delegators :@operation, :expected_files_collection,
265
+ :expected_chunks_collection,
266
+ :result,
267
+ :expected_error,
268
+ :expected_result,
269
+ :error?
270
+
271
+ # The test description.
272
+ #
273
+ # @return [ String ] The test description.
274
+ #
275
+ # @since 2.1.0
276
+ attr_reader :description
277
+
278
+ # The upload date to use in the test.
279
+ #
280
+ # @return [ Time ] The upload date.
281
+ #
282
+ # @since 2.1.0
283
+ attr_reader :upload_date
284
+
285
+ # Mapping of test error strings to driver classes.
286
+ #
287
+ # @since 2.1.0
288
+ ERROR_MAPPING = {
289
+ 'FileNotFound' => Mongo::Error::FileNotFound,
290
+ 'ChunkIsMissing' => Mongo::Error::MissingFileChunk,
291
+ 'ChunkIsWrongSize' => Mongo::Error::UnexpectedChunkLength,
292
+ 'ExtraChunk' => Mongo::Error::ExtraFileChunk,
293
+ 'RevisionNotFound' => Mongo::Error::InvalidFileRevision
294
+ }
295
+
296
+ # Instantiate the new GridFS::Test.
297
+ #
298
+ # @example Create the test.
299
+ # Test.new(data, test)
300
+ #
301
+ # @param [ Array<Hash> ] data The documents the files and chunks
302
+ # collections must have before the test runs.
303
+ # @param [ Hash ] test The test specification.
304
+ #
305
+ # @since 2.1.0
306
+ def initialize(data, test)
307
+ @pre_data = data
308
+ @description = test['description']
309
+ @upload_date = Time.now
310
+ if test['assert']['error']
311
+ @operation = UnsuccessfulOp.new(self, test)
312
+ else
313
+ @operation = SuccessfulOp.new(self, test)
314
+ end
315
+ end
316
+
317
+ # Whether the expected and actual collections should be compared after the test runs.
318
+ #
319
+ # @return [ true, false ] Whether the actual and expected collections should be compared.
320
+ #
321
+ # @since 2.1.0
322
+ def assert_data?
323
+ @operation.assert['data']
324
+ end
325
+
326
+ # Run the test.
327
+ #
328
+ # @example Run the test
329
+ # test.run(fs)
330
+ #
331
+ # @param [ Mongo::Grid::FSBucket ] fs The Grid::FSBucket to use in the test.
332
+ #
333
+ # @since 2.1.0
334
+ def run(fs)
335
+ setup(fs)
336
+ @operation.run(fs)
337
+ end
338
+
339
+ # Clear the files and chunks collection in the FSBucket and other collections used in the test.
340
+ #
341
+ # @example Clear the test collections
342
+ # test.clear_collections(fs)
343
+ #
344
+ # @param [ Mongo::Grid::FSBucket ] fs The Grid::FSBucket whose collections should be cleared.
345
+ #
346
+ # @since 2.1.0
347
+ def clear_collections(fs)
348
+ fs.files_collection.delete_many
349
+ fs.chunks_collection.delete_many
350
+ @operation.clear_collections(fs)
351
+ end
352
+
353
+ private
354
+
355
+ def setup(fs)
356
+ insert_pre_data(fs)
357
+ @operation.arrange(fs)
358
+ end
359
+
360
+ def files_data
361
+ @files_data ||= transform_docs(@pre_data['files'])
362
+ end
363
+
364
+ def chunks_data
365
+ @chunks_data ||= transform_docs(@pre_data['chunks'])
366
+ end
367
+
368
+ def insert_pre_files_data(fs)
369
+ fs.files_collection.insert_many(files_data)
370
+ fs.database['expected.files'].insert_many(files_data) if assert_data?
371
+ end
372
+
373
+ def insert_pre_chunks_data(fs)
374
+ fs.chunks_collection.insert_many(chunks_data)
375
+ fs.database['expected.chunks'].insert_many(chunks_data) if assert_data?
376
+ end
377
+
378
+ def insert_pre_data(fs)
379
+ insert_pre_files_data(fs) unless files_data.empty?
380
+ insert_pre_chunks_data(fs) unless chunks_data.empty?
381
+ end
382
+
383
+ # Contains logic and helper methods shared between a successful and
384
+ # non-successful GridFS test operation.
385
+ #
386
+ # @since 2.1.0
387
+ module Operable
388
+ extend Forwardable
389
+
390
+ def_delegators :@test, :upload_date
391
+
392
+ # The test operation name.
393
+ #
394
+ # @return [ String ] The operation name.
395
+ #
396
+ # @since 2.1.0
397
+ attr_reader :op
398
+
399
+ # The test assertion.
400
+ #
401
+ # @return [ Hash ] The test assertion definition.
402
+ #
403
+ # @since 2.1.0
404
+ attr_reader :assert
405
+
406
+ # The operation result.
407
+ #
408
+ # @return [ Object ] The operation result.
409
+ #
410
+ # @since 2.1.0
411
+ attr_reader :result
412
+
413
+ # The collection containing the expected files.
414
+ #
415
+ # @return [ Mongo::Collection ] The expected files collection.
416
+ #
417
+ # @since 2.1.0
418
+ attr_reader :expected_files_collection
419
+
420
+ # The collection containing the expected chunks.
421
+ #
422
+ # @return [ Mongo::Collection ] The expected chunks collection.
423
+ #
424
+ # @since 2.1.0
425
+ attr_reader :expected_chunks_collection
426
+
427
+ # Instantiate the new test operation.
428
+ #
429
+ # @example Create the test operation.
430
+ # Test.new(data, test)
431
+ #
432
+ # @param [ Test ] test The test.
433
+ # @param [ Hash ] spec The test specification.
434
+ #
435
+ # @since 2.1.0
436
+ def initialize(test, spec)
437
+ @test = test
438
+ @arrange = spec['arrange']
439
+ @act = spec['act']
440
+ @op = @act['operation']
441
+ @arguments = @act['arguments']
442
+ @assert = spec['assert']
443
+ end
444
+
445
+ # Arrange the data before running the operation.
446
+ # This sets up the correct scenario for the test.
447
+ #
448
+ # @example Arrange the data.
449
+ # operation.arrange(fs)
450
+ #
451
+ # @param [ Grid::FSBucket ] fs The FSBucket used in the test.
452
+ #
453
+ # @since 2.1.0
454
+ def arrange(fs)
455
+ if @arrange
456
+ @arrange['data'].each do |data|
457
+ send("#{data.keys.first}_exp_data", fs, data)
458
+ end
459
+ end
460
+ end
461
+
462
+ # Run the test operation.
463
+ #
464
+ # @example Execute the operation.
465
+ # operation.run(fs)
466
+ #
467
+ # @param [ Grid::FSBucket ] fs The FSBucket used in the test.
468
+ #
469
+ # @result [ Object ] The operation result.
470
+ #
471
+ # @since 2.1.0
472
+ def run(fs)
473
+ @expected_files_collection = fs.database['expected.files']
474
+ @expected_chunks_collection = fs.database['expected.chunks']
475
+ act(fs)
476
+ prepare_expected_collections(fs)
477
+ result
478
+ end
479
+
480
+ private
481
+
482
+ def prepare_expected_collections(fs)
483
+ if @test.assert_data?
484
+ @assert['data'].each do |data|
485
+ op = "#{data.keys.first}_exp_data"
486
+ send(op, fs, data)
487
+ end
488
+ end
489
+ end
490
+
491
+ def insert_exp_data(fs, data)
492
+ coll = fs.database[data['insert']]
493
+ if coll.name =~ /.files/
494
+ opts = { id: @result }
495
+ else
496
+ opts = { files_id: @result }
497
+ end
498
+ coll.insert_many(transform_docs(data['documents'], opts))
499
+ end
500
+
501
+ def delete_exp_data(fs, data)
502
+ coll = fs.database[data['delete']]
503
+ data['deletes'].each do |del|
504
+ id = del['q'].keys.first
505
+ coll.find(id => to_oid(del['q'][id])).send("delete_#{limit(del['limit'])}")
506
+ end
507
+ end
508
+
509
+ def update_exp_data(fs, data)
510
+ coll = fs.database[data['update']]
511
+ data['updates'].each do |update|
512
+ sel = update['q'].merge('files_id' => to_oid(update['q']['files_id']))
513
+ data = BSON::Binary.new(to_hex(update['u']['$set']['data']['$hex']), :generic)
514
+ u = update['u'].merge('$set' => { 'data' => data })
515
+ coll.find(sel).update_one(u)
516
+ end
517
+ end
518
+
519
+ def upload(fs)
520
+ io = StringIO.new(to_hex(@arguments['source']['$hex']))
521
+ fs.upload_from_stream(@arguments['filename'], io, options)
522
+ end
523
+
524
+ def download(fs)
525
+ io = StringIO.new.set_encoding(BSON::BINARY)
526
+ fs.download_to_stream(to_oid(@arguments['id']), io)
527
+ io.string
528
+ end
529
+
530
+ def download_by_name(fs)
531
+ io = StringIO.new.set_encoding(BSON::BINARY)
532
+ if @arguments['options']
533
+ fs.download_to_stream_by_name(@arguments['filename'], io, revision: @arguments['options']['revision'])
534
+ else
535
+ fs.download_to_stream_by_name(@arguments['filename'], io)
536
+ end
537
+ io.string
538
+ end
539
+
540
+ def delete(fs)
541
+ fs.delete(to_oid(@arguments['id']))
542
+ end
543
+ end
544
+
545
+ # A GridFS test operation that is expected to succeed.
546
+ #
547
+ # @since 2.1.0
548
+ class SuccessfulOp
549
+ include Convertible
550
+ include Test::Operable
551
+
552
+ # The expected result of executing the operation.
553
+ #
554
+ # @example Get the expected result.
555
+ # operation.expected_result
556
+ #
557
+ # @result [ Object ] The operation result.
558
+ #
559
+ # @since 2.1.0
560
+ def expected_result
561
+ if @assert['result'] == '&result'
562
+ @result
563
+ elsif @assert['result'] != 'void'
564
+ to_hex(@assert['result']['$hex'])
565
+ end
566
+ end
567
+
568
+ # Execute the operation.
569
+ #
570
+ # @example Execute the operation.
571
+ # operation.act(fs)
572
+ #
573
+ # @param [ Grid::FSBucket ] fs The FSBucket used in the test.
574
+ #
575
+ # @result [ Object ] The operation result.
576
+ #
577
+ # @since 2.1.0
578
+ def act(fs)
579
+ @result = send(op, fs)
580
+ end
581
+
582
+ # Whether this operation is expected to raise an error.
583
+ #
584
+ # @return [ false ] The operation is expected to succeed.
585
+ #
586
+ # @since 2.1.0
587
+ def error?
588
+ false
589
+ end
590
+ end
591
+
592
+ class UnsuccessfulOp
593
+ include Convertible
594
+ include Test::Operable
595
+
596
+ # Whether this operation is expected to raise an error.
597
+ #
598
+ # @return [ true ] The operation is expected to fail.
599
+ #
600
+ # @since 2.1.0
601
+ def error?
602
+ true
603
+ end
604
+
605
+ # The expected error.
606
+ #
607
+ # @example Execute the operation.
608
+ # operation.expected_error
609
+ #
610
+ # @return [ String ] The expected error name.
611
+ #
612
+ # @since 2.1.0
613
+ def expected_error
614
+ @assert['error']
615
+ end
616
+
617
+ # Execute the operation.
618
+ #
619
+ # @example Execute the operation.
620
+ # operation.act(fs)
621
+ #
622
+ # @param [ Grid::FSBucket ] fs The FSBucket used in the test.
623
+ #
624
+ # @result [ Mongo::Error ] The error encountered.
625
+ #
626
+ # @since 2.1.0
627
+ def act(fs)
628
+ begin
629
+ send(op, fs)
630
+ rescue => ex
631
+ @result = ex
632
+ end
633
+ end
634
+ end
635
+ end
636
+ end
637
+ end