From 96284f75948ba4fac6187aac5f532326c3b649c5 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Thu, 29 Aug 2024 10:04:14 +0700 Subject: [PATCH] Use real MongoDB connection for E2E tests (#348) A real MongoConnection is used for E2E tests, so that they're truly end-to-end, rather than the mock one that stores data in memory. The docker image `mongo:6` is used to provide the MongoDB container. * Skip updating project record if there is none In E2E tests, the SetInputSystems call would fail because there is no existing project record in the freshly-created, empty Mongo instance. We don't want to change LfMerge to create the project record, because the Language Forge code is responsible for doing that. So instead we simply skip the update if the project record doesn't exist, allowing E2E tests to pass while not changing the behavior of real code. * Delete MongoDB container after test Unless LFMERGE_E2E_LEAVE_MONGO_CONTAINER_RUNNING_ON_FAILURE is set (to any non-empty value except "false" or "0"), we'll tear down the MongoDB container after tests. Also, if the test succeeds, the MongoDB container is torn down even if that env var is set. The only time the container is retained is if the test fails *and* that env var is set. --- src/LfMerge.Core.Tests/E2E/E2ETestBase.cs | 18 +++--- .../E2E/LexboxSendReceiveTests.cs | 2 +- src/LfMerge.Core.Tests/Lcm/RoundTripTests.cs | 4 +- .../Lcm/TransferMongoToLcmActionTests.cs | 24 +++---- src/LfMerge.Core.Tests/SRTestEnvironment.cs | 64 +++++++++++++++++++ src/LfMerge.Core.Tests/TestDoubles.cs | 2 +- src/LfMerge.Core.Tests/TestEnvironment.cs | 43 +++++++++---- src/LfMerge.Core/MagicStrings.cs | 1 + .../MongoConnector/IMongoConnection.cs | 1 + .../MongoConnector/MongoConnection.cs | 12 +++- .../Settings/DefaultLfMergeSettings.cs | 2 +- src/LfMerge.Core/Settings/LfMergeSettings.cs | 26 ++++---- 12 files changed, 143 insertions(+), 56 deletions(-) diff --git a/src/LfMerge.Core.Tests/E2E/E2ETestBase.cs b/src/LfMerge.Core.Tests/E2E/E2ETestBase.cs index 3d11a912..186ff09f 100644 --- a/src/LfMerge.Core.Tests/E2E/E2ETestBase.cs +++ b/src/LfMerge.Core.Tests/E2E/E2ETestBase.cs @@ -25,7 +25,7 @@ public class E2ETestBase private readonly HashSet ProjectIdsToDelete = []; public SRTestEnvironment TestEnv { get; set; } - public MongoConnectionDouble _mongoConnection; + public IMongoConnection _mongoConnection; public MongoProjectRecordFactory _recordFactory; public E2ETestBase() @@ -81,14 +81,14 @@ public async Task TestSetup() Assert.Ignore("Can't run E2E tests without a copy of LexBox to test against. Please either launch LexBox on localhost port 80, or set the appropriate environment variables to point to a running copy of LexBox."); } await TestEnv.Login(); + TestEnv.LaunchMongo(); MagicStrings.SetMinimalModelVersion(LcmCache.ModelVersion); - _mongoConnection = MainClass.Container.Resolve() as MongoConnectionDouble; - if (_mongoConnection == null) - throw new AssertionException("E2E tests need a mock MongoConnection that stores data in order to work."); - _recordFactory = MainClass.Container.Resolve() as MongoProjectRecordFactoryDouble; - if (_recordFactory == null) - throw new AssertionException("E2E tests need a mock MongoProjectRecordFactory in order to work."); + _mongoConnection = MainClass.Container.Resolve(); + var _mongoConnectionDouble = _mongoConnection as MongoConnectionDouble; + if (_mongoConnectionDouble != null) + throw new AssertionException("E2E tests need a real MongoConnection, not a mock."); + _recordFactory = MainClass.Container.Resolve(); } [TearDown] @@ -97,7 +97,7 @@ public async Task TestTeardown() var outcome = TestContext.CurrentContext.Result.Outcome; var success = outcome == ResultState.Success || outcome == ResultState.Ignored; // Only delete temp folder if test passed, otherwise we'll want to leave it in place for post-test investigation - TestEnv.DeleteTempFolderDuringCleanup = success; + TestEnv.CleanUpTestData = success; // On failure, also leave LexBox project(s) in place for post-test investigation, even though this might tend to clutter things up a little if (success) { foreach (var projId in ProjectIdsToDelete) { @@ -220,7 +220,7 @@ public void SendReceiveToLexbox(LanguageForgeProject lfProject) public (string, DateTime, DateTime) UpdateLfGloss(LanguageForgeProject lfProject, Guid entryId, string wsId, Func textConverter) { - var lfEntry = _mongoConnection.GetLfLexEntryByGuid(entryId); + var lfEntry = _mongoConnection.GetLfLexEntryByGuid(lfProject, entryId); Assert.That(lfEntry, Is.Not.Null); var unchangedGloss = lfEntry.Senses[0].Gloss[wsId].Value; lfEntry.Senses[0].Gloss["pt"].Value = textConverter(unchangedGloss); diff --git a/src/LfMerge.Core.Tests/E2E/LexboxSendReceiveTests.cs b/src/LfMerge.Core.Tests/E2E/LexboxSendReceiveTests.cs index 358f83a6..65926905 100644 --- a/src/LfMerge.Core.Tests/E2E/LexboxSendReceiveTests.cs +++ b/src/LfMerge.Core.Tests/E2E/LexboxSendReceiveTests.cs @@ -50,7 +50,7 @@ public async Task E2E_LFDataChangedLDDataChanged_LFWins() // Verify // LF side should win conflict since its modified date was later - var lfEntryAfterSR = _mongoConnection.GetLfLexEntryByGuid(entryId); + var lfEntryAfterSR = _mongoConnection.GetLfLexEntryByGuid(lfProject, entryId); Assert.That(lfEntryAfterSR?.Senses?[0]?.Gloss?["pt"]?.Value, Is.EqualTo(unchangedGloss + " - changed in LF")); // LF's modified dates should have been updated by the sync action Assert.That(lfEntryAfterSR.AuthorInfo.ModifiedDate, Is.GreaterThan(origLfDateModified)); diff --git a/src/LfMerge.Core.Tests/Lcm/RoundTripTests.cs b/src/LfMerge.Core.Tests/Lcm/RoundTripTests.cs index 75a99f65..0b0d64d3 100644 --- a/src/LfMerge.Core.Tests/Lcm/RoundTripTests.cs +++ b/src/LfMerge.Core.Tests/Lcm/RoundTripTests.cs @@ -830,7 +830,7 @@ public void RoundTrip_MongoToLcmToMongo_ShouldBeAbleToAddAndModifyParagraphsInCu // Here we check two things: // 1) Can we add paragraphs? // 2) Can we change existing paragraphs? - LfLexEntry lfEntry = _conn.GetLfLexEntryByGuid(entryGuid); + LfLexEntry lfEntry = _conn.GetLfLexEntryByGuid(lfProject, entryGuid); // BsonDocument customFieldValues = GetCustomFieldValues(cache, lcmEntry, "entry"); BsonDocument customFieldsBson = lfEntry.CustomFields; Assert.That(customFieldsBson.Contains("customField_entry_Cust_MultiPara"), Is.True, @@ -923,7 +923,7 @@ public void RoundTrip_MongoToLcmToMongo_ShouldBeAbleToDeleteParagraphsInCustomMu // Here we check just one thing: // 1) Can we delete paragraphs? - LfLexEntry lfEntry = _conn.GetLfLexEntryByGuid(entryGuid); + LfLexEntry lfEntry = _conn.GetLfLexEntryByGuid(lfProject, entryGuid); // BsonDocument customFieldValues = GetCustomFieldValues(cache, lcmEntry, "entry"); BsonDocument customFieldsBson = lfEntry.CustomFields; Assert.That(customFieldsBson.Contains("customField_entry_Cust_MultiPara"), Is.True, diff --git a/src/LfMerge.Core.Tests/Lcm/TransferMongoToLcmActionTests.cs b/src/LfMerge.Core.Tests/Lcm/TransferMongoToLcmActionTests.cs index bec32938..c6aee22c 100644 --- a/src/LfMerge.Core.Tests/Lcm/TransferMongoToLcmActionTests.cs +++ b/src/LfMerge.Core.Tests/Lcm/TransferMongoToLcmActionTests.cs @@ -234,7 +234,7 @@ public void Action_WithOneModifiedEntry_ShouldCountOneModified() SutLcmToMongo.Run(lfProj); Guid entryGuid = Guid.Parse(TestEntryGuidStr); - LfLexEntry entry = _conn.GetLfLexEntryByGuid(entryGuid); + LfLexEntry entry = _conn.GetLfLexEntryByGuid(lfProj, entryGuid); LcmCache cache = lfProj.FieldWorksProject.Cache; string vernacularWS = cache.LanguageProject.DefaultVernacularWritingSystem.Id; string changedLexeme = "modified lexeme for this test"; @@ -263,7 +263,7 @@ public void Action_WithOneDeletedEntry_ShouldCountOneDeleted() SutLcmToMongo.Run(lfProj); Guid entryGuid = Guid.Parse(TestEntryGuidStr); - LfLexEntry entry = _conn.GetLfLexEntryByGuid(entryGuid); + LfLexEntry entry = _conn.GetLfLexEntryByGuid(lfProj, entryGuid); entry.IsDeleted = true; _conn.UpdateMockLfLexEntry(entry); @@ -325,7 +325,7 @@ public void Action_WithTwoModifiedEntries_ShouldCountTwoModified() SutLcmToMongo.Run(lfProj); Guid entryGuid = Guid.Parse(TestEntryGuidStr); - LfLexEntry entry = _conn.GetLfLexEntryByGuid(entryGuid); + LfLexEntry entry = _conn.GetLfLexEntryByGuid(lfProj, entryGuid); LcmCache cache = lfProj.FieldWorksProject.Cache; string vernacularWS = cache.LanguageProject.DefaultVernacularWritingSystem.Id; string changedLexeme = "modified lexeme for this test"; @@ -335,7 +335,7 @@ public void Action_WithTwoModifiedEntries_ShouldCountTwoModified() _conn.UpdateMockLfLexEntry(entry); Guid kenGuid = Guid.Parse(KenEntryGuidStr); - LfLexEntry kenEntry = _conn.GetLfLexEntryByGuid(kenGuid); + LfLexEntry kenEntry = _conn.GetLfLexEntryByGuid(lfProj, kenGuid); string changedLexeme2 = "modified lexeme #2 for this test"; kenEntry.Lexeme = LfMultiText.FromSingleStringMapping(vernacularWS, changedLexeme2); kenEntry.AuthorInfo = new LfAuthorInfo(); @@ -362,11 +362,11 @@ public void Action_WithTwoDeletedEntries_ShouldCountTwoDeleted() SutLcmToMongo.Run(lfProj); Guid entryGuid = Guid.Parse(TestEntryGuidStr); - LfLexEntry entry = _conn.GetLfLexEntryByGuid(entryGuid); + LfLexEntry entry = _conn.GetLfLexEntryByGuid(lfProj, entryGuid); entry.IsDeleted = true; _conn.UpdateMockLfLexEntry(entry); Guid kenGuid = Guid.Parse(KenEntryGuidStr); - entry = _conn.GetLfLexEntryByGuid(kenGuid); + entry = _conn.GetLfLexEntryByGuid(lfProj, kenGuid); entry.IsDeleted = true; _conn.UpdateMockLfLexEntry(entry); @@ -430,7 +430,7 @@ public void Action_WithOneModifiedEntry_ShouldNotCountThatModifiedEntryOnSecondR SutLcmToMongo.Run(lfProj); Guid entryGuid = Guid.Parse(TestEntryGuidStr); - LfLexEntry entry = _conn.GetLfLexEntryByGuid(entryGuid); + LfLexEntry entry = _conn.GetLfLexEntryByGuid(lfProj, entryGuid); LcmCache cache = lfProj.FieldWorksProject.Cache; string vernacularWS = cache.LanguageProject.DefaultVernacularWritingSystem.Id; string changedLexeme = "modified lexeme for this test"; @@ -470,7 +470,7 @@ public void Action_WithOneDeletedEntry_ShouldNotCountThatDeletedEntryOnSecondRun SutLcmToMongo.Run(lfProj); Guid entryGuid = Guid.Parse(TestEntryGuidStr); - LfLexEntry entry = _conn.GetLfLexEntryByGuid(entryGuid); + LfLexEntry entry = _conn.GetLfLexEntryByGuid(lfProj, entryGuid); entry.IsDeleted = true; _conn.UpdateMockLfLexEntry(entry); @@ -557,7 +557,7 @@ public void Action_RunTwiceWithTheSameEntryModifiedEachTime_ShouldCountTwoModifi SutLcmToMongo.Run(lfProj); Guid entryGuid = Guid.Parse(TestEntryGuidStr); - LfLexEntry entry = _conn.GetLfLexEntryByGuid(entryGuid); + LfLexEntry entry = _conn.GetLfLexEntryByGuid(lfProj, entryGuid); LcmCache cache = lfProj.FieldWorksProject.Cache; string vernacularWS = cache.LanguageProject.DefaultVernacularWritingSystem.Id; string changedLexeme = "modified lexeme for this test"; @@ -606,7 +606,7 @@ public void Action_RunTwiceWithTheSameEntryDeletedEachTime_ShouldCountJustOneDel SutLcmToMongo.Run(lfProj); Guid entryGuid = Guid.Parse(TestEntryGuidStr); - LfLexEntry entry = _conn.GetLfLexEntryByGuid(entryGuid); + LfLexEntry entry = _conn.GetLfLexEntryByGuid(lfProj, entryGuid); entry.IsDeleted = true; _conn.UpdateMockLfLexEntry(entry); @@ -621,7 +621,7 @@ public void Action_RunTwiceWithTheSameEntryDeletedEachTime_ShouldCountJustOneDel Assert.That(LfMergeBridgeServices.FormatCommitMessageForLfMerge(_counts.Added, _counts.Modified, _counts.Deleted), Is.EqualTo("Language Forge: 1 entry deleted")); - entry = _conn.GetLfLexEntryByGuid(entryGuid); + entry = _conn.GetLfLexEntryByGuid(lfProj, entryGuid); entry.IsDeleted = true; _conn.UpdateMockLfLexEntry(entry); @@ -689,7 +689,7 @@ public void Run_CustomMultiListRefTest(int whichSense, params string[] desiredKe SutLcmToMongo.Run(lfProj); Guid entryGuid = Guid.Parse(TestEntryGuidStr); - LfLexEntry entry = _conn.GetLfLexEntryByGuid(entryGuid); + LfLexEntry entry = _conn.GetLfLexEntryByGuid(lfProj, entryGuid); LfSense sense = entry.Senses[whichSense]; SetCustomMultiOptionList(sense, "customField_senses_Cust_Multi_ListRef", desiredKeys); entry.AuthorInfo = new LfAuthorInfo(); diff --git a/src/LfMerge.Core.Tests/SRTestEnvironment.cs b/src/LfMerge.Core.Tests/SRTestEnvironment.cs index 350321ed..1688558d 100644 --- a/src/LfMerge.Core.Tests/SRTestEnvironment.cs +++ b/src/LfMerge.Core.Tests/SRTestEnvironment.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; +using Autofac; using BirdMessenger; using BirdMessenger.Collections; using GraphQL; @@ -12,7 +13,9 @@ using GraphQL.Client.Serializer.SystemTextJson; using LfMerge.Core.FieldWorks; using LfMerge.Core.Logging; +using LfMerge.Core.MongoConnector; using NUnit.Framework; +using SIL.CommandLineProcessing; using SIL.TestUtilities; namespace LfMerge.Core.Tests @@ -39,6 +42,7 @@ public class SRTestEnvironment : TestEnvironment private bool AlreadyLoggedIn = false; private TemporaryFolder TempFolder { get; init; } private Lazy LazyGqlClient { get; init; } + private string MongoContainerId { get; set; } public GraphQLHttpClient GqlClient => LazyGqlClient.Value; public SRTestEnvironment(TemporaryFolder? tempFolder = null) @@ -51,6 +55,66 @@ public SRTestEnvironment(TemporaryFolder? tempFolder = null) Settings.CommitWhenDone = true; // For SR tests specifically, we *do* want changes to .fwdata files to be persisted } + override protected void RegisterMongoConnection(ContainerBuilder builder) + { + // E2E tests want a real Mogno connection + builder.RegisterType().As().SingleInstance(); + } + + public void LaunchMongo() + { + if (MongoContainerId is null) + { + var result = CommandLineRunner.Run("docker", "run -p 27017 -d mongo:6", ".", 30, NullProgress); + MongoContainerId = result.StandardOutput?.TrimEnd(); + if (string.IsNullOrEmpty(MongoContainerId)) { + throw new InvalidOperationException("Mongo container failed to start, aborting test"); + } + result = CommandLineRunner.Run("docker", $"port {MongoContainerId} 27017", ".", 30, NullProgress); + var hostAndPort = result.StandardOutput?.TrimEnd(); + var parts = hostAndPort.Contains(':') ? hostAndPort.Split(':') : null; + if (parts is not null && parts.Length == 2) { + Settings.MongoHostname = parts[0].Replace("0.0.0.0", "localhost"); + Settings.MongoPort = parts[1]; + } else { + throw new InvalidOperationException($"Mongo container port {hostAndPort} could not be parsed, test will not be able to proceed"); + } + } + } + + public void StopMongo() + { + if (MongoContainerId is not null) + { + CommandLineRunner.Run("docker", $"stop {MongoContainerId}", ".", 30, NullProgress); + CommandLineRunner.Run("docker", $"rm {MongoContainerId}", ".", 30, NullProgress); + MongoContainerId = null; + } + } + + private bool ShouldStopMongoOnFailure() + { + // Mongo container will be torn down on test failure unless LFMERGE_E2E_LEAVE_MONGO_CONTAINER_RUNNING_ON_FAILURE + // is set to a non-empty value (except "false" or "0", which mean the same as leaving it empty) + var envVar = Environment.GetEnvironmentVariable(MagicStrings.EnvVar_E2E_LeaveMongoContainerRunningOnFailure)?.Trim(); + return string.IsNullOrEmpty(envVar) || envVar == "false" || envVar == "0"; + } + + protected override void Dispose(bool disposing) + { + if (_disposed) return; + if (disposing) { + if (CleanUpTestData || ShouldStopMongoOnFailure()) { + StopMongo(); + } else { + Console.WriteLine($"Leaving Mongo container {MongoContainerId} around to examine data on failed test."); + Console.WriteLine($"It is listening on {Settings.MongoDbHostNameAndPort}"); + Console.WriteLine($"To delete it, run `docker stop {MongoContainerId} ; docker rm {MongoContainerId}`."); + } + } + base.Dispose(disposing); + } + public async Task Login() { if (AlreadyLoggedIn) return; diff --git a/src/LfMerge.Core.Tests/TestDoubles.cs b/src/LfMerge.Core.Tests/TestDoubles.cs index 86eec1fb..c22e5bfb 100644 --- a/src/LfMerge.Core.Tests/TestDoubles.cs +++ b/src/LfMerge.Core.Tests/TestDoubles.cs @@ -211,7 +211,7 @@ public IEnumerable GetLfLexEntries() return new List(_storedLfLexEntries.Values.Select(entry => DeepCopy(entry))); } - public LfLexEntry GetLfLexEntryByGuid(Guid key) + public LfLexEntry GetLfLexEntryByGuid(ILfProject _project, Guid key) { LfLexEntry result; if (_storedLfLexEntries.TryGetValue(key, out result)) diff --git a/src/LfMerge.Core.Tests/TestEnvironment.cs b/src/LfMerge.Core.Tests/TestEnvironment.cs index 4fbff2ca..3121144a 100644 --- a/src/LfMerge.Core.Tests/TestEnvironment.cs +++ b/src/LfMerge.Core.Tests/TestEnvironment.cs @@ -25,7 +25,8 @@ public class TestEnvironment : IDisposable protected readonly TemporaryFolder _languageForgeServerFolder; private readonly bool _resetLfProjectsDuringCleanup; private readonly bool _releaseSingletons; - public bool DeleteTempFolderDuringCleanup { get; set; } = true; + protected bool _disposed; + public bool CleanUpTestData { get; set; } = true; public LfMergeSettings Settings; private readonly MongoConnectionDouble _mongoConnection; public ILogger Logger => MainClass.Logger; @@ -57,6 +58,8 @@ public TestEnvironment(bool registerSettingsModelDouble = true, _releaseSingletons = !SingletonsContainer.Contains(); } + ~TestEnvironment() => Dispose(false); + private string TestName { get @@ -79,8 +82,7 @@ private ContainerBuilder RegisterTypes(bool registerSettingsModel, containerBuilder.RegisterType().SingleInstance().As() .WithParameter(new TypedParameter(typeof(string), TestName)); - - containerBuilder.RegisterType().As().SingleInstance(); + RegisterMongoConnection(containerBuilder); if (registerSettingsModel) { @@ -100,20 +102,33 @@ private ContainerBuilder RegisterTypes(bool registerSettingsModel, return containerBuilder; } + protected virtual void RegisterMongoConnection(ContainerBuilder builder) + { + builder.RegisterType().As().SingleInstance(); + } + public void Dispose() { - _mongoConnection?.Reset(); - - MainClass.Container?.Dispose(); - MainClass.Container = null; - if (_resetLfProjectsDuringCleanup) - LanguageForgeProjectAccessor.Reset(); - if (DeleteTempFolderDuringCleanup) - _languageForgeServerFolder?.Dispose(); - Settings = null; - if (_releaseSingletons) - SingletonsContainer.Release(); + Dispose(true); + GC.SuppressFinalize(this); + } + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + if (disposing) { + _mongoConnection?.Reset(); + + MainClass.Container?.Dispose(); + MainClass.Container = null; + if (_resetLfProjectsDuringCleanup) + LanguageForgeProjectAccessor.Reset(); + if (CleanUpTestData) + _languageForgeServerFolder?.Dispose(); + Settings = null; + if (_releaseSingletons) + SingletonsContainer.Release(); + } Environment.SetEnvironmentVariable("FW_CommonAppData", null); } diff --git a/src/LfMerge.Core/MagicStrings.cs b/src/LfMerge.Core/MagicStrings.cs index f0966513..eebf34f4 100644 --- a/src/LfMerge.Core/MagicStrings.cs +++ b/src/LfMerge.Core/MagicStrings.cs @@ -33,6 +33,7 @@ static MagicStrings() public const string EnvVar_LanguageDepotUriPort = "LFMERGE_LANGUAGE_DEPOT_HG_PORT"; public const string EnvVar_TrustToken = "LANGUAGE_DEPOT_TRUST_TOKEN"; public const string EnvVar_HgUsername = "LANGUAGE_DEPOT_HG_USERNAME"; + public const string EnvVar_E2E_LeaveMongoContainerRunningOnFailure = "LFMERGE_E2E_LEAVE_MONGO_CONTAINER_RUNNING_ON_FAILURE"; public static Dictionary LcmOptionlistNames = new Dictionary() { diff --git a/src/LfMerge.Core/MongoConnector/IMongoConnection.cs b/src/LfMerge.Core/MongoConnector/IMongoConnection.cs index 2f8d27dc..c7e11988 100644 --- a/src/LfMerge.Core/MongoConnector/IMongoConnection.cs +++ b/src/LfMerge.Core/MongoConnector/IMongoConnection.cs @@ -19,6 +19,7 @@ public interface IMongoConnection IEnumerable GetRecords(ILfProject project, string collectionName); LfOptionList GetLfOptionListByCode(ILfProject project, string listCode); long LexEntryCount(ILfProject project); + LfLexEntry GetLfLexEntryByGuid(ILfProject project, Guid key); Dictionary GetAllModifiedDatesForEntries(ILfProject project); bool UpdateRecord(ILfProject project, LfLexEntry data); bool UpdateRecord(ILfProject project, LfOptionList data, string listCode); diff --git a/src/LfMerge.Core/MongoConnector/MongoConnection.cs b/src/LfMerge.Core/MongoConnector/MongoConnection.cs index 6ef2b87a..e9c55da5 100644 --- a/src/LfMerge.Core/MongoConnector/MongoConnection.cs +++ b/src/LfMerge.Core/MongoConnector/MongoConnection.cs @@ -88,7 +88,8 @@ private MongoClient GetNewConnection() var clientSettings = MongoClientSettings.FromConnectionString(connectionString); // clientSettings.WriteConcern = WriteConcern.WMajority; // If increasing the wait queue size still doesn't help, try this as well clientSettings.WaitQueueSize = 50000; - clientSettings.Server = new MongoServerAddress(Settings.MongoHostname, Settings.MongoPort); + var mongoPort = int.TryParse(Settings.MongoPort, out var port) ? port : DefaultLfMergeSettings.MongoPort; + clientSettings.Server = new MongoServerAddress(Settings.MongoHostname, mongoPort); return new MongoClient(clientSettings); } @@ -148,6 +149,13 @@ public long LexEntryCount(ILfProject project) return lexicon.Count(allEntries); } + public LfLexEntry GetLfLexEntryByGuid(ILfProject project, Guid key) + { + IMongoDatabase db = GetProjectDatabase(project); + IMongoCollection collection = db.GetCollection(MagicStrings.LfCollectionNameForLexicon); + return collection.Find(entry => entry.Guid == key).First(); + } + public Dictionary GetAllModifiedDatesForEntries(ILfProject project) { IMongoDatabase db = GetProjectDatabase(project); @@ -554,7 +562,7 @@ public bool SetInputSystems(ILfProject project, Dictionary Environment.GetEnvironmentVariable(MagicStrings.SettingsEnvVar_MongoHostname) ?? DefaultLfMergeSettings.MongoHostname; + private string _mongoHostname; + public string MongoHostname { + get => _mongoHostname ?? Environment.GetEnvironmentVariable(MagicStrings.SettingsEnvVar_MongoHostname) ?? DefaultLfMergeSettings.MongoHostname; + set => _mongoHostname = value; + } - public int MongoPort { - get { - string _mongoPortStr = Environment.GetEnvironmentVariable(MagicStrings.SettingsEnvVar_MongoPort); - if (_mongoPortStr == null) _mongoPortStr = ""; - if (Int32.TryParse(_mongoPortStr, out int _mongoPort)) { - return _mongoPort; - } else { - return DefaultLfMergeSettings.MongoPort; - } - } + private string _mongoPort; + public string MongoPort { + get => _mongoPort ?? Environment.GetEnvironmentVariable(MagicStrings.SettingsEnvVar_MongoPort) ?? DefaultLfMergeSettings.MongoPort.ToString(); + set => _mongoPort = value; } public string MongoAuthSource => Environment.GetEnvironmentVariable(MagicStrings.SettingsEnvVar_MongoAuthSource) ?? DefaultLfMergeSettings.MongoAuthSource; @@ -74,10 +72,10 @@ public string LanguageDepotRepoUri { public bool CommitWhenDone { get; internal set; } - public string MongoDbHostNameAndPort { get { return String.Format("{0}:{1}", MongoHostname, MongoPort.ToString()); } } + public string MongoDbHostNameAndPort => $"{MongoHostname}:{MongoPort}"; public string MongoDbHostPortAndAuth => string.IsNullOrEmpty(MongoUsername) || string.IsNullOrEmpty(MongoPassword) - ? string.Format("{0}:{1}", MongoHostname, MongoPort.ToString()) - : string.Format("{0}:{1}@{2}:{3}", EncodedUsername, EncodedPassword, MongoHostname, MongoPort.ToString()); + ? string.Format("{0}:{1}", MongoHostname, MongoPort) + : string.Format("{0}:{1}@{2}:{3}", EncodedUsername, EncodedPassword, MongoHostname, MongoPort); private string QueueDirectory { get; set; }