diff --git a/src/Umbraco.Cms.Search.Core/NotificationHandlers/RebuildIndexesNotificationHandler.cs b/src/Umbraco.Cms.Search.Core/NotificationHandlers/RebuildIndexesNotificationHandler.cs index eb6ebf1..871e4f6 100644 --- a/src/Umbraco.Cms.Search.Core/NotificationHandlers/RebuildIndexesNotificationHandler.cs +++ b/src/Umbraco.Cms.Search.Core/NotificationHandlers/RebuildIndexesNotificationHandler.cs @@ -2,12 +2,15 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services.Changes; using Umbraco.Cms.Search.Core.Configuration; using Umbraco.Cms.Search.Core.Services.ContentIndexing; namespace Umbraco.Cms.Search.Core.NotificationHandlers; -internal sealed class RebuildIndexesNotificationHandler : INotificationHandler, INotificationHandler +internal sealed class RebuildIndexesNotificationHandler : INotificationHandler, + INotificationHandler, + INotificationHandler { private readonly IContentIndexingService _contentIndexingService; private readonly ILogger _logger; @@ -41,4 +44,17 @@ public void Handle(LanguageDeletedNotification notification) _contentIndexingService.Rebuild(indexAlias); } } + + public void Handle(ContentTypeChangedNotification notification) + { + if (notification.Changes.Any(x => x.ChangeTypes is ContentTypeChangeTypes.RefreshMain or ContentTypeChangeTypes.Remove )) + { + _logger.LogInformation("Rebuilding search indexes after content type update..."); + + _contentIndexingService.Rebuild(Constants.IndexAliases.PublishedContent); + _contentIndexingService.Rebuild(Constants.IndexAliases.DraftContent); + _contentIndexingService.Rebuild(Constants.IndexAliases.DraftMedia); + _contentIndexingService.Rebuild(Constants.IndexAliases.DraftMembers); + } + } } diff --git a/src/Umbraco.Test.Search.Examine.Integration/Tests/ContentTests/SearchService/DocumentTypeTests.cs b/src/Umbraco.Test.Search.Examine.Integration/Tests/ContentTests/SearchService/DocumentTypeTests.cs new file mode 100644 index 0000000..0c7de37 --- /dev/null +++ b/src/Umbraco.Test.Search.Examine.Integration/Tests/ContentTests/SearchService/DocumentTypeTests.cs @@ -0,0 +1,139 @@ +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.HostedServices; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.ContentTypeEditing; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.ContentTypeEditing; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Search.Core.DependencyInjection; +using Umbraco.Cms.Search.Core.Models.Searching; +using Umbraco.Cms.Search.Core.NotificationHandlers; +using Umbraco.Cms.Search.Core.Services; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.TestHelpers; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; +using Umbraco.Test.Search.Examine.Integration.Extensions; +using Umbraco.Test.Search.Examine.Integration.Tests.ContentTests.IndexService; + +namespace Umbraco.Test.Search.Examine.Integration.Tests.ContentTests.SearchService; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class DocumentTypeTests : UmbracoIntegrationTest +{ + private IContentTypeService ContentTypeService => GetRequiredService(); + + private ISearcher Searcher => GetRequiredService(); + + private IContentTypeEditingService ContentTypeEditingService => GetRequiredService(); + + private IContentEditingService ContentEditingService => GetRequiredService(); + + private IContentType _parentContentType = null!; + private IContentType _childContentType = null!; + + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.AddExamineSearchProviderForTest(); + builder.AddSearchCore(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + } + + [Test] + public async Task CannotSearchForRemovedProperty() + { + await CreateDocuments(); + // Act + _childContentType.RemovePropertyType("title"); + await ContentTypeService.UpdateAsync(_childContentType, Constants.Security.SuperUserKey); + + await Task.Delay(3000); + IContentType? contentType = await ContentTypeService.GetAsync(_childContentType.Key); + // Assert.That(contentType!.PropertyTypes.Any(), Is.False); + + SearchResult finalResults = await Searcher.SearchAsync( + Cms.Search.Core.Constants.IndexAliases.DraftContent, + query: "Home Page"); + + // We should still find the + Assert.That(finalResults.Total, Is.EqualTo(1)); + } + + [Test] + public async Task CannotSearchForRemovedDocument() + { + await CreateDocuments(); + + // Act + await ContentTypeService.DeleteAsync(_childContentType.Key, Constants.Security.SuperUserKey); + + await Task.Delay(3000); + IContentType? contentType = await ContentTypeService.GetAsync(_childContentType.Key); + + SearchResult finalResults = await Searcher.SearchAsync( + Cms.Search.Core.Constants.IndexAliases.DraftContent, + query: "Home Page"); + + Assert.That(finalResults.Total, Is.EqualTo(1)); + Assert.That(contentType, Is.Null); + } + + private async Task CreateDocuments() + { + ContentTypeCreateModel parentContentTypeCreateModel = ContentTypeEditingBuilder.CreateSimpleContentType( + "parentType", + "Parent Type"); + Attempt parentContentTypeAttempt = await ContentTypeEditingService.CreateAsync( + parentContentTypeCreateModel, + Constants.Security.SuperUserKey); + Assert.IsTrue(parentContentTypeAttempt.Success); + _parentContentType = parentContentTypeAttempt.Result!; + + // Create Child ContentType + ContentTypeCreateModel childContentTypeCreateModel = ContentTypeEditingBuilder.CreateSimpleContentType( + "childType", + "Child Type"); + Attempt childContentTypeAttempt = await ContentTypeEditingService.CreateAsync( + childContentTypeCreateModel, + Constants.Security.SuperUserKey); + Assert.IsTrue(childContentTypeAttempt.Success); + _childContentType = childContentTypeAttempt.Result!; + + // Update Parent ContentType to allow Child ContentType + ContentTypeUpdateModel parentContentTypeUpdateModel = ContentTypeUpdateHelper.CreateContentTypeUpdateModel(_parentContentType); + parentContentTypeUpdateModel.AllowedContentTypes = + [ + new ContentTypeSort(_childContentType.Key, 0, childContentTypeCreateModel.Alias) + ]; + Attempt updatedParentResult = await ContentTypeEditingService.UpdateAsync( + _parentContentType, + parentContentTypeUpdateModel, + Constants.Security.SuperUserKey); + Assert.IsTrue(updatedParentResult.Success); + + // Create Root Document (Parent) + ContentCreateModel rootCreateModel = ContentEditingBuilder.CreateSimpleContent(_parentContentType.Key, "Root Document"); + Attempt createRootResult = await ContentEditingService.CreateAsync(rootCreateModel, Constants.Security.SuperUserKey); + Assert.IsTrue(createRootResult.Success); + IContent? rootDocument = createRootResult.Result.Content; + + // Create Child Document under Root + ContentCreateModel childCreateModel = ContentEditingBuilder.CreateSimpleContent( + _childContentType.Key, + "Child Document", + rootDocument!.Key); + Attempt createChildResult = await ContentEditingService.CreateAsync(childCreateModel, Constants.Security.SuperUserKey); + Assert.IsTrue(createChildResult.Success); + } +}