diff --git a/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs b/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs
new file mode 100644
index 0000000000..34904db1ae
--- /dev/null
+++ b/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs
@@ -0,0 +1,22 @@
+using Lucene.Net.Store;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Umbraco.Tests.TestHelpers
+{
+
+ ///
+ /// Used for tests with Lucene so that each RAM directory is unique
+ ///
+ public class RandomIdRAMDirectory : RAMDirectory
+ {
+ private readonly string _lockId = Guid.NewGuid().ToString();
+ public override string GetLockId()
+ {
+ return _lockId;
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index f41ff1dd07..717006b702 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -157,6 +157,7 @@
+
@@ -268,6 +269,7 @@
+
diff --git a/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs b/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs
new file mode 100644
index 0000000000..b2a2741bcf
--- /dev/null
+++ b/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Examine;
+using Examine.LuceneEngine.Providers;
+using Lucene.Net.Store;
+using Moq;
+using NUnit.Framework;
+using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Examine;
+using Umbraco.Tests.TestHelpers;
+using Umbraco.Web;
+using Umbraco.Web.PublishedCache;
+
+namespace Umbraco.Tests.Web
+{
+ [TestFixture]
+ public class PublishedContentQueryTests
+ {
+
+ private class TestIndex : LuceneIndex, IUmbracoIndex
+ {
+ private readonly string[] _fieldNames;
+
+ public TestIndex(string name, Directory luceneDirectory, string[] fieldNames)
+ : base(name, luceneDirectory, null, null, null, null)
+ {
+ _fieldNames = fieldNames;
+ }
+ public bool EnableDefaultEventHandler => throw new NotImplementedException();
+ public bool PublishedValuesOnly => throw new NotImplementedException();
+ public IEnumerable GetFields() => _fieldNames;
+ }
+
+ private TestIndex CreateTestIndex(Directory luceneDirectory, string[] fieldNames)
+ {
+ var indexer = new TestIndex("TestIndex", luceneDirectory, fieldNames);
+
+ //populate with some test data
+ indexer.IndexItem(new ValueSet("1", "content", new Dictionary
+ {
+ [fieldNames[0]] = "Hello world, there are products here",
+ [UmbracoContentIndex.VariesByCultureFieldName] = "n"
+ }));
+ indexer.IndexItem(new ValueSet("2", "content", new Dictionary
+ {
+ [fieldNames[1]] = "Hello world, there are products here",
+ [UmbracoContentIndex.VariesByCultureFieldName] = "y"
+ }));
+ indexer.IndexItem(new ValueSet("3", "content", new Dictionary
+ {
+ [fieldNames[2]] = "Hello world, there are products here",
+ [UmbracoContentIndex.VariesByCultureFieldName] = "y"
+ }));
+ return indexer;
+ }
+
+ private PublishedContentQuery CreatePublishedContentQuery(IIndex indexer)
+ {
+ var examineManager = new Mock();
+ IIndex outarg = indexer;
+ examineManager.Setup(x => x.TryGetIndex("TestIndex", out outarg)).Returns(true);
+
+ var contentCache = new Mock();
+ contentCache.Setup(x => x.GetById(It.IsAny())).Returns((int intId) => Mock.Of(x => x.Id == intId));
+ var snapshot = Mock.Of(x => x.Content == contentCache.Object);
+ var variationContext = new VariationContext();
+ var variationContextAccessor = Mock.Of(x => x.VariationContext == variationContext);
+
+ return new PublishedContentQuery(snapshot, variationContextAccessor, examineManager.Object);
+ }
+
+ [Test]
+ public void Search_Wildcard()
+ {
+ using (var luceneDir = new RandomIdRAMDirectory())
+ {
+ var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" };
+ using (var indexer = CreateTestIndex(luceneDir, fieldNames))
+ {
+ var pcq = CreatePublishedContentQuery(indexer);
+
+ var results = pcq.Search("Products", "*", "TestIndex");
+
+ var ids = results.Select(x => x.Content.Id).ToList();
+ Assert.AreEqual(3, ids.Count);
+
+ //returns results for all fields and document types
+ Assert.IsTrue(ids.Contains(1) && ids.Contains(2) && ids.Contains(3));
+ }
+ }
+ }
+
+ [Test]
+ public void Search_Invariant()
+ {
+ using (var luceneDir = new RandomIdRAMDirectory())
+ {
+ var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" };
+ using (var indexer = CreateTestIndex(luceneDir, fieldNames))
+ {
+ var pcq = CreatePublishedContentQuery(indexer);
+
+ var results = pcq.Search("Products", null, "TestIndex");
+
+ var ids = results.Select(x => x.Content.Id).ToList();
+ Assert.AreEqual(1, ids.Count);
+
+ //returns results for only invariant fields and invariant documents
+ Assert.IsTrue(ids.Contains(1) && !ids.Contains(2) && !ids.Contains(3));
+ }
+ }
+ }
+
+ [Test]
+ public void Search_Culture1()
+ {
+ using (var luceneDir = new RandomIdRAMDirectory())
+ {
+ var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" };
+ using (var indexer = CreateTestIndex(luceneDir, fieldNames))
+ {
+ var pcq = CreatePublishedContentQuery(indexer);
+
+ var results = pcq.Search("Products", "en-us", "TestIndex");
+
+ var ids = results.Select(x => x.Content.Id).ToList();
+ Assert.AreEqual(2, ids.Count);
+
+ //returns results for en-us fields and invariant fields for all document types
+ Assert.IsTrue(ids.Contains(1) && ids.Contains(2) && !ids.Contains(3));
+ }
+ }
+ }
+
+ [Test]
+ public void Search_Culture2()
+ {
+ using (var luceneDir = new RandomIdRAMDirectory())
+ {
+ var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" };
+ using (var indexer = CreateTestIndex(luceneDir, fieldNames))
+ {
+ var pcq = CreatePublishedContentQuery(indexer);
+
+ var results = pcq.Search("Products", "fr-fr", "TestIndex");
+
+ var ids = results.Select(x => x.Content.Id).ToList();
+ Assert.AreEqual(2, ids.Count);
+
+ //returns results for fr-fr fields and invariant fields for all document types
+ Assert.IsTrue(ids.Contains(1) && !ids.Contains(2) && ids.Contains(3));
+ }
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs
index b23b5bd6b7..26d85f60cf 100644
--- a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs
+++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs
@@ -1,5 +1,8 @@
using System;
using System.Text;
+using Examine.LuceneEngine;
+using Lucene.Net.Analysis;
+using Lucene.Net.Analysis.Standard;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
@@ -13,18 +16,19 @@ using Umbraco.Web;
namespace Umbraco.Tests.Web
{
+
[TestFixture]
public class UmbracoHelperTests
- {
+ {
[TearDown]
public void TearDown()
{
Current.Reset();
}
-
-
+
+
// ------- Int32 conversion tests
[Test]
public static void Converting_Boxed_34_To_An_Int_Returns_34()
diff --git a/src/Umbraco.Web/IPublishedContentQuery.cs b/src/Umbraco.Web/IPublishedContentQuery.cs
index 35db121a60..8a8d678aba 100644
--- a/src/Umbraco.Web/IPublishedContentQuery.cs
+++ b/src/Umbraco.Web/IPublishedContentQuery.cs
@@ -39,7 +39,11 @@ namespace Umbraco.Web
/// Optional culture.
/// Optional index name.
///
- /// When the is not specified or is *, all cultures are searched. To search only invariant use null.
+ ///
+ /// When the is not specified or is *, all cultures are searched.
+ /// To search for only invariant documents and fields use null.
+ /// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents.
+ ///
/// While enumerating results, the ambient culture is changed to be the searched culture.
///
IEnumerable Search(string term, string culture = "*", string indexName = null);
@@ -54,7 +58,11 @@ namespace Umbraco.Web
/// Optional culture.
/// Optional index name.
///
- /// When the is not specified or is *, all cultures are searched. To search only invariant use null.
+ ///
+ /// When the is not specified or is *, all cultures are searched.
+ /// To search for only invariant documents and fields use null.
+ /// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents.
+ ///
/// While enumerating results, the ambient culture is changed to be the searched culture.
///
IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null);
diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs
index cfdd31f138..2dbe4de4c5 100644
--- a/src/Umbraco.Web/PublishedContentQuery.cs
+++ b/src/Umbraco.Web/PublishedContentQuery.cs
@@ -20,14 +20,22 @@ namespace Umbraco.Web
{
private readonly IPublishedSnapshot _publishedSnapshot;
private readonly IVariationContextAccessor _variationContextAccessor;
+ private readonly IExamineManager _examineManager;
+
+ [Obsolete("Use the constructor with all parameters instead")]
+ public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor)
+ : this (publishedSnapshot, variationContextAccessor, ExamineManager.Instance)
+ {
+ }
///
/// Initializes a new instance of the class.
///
- public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor)
+ public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor, IExamineManager examineManager)
{
_publishedSnapshot = publishedSnapshot ?? throw new ArgumentNullException(nameof(publishedSnapshot));
_variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
+ _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager));
}
#region Content
@@ -187,7 +195,7 @@ namespace Umbraco.Web
? Constants.UmbracoIndexes.ExternalIndexName
: indexName;
- if (!ExamineManager.Instance.TryGetIndex(indexName, out var index) || !(index is IUmbracoIndex umbIndex))
+ if (!_examineManager.TryGetIndex(indexName, out var index) || !(index is IUmbracoIndex umbIndex))
throw new InvalidOperationException($"No index found by name {indexName} or is not of type {typeof(IUmbracoIndex)}");
var searcher = umbIndex.GetSearcher();
@@ -216,8 +224,7 @@ namespace Umbraco.Web
//get all index fields suffixed with the culture name supplied
var cultureFields = umbIndex.GetCultureAndInvariantFields(culture).ToArray();
- var qry = searcher.CreateQuery();
- qry = qry.ManagedQuery(term, cultureFields);
+ var qry = searcher.CreateQuery().ManagedQuery(term, cultureFields);
results = qry.Execute(count);
}
@@ -313,7 +320,7 @@ namespace Umbraco.Web
}
}
-
+
#endregion