feat: Initialize Zastava Webhook service with TLS and Authority authentication

- Added Program.cs to set up the web application with Serilog for logging, health check endpoints, and a placeholder admission endpoint.
- Configured Kestrel server to use TLS 1.3 and handle client certificates appropriately.
- Created StellaOps.Zastava.Webhook.csproj with necessary dependencies including Serilog and Polly.
- Documented tasks in TASKS.md for the Zastava Webhook project, outlining current work and exit criteria for each task.
This commit is contained in:
2025-10-19 18:36:22 +03:00
parent 7e2fa0a42a
commit 5ce40d2eeb
966 changed files with 91038 additions and 1850 deletions

View File

@@ -0,0 +1,146 @@
using System;
using System.IO;
using System.Reflection;
using FluentAssertions;
using Machine.Specifications;
using Mongo2Go.Helper;
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local
namespace Mongo2GoTests
{
[Subject("FolderSearch")]
public class when_requesting_current_executing_directory
{
public static string directory;
Because of = () => directory = FolderSearch.CurrentExecutingDirectory();
It should_contain_correct_path = () => directory.Should().Contain(Path.Combine("Mongo2GoTests", "bin"));
}
[Subject("FolderSearch")]
public class when_searching_for_folder : FolderSearchSpec
{
static string startDirectory = Path.Combine(BaseDir, "test1", "test2");
static string searchPattern = Path.Combine("packages", "Mongo2Go*", "tools", "mongodb-win32-i386*", "bin");
static string directory;
Because of = () => directory = startDirectory.FindFolder(searchPattern);
It should_find_the_path_with_the_highest_version_number = () => directory.Should().Be(MongoBinaries);
}
[Subject("FolderSearch")]
public class when_searching_for_not_existing_folder : FolderSearchSpec
{
static string startDirectory = Path.Combine(BaseDir, "test1", "test2");
static string searchPattern = Path.Combine("packages", "Mongo2Go*", "XXX", "mongodb-win32-i386*", "bin");
static string directory;
Because of = () => directory = startDirectory.FindFolder(searchPattern);
It should_return_null = () => directory.Should().BeNull();
}
[Subject("FolderSearch")]
public class when_searching_for_not_existing_start_dir : FolderSearchSpec
{
static string startDirectory = Path.Combine(Path.GetRandomFileName());
static string searchPattern = Path.Combine("packages", "Mongo2Go*", "XXX", "mongodb-win32-i386*", "bin");
static string directory;
Because of = () => directory = startDirectory.FindFolder(searchPattern);
It should_return_null = () => directory.Should().BeNull();
}
[Subject("FolderSearch")]
public class when_searching_for_folder_upwards : FolderSearchSpec
{
static string searchPattern = Path.Combine("packages", "Mongo2Go*", "tools", "mongodb-win32-i386*", "bin");
static string directory;
Because of = () => directory = LocationOfAssembly.FindFolderUpwards(searchPattern);
It should_find_the_path_with_the_highest_version_number = () => directory.Should().Be(MongoBinaries);
}
[Subject("FolderSearch")]
public class when_searching_for_not_existing_folder_upwards : FolderSearchSpec
{
static string searchPattern = Path.Combine("packages", "Mongo2Go*", "XXX", "mongodb-win32-i386*", "bin");
static string directory;
Because of = () => directory = LocationOfAssembly.FindFolderUpwards(searchPattern);
It should_return_null = () => directory.Should().BeNull();
}
[Subject("FolderSearch")]
public class when_remove_last_part_of_path
{
static string directory;
Because of = () => directory = Path.Combine("test1", "test2", "test3").RemoveLastPart();
It should_remove_the_element = () => directory.Should().Be(Path.Combine("test1", "test2"));
}
[Subject("FolderSearch")]
public class when_remove_last_part_of_single_element_path
{
static string directory;
Because of = () => directory = "test1".RemoveLastPart();
It should_return_null = () => directory.Should().BeNull();
}
[Subject("FolderSearch")]
public class when_directory_contains_multiple_versions_mongo2go
{
private readonly string[] directories;
private static string getAssemblyVersion()
{
// ReSharper disable once PossibleNullReferenceException
return typeof(FolderSearch).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
}
public when_directory_contains_multiple_versions_mongo2go()
{
// setup some directories
directories = new[]
{
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, getAssemblyVersion() + "a"),
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "2.2.9"),
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, getAssemblyVersion())
};
foreach (var d in directories)
Directory.CreateDirectory(d);
}
private static string path;
private Because of = () => path = FolderSearch.FindFolder(AppDomain.CurrentDomain.BaseDirectory, "*");
private It should_return_the_one_that_matches_our_own_assembly_version =
() => path.Should().Be(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, getAssemblyVersion()));
}
public class FolderSearchSpec
{
public static string BaseDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
public static string MongoBinaries = Path.Combine(BaseDir, "test1", "test2", "packages", "Mongo2Go.1.2.3", "tools", "mongodb-win32-i386-2.0.7-rc0", "bin");
public static string MongoOlderBinaries = Path.Combine(BaseDir, "test1", "test2", "packages", "Mongo2Go.1.1.1", "tools", "mongodb-win32-i386-2.0.7-rc0", "bin");
public static string LocationOfAssembly = Path.Combine(BaseDir, "test1", "test2", "Project", "bin");
Establish context = () =>
{
if (!Directory.Exists(BaseDir)) { Directory.CreateDirectory(BaseDir); }
if (!Directory.Exists(MongoBinaries)) { Directory.CreateDirectory(MongoBinaries); }
if (!Directory.Exists(MongoOlderBinaries)) { Directory.CreateDirectory(MongoOlderBinaries); }
if (!Directory.Exists(LocationOfAssembly)) { Directory.CreateDirectory(LocationOfAssembly); }
};
Cleanup stuff = () => { if (Directory.Exists(BaseDir)) { Directory.Delete(BaseDir, true); }};
}
}
// ReSharper restore UnusedMember.Local
// ReSharper restore InconsistentNaming

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Mongo2Go\Mongo2Go.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Machine.Specifications" Version="1.0.0" />
<PackageReference Include="Machine.Specifications.Runner.VisualStudio" Version="2.10.1" />
<PackageReference Include="MELT" Version="0.7.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,76 @@
using FluentAssertions;
using Machine.Specifications;
using Mongo2Go.Helper;
using System;
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local
namespace Mongo2GoTests
{
[Subject(typeof(MongodArguments))]
public class when_null_additional_arguments_return_empty_string
{
private static string validAdditionalArguments;
Because of = () => validAdditionalArguments = MongodArguments.GetValidAdditionalArguments(string.Empty, null);
It should_be_empty_string = () => validAdditionalArguments.Should().BeEmpty();
}
[Subject(typeof(MongodArguments))]
public class when_no_additional_arguments_return_empty_string
{
private static string validAdditionalArguments;
Because of = () => validAdditionalArguments = MongodArguments.GetValidAdditionalArguments(string.Empty, string.Empty);
It should_be_empty_string = () => validAdditionalArguments.Should().BeEmpty();
}
[Subject(typeof(MongodArguments))]
public class when_additional_arguments_start_with_argument_separator_return_additional_arguments
{
private static string validAdditionalArguments;
private const string additionalArgumentsUnderTest = " --argument_1 under_test --argument_2 under test";
private const string expectedAdditionalArguments = " --argument_1 under_test --argument_2 under test";
Because of = () => validAdditionalArguments = MongodArguments.GetValidAdditionalArguments(string.Empty, additionalArgumentsUnderTest);
It should_be_expected_additional_arguments = () => validAdditionalArguments.Should().Be(expectedAdditionalArguments);
}
[Subject(typeof(MongodArguments))]
public class when_additional_arguments_does_not_start_with_argument_separator_return_additional_arguments
{
private static string validAdditionalArguments;
private const string additionalArgumentsUnderTest = "argument_1 under_test --argument_2 under test";
private const string expectedAdditionalArguments = " --argument_1 under_test --argument_2 under test";
Because of = () => validAdditionalArguments = MongodArguments.GetValidAdditionalArguments(string.Empty, additionalArgumentsUnderTest);
It should_be_expected_additional_arguments = () => validAdditionalArguments.Should().Be(expectedAdditionalArguments);
}
[Subject(typeof(MongodArguments))]
public class when_existing_arguments_and_additional_arguments_do_not_have_shared_options_return_additional_arguments
{
private static string validAdditionalArguments;
private const string existingArguments = "--existing_argument1 --existing_argument2";
private const string additionalArgumentsUnderTest = " --argument_1 under_test --argument_2 under test";
private const string expectedAdditionalArguments = " --argument_1 under_test --argument_2 under test";
Because of = () => validAdditionalArguments = MongodArguments.GetValidAdditionalArguments(existingArguments, additionalArgumentsUnderTest);
It should_be_expected_additional_arguments = () => validAdditionalArguments.Should().Be(expectedAdditionalArguments);
}
[Subject(typeof(MongodArguments))]
public class when_existing_arguments_and_additional_arguments_have_shared_options_throw_argument_exception
{
private static Exception exception;
private const string duplicateArgument = "existing_argument2";
private static readonly string existingArguments = $"--existing_argument1 --{duplicateArgument}";
private static readonly string additionalArgumentsUnderTest = $" --argument_1 under_test --{duplicateArgument} argument2_new_value --argument_2 under test";
Because of = () => exception = Catch.Exception(() => MongodArguments.GetValidAdditionalArguments(existingArguments, additionalArgumentsUnderTest));
It should_throw_argument_exception = () => exception.Should().BeOfType<ArgumentException>();
It should_contain_more_than_instance_of_the_duplicate_argument = () => exception.Message.IndexOf(duplicateArgument, StringComparison.InvariantCultureIgnoreCase).Should().NotBe(exception.Message.LastIndexOf(duplicateArgument, StringComparison.InvariantCultureIgnoreCase));
}
}
// ReSharper restore UnusedMember.Local
// ReSharper restore InconsistentNaming

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Mongo2Go;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
namespace Mongo2GoTests.Runner
{
public class MongoDebuggingTest
{
internal static MongoDbRunner _runner;
internal static IMongoCollection<TestDocument> _collection;
internal static string _databaseName = "IntegrationTest";
internal static string _collectionName = "TestCollection";
internal static IMongoDatabase _database;
internal static void CreateConnection()
{
_runner = MongoDbRunner.StartForDebugging(singleNodeReplSet: false);
MongoClient client = new MongoClient(_runner.ConnectionString);
_database = client.GetDatabase(_databaseName);
_collection = _database.GetCollection<TestDocument>(_collectionName);
}
public static IList<T> ReadBsonFile<T>(string fileName)
{
string[] content = File.ReadAllLines(fileName);
return content.Select(s => BsonSerializer.Deserialize<T>(s)).ToList();
}
}
}

View File

@@ -0,0 +1,47 @@
using Microsoft.Extensions.Logging;
using Mongo2Go;
using MongoDB.Driver;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Mongo2GoTests.Runner
{
public class MongoIntegrationTest
{
internal static MongoDbRunner _runner;
internal static IMongoCollection<TestDocument> _collection;
internal static string _databaseName = "IntegrationTest";
internal static string _collectionName = "TestCollection";
internal static void CreateConnection(ILogger logger = null)
{
_runner = MongoDbRunner.Start(singleNodeReplSet: false, logger: logger);
MongoClient client = new MongoClient(_runner.ConnectionString);
IMongoDatabase database = client.GetDatabase(_databaseName);
_collection = database.GetCollection<TestDocument>(_collectionName);
}
}
public static class TaskExtensions
{
public static async Task WithTimeout(this Task task, TimeSpan timeout)
{
using (var cancellationTokenSource = new CancellationTokenSource())
{
var completedTask = await Task.WhenAny(task, Task.Delay(timeout, cancellationTokenSource.Token));
if (completedTask == task)
{
cancellationTokenSource.Cancel();
await task;
}
else
{
throw new TimeoutException("The operation has timed out.");
}
}
}
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Text;
using Mongo2Go;
using MongoDB.Driver;
namespace Mongo2GoTests.Runner
{
public class MongoTransactionTest
{
internal static MongoDbRunner _runner;
internal static IMongoCollection<TestDocument> _mainCollection;
internal static IMongoCollection<TestDocument> _dependentCollection;
internal static string _databaseName = "TransactionTest";
internal static string _mainCollectionName = "MainCollection";
internal static string _dependentCollectionName = "DependentCollection";
internal static IMongoDatabase database;
internal static IMongoClient client;
internal static void CreateConnection(ushort? singleNodeReplSetWaitTimeout = null)
{
if (singleNodeReplSetWaitTimeout.HasValue)
{
_runner = MongoDbRunner.Start(singleNodeReplSet: true, singleNodeReplSetWaitTimeout: singleNodeReplSetWaitTimeout.Value);
}
else
{
_runner = MongoDbRunner.Start(singleNodeReplSet: true);
}
client = new MongoClient(_runner.ConnectionString);
database = client.GetDatabase(_databaseName);
_mainCollection = database.GetCollection<TestDocument>(_mainCollectionName);
_dependentCollection = database.GetCollection<TestDocument>(_dependentCollectionName);
}
}
}

View File

@@ -0,0 +1,89 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using FluentAssertions;
using Machine.Specifications;
using Mongo2Go.Helper;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using It = Machine.Specifications.It;
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local
namespace Mongo2GoTests.Runner
{
[Subject("Runner Integration Test")]
public class when_using_monogoexport : MongoDebuggingTest
{
static readonly string _testFile = Path.GetTempPath() + "testExport.json";
static IList<TestDocument> parsedContent;
Establish context = () =>
{
CreateConnection();
_database.DropCollection(_collectionName);
_collection.InsertOne(TestDocument.DummyData1());
_collection.InsertOne(TestDocument.DummyData2());
_collection.InsertOne(TestDocument.DummyData3());
};
Because of = () =>
{
_runner.Export(_databaseName, _collectionName, _testFile);
Thread.Sleep(500);
parsedContent = ReadBsonFile<TestDocument>(_testFile);
};
It should_preserve_all_values1 = () => parsedContent[0].Should().BeEquivalentTo(TestDocument.DummyData1(), cfg => cfg.Excluding(d => d.Id));
It should_preserve_all_values2 = () => parsedContent[1].Should().BeEquivalentTo(TestDocument.DummyData2(), cfg => cfg.Excluding(d => d.Id));
It should_preserve_all_values3 = () => parsedContent[2].Should().BeEquivalentTo(TestDocument.DummyData3(), cfg => cfg.Excluding(d => d.Id));
Cleanup stuff = () =>
{
new FileSystem().DeleteFile(_testFile);
_runner.Dispose();
};
}
[Subject("Runner Integration Test")]
public class when_using_monogoimport : MongoDebuggingTest
{
static IQueryable<TestDocument> query;
static readonly string _testFile = Path.GetTempPath() + "testImport.json";
const string _filecontent =
@"{ ""_id"" : { ""$oid"" : ""50227b375dff9218248eadc4"" }, ""StringTest"" : ""Hello World"", ""IntTest"" : 42, ""DateTest"" : { ""$date"" : ""1984-09-30T06:06:06.171Z"" }, ""ListTest"" : [ ""I"", ""am"", ""a"", ""list"", ""of"", ""strings"" ] }" + "\r\n" +
@"{ ""_id"" : { ""$oid"" : ""50227b375dff9218248eadc5"" }, ""StringTest"" : ""Foo"", ""IntTest"" : 23, ""DateTest"" : null, ""ListTest"" : null }" + "\r\n" +
@"{ ""_id"" : { ""$oid"" : ""50227b375dff9218248eadc6"" }, ""StringTest"" : ""Bar"", ""IntTest"" : 77, ""DateTest"" : null, ""ListTest"" : null }" + "\r\n";
Establish context = () =>
{
CreateConnection();
_database.DropCollection(_collectionName);
File.WriteAllText(_testFile, _filecontent);
};
Because of = () =>
{
_runner.Import(_databaseName, _collectionName, _testFile, true);
Thread.Sleep(500);
query = _collection.AsQueryable().Select(c => c).OrderBy(c => c.Id); ;
};
It should_return_document1 = () => query.ToList().ElementAt(0).Should().BeEquivalentTo(TestDocument.DummyData1(), cfg => cfg.Excluding(d => d.Id));
It should_return_document2 = () => query.ToList().ElementAt(1).Should().BeEquivalentTo(TestDocument.DummyData2(), cfg => cfg.Excluding(d => d.Id));
It should_return_document3 = () => query.ToList().ElementAt(2).Should().BeEquivalentTo(TestDocument.DummyData3(), cfg => cfg.Excluding(d => d.Id));
Cleanup stuff = () =>
{
new FileSystem().DeleteFile(_testFile);
_runner.Dispose();
};
}
}
// ReSharper restore UnusedMember.Local
// ReSharper restore InconsistentNaming

View File

@@ -0,0 +1,118 @@
using FluentAssertions;
using Machine.Specifications;
using MELT;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using It = Machine.Specifications.It;
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local
namespace Mongo2GoTests.Runner
{
[Subject("Runner Integration Test")]
public class when_using_the_inbuild_serialization : MongoIntegrationTest
{
static TestDocument findResult;
Establish context = () =>
{
CreateConnection();
_collection.InsertOne(TestDocument.DummyData1());
};
Because of = () => findResult = _collection.FindSync<TestDocument>(_ => true).First();
It should_return_a_result = () => findResult.Should().NotBeNull();
It should_hava_expected_data = () => findResult.Should().BeEquivalentTo(TestDocument.DummyData1(), cfg => cfg.Excluding(d => d.Id));
Cleanup stuff = () => _runner.Dispose();
}
[Subject("Runner Integration Test")]
public class when_using_the_new_linq_support : MongoIntegrationTest
{
static List<TestDocument> queryResult;
Establish context = () =>
{
CreateConnection();
_collection.InsertOne(TestDocument.DummyData1());
_collection.InsertOne(TestDocument.DummyData2());
_collection.InsertOne(TestDocument.DummyData3());
};
Because of = () =>
{
queryResult = (from c in _collection.AsQueryable()
where c.StringTest == TestDocument.DummyData2().StringTest || c.StringTest == TestDocument.DummyData3().StringTest
select c).ToList();
};
It should_return_two_documents = () => queryResult.Count().Should().Be(2);
It should_return_document2 = () => queryResult.ElementAt(0).IntTest = TestDocument.DummyData2().IntTest;
It should_return_document3 = () => queryResult.ElementAt(1).IntTest = TestDocument.DummyData3().IntTest;
Cleanup stuff = () => _runner.Dispose();
}
[Subject("Runner Integration Test")]
public class when_using_commands_that_create_console_output : MongoIntegrationTest
{
static List<Task> taskList = new List<Task>();
private Establish context = () =>
{
CreateConnection();
};
private Because of = () =>
{
var createIndexModel = new CreateIndexModel<TestDocument>(Builders<TestDocument>.IndexKeys.Ascending(x => x.IntTest));
taskList.Add(_collection.Indexes.CreateOneAsync(createIndexModel).WithTimeout(TimeSpan.FromMilliseconds(5000)));
taskList.Add(_collection.Indexes.DropAllAsync().WithTimeout(TimeSpan.FromMilliseconds(5000)));
};
It should_not_timeout = () => Task.WaitAll(taskList.ToArray());
Cleanup stuff = () => _runner.Dispose();
}
[Subject("Runner Integration Test")]
public class when_using_microsoft_ilogger : MongoIntegrationTest
{
static List<Task> taskList = new List<Task>();
static ITestLoggerFactory loggerFactory;
private Establish context = () =>
{
loggerFactory = TestLoggerFactory.Create();
var logger = loggerFactory.CreateLogger("MyTestLogger");
CreateConnection(logger);
};
private Because of = () =>
{
var createIndexModel = new CreateIndexModel<TestDocument>(Builders<TestDocument>.IndexKeys.Ascending(x => x.IntTest));
taskList.Add(_collection.Indexes.CreateOneAsync(createIndexModel).WithTimeout(TimeSpan.FromMilliseconds(5000)));
taskList.Add(_collection.Indexes.DropAllAsync().WithTimeout(TimeSpan.FromMilliseconds(5000)));
};
It should_not_timeout = () => Task.WaitAll(taskList.ToArray());
It should_have_received_many_logs = () =>
loggerFactory.Sink.LogEntries.Count(l => l.LogLevel == Microsoft.Extensions.Logging.LogLevel.Information)
.Should().BeGreaterThan(10);
It should_have_created_collection_statement = () => loggerFactory.Sink.LogEntries
.Count(l => l.Properties.Any(p => p.Key == "message" && (string)p.Value == "createCollection"))
.Should().BeGreaterOrEqualTo(1);
Cleanup stuff = () => _runner.Dispose();
}
}
// ReSharper restore UnusedMember.Local
// ReSharper restore InconsistentNaming

View File

@@ -0,0 +1,108 @@
using FluentAssertions;
using Machine.Specifications;
using Mongo2Go;
using Mongo2Go.Helper;
using Moq;
using System.IO;
using It = Machine.Specifications.It;
#pragma warning disable CS0618 // Type or member is obsolete
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local
namespace Mongo2GoTests.Runner
{
[Subject("Runner")]
public class when_instantiating_the_runner_for_integration_test
{
static MongoDbRunner runner;
static Mock<IPortPool> portPoolMock;
static Mock<IFileSystem> fileSystemMock;
static Mock<IMongoDbProcessStarter> processStarterMock;
static Mock<IMongoBinaryLocator> binaryLocatorMock;
static string exptectedDataDirectory;
static string exptectedLogfile;
static readonly string exptectedConnectString = "mongodb://127.0.0.1:{0}/".Formatted(MongoDbDefaults.TestStartPort + 1);
Establish context = () =>
{
portPoolMock = new Mock<IPortPool>();
portPoolMock.Setup(m => m.GetNextOpenPort()).Returns(MongoDbDefaults.TestStartPort + 1);
fileSystemMock = new Mock<IFileSystem>();
fileSystemMock.Setup(m => m.CreateFolder(Moq.It.IsAny<string>())).Callback<string>(s =>
{
exptectedDataDirectory = s;
exptectedLogfile = Path.Combine(exptectedDataDirectory, MongoDbDefaults.Lockfile);
});
var processMock = new Mock<IMongoDbProcess>();
processStarterMock = new Mock<IMongoDbProcessStarter>();
processStarterMock.Setup(m => m.Start(Moq.It.IsAny<string>(), Moq.It.IsAny<string>(), Moq.It.IsAny<int>(), false, Moq.It.IsAny<string>(), Moq.It.IsAny<ushort>(), null)).Returns(processMock.Object);
binaryLocatorMock = new Mock<IMongoBinaryLocator> ();
binaryLocatorMock.Setup(m => m.Directory).Returns(string.Empty);
};
Because of = () => runner = MongoDbRunner.StartUnitTest(portPoolMock.Object, fileSystemMock.Object, processStarterMock.Object, binaryLocatorMock.Object);
It should_create_the_data_directory = () => fileSystemMock.Verify(x => x.CreateFolder(Moq.It.Is<string>(s => s.StartsWith(Path.GetTempPath()))), Times.Exactly(1));
It should_delete_old_lock_file = () => fileSystemMock.Verify(x => x.DeleteFile(exptectedLogfile), Times.Exactly(1));
It should_start_the_process = () => processStarterMock.Verify(x => x.Start(Moq.It.IsAny<string>(), Moq.It.IsAny<string>(), Moq.It.IsAny<int>(), false, Moq.It.IsAny<string>(), Moq.It.IsAny<ushort>(), null), Times.Exactly(1));
It should_have_expected_connection_string = () => runner.ConnectionString.Should().Be(exptectedConnectString);
It should_return_an_instance_with_state_running = () => runner.State.Should().Be(State.Running);
}
[Subject("Runner")]
public class when_instantiating_the_runner_for_local_debugging
{
static MongoDbRunner runner;
static Mock<IPortWatcher> portWatcherMock;
static Mock<IProcessWatcher> processWatcherMock;
static Mock<IFileSystem> fileSystemMock;
static Mock<IMongoDbProcessStarter> processStarterMock;
static Mock<IMongoBinaryLocator> binaryLocatorMock;
static string exptectedDataDirectory;
static string exptectedLogfile;
Establish context = () =>
{
processWatcherMock = new Mock<IProcessWatcher>();
processWatcherMock.Setup(m => m.IsProcessRunning(Moq.It.IsAny<string>())).Returns(false);
portWatcherMock = new Mock<IPortWatcher>();
portWatcherMock.Setup(m => m.IsPortAvailable(Moq.It.IsAny<int>())).Returns(true);
fileSystemMock = new Mock<IFileSystem>();
fileSystemMock.Setup(m => m.CreateFolder(Moq.It.IsAny<string>())).Callback<string>(s =>
{
exptectedDataDirectory = s;
exptectedLogfile = Path.Combine(exptectedDataDirectory, MongoDbDefaults.Lockfile);
});
var processMock = new Mock<IMongoDbProcess>();
processStarterMock = new Mock<IMongoDbProcessStarter>();
processStarterMock.Setup(m => m.Start(Moq.It.IsAny<string>(), exptectedDataDirectory, MongoDbDefaults.DefaultPort, true, false, Moq.It.IsAny<string>(), Moq.It.IsAny<ushort>(), null)).Returns(processMock.Object);
binaryLocatorMock = new Mock<IMongoBinaryLocator> ();
binaryLocatorMock.Setup(m => m.Directory).Returns(string.Empty);
};
Because of = () => runner = MongoDbRunner.StartForDebuggingUnitTest(processWatcherMock.Object, portWatcherMock.Object, fileSystemMock.Object, processStarterMock.Object, binaryLocatorMock.Object);
It should_check_for_already_running_process = () => processWatcherMock.Verify(x => x.IsProcessRunning(MongoDbDefaults.ProcessName), Times.Exactly(1));
It should_check_the_default_port = () => portWatcherMock.Verify(x => x.IsPortAvailable(MongoDbDefaults.DefaultPort), Times.Exactly(1));
It should_create_the_data_directory = () => fileSystemMock.Verify(x => x.CreateFolder(Moq.It.Is<string>(s => s.StartsWith(Path.GetTempPath()))), Times.Exactly(1));
It should_delete_old_lock_file = () => fileSystemMock.Verify(x => x.DeleteFile(exptectedLogfile), Times.Exactly(1));
It should_return_an_instance_with_state_running = () => runner.State.Should().Be(State.Running);
It should_start_the_process_without_kill = () => processStarterMock.Verify(x => x.Start(Moq.It.IsAny<string>(), exptectedDataDirectory, MongoDbDefaults.DefaultPort, true, false, Moq.It.IsAny<string>(), Moq.It.IsAny<ushort>(), null), Times.Exactly(1));
}
}
// ReSharper restore UnusedMember.Local
// ReSharper restore InconsistentNaming

View File

@@ -0,0 +1,190 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentAssertions;
using Machine.Specifications;
using MongoDB.Driver;
namespace Mongo2GoTests.Runner
{
[Subject("Runner Transaction Test")]
public class when_transaction_completes : MongoTransactionTest
{
private static TestDocument mainDocument;
private static TestDocument dependentDocument;
Establish context = () =>
{
CreateConnection();
database.DropCollection(_mainCollectionName);
database.DropCollection(_dependentCollectionName);
_mainCollection.InsertOne(TestDocument.DummyData2());
_dependentCollection.InsertOne(TestDocument.DummyData2());
};
private Because of = () =>
{
var filter = Builders<TestDocument>.Filter.Where(x => x.IntTest == 23);
var update = Builders<TestDocument>.Update.Inc(i => i.IntTest, 10);
using (var sessionHandle = client.StartSession())
{
try
{
var i = 0;
while (i < 10)
{
try
{
i++;
sessionHandle.StartTransaction(new TransactionOptions(
readConcern: ReadConcern.Local,
writeConcern: WriteConcern.W1));
try
{
var first = _mainCollection.UpdateOne(sessionHandle, filter, update);
var second = _dependentCollection.UpdateOne(sessionHandle, filter, update);
}
catch (Exception)
{
sessionHandle.AbortTransaction();
throw;
}
var j = 0;
while (j < 10)
{
try
{
j++;
sessionHandle.CommitTransaction();
break;
}
catch (MongoException e)
{
if (e.HasErrorLabel("UnknownTransactionCommitResult"))
continue;
throw;
}
}
break;
}
catch (MongoException e)
{
if (e.HasErrorLabel("TransientTransactionError"))
continue;
throw;
}
}
}
catch (Exception)
{
}
}
mainDocument = _mainCollection.FindSync(Builders<TestDocument>.Filter.Empty).FirstOrDefault();
dependentDocument = _dependentCollection.FindSync(Builders<TestDocument>.Filter.Empty).FirstOrDefault();
};
It main_should_be_33 = () => mainDocument.IntTest.Should().Be(33);
It dependent_should_be_33 = () => dependentDocument.IntTest.Should().Be(33);
Cleanup cleanup = () => _runner.Dispose();
}
[Subject("Runner Transaction Test")]
public class when_transaction_is_aborted_before_commit : MongoTransactionTest
{
private static TestDocument mainDocument;
private static TestDocument dependentDocument;
private static TestDocument mainDocument_before_commit;
private static TestDocument dependentDocument_before_commit;
Establish context = () =>
{
CreateConnection();
database.DropCollection(_mainCollectionName);
database.DropCollection(_dependentCollectionName);
_mainCollection.InsertOne(TestDocument.DummyData2());
_dependentCollection.InsertOne(TestDocument.DummyData2());
};
private Because of = () =>
{
var filter = Builders<TestDocument>.Filter.Where(x => x.IntTest == 23);
var update = Builders<TestDocument>.Update.Inc(i => i.IntTest, 10);
using (var sessionHandle = client.StartSession())
{
try
{
var i = 0;
while (i < 2)
{
try
{
i++;
sessionHandle.StartTransaction(new TransactionOptions(
readConcern: ReadConcern.Local,
writeConcern: WriteConcern.W1));
try
{
var first = _mainCollection.UpdateOne(sessionHandle, filter, update);
var second = _dependentCollection.UpdateOne(sessionHandle, filter, update);
mainDocument_before_commit = _mainCollection.FindSync(sessionHandle, Builders<TestDocument>.Filter.Empty).ToList().FirstOrDefault();
dependentDocument_before_commit = _dependentCollection.FindSync(sessionHandle, Builders<TestDocument>.Filter.Empty).ToList().FirstOrDefault();
}
catch (Exception)
{
sessionHandle.AbortTransaction();
throw;
}
//Throw exception and do not commit
throw new ApplicationException();
}
catch (MongoException e)
{
if (e.HasErrorLabel("TransientTransactionError"))
continue;
throw;
}
}
}
catch (Exception)
{
}
}
mainDocument = _mainCollection.FindSync(Builders<TestDocument>.Filter.Empty).FirstOrDefault();
dependentDocument = _dependentCollection.FindSync(Builders<TestDocument>.Filter.Empty).FirstOrDefault();
};
It main_should_be_still_23_after_aborting = () => mainDocument.IntTest.Should().Be(23);
It dependent_should_be_still_23_after_aborting = () => dependentDocument.IntTest.Should().Be(23);
It main_should_be_33_before_aborting = () => mainDocument_before_commit.IntTest.Should().Be(33);
It dependent_should_be_33_before_aborting = () => dependentDocument_before_commit.IntTest.Should().Be(33);
Cleanup cleanup = () => _runner.Dispose();
}
[Subject("Runner Transaction Test")]
public class when_replica_set_not_ready_before_timeout_expires : MongoTransactionTest
{
private static Exception exception;
Because of = () => exception = Catch.Exception(() => CreateConnection(0));
// this passes on Windows (TimeoutException as expected)
// but breaks on my Mac (MongoDB.Driver.MongoCommandException: Command replSetInitiate failed: already initialized.)
It should_throw_timeout_exception = () => {
Console.WriteLine(exception.ToString());
exception.Should().BeOfType<TimeoutException>();
};
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Mongo2GoTests.Runner
{
public class TestDocument
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string StringTest { get; set; }
public int IntTest { get; set; }
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime? DateTest { get; set; }
public List<string> ListTest { get; set; }
public static TestDocument DummyData1()
{
return new TestDocument
{
StringTest = "Hello World",
IntTest = 42,
DateTest = new DateTime(1984, 09, 30, 6, 6, 6, 171, DateTimeKind.Utc).ToLocalTime(),
ListTest = new List<string> {"I", "am", "a", "list", "of", "strings"}
};
}
public static TestDocument DummyData2()
{
return new TestDocument
{
StringTest = "Foo",
IntTest = 23,
};
}
public static TestDocument DummyData3()
{
return new TestDocument
{
StringTest = "Bar",
IntTest = 77,
};
}
}
}