using System.Data; using Examine; using Examine.Lucene.Providers; using Microsoft.Extensions.DependencyInjection; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Infrastructure.Examine; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Tests.Integration.Testing; using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; using IScopeProvider = Umbraco.Cms.Infrastructure.Scoping.IScopeProvider; namespace Umbraco.Cms.Tests.Integration.Umbraco.Examine.Lucene.UmbracoExamine; [TestFixture] public abstract class ExamineBaseTest : UmbracoIntegrationTest { private const int IndexingTimoutInMilliseconds = 3000; protected IndexInitializer IndexInitializer => Services.GetRequiredService(); protected IHostingEnvironment HostingEnvironment => Services.GetRequiredService(); protected IRuntimeState RunningRuntimeState { get; } = Mock.Of(x => x.Level == RuntimeLevel.Run); protected IExamineManager ExamineManager => GetRequiredService(); protected override void ConfigureTestServices(IServiceCollection services) => services.AddSingleton(); protected override void CustomTestSetup(IUmbracoBuilder builder) { base.CustomTestSetup(builder); builder.Services.AddUnique(); builder .AddNotificationHandler(); } /// /// Used to create and manage a testable index /// /// /// /// /// /// /// /// protected IDisposable GetSynchronousContentIndex( bool publishedValuesOnly, out UmbracoContentIndex index, out ContentIndexPopulator contentRebuilder, out ContentValueSetBuilder contentValueSetBuilder, int? parentId = null, IContentService contentService = null) { contentValueSetBuilder = IndexInitializer.GetContentValueSetBuilder(publishedValuesOnly); var sqlContext = Mock.Of(x => x.Query() == Mock.Of>()); var dbFactory = Mock.Of(x => x.SqlContext == sqlContext); if (contentService == null) { contentService = IndexInitializer.GetMockContentService(); } contentRebuilder = IndexInitializer.GetContentIndexRebuilder(contentService, publishedValuesOnly, dbFactory); var luceneDir = new RandomIdRAMDirectory(); ContentValueSetValidator validator; // if only published values then we'll change the validator for tests to // ensure we don't support protected nodes and that we // mock the public access service for the special protected node. if (publishedValuesOnly) { var publicAccessServiceMock = new Mock(); publicAccessServiceMock.Setup(x => x.IsProtected(It.IsAny())) .Returns((string path) => { if (path.EndsWith("," + ExamineDemoDataContentService.ProtectedNode)) { return Attempt.Succeed(); } return Attempt.Fail(); }); var scopeProviderMock = new Mock(); scopeProviderMock.Setup(x => x.CreateScope( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Mock.Of); validator = new ContentValueSetValidator( publishedValuesOnly, false, publicAccessServiceMock.Object, scopeProviderMock.Object, parentId, null, null); } else { validator = new ContentValueSetValidator(publishedValuesOnly, parentId); } index = IndexInitializer.GetUmbracoIndexer( HostingEnvironment, RunningRuntimeState, luceneDir, validator: validator); var syncMode = index.WithThreadingMode(IndexThreadingMode.Synchronous); return new DisposableWrapper(syncMode, index, luceneDir); } private AutoResetEvent indexingHandle = new(false); protected async Task ExecuteAndWaitForIndexing(Action indexUpdatingAction, string indexName) => await ExecuteAndWaitForIndexing( () => { indexUpdatingAction(); return null; }, indexName); /// /// Performs and action and waits for the specified index to be done indexing. /// /// The action that causes the index to be updated. /// The name of the index to wait for rebuild. /// The type returned from the action. /// The result of the action. protected async Task ExecuteAndWaitForIndexing (Func indexUpdatingAction, string indexName) { // Set up an action to release the handle when the index is populated. if (ExamineManager.TryGetIndex(indexName, out IIndex index) is false) { throw new InvalidOperationException($"Could not find index: {indexName}"); } index.IndexOperationComplete += (_, _) => { indexingHandle.Set(); }; // Perform the action, and wait for the handle to be freed, meaning the index is done populating. var result = indexUpdatingAction(); await indexingHandle.WaitOneAsync(millisecondsTimeout: IndexingTimoutInMilliseconds); return result; } private class DisposableWrapper : IDisposable { private readonly IDisposable[] _disposables; public DisposableWrapper(params IDisposable[] disposables) => _disposables = disposables; public void Dispose() { foreach (var d in _disposables) { d.Dispose(); } } } protected string GetIndexPath(string indexName) { var root = TestContext.CurrentContext.TestDirectory.Split("Umbraco.Tests.Integration")[0]; return Path.Combine(root, "Umbraco.Tests.Integration", "umbraco", "Data", "TEMP", "ExamineIndexes", indexName); } }