diff --git a/src/Web/Shared/Services/PersistentObjectsLookupController.cs b/src/Web/Shared/Services/PersistentObjectsLookupController.cs index ecdcbaf77..7a92ca564 100644 --- a/src/Web/Shared/Services/PersistentObjectsLookupController.cs +++ b/src/Web/Shared/Services/PersistentObjectsLookupController.cs @@ -48,9 +48,14 @@ public async Task> GetSuggestionsAsync(string? text, IContext? } var owner = await this._gameConfigurationSource.GetOwnerAsync().ConfigureAwait(true); + var gameConfigurationContext = await this._gameConfigurationSource.GetContextAsync().ConfigureAwait(true); IEnumerable values; + + // When the context is the one of the game configuration source, its objects are already loaded, + // so we don't need to query the whole object graph from the database on every keystroke again. if (this._gameConfigurationSource.IsSupporting(typeof(T)) - && persistenceContext?.IsSupporting(typeof(T)) is not true) + && (ReferenceEquals(persistenceContext, gameConfigurationContext) + || persistenceContext?.IsSupporting(typeof(T)) is not true)) { values = this._gameConfigurationSource.GetAll(); } diff --git a/tests/MUnique.OpenMU.Web.Tests/PersistentObjectsLookupControllerTests.cs b/tests/MUnique.OpenMU.Web.Tests/PersistentObjectsLookupControllerTests.cs new file mode 100644 index 000000000..69fe9faf2 --- /dev/null +++ b/tests/MUnique.OpenMU.Web.Tests/PersistentObjectsLookupControllerTests.cs @@ -0,0 +1,87 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Web.Tests; + +using System.Threading; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.DataModel.Configuration.Items; +using MUnique.OpenMU.Persistence; +using MUnique.OpenMU.Web.Shared.Services; + +/// +/// Tests for the . +/// +[TestFixture] +public class PersistentObjectsLookupControllerTests +{ + private readonly ItemDefinition _jewelOfBless = new() { Name = "Jewel of Bless" }; + private readonly ItemDefinition _jewelOfSoul = new() { Name = "Jewel of Soul" }; + + private Mock> _dataSource = null!; + private Mock _gameConfigurationContext = null!; + private PersistentObjectsLookupController _controller = null!; + + /// + /// Sets up the game configuration data source with its context. + /// + [SetUp] + public void Setup() + { + this._gameConfigurationContext = CreateContextMock(); + + this._dataSource = new Mock>(); + this._dataSource.Setup(s => s.GetOwnerAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new Mock().Object); + this._dataSource.Setup(s => s.GetContextAsync(It.IsAny())) + .ReturnsAsync(this._gameConfigurationContext.Object); + this._dataSource.Setup(s => s.IsSupporting(typeof(ItemDefinition))).Returns(true); + this._dataSource.Setup(s => s.GetAll()).Returns([this._jewelOfBless, this._jewelOfSoul]); + + this._controller = new PersistentObjectsLookupController( + Mock.Of(), + this._dataSource.Object, + NullLogger.Instance); + } + + /// + /// When the context of the game configuration source is passed, the suggestions + /// come from the already loaded configuration instead of querying the context again. + /// + [Test] + public async Task GameConfigurationContext_UsesLoadedConfigurationAsync() + { + var result = await this._controller.GetSuggestionsAsync("bless", this._gameConfigurationContext.Object).ConfigureAwait(false); + + Assert.That(result, Is.EquivalentTo(new[] { this._jewelOfBless })); + this._gameConfigurationContext.Verify(c => c.GetAsync(It.IsAny()), Times.Never); + } + + /// + /// When another context which supports the type is passed, the suggestions are queried + /// from that context, so that the selected object belongs to it. + /// + [Test] + public async Task OtherSupportingContext_QueriesContextAsync() + { + var otherItem = new ItemDefinition { Name = "Jewel of Bless" }; + var otherContext = CreateContextMock(); + otherContext.Setup(c => c.GetAsync(It.IsAny())) + .ReturnsAsync([otherItem]); + + var result = await this._controller.GetSuggestionsAsync("bless", otherContext.Object).ConfigureAwait(false); + + Assert.That(result, Is.EquivalentTo(new[] { otherItem })); + this._dataSource.Verify(s => s.GetAll(), Times.Never); + } + + private static Mock CreateContextMock() + { + var context = new Mock(); + context.Setup(c => c.IsSupporting(typeof(ItemDefinition))).Returns(true); + return context; + } +}