PageRenderTime 49ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/MongoDB.Driver.Tests/MongoClientTests.cs

http://github.com/mongodb/mongo-csharp-driver
C# | 495 lines | 425 code | 54 blank | 16 comment | 19 complexity | 1100b180bc42e53cd89f927a946e47be MD5 | raw file
Possible License(s): Apache-2.0
  1. /* Copyright 2010-present 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. using System;
  16. using System.Collections.Generic;
  17. using System.Linq;
  18. using System.Reflection;
  19. using System.Threading;
  20. using FluentAssertions;
  21. using MongoDB.Bson;
  22. using MongoDB.Bson.TestHelpers;
  23. using MongoDB.Bson.TestHelpers.XunitExtensions;
  24. using MongoDB.Driver.Core.Bindings;
  25. using MongoDB.Driver.Core.Clusters;
  26. using MongoDB.Driver.Core.Clusters.ServerSelectors;
  27. using MongoDB.Driver.Core.Operations;
  28. using MongoDB.Driver.Core.Servers;
  29. using MongoDB.Driver.Core.TestHelpers;
  30. using Moq;
  31. using Xunit;
  32. namespace MongoDB.Driver.Tests
  33. {
  34. public class MongoClientTests
  35. {
  36. [Fact]
  37. public void constructor_with_settings_should_throw_when_settings_is_null()
  38. {
  39. var exception = Record.Exception(() => new MongoClient((MongoClientSettings)null));
  40. var argumentNullException = exception.Should().BeOfType<ArgumentNullException>().Subject;
  41. argumentNullException.ParamName.Should().Be("settings");
  42. }
  43. [Fact]
  44. public void UsesSameClusterForIdenticalSettings()
  45. {
  46. var client1 = new MongoClient("mongodb://localhost");
  47. var cluster1 = client1.Cluster;
  48. var client2 = new MongoClient("mongodb://localhost");
  49. var cluster2 = client2.Cluster;
  50. Assert.Same(cluster1, cluster2);
  51. }
  52. [Fact]
  53. public void UsesSameClusterWhenReadPreferenceTagsAreTheSame()
  54. {
  55. var client1 = new MongoClient("mongodb://localhost/?readPreference=secondary;readPreferenceTags=dc:ny");
  56. var cluster1 = client1.Cluster;
  57. var client2 = new MongoClient("mongodb://localhost/?readPreference=secondary;readPreferenceTags=dc:ny");
  58. var cluster2 = client2.Cluster;
  59. Assert.Same(cluster1, cluster2);
  60. }
  61. [Theory]
  62. [ParameterAttributeData]
  63. public void DropDatabase_should_invoke_the_correct_operation(
  64. [Values(false, true)] bool usingSession,
  65. [Values(false, true)] bool async)
  66. {
  67. var operationExecutor = new MockOperationExecutor();
  68. var writeConcern = new WriteConcern(1);
  69. var subject = new MongoClient(operationExecutor, DriverTestConfiguration.GetClientSettings()).WithWriteConcern(writeConcern);
  70. var session = CreateClientSession();
  71. var cancellationToken = new CancellationTokenSource().Token;
  72. if (usingSession)
  73. {
  74. if (async)
  75. {
  76. subject.DropDatabaseAsync(session, "awesome", cancellationToken).GetAwaiter().GetResult();
  77. }
  78. else
  79. {
  80. subject.DropDatabase(session, "awesome", cancellationToken);
  81. }
  82. }
  83. else
  84. {
  85. if (async)
  86. {
  87. subject.DropDatabaseAsync("awesome", cancellationToken).GetAwaiter().GetResult();
  88. }
  89. else
  90. {
  91. subject.DropDatabase("awesome", cancellationToken);
  92. }
  93. }
  94. var call = operationExecutor.GetWriteCall<BsonDocument>();
  95. if (usingSession)
  96. {
  97. call.SessionId.Should().BeSameAs(session.ServerSession.Id);
  98. }
  99. else
  100. {
  101. call.UsedImplicitSession.Should().BeTrue();
  102. }
  103. call.CancellationToken.Should().Be(cancellationToken);
  104. var dropDatabaseOperation = call.Operation.Should().BeOfType<DropDatabaseOperation>().Subject;
  105. dropDatabaseOperation.DatabaseNamespace.Should().Be(new DatabaseNamespace("awesome"));
  106. dropDatabaseOperation.WriteConcern.Should().BeSameAs(writeConcern);
  107. }
  108. [Theory]
  109. [ParameterAttributeData]
  110. public void ListDatabaseNames_should_invoke_the_correct_operation(
  111. [Values(false, true)] bool usingSession,
  112. [Values(false, true)] bool async)
  113. {
  114. var operationExecutor = new MockOperationExecutor();
  115. var subject = new MongoClient(operationExecutor, DriverTestConfiguration.GetClientSettings());
  116. var session = CreateClientSession();
  117. var cancellationToken = new CancellationTokenSource().Token;
  118. var listDatabaseNamesResult = @"
  119. {
  120. ""databases"" : [
  121. {
  122. ""name"" : ""admin"",
  123. ""sizeOnDisk"" : 131072,
  124. ""empty"" : false
  125. },
  126. {
  127. ""name"" : ""blog"",
  128. ""sizeOnDisk"" : 11669504,
  129. ""empty"" : false
  130. },
  131. {
  132. ""name"" : ""test-chambers"",
  133. ""sizeOnDisk"" : 222883840,
  134. ""empty"" : false
  135. },
  136. {
  137. ""name"" : ""recipes"",
  138. ""sizeOnDisk"" : 73728,
  139. ""empty"" : false
  140. },
  141. {
  142. ""name"" : ""employees"",
  143. ""sizeOnDisk"" : 225280,
  144. ""empty"" : false
  145. }
  146. ],
  147. ""totalSize"" : 252534784,
  148. ""ok"" : 1
  149. }";
  150. var operationResult = BsonDocument.Parse(listDatabaseNamesResult);
  151. operationExecutor.EnqueueResult(CreateListDatabasesOperationCursor(operationResult));
  152. IList<string> databaseNames;
  153. if (async)
  154. {
  155. if (usingSession)
  156. {
  157. databaseNames = subject.ListDatabaseNamesAsync(session, cancellationToken).GetAwaiter().GetResult().ToList();
  158. }
  159. else
  160. {
  161. databaseNames = subject.ListDatabaseNamesAsync(cancellationToken).GetAwaiter().GetResult().ToList();
  162. }
  163. }
  164. else
  165. {
  166. if (usingSession)
  167. {
  168. databaseNames = subject.ListDatabaseNames(session, cancellationToken).ToList();
  169. }
  170. else
  171. {
  172. databaseNames = subject.ListDatabaseNames(cancellationToken).ToList();
  173. }
  174. }
  175. var call = operationExecutor.GetReadCall<IAsyncCursor<BsonDocument>>();
  176. if (usingSession)
  177. {
  178. call.SessionId.Should().BeSameAs(session.ServerSession.Id);
  179. }
  180. else
  181. {
  182. call.UsedImplicitSession.Should().BeTrue();
  183. }
  184. call.CancellationToken.Should().Be(cancellationToken);
  185. var operation = call.Operation.Should().BeOfType<ListDatabasesOperation>().Subject;
  186. operation.NameOnly.Should().Be(true);
  187. databaseNames.Should().Equal(operationResult["databases"].AsBsonArray.Select(record => record["name"].AsString));
  188. }
  189. private IAsyncCursor<BsonDocument> CreateListDatabasesOperationCursor(BsonDocument reply)
  190. {
  191. var databases = reply["databases"].AsBsonArray.OfType<BsonDocument>();
  192. return new SingleBatchAsyncCursor<BsonDocument>(databases.ToList());
  193. }
  194. [Theory]
  195. [ParameterAttributeData]
  196. public void ListDatabases_should_invoke_the_correct_operation(
  197. [Values(false, true)] bool usingSession,
  198. [Values(false, true)] bool async)
  199. {
  200. var operationExecutor = new MockOperationExecutor();
  201. var subject = new MongoClient(operationExecutor, DriverTestConfiguration.GetClientSettings());
  202. var session = CreateClientSession();
  203. var cancellationToken = new CancellationTokenSource().Token;
  204. var filterDocument = BsonDocument.Parse("{ name : \"awesome\" }");
  205. var filterDefinition = (FilterDefinition<BsonDocument>)filterDocument;
  206. var nameOnly = true;
  207. var options = new ListDatabasesOptions
  208. {
  209. Filter = filterDefinition,
  210. NameOnly = nameOnly
  211. };
  212. if (usingSession)
  213. {
  214. if (async)
  215. {
  216. subject.ListDatabasesAsync(session, options, cancellationToken).GetAwaiter().GetResult();
  217. }
  218. else
  219. {
  220. subject.ListDatabases(session, options, cancellationToken);
  221. }
  222. }
  223. else
  224. {
  225. if (async)
  226. {
  227. subject.ListDatabasesAsync(options, cancellationToken).GetAwaiter().GetResult();
  228. }
  229. else
  230. {
  231. subject.ListDatabases(options, cancellationToken);
  232. }
  233. }
  234. var call = operationExecutor.GetReadCall<IAsyncCursor<BsonDocument>>();
  235. if (usingSession)
  236. {
  237. call.SessionId.Should().BeSameAs(session.ServerSession.Id);
  238. }
  239. else
  240. {
  241. call.UsedImplicitSession.Should().BeTrue();
  242. }
  243. call.CancellationToken.Should().Be(cancellationToken);
  244. var operation = call.Operation.Should().BeOfType<ListDatabasesOperation>().Subject;
  245. operation.Filter.Should().Be(filterDocument);
  246. operation.NameOnly.Should().Be(nameOnly);
  247. }
  248. [Theory]
  249. [ParameterAttributeData]
  250. public void Watch_should_invoke_the_correct_operation(
  251. [Values(false, true)] bool usingSession,
  252. [Values(false, true)] bool async)
  253. {
  254. var operationExecutor = new MockOperationExecutor();
  255. var clientSettings = DriverTestConfiguration.GetClientSettings();
  256. var subject = new MongoClient(operationExecutor, clientSettings);
  257. var session = usingSession ? CreateClientSession() : null;
  258. var pipeline = new EmptyPipelineDefinition<ChangeStreamDocument<BsonDocument>>().Limit(1);
  259. var options = new ChangeStreamOptions
  260. {
  261. BatchSize = 123,
  262. Collation = new Collation("en-us"),
  263. FullDocument = ChangeStreamFullDocumentOption.UpdateLookup,
  264. MaxAwaitTime = TimeSpan.FromSeconds(123),
  265. ResumeAfter = new BsonDocument(),
  266. StartAfter = new BsonDocument(),
  267. StartAtOperationTime = new BsonTimestamp(1, 2)
  268. };
  269. var cancellationToken = new CancellationTokenSource().Token;
  270. var renderedPipeline = new[] { BsonDocument.Parse("{ $limit : 1 }") };
  271. if (usingSession)
  272. {
  273. if (async)
  274. {
  275. subject.WatchAsync(session, pipeline, options, cancellationToken).GetAwaiter().GetResult();
  276. }
  277. else
  278. {
  279. subject.Watch(session, pipeline, options, cancellationToken);
  280. }
  281. }
  282. else
  283. {
  284. if (async)
  285. {
  286. subject.WatchAsync(pipeline, options, cancellationToken).GetAwaiter().GetResult();
  287. }
  288. else
  289. {
  290. subject.Watch(pipeline, options, cancellationToken);
  291. }
  292. }
  293. var call = operationExecutor.GetReadCall<IChangeStreamCursor<ChangeStreamDocument<BsonDocument>>>();
  294. if (usingSession)
  295. {
  296. call.SessionId.Should().BeSameAs(session.ServerSession.Id);
  297. }
  298. else
  299. {
  300. call.UsedImplicitSession.Should().BeTrue();
  301. }
  302. call.CancellationToken.Should().Be(cancellationToken);
  303. var changeStreamOperation = call.Operation.Should().BeOfType<ChangeStreamOperation<ChangeStreamDocument<BsonDocument>>>().Subject;
  304. changeStreamOperation.BatchSize.Should().Be(options.BatchSize);
  305. changeStreamOperation.Collation.Should().BeSameAs(options.Collation);
  306. changeStreamOperation.CollectionNamespace.Should().BeNull();
  307. changeStreamOperation.DatabaseNamespace.Should().BeNull();
  308. changeStreamOperation.FullDocument.Should().Be(options.FullDocument);
  309. changeStreamOperation.MaxAwaitTime.Should().Be(options.MaxAwaitTime);
  310. changeStreamOperation.MessageEncoderSettings.Should().NotBeNull();
  311. changeStreamOperation.Pipeline.Should().Equal(renderedPipeline);
  312. changeStreamOperation.ReadConcern.Should().Be(clientSettings.ReadConcern);
  313. changeStreamOperation.ResultSerializer.Should().BeOfType<ChangeStreamDocumentSerializer<BsonDocument>>();
  314. changeStreamOperation.ResumeAfter.Should().Be(options.ResumeAfter);
  315. changeStreamOperation.StartAfter.Should().Be(options.StartAfter);
  316. changeStreamOperation.StartAtOperationTime.Should().Be(options.StartAtOperationTime);
  317. }
  318. [Fact]
  319. public void WithReadConcern_should_return_expected_result()
  320. {
  321. var originalReadConcern = new ReadConcern(ReadConcernLevel.Linearizable);
  322. var subject = new MongoClient().WithReadConcern(originalReadConcern);
  323. var newReadConcern = new ReadConcern(ReadConcernLevel.Majority);
  324. var result = subject.WithReadConcern(newReadConcern);
  325. subject.Settings.ReadConcern.Should().BeSameAs(originalReadConcern);
  326. result.Settings.ReadConcern.Should().BeSameAs(newReadConcern);
  327. result.WithReadConcern(originalReadConcern).Settings.Should().Be(subject.Settings);
  328. }
  329. [Fact]
  330. public void WithReadPreference_should_return_expected_result()
  331. {
  332. var originalReadPreference = new ReadPreference(ReadPreferenceMode.Secondary);
  333. var subject = new MongoClient().WithReadPreference(originalReadPreference);
  334. var newReadPreference = new ReadPreference(ReadPreferenceMode.SecondaryPreferred);
  335. var result = subject.WithReadPreference(newReadPreference);
  336. subject.Settings.ReadPreference.Should().BeSameAs(originalReadPreference);
  337. result.Settings.ReadPreference.Should().BeSameAs(newReadPreference);
  338. result.WithReadPreference(originalReadPreference).Settings.Should().Be(subject.Settings);
  339. }
  340. [Fact]
  341. public void WithWriteConcern_should_return_expected_result()
  342. {
  343. var originalWriteConcern = new WriteConcern(2);
  344. var subject = new MongoClient().WithWriteConcern(originalWriteConcern);
  345. var newWriteConcern = new WriteConcern(3);
  346. var result = subject.WithWriteConcern(newWriteConcern);
  347. subject.Settings.WriteConcern.Should().BeSameAs(originalWriteConcern);
  348. result.Settings.WriteConcern.Should().BeSameAs(newWriteConcern);
  349. result.WithWriteConcern(originalWriteConcern).Settings.Should().Be(subject.Settings);
  350. }
  351. [Theory]
  352. [InlineData("{ connectionMode : 'Automatic', clusterType : 'Standalone', servers : [ { state : 'Disconnected', type : 'Unknown' } ] }", null)]
  353. [InlineData("{ connectionMode : 'Automatic', clusterType : 'Standalone', servers : [ { state : 'Connected', type : 'Standalone' } ] }", false)]
  354. [InlineData("{ connectionMode : 'Automatic', clusterType : 'Standalone', servers : [ { state : 'Connected', type : 'Standalone', logicalSessionTimeoutMinutes : 30 } ] }", true)]
  355. [InlineData("{ connectionMode : 'Automatic', clusterType : 'ReplicaSet', servers : [ { state : 'Disconnected', type : 'Unknown' }, { state : 'Disconnected', type : 'Unknown' } ] }", null)]
  356. [InlineData("{ connectionMode : 'Automatic', clusterType : 'ReplicaSet', servers : [ { state : 'Disconnected', type : 'Unknown' }, { state : 'Connected', type : 'ReplicaSetPrimary' } ] }", false)]
  357. [InlineData("{ connectionMode : 'Automatic', clusterType : 'ReplicaSet', servers : [ { state : 'Disconnected', type : 'Unknown' }, { state : 'Connected', type : 'ReplicaSetSecondary' } ] }", false)]
  358. [InlineData("{ connectionMode : 'Automatic', clusterType : 'ReplicaSet', servers : [ { state : 'Disconnected', type : 'Unknown' }, { state : 'Connected', type : 'ReplicaSetArbiter' } ] }", null)]
  359. [InlineData("{ connectionMode : 'Automatic', clusterType : 'ReplicaSet', servers : [ { state : 'Disconnected', type : 'Unknown' }, { state : 'Connected', type : 'ReplicaSetPrimary', logicalSessionTimeoutMinutes : 30 } ] }", true)]
  360. [InlineData("{ connectionMode : 'Automatic', clusterType : 'ReplicaSet', servers : [ { state : 'Disconnected', type : 'Unknown' }, { state : 'Connected', type : 'ReplicaSetSecondary', logicalSessionTimeoutMinutes : 30 } ] }", true)]
  361. [InlineData("{ connectionMode : 'Automatic', clusterType : 'ReplicaSet', servers : [ { state : 'Disconnected', type : 'Unknown' }, { state : 'Connected', type : 'ReplicaSetArbiter', logicalSessionTimeoutMinutes : 30 } ] }", null)]
  362. [InlineData("{ connectionMode : 'Automatic', clusterType : 'Sharded', servers : [ { state : 'Disconnected', type : 'Unknown' }, { state : 'Disconnected', type : 'Unknown' } ] }", null)]
  363. [InlineData("{ connectionMode : 'Automatic', clusterType : 'Sharded', servers : [ { state : 'Disconnected', type : 'Unknown' }, { state : 'Connected', type : 'ShardRouter' } ] }", false)]
  364. [InlineData("{ connectionMode : 'Automatic', clusterType : 'Sharded', servers : [ { state : 'Disconnected', type : 'Unknown' }, { state : 'Connected', type : 'ShardRouter', logicalSessionTimeoutMinutes : 30 } ] }", true)]
  365. [InlineData("{ connectionMode : 'Direct', clusterType : 'ReplicaSet', servers : [ { state : 'Disconnected', type : 'Unknown' } ] }", null)]
  366. [InlineData("{ connectionMode : 'Direct', clusterType : 'ReplicaSet', servers : [ { state : 'Connected', type : 'ReplicaSetOther' } ] }", false)]
  367. [InlineData("{ connectionMode : 'Direct', clusterType : 'ReplicaSet', servers : [ { state : 'Connected', type : 'ReplicaSetOther', logicalSessionTimeoutMinutes : 30 } ] }", true)]
  368. [InlineData("{ connectionMode : 'Direct', clusterType : 'ReplicaSet', servers : [ { state : 'Connected', type : 'ReplicaSetPrimary' } ] }", false)]
  369. [InlineData("{ connectionMode : 'Direct', clusterType : 'ReplicaSet', servers : [ { state : 'Connected', type : 'ReplicaSetPrimary', logicalSessionTimeoutMinutes : 30 } ] }", true)]
  370. public void AreSessionsSupported_should_return_expected_result(string clusterDescriptionJson, bool? expectedResult)
  371. {
  372. var subject = new MongoClient("mongodb://localhost");
  373. var clusterDescription = ClusterDescriptionParser.Parse(clusterDescriptionJson);
  374. var result = subject.AreSessionsSupported(clusterDescription);
  375. result.Should().Be(expectedResult);
  376. }
  377. // private methods
  378. private IClientSessionHandle CreateClientSession()
  379. {
  380. var client = new Mock<IMongoClient>().Object;
  381. var options = new ClientSessionOptions();
  382. var cluster = Mock.Of<ICluster>();
  383. var coreServerSession = new CoreServerSession();
  384. var coreSession = new CoreSession(cluster, coreServerSession, options.ToCore());
  385. var coreSessionHandle = new CoreSessionHandle(coreSession);
  386. return new ClientSessionHandle(client, options, coreSessionHandle);
  387. }
  388. }
  389. public class AreSessionsSupportedServerSelectorTests
  390. {
  391. [Theory]
  392. [InlineData("{ clusterType : 'Standalone', servers : [ { state : 'Disconnected', type : 'Unknown' } ]}")]
  393. public void SelectServers_should_set_ClusterDescription(string clusterDescriptionJson)
  394. {
  395. var subject = CreateSubject();
  396. var cluster = ClusterDescriptionParser.Parse(clusterDescriptionJson);
  397. var connectedServers = cluster.Servers.Where(s => s.State == ServerState.Connected);
  398. var result = subject.SelectServers(cluster, connectedServers);
  399. AreSessionsSupportedServerSelectorReflector.ClusterDescription(subject).Should().BeSameAs(cluster);
  400. }
  401. [Theory]
  402. [InlineData("{ connectionMode : 'Direct', clusterType : 'ReplicaSet', servers : [ { state : 'Connected', type : 'ReplicaSetArbiter' } ]}")]
  403. public void SelectServers_should_return_all_servers_when_connection_mode_is_direct(string clusterDescriptionJson)
  404. {
  405. var subject = CreateSubject();
  406. var cluster = ClusterDescriptionParser.Parse(clusterDescriptionJson);
  407. var connectedServers = cluster.Servers.Where(s => s.State == ServerState.Connected).ToList();
  408. var result = subject.SelectServers(cluster, connectedServers);
  409. result.Should().Equal(connectedServers);
  410. }
  411. [Theory]
  412. [InlineData("{ clusterType : 'ReplicaSet', servers : [ { state : 'Connected', type : 'ReplicaSetArbiter' }, { state : 'Connected', type : 'ReplicaSetPrimary' } ]}")]
  413. public void SelectServers_should_return_data_bearing_servers_when_connection_mode_is__not_direct(string clusterDescriptionJson)
  414. {
  415. var subject = CreateSubject();
  416. var cluster = ClusterDescriptionParser.Parse(clusterDescriptionJson);
  417. var connectedServers = cluster.Servers.Where(s => s.State == ServerState.Connected).ToList();
  418. var dataBearingServers = connectedServers.Skip(1).Take(1);
  419. var result = subject.SelectServers(cluster, connectedServers);
  420. result.Should().Equal(dataBearingServers);
  421. }
  422. // private methods
  423. private IServerSelector CreateSubject()
  424. {
  425. return AreSessionsSupportedServerSelectorReflector.CreateInstance();
  426. }
  427. }
  428. public static class MongoClientReflector
  429. {
  430. public static bool? AreSessionsSupported(this MongoClient obj, ClusterDescription clusterDescription) => (bool?)Reflector.Invoke(obj, nameof(AreSessionsSupported), clusterDescription);
  431. }
  432. public static class AreSessionsSupportedServerSelectorReflector
  433. {
  434. public static IServerSelector CreateInstance()
  435. {
  436. var type = typeof(MongoClient).GetTypeInfo().Assembly.GetType("MongoDB.Driver.MongoClient+AreSessionsSupportedServerSelector");
  437. return (IServerSelector)Activator.CreateInstance(type);
  438. }
  439. public static ClusterDescription ClusterDescription(IServerSelector obj) => (ClusterDescription)Reflector.GetFieldValue(obj, nameof(ClusterDescription), BindingFlags.Public | BindingFlags.Instance);
  440. }
  441. }