Migrating tests that depend on Published Cache from the old test project (#11242)
* starts cleaning up old test project, removing ones we'll never convert, moves new test to where it should be. * Makes ContentNodeKit immutable properties, moves first nucache tests over * Gets the Nucache unit tests working and refactors a bit to use builder pattern for models. * Migrates first xml based cache test to use nucache. * Migrates a bunch more * Migrates remaining tests for PublishedContentTests * Moves PublishedRouterTests * Moves PublishedContentExtensionTests * Moves more tests. * committing wip * committing wip * Gets PublishedContentLanguageVariantTests converted and working. * Fixes DataTable ext method and moves PublishedContentDataTableTests * Moves PublishedMediaTests * wip - moving EntityXmlSerializerTests * Moves more tests * moves more tests * moves more tests * Move another test * Moves more tests * Fix test * move another test * Moves more tests * Moves more tests * Moves more tests * wip before merge * More tests * More tests * More tests * More tests * More tests * More tests * Cleanup and moving classes. * Remove unused code * Fixed failing tests, due to new null checks, that did not exist in v8 * Avoid breaking changes * Unbreak more things, even that it the old solution was crazy.. * Fixed bug where ordering of stream readings was changed.. * cleanup Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -18,6 +18,8 @@ namespace Umbraco.Cms.Core.Cache
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, Lazy<object>> _items = new ConcurrentDictionary<string, Lazy<object>>();
|
||||
|
||||
public IEnumerable<string> Keys => _items.Keys;
|
||||
|
||||
public int Count => _items.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1328,8 +1328,8 @@ namespace Umbraco.Extensions
|
||||
{"NodeTypeAlias", "NodeTypeAlias"},
|
||||
{"CreateDate", "CreateDate"},
|
||||
{"UpdateDate", "UpdateDate"},
|
||||
{"CreatorName", "CreatorName"},
|
||||
{"WriterName", "WriterName"},
|
||||
{"CreatorId", "CreatorId"},
|
||||
{"WriterId", "WriterId"},
|
||||
{"Url", "Url"}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
@@ -16,8 +16,6 @@ namespace Umbraco.Cms.Core.PropertyEditors
|
||||
[HideFromTypeFinder]
|
||||
public class VoidEditor : DataEditor
|
||||
{
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VoidEditor"/> class.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PublishedCache.Internal
|
||||
{
|
||||
|
||||
// TODO: Only used in unit tests, needs to be moved to test project
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class InternalPublishedContent : IPublishedContent
|
||||
{
|
||||
public InternalPublishedContent(IPublishedContentType contentType)
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Xml;
|
||||
|
||||
namespace Umbraco.Cms.Core.PublishedCache.Internal
|
||||
{
|
||||
// TODO: Only used in unit tests, needs to be moved to test project
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class InternalPublishedContentCache : PublishedCacheBase, IPublishedContentCache, IPublishedMediaCache
|
||||
{
|
||||
private readonly Dictionary<int, IPublishedContent> _content = new Dictionary<int, IPublishedContent>();
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using System.ComponentModel;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Cms.Core.PublishedCache.Internal
|
||||
{
|
||||
// TODO: Only used in unit tests, needs to be moved to test project
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class InternalPublishedProperty : IPublishedProperty
|
||||
{
|
||||
public IPublishedPropertyType PropertyType { get; set; }
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
|
||||
namespace Umbraco.Cms.Core.PublishedCache.Internal
|
||||
{
|
||||
|
||||
// TODO: Only used in unit tests, needs to be moved to test project
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class InternalPublishedSnapshot : IPublishedSnapshot
|
||||
{
|
||||
public InternalPublishedContentCache InnerContentCache { get; } = new InternalPublishedContentCache();
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PublishedCache.Internal
|
||||
{
|
||||
// TODO: Only used in unit tests, needs to be moved to test project
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class InternalPublishedSnapshotService : IPublishedSnapshotService
|
||||
{
|
||||
private InternalPublishedSnapshot _snapshot;
|
||||
|
||||
@@ -1,116 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides urls.
|
||||
/// Provides urls.
|
||||
/// </summary>
|
||||
public class DefaultUrlProvider : IUrlProvider
|
||||
{
|
||||
private readonly RequestHandlerSettings _requestSettings;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly ILogger<DefaultUrlProvider> _logger;
|
||||
private readonly RequestHandlerSettings _requestSettings;
|
||||
private readonly ISiteDomainMapper _siteDomainMapper;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly UriUtility _uriUtility;
|
||||
|
||||
public DefaultUrlProvider(IOptions<RequestHandlerSettings> requestSettings, ILogger<DefaultUrlProvider> logger, ISiteDomainMapper siteDomainMapper, IUmbracoContextAccessor umbracoContextAccessor, UriUtility uriUtility)
|
||||
[Obsolete("Use ctor with all parameters")]
|
||||
public DefaultUrlProvider(IOptions<RequestHandlerSettings> requestSettings, ILogger<DefaultUrlProvider> logger,
|
||||
ISiteDomainMapper siteDomainMapper, IUmbracoContextAccessor umbracoContextAccessor, UriUtility uriUtility)
|
||||
: this(requestSettings, logger, siteDomainMapper, umbracoContextAccessor, uriUtility,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILocalizationService>())
|
||||
{
|
||||
}
|
||||
|
||||
public DefaultUrlProvider(
|
||||
IOptions<RequestHandlerSettings> requestSettings,
|
||||
ILogger<DefaultUrlProvider> logger,
|
||||
ISiteDomainMapper siteDomainMapper,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
UriUtility uriUtility,
|
||||
ILocalizationService localizationService)
|
||||
{
|
||||
_requestSettings = requestSettings.Value;
|
||||
_logger = logger;
|
||||
_siteDomainMapper = siteDomainMapper;
|
||||
_uriUtility = uriUtility;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_uriUtility = uriUtility;
|
||||
_localizationService = localizationService;
|
||||
}
|
||||
|
||||
#region GetUrl
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual UrlInfo GetUrl(IPublishedContent content, UrlMode mode, string culture, Uri current)
|
||||
{
|
||||
if (!current.IsAbsoluteUri) throw new ArgumentException("Current URL must be absolute.", nameof(current));
|
||||
var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
// will not use cache if previewing
|
||||
var route = umbracoContext.Content.GetRouteById(content.Id, culture);
|
||||
|
||||
return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture);
|
||||
}
|
||||
|
||||
internal UrlInfo GetUrlFromRoute(string route, IUmbracoContext umbracoContext, int id, Uri current, UrlMode mode, string culture)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(route))
|
||||
{
|
||||
_logger.LogDebug("Couldn't find any page with nodeId={NodeId}. This is most likely caused by the page not being published.", id);
|
||||
return null;
|
||||
}
|
||||
|
||||
// extract domainUri and path
|
||||
// route is /<path> or <domainRootId>/<path>
|
||||
var pos = route.IndexOf('/');
|
||||
var path = pos == 0 ? route : route.Substring(pos);
|
||||
var domainUri = pos == 0
|
||||
? null
|
||||
: DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current, culture);
|
||||
|
||||
// assemble the URL from domainUri (maybe null) and path
|
||||
var url = AssembleUrl(domainUri, path, current, mode).ToString();
|
||||
|
||||
return UrlInfo.Url(url, culture);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetOtherUrls
|
||||
|
||||
/// <summary>
|
||||
/// Gets the other URLs of a published content.
|
||||
/// Gets the other URLs of a published content.
|
||||
/// </summary>
|
||||
/// <param name="umbracoContextAccessor">The Umbraco context.</param>
|
||||
/// <param name="id">The published content id.</param>
|
||||
/// <param name="current">The current absolute URL.</param>
|
||||
/// <returns>The other URLs for the published content.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Other URLs are those that <c>GetUrl</c> would not return in the current context, but would be valid
|
||||
/// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...).</para>
|
||||
/// <para>
|
||||
/// Other URLs are those that <c>GetUrl</c> would not return in the current context, but would be valid
|
||||
/// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public virtual IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
|
||||
{
|
||||
var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
var node = umbracoContext.Content.GetById(id);
|
||||
IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
IPublishedContent node = umbracoContext.Content.GetById(id);
|
||||
if (node == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// look for domains, walking up the tree
|
||||
var n = node;
|
||||
var domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, false);
|
||||
IPublishedContent n = node;
|
||||
IEnumerable<DomainAndUri> domainUris =
|
||||
DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id,
|
||||
current, false);
|
||||
while (domainUris == null && n != null) // n is null at root
|
||||
{
|
||||
n = n.Parent; // move to parent node
|
||||
domainUris = n == null ? null : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, excludeDefault: true);
|
||||
domainUris = n == null
|
||||
? null
|
||||
: DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id,
|
||||
current);
|
||||
}
|
||||
|
||||
// no domains = exit
|
||||
if (domainUris ==null)
|
||||
if (domainUris == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var d in domainUris)
|
||||
foreach (DomainAndUri d in domainUris)
|
||||
{
|
||||
var culture = d?.Culture;
|
||||
|
||||
// although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok
|
||||
var route = umbracoContext.Content.GetRouteById(id, culture);
|
||||
if (route == null) continue;
|
||||
if (route == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned)
|
||||
var pos = route.IndexOf('/');
|
||||
@@ -124,9 +117,57 @@ namespace Umbraco.Cms.Core.Routing
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetUrl
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual UrlInfo GetUrl(IPublishedContent content, UrlMode mode, string culture, Uri current)
|
||||
{
|
||||
if (!current.IsAbsoluteUri)
|
||||
{
|
||||
throw new ArgumentException("Current URL must be absolute.", nameof(current));
|
||||
}
|
||||
|
||||
IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
// will not use cache if previewing
|
||||
var route = umbracoContext.Content.GetRouteById(content.Id, culture);
|
||||
|
||||
return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture);
|
||||
}
|
||||
|
||||
internal UrlInfo GetUrlFromRoute(string route, IUmbracoContext umbracoContext, int id, Uri current,
|
||||
UrlMode mode, string culture)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(route))
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Couldn't find any page with nodeId={NodeId}. This is most likely caused by the page not being published.",
|
||||
id);
|
||||
return null;
|
||||
}
|
||||
|
||||
// extract domainUri and path
|
||||
// route is /<path> or <domainRootId>/<path>
|
||||
var pos = route.IndexOf('/');
|
||||
var path = pos == 0 ? route : route.Substring(pos);
|
||||
var domainUri = pos == 0
|
||||
? null
|
||||
: DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current, culture);
|
||||
|
||||
var defaultCulture = _localizationService.GetDefaultLanguageIsoCode();
|
||||
if (domainUri is not null || culture == defaultCulture || culture is null)
|
||||
{
|
||||
var url = AssembleUrl(domainUri, path, current, mode).ToString();
|
||||
return UrlInfo.Url(url, culture);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utilities
|
||||
|
||||
Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, UrlMode mode)
|
||||
private Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, UrlMode mode)
|
||||
{
|
||||
Uri uri;
|
||||
|
||||
@@ -135,7 +176,9 @@ namespace Umbraco.Cms.Core.Routing
|
||||
if (domainUri == null) // no domain was found
|
||||
{
|
||||
if (current == null)
|
||||
{
|
||||
mode = UrlMode.Relative; // best we can do
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
@@ -155,10 +198,15 @@ namespace Umbraco.Cms.Core.Routing
|
||||
if (mode == UrlMode.Auto)
|
||||
{
|
||||
//this check is a little tricky, we can't just compare domains
|
||||
if (current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority))
|
||||
if (current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) ==
|
||||
current.GetLeftPart(UriPartial.Authority))
|
||||
{
|
||||
mode = UrlMode.Relative;
|
||||
}
|
||||
else
|
||||
{
|
||||
mode = UrlMode.Absolute;
|
||||
}
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
@@ -179,9 +227,9 @@ namespace Umbraco.Cms.Core.Routing
|
||||
return _uriUtility.UriFromUmbraco(uri, _requestSettings);
|
||||
}
|
||||
|
||||
string CombinePaths(string path1, string path2)
|
||||
private string CombinePaths(string path1, string path2)
|
||||
{
|
||||
string path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2;
|
||||
var path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2;
|
||||
return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,18 +4,36 @@ using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Umbraco.Extensions;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Umbraco.Cms.Core.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utilities to handle site domains.
|
||||
/// Provides utilities to handle site domains.
|
||||
/// </summary>
|
||||
public class SiteDomainMapper : ISiteDomainMapper, IDisposable
|
||||
{
|
||||
public void Dispose() =>
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(true);
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// This is pretty nasty disposing a static on an instance but it's because this whole class
|
||||
// is pretty fubar. I'm sure we've fixed this all up in netcore now? We need to remove all statics.
|
||||
_configLock.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
#region Configure
|
||||
|
||||
private readonly ReaderWriterLockSlim _configLock = new ReaderWriterLockSlim();
|
||||
private readonly ReaderWriterLockSlim _configLock = new();
|
||||
private Dictionary<string, Dictionary<string, string[]>> _qualifiedSites;
|
||||
private bool _disposedValue;
|
||||
|
||||
@@ -25,10 +43,12 @@ namespace Umbraco.Cms.Core.Routing
|
||||
// these are for validation
|
||||
//private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$";
|
||||
private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$";
|
||||
private static readonly Regex s_domainValidation = new Regex(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex s_domainValidation =
|
||||
new(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the entire configuration.
|
||||
/// Clears the entire configuration.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
@@ -49,20 +69,21 @@ namespace Umbraco.Cms.Core.Routing
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<string> ValidateDomains(IEnumerable<string> domains)
|
||||
{
|
||||
private IEnumerable<string> ValidateDomains(IEnumerable<string> domains) =>
|
||||
// must use authority format w/optional scheme and port, but no path
|
||||
// any domain should appear only once
|
||||
return domains.Select(domain =>
|
||||
domains.Select(domain =>
|
||||
{
|
||||
if (!s_domainValidation.IsMatch(domain))
|
||||
{
|
||||
if (!s_domainValidation.IsMatch(domain))
|
||||
throw new ArgumentOutOfRangeException(nameof(domains), $"Invalid domain: \"{domain}\".");
|
||||
return domain;
|
||||
});
|
||||
}
|
||||
throw new ArgumentOutOfRangeException(nameof(domains), $"Invalid domain: \"{domain}\".");
|
||||
}
|
||||
|
||||
return domain;
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Adds a site.
|
||||
/// Adds a site.
|
||||
/// </summary>
|
||||
/// <param name="key">A key uniquely identifying the site.</param>
|
||||
/// <param name="domains">The site domains.</param>
|
||||
@@ -87,7 +108,7 @@ namespace Umbraco.Cms.Core.Routing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a site.
|
||||
/// Adds a site.
|
||||
/// </summary>
|
||||
/// <param name="key">A key uniquely identifying the site.</param>
|
||||
/// <param name="domains">The site domains.</param>
|
||||
@@ -112,7 +133,7 @@ namespace Umbraco.Cms.Core.Routing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a site.
|
||||
/// Removes a site.
|
||||
/// </summary>
|
||||
/// <param name="key">A key uniquely identifying the site.</param>
|
||||
internal void RemoveSite(string key)
|
||||
@@ -122,11 +143,15 @@ namespace Umbraco.Cms.Core.Routing
|
||||
_configLock.EnterWriteLock();
|
||||
|
||||
if (Sites == null || !Sites.ContainsKey(key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Sites.Remove(key);
|
||||
if (Sites.Count == 0)
|
||||
{
|
||||
Sites = null;
|
||||
}
|
||||
|
||||
if (Bindings != null && Bindings.ContainsKey(key))
|
||||
{
|
||||
@@ -134,11 +159,16 @@ namespace Umbraco.Cms.Core.Routing
|
||||
{
|
||||
Bindings[b].Remove(key);
|
||||
if (Bindings[b].Count == 0)
|
||||
{
|
||||
Bindings.Remove(b);
|
||||
}
|
||||
}
|
||||
|
||||
Bindings.Remove(key);
|
||||
if (Bindings.Count > 0)
|
||||
{
|
||||
Bindings = null;
|
||||
}
|
||||
}
|
||||
|
||||
_qualifiedSites = null;
|
||||
@@ -153,12 +183,12 @@ namespace Umbraco.Cms.Core.Routing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds some sites.
|
||||
/// Binds some sites.
|
||||
/// </summary>
|
||||
/// <param name="keys">The keys uniquely identifying the sites to bind.</param>
|
||||
/// <remarks>
|
||||
/// <para>At the moment there is no public way to unbind sites. Clear and reconfigure.</para>
|
||||
/// <para>If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3.</para>
|
||||
/// <para>At the moment there is no public way to unbind sites. Clear and reconfigure.</para>
|
||||
/// <para>If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3.</para>
|
||||
/// </remarks>
|
||||
public void BindSites(params string[] keys)
|
||||
{
|
||||
@@ -167,7 +197,9 @@ namespace Umbraco.Cms.Core.Routing
|
||||
_configLock.EnterWriteLock();
|
||||
|
||||
foreach (var key in keys.Where(key => !Sites.ContainsKey(key)))
|
||||
{
|
||||
throw new ArgumentException($"Not an existing site key: {key}.", nameof(keys));
|
||||
}
|
||||
|
||||
Bindings = Bindings ?? new Dictionary<string, List<string>>();
|
||||
|
||||
@@ -180,9 +212,12 @@ namespace Umbraco.Cms.Core.Routing
|
||||
foreach (var key in allkeys)
|
||||
{
|
||||
if (!Bindings.ContainsKey(key))
|
||||
{
|
||||
Bindings[key] = new List<string>();
|
||||
}
|
||||
|
||||
var xkey = key;
|
||||
var addKeys = allkeys.Where(k => k != xkey).Except(Bindings[key]);
|
||||
IEnumerable<string> addKeys = allkeys.Where(k => k != xkey).Except(Bindings[key]);
|
||||
Bindings[key].AddRange(addKeys);
|
||||
}
|
||||
}
|
||||
@@ -200,16 +235,18 @@ namespace Umbraco.Cms.Core.Routing
|
||||
#region Map domains
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual DomainAndUri MapDomain(IReadOnlyCollection<DomainAndUri> domainAndUris, Uri current, string culture, string defaultCulture)
|
||||
public virtual DomainAndUri MapDomain(IReadOnlyCollection<DomainAndUri> domainAndUris, Uri current,
|
||||
string culture, string defaultCulture)
|
||||
{
|
||||
var currentAuthority = current.GetLeftPart(UriPartial.Authority);
|
||||
var qualifiedSites = GetQualifiedSites(current);
|
||||
Dictionary<string, string[]> qualifiedSites = GetQualifiedSites(current);
|
||||
|
||||
return MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, defaultCulture);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<DomainAndUri> MapDomains(IReadOnlyCollection<DomainAndUri> domainAndUris, Uri current, bool excludeDefault, string culture, string defaultCulture)
|
||||
public virtual IEnumerable<DomainAndUri> MapDomains(IReadOnlyCollection<DomainAndUri> domainAndUris,
|
||||
Uri current, bool excludeDefault, string culture, string defaultCulture)
|
||||
{
|
||||
// TODO: ignoring cultures entirely?
|
||||
|
||||
@@ -221,15 +258,18 @@ namespace Umbraco.Cms.Core.Routing
|
||||
{
|
||||
_configLock.EnterReadLock();
|
||||
|
||||
var qualifiedSites = GetQualifiedSitesInsideLock(current);
|
||||
Dictionary<string, string[]> qualifiedSites = GetQualifiedSitesInsideLock(current);
|
||||
|
||||
if (excludeDefault)
|
||||
{
|
||||
// exclude the current one (avoid producing the absolute equivalent of what GetUrl returns)
|
||||
var hintWithSlash = current.EndPathWithSlash();
|
||||
var hinted = domainAndUris.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash));
|
||||
Uri hintWithSlash = current.EndPathWithSlash();
|
||||
DomainAndUri hinted =
|
||||
domainAndUris.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash));
|
||||
if (hinted != null)
|
||||
{
|
||||
ret = ret.Where(d => d != hinted);
|
||||
}
|
||||
|
||||
// exclude the default one (avoid producing a possible duplicate of what GetUrl returns)
|
||||
// only if the default one cannot be the current one ie if hinted is not null
|
||||
@@ -237,17 +277,21 @@ namespace Umbraco.Cms.Core.Routing
|
||||
{
|
||||
// it is illegal to call MapDomain if domainAndUris is empty
|
||||
// also, domainAndUris should NOT contain current, hence the test on hinted
|
||||
var mainDomain = MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, defaultCulture); // what GetUrl would get
|
||||
DomainAndUri mainDomain = MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture,
|
||||
defaultCulture); // what GetUrl would get
|
||||
ret = ret.Where(d => d != mainDomain);
|
||||
}
|
||||
}
|
||||
|
||||
// we do our best, but can't do the impossible
|
||||
if (qualifiedSites == null)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
// find a site that contains the current authority
|
||||
var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority));
|
||||
KeyValuePair<string, string[]> currentSite =
|
||||
qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority));
|
||||
|
||||
// if current belongs to a site, pick every element from domainAndUris that also belong
|
||||
// to that site -- or to any site bound to that site
|
||||
@@ -257,7 +301,8 @@ namespace Umbraco.Cms.Core.Routing
|
||||
candidateSites = new[] { currentSite };
|
||||
if (Bindings != null && Bindings.ContainsKey(currentSite.Key))
|
||||
{
|
||||
var boundSites = qualifiedSites.Where(site => Bindings[currentSite.Key].Contains(site.Key));
|
||||
IEnumerable<KeyValuePair<string, string[]>> boundSites =
|
||||
qualifiedSites.Where(site => Bindings[currentSite.Key].Contains(site.Key));
|
||||
candidateSites = candidateSites.Union(boundSites).ToArray();
|
||||
|
||||
// .ToArray ensures it is evaluated before the configuration lock is exited
|
||||
@@ -273,7 +318,9 @@ namespace Umbraco.Cms.Core.Routing
|
||||
}
|
||||
|
||||
// if we are able to filter, then filter, else return the whole lot
|
||||
return candidateSites == null ? ret : ret.Where(d =>
|
||||
return candidateSites == null
|
||||
? ret
|
||||
: ret.Where(d =>
|
||||
{
|
||||
var authority = d.Uri.GetLeftPart(UriPartial.Authority);
|
||||
return candidateSites.Any(site => site.Value.Contains(authority));
|
||||
@@ -301,11 +348,15 @@ namespace Umbraco.Cms.Core.Routing
|
||||
{
|
||||
// we do our best, but can't do the impossible
|
||||
if (Sites == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// cached?
|
||||
if (_qualifiedSites != null && _qualifiedSites.ContainsKey(current.Scheme))
|
||||
{
|
||||
return _qualifiedSites[current.Scheme];
|
||||
}
|
||||
|
||||
_qualifiedSites = _qualifiedSites ?? new Dictionary<string, Dictionary<string, string[]>>();
|
||||
|
||||
@@ -314,7 +365,10 @@ namespace Umbraco.Cms.Core.Routing
|
||||
return _qualifiedSites[current.Scheme] = Sites
|
||||
.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => kvp.Value.Select(d => new Uri(UriUtilityCore.StartWithScheme(d, current.Scheme)).GetLeftPart(UriPartial.Authority)).ToArray()
|
||||
kvp => kvp.Value.Select(d =>
|
||||
new Uri(UriUtilityCore.StartWithScheme(d, current.Scheme))
|
||||
.GetLeftPart(UriPartial.Authority))
|
||||
.ToArray()
|
||||
);
|
||||
|
||||
// .ToDictionary will evaluate and create the dictionary immediately
|
||||
@@ -322,61 +376,54 @@ namespace Umbraco.Cms.Core.Routing
|
||||
// therefore it is safe to return and exit the configuration lock
|
||||
}
|
||||
|
||||
private DomainAndUri MapDomain(IReadOnlyCollection<DomainAndUri> domainAndUris, Dictionary<string, string[]> qualifiedSites, string currentAuthority, string culture, string defaultCulture)
|
||||
private DomainAndUri MapDomain(IReadOnlyCollection<DomainAndUri> domainAndUris,
|
||||
Dictionary<string, string[]> qualifiedSites, string currentAuthority, string culture, string defaultCulture)
|
||||
{
|
||||
if (domainAndUris == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(domainAndUris));
|
||||
}
|
||||
|
||||
if (domainAndUris.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("Cannot be empty.", nameof(domainAndUris));
|
||||
}
|
||||
|
||||
// TODO: how shall we deal with cultures?
|
||||
|
||||
// we do our best, but can't do the impossible
|
||||
// get the "default" domain ie the first one for the culture, else the first one (exists, length > 0)
|
||||
if (qualifiedSites == null)
|
||||
{
|
||||
return domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture))
|
||||
?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture));
|
||||
?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture))
|
||||
?? (culture is null ? domainAndUris.First() : null);
|
||||
}
|
||||
|
||||
// find a site that contains the current authority
|
||||
var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority));
|
||||
KeyValuePair<string, string[]> currentSite =
|
||||
qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority));
|
||||
|
||||
// if current belongs to a site - try to pick the first element
|
||||
// from domainAndUris that also belongs to that site
|
||||
var ret = currentSite.Equals(default(KeyValuePair<string, string[]>))
|
||||
DomainAndUri ret = currentSite.Equals(default(KeyValuePair<string, string[]>))
|
||||
? null
|
||||
: domainAndUris.FirstOrDefault(d => currentSite.Value.Contains(d.Uri.GetLeftPart(UriPartial.Authority)));
|
||||
: domainAndUris.FirstOrDefault(d =>
|
||||
currentSite.Value.Contains(d.Uri.GetLeftPart(UriPartial.Authority)));
|
||||
|
||||
// no match means that either current does not belong to a site, or the site it belongs to
|
||||
// does not contain any of domainAndUris.
|
||||
// does not contain any of domainAndUris. Yet we have to return something. here, it becomes
|
||||
// a bit arbitrary.
|
||||
|
||||
// look through sites in order and pick the first domainAndUri that belongs to a site
|
||||
ret = ret ?? qualifiedSites
|
||||
.Where(site => site.Key != currentSite.Key)
|
||||
.Select(site => domainAndUris.FirstOrDefault(domainAndUri =>
|
||||
site.Value.Contains(domainAndUri.Uri.GetLeftPart(UriPartial.Authority))))
|
||||
.FirstOrDefault(domainAndUri => domainAndUri != null);
|
||||
|
||||
// random, really
|
||||
ret = ret ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) ?? domainAndUris.First();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// This is pretty nasty disposing a static on an instance but it's because this whole class
|
||||
// is pretty fubar. I'm sure we've fixed this all up in netcore now? We need to remove all statics.
|
||||
_configLock.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ namespace Umbraco.Extensions
|
||||
public static class UrlProviderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the URLs of the content item.
|
||||
/// Gets the URLs of the content item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Use when displaying URLs. If errors occur when generating the URLs, they will show in the list.</para>
|
||||
/// <para>Contains all the URLs that we can figure out (based upon domains, etc).</para>
|
||||
/// <para>Use when displaying URLs. If errors occur when generating the URLs, they will show in the list.</para>
|
||||
/// <para>Contains all the URLs that we can figure out (based upon domains, etc).</para>
|
||||
/// </remarks>
|
||||
public static async Task<IEnumerable<UrlInfo>> GetContentUrlsAsync(
|
||||
this IContent content,
|
||||
@@ -33,16 +33,55 @@ namespace Umbraco.Extensions
|
||||
UriUtility uriUtility,
|
||||
IPublishedUrlProvider publishedUrlProvider)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException(nameof(content));
|
||||
if (publishedRouter == null) throw new ArgumentNullException(nameof(publishedRouter));
|
||||
if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext));
|
||||
if (localizationService == null) throw new ArgumentNullException(nameof(localizationService));
|
||||
if (textService == null) throw new ArgumentNullException(nameof(textService));
|
||||
if (contentService == null) throw new ArgumentNullException(nameof(contentService));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
if (publishedUrlProvider == null) throw new ArgumentNullException(nameof(publishedUrlProvider));
|
||||
if (uriUtility == null) throw new ArgumentNullException(nameof(uriUtility));
|
||||
if (variationContextAccessor == null) throw new ArgumentNullException(nameof(variationContextAccessor));
|
||||
if (content == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(content));
|
||||
}
|
||||
|
||||
if (publishedRouter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(publishedRouter));
|
||||
}
|
||||
|
||||
if (umbracoContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(umbracoContext));
|
||||
}
|
||||
|
||||
if (localizationService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(localizationService));
|
||||
}
|
||||
|
||||
if (textService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textService));
|
||||
}
|
||||
|
||||
if (contentService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contentService));
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
if (publishedUrlProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(publishedUrlProvider));
|
||||
}
|
||||
|
||||
if (uriUtility == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(uriUtility));
|
||||
}
|
||||
|
||||
if (variationContextAccessor == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(variationContextAccessor));
|
||||
}
|
||||
|
||||
var result = new List<UrlInfo>();
|
||||
|
||||
@@ -68,7 +107,9 @@ namespace Umbraco.Extensions
|
||||
|
||||
// get all URLs for all cultures
|
||||
// in a HashSet, so de-duplicates too
|
||||
foreach (UrlInfo cultureUrl in await GetContentUrlsByCultureAsync(content, cultures, publishedRouter, umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility, publishedUrlProvider))
|
||||
foreach (UrlInfo cultureUrl in await GetContentUrlsByCultureAsync(content, cultures, publishedRouter,
|
||||
umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility,
|
||||
publishedUrlProvider))
|
||||
{
|
||||
urls.Add(cultureUrl);
|
||||
}
|
||||
@@ -79,15 +120,25 @@ namespace Umbraco.Extensions
|
||||
// in some cases there will be the same URL for multiple cultures:
|
||||
// * The entire branch is invariant
|
||||
// * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed
|
||||
foreach (UrlInfo dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant()).OrderBy(x => x.Text).ThenBy(x => x.Culture))
|
||||
|
||||
if (urlGroup.Key)
|
||||
{
|
||||
result.Add(dUrl);
|
||||
result.AddRange(urlGroup.DistinctBy(x => x.Text.ToUpperInvariant())
|
||||
.OrderBy(x => x.Text).ThenBy(x => x.Culture));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.AddRange(urlGroup);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// get the 'other' URLs - ie not what you'd get with GetUrl() but URLs that would route to the document, nevertheless.
|
||||
// for these 'other' URLs, we don't check whether they are routable, collide, anything - we just report them.
|
||||
foreach (var otherUrl in publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text).ThenBy(x => x.Culture))
|
||||
foreach (UrlInfo otherUrl in publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text)
|
||||
.ThenBy(x => x.Culture))
|
||||
{
|
||||
// avoid duplicates
|
||||
if (urls.Add(otherUrl))
|
||||
@@ -100,7 +151,7 @@ namespace Umbraco.Extensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to return a <see cref="UrlInfo"/> for each culture for the content while detecting collisions/errors
|
||||
/// Tries to return a <see cref="UrlInfo" /> for each culture for the content while detecting collisions/errors
|
||||
/// </summary>
|
||||
private static async Task<IEnumerable<UrlInfo>> GetContentUrlsByCultureAsync(
|
||||
IContent content,
|
||||
@@ -151,7 +202,8 @@ namespace Umbraco.Extensions
|
||||
// got a URL, deal with collisions, add URL
|
||||
default:
|
||||
// detect collisions, etc
|
||||
Attempt<UrlInfo> hasCollision = await DetectCollisionAsync(logger, content, url, culture, umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility);
|
||||
Attempt<UrlInfo> hasCollision = await DetectCollisionAsync(logger, content, url, culture,
|
||||
umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility);
|
||||
if (hasCollision)
|
||||
{
|
||||
result.Add(hasCollision.Result);
|
||||
@@ -168,7 +220,8 @@ namespace Umbraco.Extensions
|
||||
return result;
|
||||
}
|
||||
|
||||
private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, ILocalizedTextService textService)
|
||||
private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService,
|
||||
ILocalizedTextService textService)
|
||||
{
|
||||
// document has a published version yet its URL is "#" => a parent must be
|
||||
// unpublished, walk up the tree until we find it, and report.
|
||||
@@ -176,27 +229,31 @@ namespace Umbraco.Extensions
|
||||
do
|
||||
{
|
||||
parent = parent.ParentId > 0 ? contentService.GetParent(parent) : null;
|
||||
}
|
||||
while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture)));
|
||||
} while (parent != null && parent.Published &&
|
||||
(!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture)));
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
// oops, internal error
|
||||
return UrlInfo.Message(textService.Localize("content", "parentNotPublishedAnomaly"), culture);
|
||||
}
|
||||
else if (!parent.Published)
|
||||
|
||||
if (!parent.Published)
|
||||
{
|
||||
// totally not published
|
||||
return UrlInfo.Message(textService.Localize("content", "parentNotPublished", new[] { parent.Name }), culture);
|
||||
}
|
||||
else
|
||||
{
|
||||
// culture not published
|
||||
return UrlInfo.Message(textService.Localize("content", "parentCultureNotPublished", new[] {parent.Name}), culture);
|
||||
return UrlInfo.Message(textService.Localize("content", "parentNotPublished", new[] { parent.Name }),
|
||||
culture);
|
||||
}
|
||||
|
||||
// culture not published
|
||||
return UrlInfo.Message(textService.Localize("content", "parentCultureNotPublished", new[] { parent.Name }),
|
||||
culture);
|
||||
}
|
||||
|
||||
private static async Task<Attempt<UrlInfo>> DetectCollisionAsync(ILogger logger, IContent content, string url, string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility)
|
||||
private static async Task<Attempt<UrlInfo>> DetectCollisionAsync(ILogger logger, IContent content, string url,
|
||||
string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter,
|
||||
ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor,
|
||||
UriUtility uriUtility)
|
||||
{
|
||||
// test for collisions on the 'main' URL
|
||||
var uri = new Uri(url.TrimEnd(Constants.CharArrays.ForwardSlash), UriKind.RelativeOrAbsolute);
|
||||
@@ -207,11 +264,13 @@ namespace Umbraco.Extensions
|
||||
|
||||
uri = uriUtility.UriToUmbraco(uri);
|
||||
IPublishedRequestBuilder builder = await publishedRouter.CreateRequestAsync(uri);
|
||||
IPublishedRequest pcr = await publishedRouter.RouteRequestAsync(builder, new RouteRequestOptions(RouteDirection.Outbound));
|
||||
IPublishedRequest pcr =
|
||||
await publishedRouter.RouteRequestAsync(builder, new RouteRequestOptions(RouteDirection.Outbound));
|
||||
|
||||
if (!pcr.HasPublishedContent())
|
||||
{
|
||||
var logMsg = nameof(DetectCollisionAsync) + " did not resolve a content item for original url: {Url}, translated to {TranslatedUrl} and culture: {Culture}";
|
||||
var logMsg = nameof(DetectCollisionAsync) +
|
||||
" did not resolve a content item for original url: {Url}, translated to {TranslatedUrl} and culture: {Culture}";
|
||||
if (pcr.IgnorePublishedContentCollisions)
|
||||
{
|
||||
logger.LogDebug(logMsg, url, uri, culture);
|
||||
@@ -243,14 +302,7 @@ namespace Umbraco.Extensions
|
||||
l.Reverse();
|
||||
var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")";
|
||||
|
||||
var urlInfo = UrlInfo.Message(textService.Localize("content", "routeError", new[] { s }), culture);
|
||||
return Attempt.Succeed(urlInfo);
|
||||
}
|
||||
|
||||
// collisions with a different culture of the same content can never be routed.
|
||||
if (!culture.InvariantEquals(pcr.Culture))
|
||||
{
|
||||
var urlInfo = UrlInfo.Message(textService.Localize("content", "routeErrorCannotRoute"), culture);
|
||||
var urlInfo = UrlInfo.Message(textService.Localize("content", "routeError", new[] { s }), culture);
|
||||
return Attempt.Succeed(urlInfo);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Umbraco.Cms.Core.PublishedCache
|
||||
private readonly IPublishedContentTypeFactory _publishedContentTypeFactory;
|
||||
private readonly ILogger<PublishedContentTypeCache> _logger;
|
||||
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
|
||||
private bool _disposedValue;
|
||||
|
||||
// default ctor
|
||||
public PublishedContentTypeCache(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, IPublishedContentTypeFactory publishedContentTypeFactory, ILogger<PublishedContentTypeCache> logger)
|
||||
@@ -259,8 +260,6 @@ namespace Umbraco.Cms.Core.PublishedCache
|
||||
|
||||
private IPublishedContentType CreatePublishedContentType(PublishedItemType itemType, string alias)
|
||||
{
|
||||
if (GetPublishedContentTypeByAlias != null)
|
||||
return GetPublishedContentTypeByAlias(alias);
|
||||
IContentTypeComposition contentType = itemType switch
|
||||
{
|
||||
PublishedItemType.Content => _contentTypeService.Get(alias),
|
||||
@@ -276,8 +275,6 @@ namespace Umbraco.Cms.Core.PublishedCache
|
||||
|
||||
private IPublishedContentType CreatePublishedContentType(PublishedItemType itemType, int id)
|
||||
{
|
||||
if (GetPublishedContentTypeById != null)
|
||||
return GetPublishedContentTypeById(id);
|
||||
IContentTypeComposition contentType = itemType switch
|
||||
{
|
||||
PublishedItemType.Content => _contentTypeService.Get(id),
|
||||
@@ -291,56 +288,6 @@ namespace Umbraco.Cms.Core.PublishedCache
|
||||
return _publishedContentTypeFactory.CreateContentType(contentType);
|
||||
}
|
||||
|
||||
// for unit tests - changing the callback must reset the cache obviously
|
||||
// TODO: Why does this even exist? For testing you'd pass in a mocked service to get by id
|
||||
private Func<string, IPublishedContentType> _getPublishedContentTypeByAlias;
|
||||
internal Func<string, IPublishedContentType> GetPublishedContentTypeByAlias
|
||||
{
|
||||
get => _getPublishedContentTypeByAlias;
|
||||
set
|
||||
{
|
||||
try
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
|
||||
_typesByAlias.Clear();
|
||||
_typesById.Clear();
|
||||
_getPublishedContentTypeByAlias = value;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsWriteLockHeld)
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for unit tests - changing the callback must reset the cache obviously
|
||||
// TODO: Why does this even exist? For testing you'd pass in a mocked service to get by id
|
||||
private Func<int, IPublishedContentType> _getPublishedContentTypeById;
|
||||
private bool _disposedValue;
|
||||
|
||||
internal Func<int, IPublishedContentType> GetPublishedContentTypeById
|
||||
{
|
||||
get => _getPublishedContentTypeById;
|
||||
set
|
||||
{
|
||||
try
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
|
||||
_typesByAlias.Clear();
|
||||
_typesById.Clear();
|
||||
_getPublishedContentTypeById = value;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsWriteLockHeld)
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetAliasKey(PublishedItemType itemType, string alias)
|
||||
{
|
||||
string k;
|
||||
|
||||
@@ -13,19 +13,24 @@ using Umbraco.Cms.Core.Xml;
|
||||
using Umbraco.Cms.Core.Xml.XPath;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.Navigable;
|
||||
using Umbraco.Extensions;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
{
|
||||
public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigableData, IDisposable
|
||||
{
|
||||
private readonly IDomainCache _domainCache;
|
||||
private readonly IAppCache _elementsCache;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly ContentStore.Snapshot _snapshot;
|
||||
private readonly IAppCache _snapshotCache;
|
||||
private readonly IAppCache _elementsCache;
|
||||
private readonly IDomainCache _domainCache;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose() => _snapshot.Dispose();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
// TODO: figure this out
|
||||
@@ -33,7 +38,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
// it's too late for UmbracoContext which has captured previewDefault and stuff into these ctor vars
|
||||
// but, no, UmbracoContext returns snapshot.Content which comes from elements SO a resync should create a new cache
|
||||
|
||||
public ContentCache(bool previewDefault, ContentStore.Snapshot snapshot, IAppCache snapshotCache, IAppCache elementsCache, IDomainCache domainCache, IOptions<GlobalSettings> globalSettings, IVariationContextAccessor variationContextAccessor)
|
||||
public ContentCache(bool previewDefault, ContentStore.Snapshot snapshot, IAppCache snapshotCache,
|
||||
IAppCache elementsCache, IDomainCache domainCache, IOptions<GlobalSettings> globalSettings,
|
||||
IVariationContextAccessor variationContextAccessor)
|
||||
: base(previewDefault)
|
||||
{
|
||||
_snapshot = snapshot;
|
||||
@@ -59,18 +66,23 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
// at the moment we try our best to be backward compatible, but really,
|
||||
// should get rid of hideTopLevelNode and other oddities entirely, eventually
|
||||
|
||||
public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string culture = null)
|
||||
{
|
||||
return GetByRoute(PreviewDefault, route, hideTopLevelNode, culture);
|
||||
}
|
||||
public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string culture = null) =>
|
||||
GetByRoute(PreviewDefault, route, hideTopLevelNode, culture);
|
||||
|
||||
public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string culture = null)
|
||||
public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null,
|
||||
string culture = null)
|
||||
{
|
||||
if (route == null) throw new ArgumentNullException(nameof(route));
|
||||
if (route == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
}
|
||||
|
||||
var cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing ? _elementsCache : _snapshotCache;
|
||||
|
||||
IAppCache cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing
|
||||
? _elementsCache
|
||||
: _snapshotCache;
|
||||
var key = CacheKeys.ContentCacheContentByRoute(route, preview, culture);
|
||||
return cache.GetCacheItem<IPublishedContent>(key, () => GetByRouteInternal(preview, route, hideTopLevelNode, culture));
|
||||
return cache.GetCacheItem(key, () => GetByRouteInternal(preview, route, hideTopLevelNode, culture));
|
||||
}
|
||||
|
||||
private IPublishedContent GetByRouteInternal(bool preview, string route, bool? hideTopLevelNode, string culture)
|
||||
@@ -108,8 +120,10 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
// hideTopLevelNode = support legacy stuff, look for /*/path/to/node
|
||||
// else normal, look for /path/to/node
|
||||
content = hideTopLevelNode.Value
|
||||
? GetAtRoot(preview).SelectMany(x => x.Children(_variationContextAccessor, culture)).FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0])
|
||||
: GetAtRoot(preview).FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]);
|
||||
? GetAtRoot(preview).SelectMany(x => x.Children(_variationContextAccessor, culture))
|
||||
.FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0])
|
||||
: GetAtRoot(preview)
|
||||
.FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]);
|
||||
content = FollowRoute(content, parts, 1, culture);
|
||||
}
|
||||
|
||||
@@ -118,59 +132,72 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
// have to look for /foo (see note in ApplyHideTopLevelNodeFromPath).
|
||||
if (content == null && hideTopLevelNode.Value && parts.Length == 1)
|
||||
{
|
||||
content = GetAtRoot(preview).FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]);
|
||||
content = GetAtRoot(preview)
|
||||
.FirstOrDefault(x => x.UrlSegment(_variationContextAccessor, culture) == parts[0]);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
public string GetRouteById(int contentId, string culture = null)
|
||||
{
|
||||
return GetRouteById(PreviewDefault, contentId, culture);
|
||||
}
|
||||
public string GetRouteById(int contentId, string culture = null) =>
|
||||
GetRouteById(PreviewDefault, contentId, culture);
|
||||
|
||||
public string GetRouteById(bool preview, int contentId, string culture = null)
|
||||
{
|
||||
var cache = (preview == false || PublishedSnapshotService.FullCacheWhenPreviewing) ? _elementsCache : _snapshotCache;
|
||||
IAppCache cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing
|
||||
? _elementsCache
|
||||
: _snapshotCache;
|
||||
var key = CacheKeys.ContentCacheRouteByContent(contentId, preview, culture);
|
||||
return cache.GetCacheItem<string>(key, () => GetRouteByIdInternal(preview, contentId, null, culture));
|
||||
return cache.GetCacheItem(key, () => GetRouteByIdInternal(preview, contentId, null, culture));
|
||||
}
|
||||
|
||||
private string GetRouteByIdInternal(bool preview, int contentId, bool? hideTopLevelNode, string culture)
|
||||
{
|
||||
var node = GetById(preview, contentId);
|
||||
IPublishedContent node = GetById(preview, contentId);
|
||||
if (node == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
hideTopLevelNode = hideTopLevelNode ?? HideTopLevelNodeFromPath; // default = settings
|
||||
|
||||
// walk up from that node until we hit a node with a domain,
|
||||
// or we reach the content root, collecting URLs in the way
|
||||
var pathParts = new List<string>();
|
||||
var n = node;
|
||||
IPublishedContent n = node;
|
||||
var urlSegment = n.UrlSegment(_variationContextAccessor, culture);
|
||||
var hasDomains = _domainCache.HasAssigned(n.Id);
|
||||
var hasDomains = _domainCache.GetAssignedWithCulture(culture, n.Id);
|
||||
while (hasDomains == false && n != null) // n is null at root
|
||||
{
|
||||
// no segment indicates this is not published when this is a variant
|
||||
if (urlSegment.IsNullOrWhiteSpace()) return null;
|
||||
if (urlSegment.IsNullOrWhiteSpace())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
pathParts.Add(urlSegment);
|
||||
|
||||
// move to parent node
|
||||
n = n.Parent;
|
||||
if (n != null)
|
||||
{
|
||||
urlSegment = n.UrlSegment(_variationContextAccessor, culture);
|
||||
}
|
||||
|
||||
hasDomains = n != null && _domainCache.HasAssigned(n.Id);
|
||||
hasDomains = n != null && _domainCache.GetAssignedWithCulture(culture, n.Id);
|
||||
}
|
||||
|
||||
// at this point this will be the urlSegment of the root, no segment indicates this is not published when this is a variant
|
||||
if (urlSegment.IsNullOrWhiteSpace()) return null;
|
||||
if (urlSegment.IsNullOrWhiteSpace())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
|
||||
if (hasDomains == false && hideTopLevelNode.Value)
|
||||
{
|
||||
ApplyHideTopLevelNodeFromPath(node, pathParts, preview);
|
||||
}
|
||||
|
||||
// assemble the route
|
||||
pathParts.Reverse();
|
||||
@@ -182,7 +209,8 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
return route;
|
||||
}
|
||||
|
||||
private IPublishedContent FollowRoute(IPublishedContent content, IReadOnlyList<string> parts, int start, string culture)
|
||||
private IPublishedContent FollowRoute(IPublishedContent content, IReadOnlyList<string> parts, int start,
|
||||
string culture)
|
||||
{
|
||||
var i = start;
|
||||
while (content != null && i < parts.Count)
|
||||
@@ -194,6 +222,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
return urlSegment == part;
|
||||
});
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -209,11 +238,16 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
// that's the way it works pre-4.10 and we try to be backward compat for the time being
|
||||
if (content.Parent == null)
|
||||
{
|
||||
var rootNode = GetByRoute(preview, "/", true);
|
||||
IPublishedContent rootNode = GetByRoute(preview, "/", true);
|
||||
if (rootNode == null)
|
||||
{
|
||||
throw new Exception("Failed to get node at /.");
|
||||
}
|
||||
|
||||
if (rootNode.Id == content.Id) // remove only if we're the default node
|
||||
{
|
||||
segments.RemoveAt(segments.Count - 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -227,13 +261,13 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
|
||||
public override IPublishedContent GetById(bool preview, int contentId)
|
||||
{
|
||||
var node = _snapshot.Get(contentId);
|
||||
ContentNode node = _snapshot.Get(contentId);
|
||||
return GetNodePublishedContent(node, preview);
|
||||
}
|
||||
|
||||
public override IPublishedContent GetById(bool preview, Guid contentId)
|
||||
{
|
||||
var node = _snapshot.Get(contentId);
|
||||
ContentNode node = _snapshot.Get(contentId);
|
||||
return GetNodePublishedContent(node, preview);
|
||||
}
|
||||
|
||||
@@ -241,18 +275,26 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
{
|
||||
var guidUdi = contentId as GuidUdi;
|
||||
if (guidUdi == null)
|
||||
{
|
||||
throw new ArgumentException($"Udi must be of type {typeof(GuidUdi).Name}.", nameof(contentId));
|
||||
}
|
||||
|
||||
if (guidUdi.EntityType != Constants.UdiEntityType.Document)
|
||||
throw new ArgumentException($"Udi entity type must be \"{Constants.UdiEntityType.Document}\".", nameof(contentId));
|
||||
{
|
||||
throw new ArgumentException($"Udi entity type must be \"{Constants.UdiEntityType.Document}\".",
|
||||
nameof(contentId));
|
||||
}
|
||||
|
||||
return GetById(preview, guidUdi.Guid);
|
||||
}
|
||||
|
||||
public override bool HasById(bool preview, int contentId)
|
||||
{
|
||||
var n = _snapshot.Get(contentId);
|
||||
if (n == null) return false;
|
||||
ContentNode n = _snapshot.Get(contentId);
|
||||
if (n == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return preview || n.PublishedModel != null;
|
||||
}
|
||||
@@ -263,7 +305,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
{
|
||||
// handle context culture for variant
|
||||
if (culture == null)
|
||||
{
|
||||
culture = _variationContextAccessor?.VariationContext?.Culture ?? "";
|
||||
}
|
||||
|
||||
// _snapshot.GetAtRoot() returns all ContentNode at root
|
||||
// both .Draft and .Published cannot be null at the same time
|
||||
@@ -272,13 +316,15 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
// GetNodePublishedContent may return null if !preview and there is no
|
||||
// published model, so we need to filter these nulls out
|
||||
|
||||
var atRoot = _snapshot.GetAtRoot()
|
||||
IEnumerable<IPublishedContent> atRoot = _snapshot.GetAtRoot()
|
||||
.Select(n => GetNodePublishedContent(n, preview))
|
||||
.WhereNotNull();
|
||||
|
||||
// if a culture is specified, we must ensure that it is avail/published
|
||||
if (culture != "*")
|
||||
{
|
||||
atRoot = atRoot.Where(x => x.IsInvariantOrHasCulture(culture));
|
||||
}
|
||||
|
||||
return atRoot;
|
||||
}
|
||||
@@ -286,7 +332,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
private static IPublishedContent GetNodePublishedContent(ContentNode node, bool preview)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// both .Draft and .Published cannot be null at the same time
|
||||
|
||||
@@ -299,7 +347,10 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
// this is for published content when previewing
|
||||
private static IPublishedContent GetPublishedContentAsDraft(IPublishedContent content /*, bool preview*/)
|
||||
{
|
||||
if (content == null /*|| preview == false*/) return null; //content;
|
||||
if (content == null /*|| preview == false*/)
|
||||
{
|
||||
return null; //content;
|
||||
}
|
||||
|
||||
// an object in the cache is either an IPublishedContentOrMedia,
|
||||
// or a model inheriting from PublishedContentExtended - in which
|
||||
@@ -309,12 +360,10 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
return inner.AsDraft();
|
||||
}
|
||||
|
||||
public override bool HasContent(bool preview)
|
||||
{
|
||||
return preview
|
||||
public override bool HasContent(bool preview) =>
|
||||
preview
|
||||
? _snapshot.IsEmpty == false
|
||||
: _snapshot.GetAtRoot().Any(x => x.PublishedModel != null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -322,21 +371,24 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
|
||||
public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars)
|
||||
{
|
||||
var navigator = CreateNavigator(preview);
|
||||
var iterator = navigator.Select(xpath, vars);
|
||||
XPathNavigator navigator = CreateNavigator(preview);
|
||||
XPathNodeIterator iterator = navigator.Select(xpath, vars);
|
||||
return GetSingleByXPath(iterator);
|
||||
}
|
||||
|
||||
public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars)
|
||||
{
|
||||
var navigator = CreateNavigator(preview);
|
||||
var iterator = navigator.Select(xpath, vars);
|
||||
XPathNavigator navigator = CreateNavigator(preview);
|
||||
XPathNodeIterator iterator = navigator.Select(xpath, vars);
|
||||
return GetSingleByXPath(iterator);
|
||||
}
|
||||
|
||||
private static IPublishedContent GetSingleByXPath(XPathNodeIterator iterator)
|
||||
{
|
||||
if (iterator.MoveNext() == false) return null;
|
||||
if (iterator.MoveNext() == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var xnav = iterator.Current as NavigableNavigator;
|
||||
var xcontent = xnav?.UnderlyingObject as NavigableContent;
|
||||
@@ -345,15 +397,16 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
|
||||
public override IEnumerable<IPublishedContent> GetByXPath(bool preview, string xpath, XPathVariable[] vars)
|
||||
{
|
||||
var navigator = CreateNavigator(preview);
|
||||
var iterator = navigator.Select(xpath, vars);
|
||||
XPathNavigator navigator = CreateNavigator(preview);
|
||||
XPathNodeIterator iterator = navigator.Select(xpath, vars);
|
||||
return GetByXPath(iterator);
|
||||
}
|
||||
|
||||
public override IEnumerable<IPublishedContent> GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars)
|
||||
public override IEnumerable<IPublishedContent> GetByXPath(bool preview, XPathExpression xpath,
|
||||
XPathVariable[] vars)
|
||||
{
|
||||
var navigator = CreateNavigator(preview);
|
||||
var iterator = navigator.Select(xpath, vars);
|
||||
XPathNavigator navigator = CreateNavigator(preview);
|
||||
XPathNodeIterator iterator = navigator.Select(xpath, vars);
|
||||
return GetByXPath(iterator);
|
||||
}
|
||||
|
||||
@@ -364,7 +417,10 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
{
|
||||
var xnav = iterator.Current as NavigableNavigator;
|
||||
var xcontent = xnav?.UnderlyingObject as NavigableContent;
|
||||
if (xcontent == null) continue;
|
||||
if (xcontent == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return xcontent.InnerContent;
|
||||
}
|
||||
@@ -395,14 +451,5 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
public override IPublishedContentType GetContentType(Guid key) => _snapshot.GetContentType(key);
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_snapshot.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,13 +175,6 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
public IPublishedContent PublishedModel => GetModel(ref _publishedModel, _publishedData);
|
||||
|
||||
public ContentNodeKit ToKit()
|
||||
=> new ContentNodeKit
|
||||
{
|
||||
Node = this,
|
||||
ContentTypeId = ContentType.Id,
|
||||
|
||||
DraftData = _draftData,
|
||||
PublishedData = _publishedData
|
||||
};
|
||||
=> new ContentNodeKit(this, ContentType.Id, _draftData, _publishedData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,39 @@
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using System;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
{
|
||||
// what's needed to actually build a content node
|
||||
public struct ContentNodeKit
|
||||
{
|
||||
[Obsolete("This will be changed to a property in future versions")]
|
||||
public ContentNode Node;
|
||||
|
||||
[Obsolete("This will be changed to a property in future versions")]
|
||||
public int ContentTypeId;
|
||||
|
||||
[Obsolete("This will be changed to a property in future versions")]
|
||||
public ContentData DraftData;
|
||||
|
||||
[Obsolete("This will be changed to a property in future versions")]
|
||||
public ContentData PublishedData;
|
||||
|
||||
public ContentNodeKit(ContentNode node, int contentTypeId, ContentData draftData, ContentData publishedData)
|
||||
{
|
||||
Node = node;
|
||||
ContentTypeId = contentTypeId;
|
||||
DraftData = draftData;
|
||||
PublishedData = publishedData;
|
||||
}
|
||||
|
||||
|
||||
public bool IsEmpty => Node == null;
|
||||
|
||||
public bool IsNull => ContentTypeId < 0;
|
||||
|
||||
public static ContentNodeKit Empty { get; } = new ContentNodeKit();
|
||||
public static ContentNodeKit Null { get; } = new ContentNodeKit { ContentTypeId = -1 };
|
||||
public static ContentNodeKit Null { get; } = new ContentNodeKit(null, -1, null, null);
|
||||
|
||||
public void Build(
|
||||
IPublishedContentType contentType,
|
||||
@@ -41,12 +57,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
}
|
||||
|
||||
public ContentNodeKit Clone(IPublishedModelFactory publishedModelFactory)
|
||||
=> new ContentNodeKit
|
||||
{
|
||||
ContentTypeId = ContentTypeId,
|
||||
DraftData = DraftData,
|
||||
PublishedData = PublishedData,
|
||||
Node = new ContentNode(Node, publishedModelFactory)
|
||||
};
|
||||
=> new ContentNodeKit(new ContentNode(Node, publishedModelFactory), ContentTypeId, DraftData, PublishedData);
|
||||
|
||||
public ContentNodeKit Clone(IPublishedModelFactory publishedModelFactory, ContentData draftData, ContentData publishedData)
|
||||
=> new ContentNodeKit(new ContentNode(Node, publishedModelFactory), ContentTypeId, draftData, publishedData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,11 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
|
||||
private class WriteLockInfo
|
||||
{
|
||||
#pragma warning disable IDE1006 // Naming Styles
|
||||
|
||||
// This is a field that is used for ref operations
|
||||
public bool Taken;
|
||||
#pragma warning restore IDE1006 // Naming Styles
|
||||
}
|
||||
|
||||
// a scope contextual that represents a locked writer to the dictionary
|
||||
|
||||
@@ -13,27 +13,25 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
|
||||
_dictionaryOfPropertyDataSerializer = dictionaryOfPropertyDataSerializer;
|
||||
if(_dictionaryOfPropertyDataSerializer == null)
|
||||
{
|
||||
_dictionaryOfPropertyDataSerializer = DefaultPropertiesSerializer;
|
||||
_dictionaryOfPropertyDataSerializer = s_defaultPropertiesSerializer;
|
||||
}
|
||||
}
|
||||
private static readonly DictionaryOfPropertyDataSerializer DefaultPropertiesSerializer = new DictionaryOfPropertyDataSerializer();
|
||||
private static readonly DictionaryOfCultureVariationSerializer DefaultCultureVariationsSerializer = new DictionaryOfCultureVariationSerializer();
|
||||
private static readonly DictionaryOfPropertyDataSerializer s_defaultPropertiesSerializer = new DictionaryOfPropertyDataSerializer();
|
||||
private static readonly DictionaryOfCultureVariationSerializer s_defaultCultureVariationsSerializer = new DictionaryOfCultureVariationSerializer();
|
||||
private readonly IDictionaryOfPropertyDataSerializer _dictionaryOfPropertyDataSerializer;
|
||||
|
||||
public ContentData ReadFrom(Stream stream)
|
||||
{
|
||||
return new ContentData
|
||||
{
|
||||
Published = PrimitiveSerializer.Boolean.ReadFrom(stream),
|
||||
Name = PrimitiveSerializer.String.ReadFrom(stream),
|
||||
UrlSegment = PrimitiveSerializer.String.ReadFrom(stream),
|
||||
VersionId = PrimitiveSerializer.Int32.ReadFrom(stream),
|
||||
VersionDate = PrimitiveSerializer.DateTime.ReadFrom(stream),
|
||||
WriterId = PrimitiveSerializer.Int32.ReadFrom(stream),
|
||||
TemplateId = PrimitiveSerializer.Int32.ReadFrom(stream),
|
||||
Properties = _dictionaryOfPropertyDataSerializer.ReadFrom(stream), // TODO: We don't want to allocate empty arrays
|
||||
CultureInfos = DefaultCultureVariationsSerializer.ReadFrom(stream) // TODO: We don't want to allocate empty arrays
|
||||
};
|
||||
var published = PrimitiveSerializer.Boolean.ReadFrom(stream);
|
||||
var name = PrimitiveSerializer.String.ReadFrom(stream);
|
||||
var urlSegment = PrimitiveSerializer.String.ReadFrom(stream);
|
||||
var versionId = PrimitiveSerializer.Int32.ReadFrom(stream);
|
||||
var versionDate = PrimitiveSerializer.DateTime.ReadFrom(stream);
|
||||
var writerId = PrimitiveSerializer.Int32.ReadFrom(stream);
|
||||
var templateId = PrimitiveSerializer.Int32.ReadFrom(stream);
|
||||
var properties = _dictionaryOfPropertyDataSerializer.ReadFrom(stream); // TODO: We don't want to allocate empty arrays
|
||||
var cultureInfos = s_defaultCultureVariationsSerializer.ReadFrom(stream); // TODO: We don't want to allocate empty arrays
|
||||
return new ContentData(name, urlSegment, versionId, versionDate, writerId, templateId, published, properties, cultureInfos);
|
||||
}
|
||||
|
||||
public void WriteTo(ContentData value, Stream stream)
|
||||
@@ -49,7 +47,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
|
||||
PrimitiveSerializer.Int32.WriteTo(value.TemplateId.Value, stream);
|
||||
}
|
||||
_dictionaryOfPropertyDataSerializer.WriteTo(value.Properties, stream);
|
||||
DefaultCultureVariationsSerializer.WriteTo(value.CultureInfos, stream);
|
||||
s_defaultCultureVariationsSerializer.WriteTo(value.CultureInfos, stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using CSharpTest.Net.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
|
||||
@@ -10,19 +10,17 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
|
||||
_contentDataSerializer = contentDataSerializer;
|
||||
if(_contentDataSerializer == null)
|
||||
{
|
||||
_contentDataSerializer = DefaultDataSerializer;
|
||||
_contentDataSerializer = s_defaultDataSerializer;
|
||||
}
|
||||
}
|
||||
static readonly ContentDataSerializer DefaultDataSerializer = new ContentDataSerializer();
|
||||
static readonly ContentDataSerializer s_defaultDataSerializer = new ContentDataSerializer();
|
||||
private readonly ContentDataSerializer _contentDataSerializer;
|
||||
|
||||
//static readonly ListOfIntSerializer ChildContentIdsSerializer = new ListOfIntSerializer();
|
||||
|
||||
public ContentNodeKit ReadFrom(Stream stream)
|
||||
{
|
||||
var kit = new ContentNodeKit
|
||||
{
|
||||
Node = new ContentNode(
|
||||
var contentNode = new ContentNode(
|
||||
PrimitiveSerializer.Int32.ReadFrom(stream), // id
|
||||
PrimitiveSerializer.Guid.ReadFrom(stream), // uid
|
||||
PrimitiveSerializer.Int32.ReadFrom(stream), // level
|
||||
@@ -31,15 +29,27 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
|
||||
PrimitiveSerializer.Int32.ReadFrom(stream), // parent id
|
||||
PrimitiveSerializer.DateTime.ReadFrom(stream), // date created
|
||||
PrimitiveSerializer.Int32.ReadFrom(stream) // creator id
|
||||
),
|
||||
ContentTypeId = PrimitiveSerializer.Int32.ReadFrom(stream)
|
||||
};
|
||||
);
|
||||
|
||||
int contentTypeId = PrimitiveSerializer.Int32.ReadFrom(stream);
|
||||
var hasDraft = PrimitiveSerializer.Boolean.ReadFrom(stream);
|
||||
ContentData draftData = null;
|
||||
ContentData publishedData = null;
|
||||
if (hasDraft)
|
||||
kit.DraftData = _contentDataSerializer.ReadFrom(stream);
|
||||
{
|
||||
draftData = _contentDataSerializer.ReadFrom(stream);
|
||||
}
|
||||
var hasPublished = PrimitiveSerializer.Boolean.ReadFrom(stream);
|
||||
if (hasPublished)
|
||||
kit.PublishedData = _contentDataSerializer.ReadFrom(stream);
|
||||
{
|
||||
publishedData = _contentDataSerializer.ReadFrom(stream);
|
||||
}
|
||||
var kit = new ContentNodeKit(
|
||||
contentNode,
|
||||
contentTypeId,
|
||||
draftData,
|
||||
publishedData);
|
||||
|
||||
return kit;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,19 +8,38 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource
|
||||
/// </summary>
|
||||
public class ContentData
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string UrlSegment { get; set; }
|
||||
public int VersionId { get; set; }
|
||||
public DateTime VersionDate { get; set; }
|
||||
public int WriterId { get; set; }
|
||||
public int? TemplateId { get; set; }
|
||||
public bool Published { get; set; }
|
||||
[Obsolete("Use ctor with all params, as the pros should be immutable")]
|
||||
public ContentData()
|
||||
{
|
||||
|
||||
public IDictionary<string, PropertyData[]> Properties { get; set; }
|
||||
}
|
||||
|
||||
public ContentData(string name, string urlSegment, int versionId, DateTime versionDate, int writerId, int? templateId, bool published, IDictionary<string, PropertyData[]> properties, IReadOnlyDictionary<string, CultureVariation> cultureInfos)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
UrlSegment = urlSegment;
|
||||
VersionId = versionId;
|
||||
VersionDate = versionDate;
|
||||
WriterId = writerId;
|
||||
TemplateId = templateId;
|
||||
Published = published;
|
||||
Properties = properties ?? throw new ArgumentNullException(nameof(properties));
|
||||
CultureInfos = cultureInfos;
|
||||
}
|
||||
|
||||
public string Name { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
|
||||
public string UrlSegment { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
|
||||
public int VersionId { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
|
||||
public DateTime VersionDate { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
|
||||
public int WriterId { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
|
||||
public int? TemplateId { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
|
||||
public bool Published { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
|
||||
|
||||
public IDictionary<string, PropertyData[]> Properties { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
|
||||
|
||||
/// <summary>
|
||||
/// The collection of language Id to name for the content item
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, CultureVariation> CultureInfos { get; set; }
|
||||
public IReadOnlyDictionary<string, CultureVariation> CultureInfos { get; [Obsolete("Do not change this, use ctor with params and have this object immutable.")] set; }
|
||||
}
|
||||
}
|
||||
|
||||
15
src/Umbraco.PublishedCache.NuCache/DomainCacheExtensions.cs
Normal file
15
src/Umbraco.PublishedCache.NuCache/DomainCacheExtensions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
{
|
||||
public static class DomainCacheExtensions
|
||||
{
|
||||
public static bool GetAssignedWithCulture(this IDomainCache domainCache, string culture, int documentId, bool includeWildcards = false)
|
||||
{
|
||||
var assigned = domainCache.GetAssigned(documentId, includeWildcards);
|
||||
|
||||
return culture is null ? assigned.Any() : assigned.Any(x => x.Culture == culture);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -820,18 +820,16 @@ AND cmsContentNu.nodeId IS NULL
|
||||
bool published = false;
|
||||
var deserializedContent = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw, published);
|
||||
|
||||
d = new ContentData
|
||||
{
|
||||
Name = dto.EditName,
|
||||
Published = published,
|
||||
TemplateId = dto.EditTemplateId,
|
||||
VersionId = dto.VersionId,
|
||||
VersionDate = dto.EditVersionDate,
|
||||
WriterId = dto.EditWriterId,
|
||||
Properties = deserializedContent.PropertyData, // TODO: We don't want to allocate empty arrays
|
||||
CultureInfos = deserializedContent.CultureData,
|
||||
UrlSegment = deserializedContent.UrlSegment
|
||||
};
|
||||
d = new ContentData(
|
||||
dto.EditName,
|
||||
deserializedContent.UrlSegment,
|
||||
dto.VersionId,
|
||||
dto.EditVersionDate,
|
||||
dto.EditWriterId,
|
||||
dto.EditTemplateId,
|
||||
published,
|
||||
deserializedContent.PropertyData,
|
||||
deserializedContent.CultureData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -851,31 +849,23 @@ AND cmsContentNu.nodeId IS NULL
|
||||
bool published = true;
|
||||
var deserializedContent = serializer.Deserialize(dto, dto.PubData, dto.PubDataRaw, published);
|
||||
|
||||
p = new ContentData
|
||||
{
|
||||
Name = dto.PubName,
|
||||
UrlSegment = deserializedContent.UrlSegment,
|
||||
Published = published,
|
||||
TemplateId = dto.PubTemplateId,
|
||||
VersionId = dto.VersionId,
|
||||
VersionDate = dto.PubVersionDate,
|
||||
WriterId = dto.PubWriterId,
|
||||
Properties = deserializedContent.PropertyData, // TODO: We don't want to allocate empty arrays
|
||||
CultureInfos = deserializedContent.CultureData
|
||||
};
|
||||
p = new ContentData(
|
||||
dto.PubName,
|
||||
deserializedContent.UrlSegment,
|
||||
dto.VersionId,
|
||||
dto.PubVersionDate,
|
||||
dto.PubWriterId,
|
||||
dto.PubTemplateId,
|
||||
published,
|
||||
deserializedContent.PropertyData,
|
||||
deserializedContent.CultureData);
|
||||
}
|
||||
}
|
||||
|
||||
var n = new ContentNode(dto.Id, dto.Key,
|
||||
dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId);
|
||||
|
||||
var s = new ContentNodeKit
|
||||
{
|
||||
Node = n,
|
||||
ContentTypeId = dto.ContentTypeId,
|
||||
DraftData = d,
|
||||
PublishedData = p
|
||||
};
|
||||
var s = new ContentNodeKit(n, dto.ContentTypeId, d, p);
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -888,27 +878,21 @@ AND cmsContentNu.nodeId IS NULL
|
||||
bool published = true;
|
||||
var deserializedMedia = serializer.Deserialize(dto, dto.EditData, dto.EditDataRaw, published);
|
||||
|
||||
var p = new ContentData
|
||||
{
|
||||
Name = dto.EditName,
|
||||
Published = published,
|
||||
TemplateId = -1,
|
||||
VersionId = dto.VersionId,
|
||||
VersionDate = dto.EditVersionDate,
|
||||
WriterId = dto.CreatorId, // what-else?
|
||||
Properties = deserializedMedia.PropertyData, // TODO: We don't want to allocate empty arrays
|
||||
CultureInfos = deserializedMedia.CultureData
|
||||
};
|
||||
var p = new ContentData(
|
||||
dto.EditName,
|
||||
null,
|
||||
dto.VersionId,
|
||||
dto.EditVersionDate,
|
||||
dto.CreatorId,
|
||||
-1,
|
||||
published,
|
||||
deserializedMedia.PropertyData,
|
||||
deserializedMedia.CultureData);
|
||||
|
||||
var n = new ContentNode(dto.Id, dto.Key,
|
||||
dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId);
|
||||
|
||||
var s = new ContentNodeKit
|
||||
{
|
||||
Node = n,
|
||||
ContentTypeId = dto.ContentTypeId,
|
||||
PublishedData = p
|
||||
};
|
||||
var s = new ContentNodeKit(n, dto.ContentTypeId, null, p);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -34,15 +34,8 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IPublishedModelFactory publishedModelFactory)
|
||||
{
|
||||
var d = new ContentData
|
||||
{
|
||||
Name = member.Name,
|
||||
Published = previewing,
|
||||
TemplateId = -1,
|
||||
VersionDate = member.UpdateDate,
|
||||
WriterId = member.CreatorId, // what else?
|
||||
Properties = GetPropertyValues(contentType, member)
|
||||
};
|
||||
var d = new ContentData(member.Name, null, 0, member.UpdateDate, member.CreatorId, -1, previewing, GetPropertyValues(contentType, member), null);
|
||||
|
||||
var n = new ContentNode(
|
||||
member.Id,
|
||||
member.Key,
|
||||
|
||||
@@ -46,7 +46,6 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
private readonly IPublishedModelFactory _publishedModelFactory;
|
||||
private readonly IDefaultCultureAccessor _defaultCultureAccessor;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IContentCacheDataSerializerFactory _contentCacheDataSerializerFactory;
|
||||
private readonly ContentDataSerializer _contentDataSerializer;
|
||||
private readonly NuCacheSettings _config;
|
||||
|
||||
@@ -93,7 +92,6 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
IPublishedModelFactory publishedModelFactory,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IOptions<NuCacheSettings> config,
|
||||
IContentCacheDataSerializerFactory contentCacheDataSerializerFactory,
|
||||
ContentDataSerializer contentDataSerializer)
|
||||
{
|
||||
_options = options;
|
||||
@@ -111,7 +109,6 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache
|
||||
_defaultCultureAccessor = defaultCultureAccessor;
|
||||
_globalSettings = globalSettings.Value;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_contentCacheDataSerializerFactory = contentCacheDataSerializerFactory;
|
||||
_contentDataSerializer = contentDataSerializer;
|
||||
_config = config.Value;
|
||||
_publishedModelFactory = publishedModelFactory;
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace Umbraco.Cms.Web.BackOffice.Security
|
||||
private readonly ISystemClock _systemClock;
|
||||
private readonly UmbracoRequestPaths _umbracoRequestPaths;
|
||||
private readonly IBasicAuthService _basicAuthService;
|
||||
private readonly IOptionsMonitor<BasicAuthSettings> _optionsSnapshot;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigureBackOfficeCookieOptions"/> class.
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Umbraco.Cms.Tests.Common.Builders
|
||||
public ContentBuilder WithContentType(IContentType contentType)
|
||||
{
|
||||
_contentTypeBuilder = null;
|
||||
_contentType = contentType;
|
||||
_contentType = contentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -172,6 +172,11 @@ namespace Umbraco.Cms.Tests.Common.Builders
|
||||
content.SortOrder = sortOrder;
|
||||
content.Trashed = trashed;
|
||||
|
||||
if (contentType.DefaultTemplate?.Id > 0)
|
||||
{
|
||||
content.TemplateId = contentType.DefaultTemplate.Id;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, string> cultureName in _cultureNames)
|
||||
{
|
||||
content.SetCultureName(cultureName.Value, cultureName.Key);
|
||||
|
||||
221
tests/Umbraco.Tests.Common/Builders/ContentDataBuilder.cs
Normal file
221
tests/Umbraco.Tests.Common/Builders/ContentDataBuilder.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Interfaces;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Moq;
|
||||
using Umbraco.Cms.Infrastructure.Serialization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.Builders
|
||||
{
|
||||
|
||||
public class ContentDataBuilder : BuilderBase<ContentData>, IWithNameBuilder
|
||||
{
|
||||
private string _name;
|
||||
private DateTime? _now;
|
||||
private string _segment;
|
||||
private int? _versionId;
|
||||
private int? _writerId;
|
||||
private int? _templateId;
|
||||
private bool? _published;
|
||||
private Dictionary<string, PropertyData[]> _properties;
|
||||
private Dictionary<string, CultureVariation> _cultureInfos;
|
||||
|
||||
string IWithNameBuilder.Name
|
||||
{
|
||||
get => _name;
|
||||
set => _name = value;
|
||||
}
|
||||
|
||||
public ContentDataBuilder WithVersionDate(DateTime now)
|
||||
{
|
||||
_now = now;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentDataBuilder WithUrlSegment(string segment)
|
||||
{
|
||||
_segment = segment;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentDataBuilder WithVersionId(int versionId)
|
||||
{
|
||||
_versionId = versionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentDataBuilder WithWriterId(int writerId)
|
||||
{
|
||||
_writerId = writerId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentDataBuilder WithTemplateId(int templateId)
|
||||
{
|
||||
_templateId = templateId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentDataBuilder WithPublished(bool published)
|
||||
{
|
||||
_published = published;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentDataBuilder WithProperties(Dictionary<string, PropertyData[]> properties)
|
||||
{
|
||||
_properties = properties;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentDataBuilder WithCultureInfos(Dictionary<string, CultureVariation> cultureInfos)
|
||||
{
|
||||
_cultureInfos = cultureInfos;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build and dynamically update an existing content type
|
||||
/// </summary>
|
||||
/// <typeparam name="TContentType"></typeparam>
|
||||
/// <param name="shortStringHelper"></param>
|
||||
/// <param name="propertyDataTypes"></param>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="contentTypeAlias">
|
||||
/// Will configure the content type with this alias/name if supplied when it's not already set on the content type.
|
||||
/// </param>
|
||||
/// <param name="autoCreateCultureNames"></param>
|
||||
/// <returns></returns>
|
||||
public ContentData Build<TContentType>(
|
||||
IShortStringHelper shortStringHelper,
|
||||
Dictionary<string, IDataType> propertyDataTypes,
|
||||
TContentType contentType,
|
||||
string contentTypeAlias = null,
|
||||
bool autoCreateCultureNames = false) where TContentType : class, IContentTypeComposition
|
||||
{
|
||||
if (_name.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new InvalidOperationException("Cannot build without a name");
|
||||
}
|
||||
_segment ??= _name.ToLower().ReplaceNonAlphanumericChars('-');
|
||||
|
||||
// create or copy the current culture infos for the content
|
||||
Dictionary<string, CultureVariation> contentCultureInfos = _cultureInfos == null
|
||||
? new Dictionary<string, CultureVariation>()
|
||||
: new Dictionary<string, CultureVariation>(_cultureInfos);
|
||||
|
||||
contentType.Alias ??= contentTypeAlias;
|
||||
contentType.Name ??= contentTypeAlias;
|
||||
contentType.Key = contentType.Key == default ? Guid.NewGuid() : contentType.Key;
|
||||
contentType.Id = contentType.Id == default ? Math.Abs(contentTypeAlias.GetHashCode()) : contentType.Id;
|
||||
|
||||
if (_properties == null)
|
||||
{
|
||||
_properties = new Dictionary<string, PropertyData[]>();
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, PropertyData[]> prop in _properties)
|
||||
{
|
||||
//var dataType = new DataType(new VoidEditor("Label", Mock.Of<IDataValueEditorFactory>()), new ConfigurationEditorJsonSerializer())
|
||||
//{
|
||||
// Id = 4
|
||||
//};
|
||||
|
||||
if (!propertyDataTypes.TryGetValue(prop.Key, out IDataType dataType))
|
||||
{
|
||||
dataType = propertyDataTypes.First().Value;
|
||||
}
|
||||
|
||||
var propertyType = new PropertyType(shortStringHelper, dataType, prop.Key);
|
||||
|
||||
// check each property for culture and set variations accordingly,
|
||||
// this will also ensure that we have the correct culture name on the content
|
||||
// set for each culture too.
|
||||
foreach (PropertyData cultureValue in prop.Value.Where(x => !x.Culture.IsNullOrWhiteSpace()))
|
||||
{
|
||||
// set the property type to vary based on the values
|
||||
propertyType.Variations |= ContentVariation.Culture;
|
||||
|
||||
// if there isn't already a culture, then add one with the default name
|
||||
if (autoCreateCultureNames && !contentCultureInfos.TryGetValue(cultureValue.Culture, out CultureVariation cultureVariation))
|
||||
{
|
||||
cultureVariation = new CultureVariation
|
||||
{
|
||||
Date = DateTime.Now,
|
||||
IsDraft = true,
|
||||
Name = _name,
|
||||
UrlSegment = _segment
|
||||
};
|
||||
contentCultureInfos[cultureValue.Culture] = cultureVariation;
|
||||
}
|
||||
}
|
||||
|
||||
// set variations for segments if there is any
|
||||
if (prop.Value.Any(x => !x.Segment.IsNullOrWhiteSpace()))
|
||||
{
|
||||
propertyType.Variations |= ContentVariation.Segment;
|
||||
contentType.Variations |= ContentVariation.Segment;
|
||||
}
|
||||
|
||||
if (!contentType.PropertyTypeExists(propertyType.Alias))
|
||||
{
|
||||
contentType.AddPropertyType(propertyType);
|
||||
}
|
||||
}
|
||||
|
||||
if (contentCultureInfos.Count > 0)
|
||||
{
|
||||
contentType.Variations |= ContentVariation.Culture;
|
||||
WithCultureInfos(contentCultureInfos);
|
||||
}
|
||||
|
||||
var result = Build();
|
||||
return result;
|
||||
}
|
||||
|
||||
public override ContentData Build()
|
||||
{
|
||||
var now = _now ?? DateTime.Now;
|
||||
var versionId = _versionId ?? 1;
|
||||
var writerId = _writerId ?? -1;
|
||||
var templateId = _templateId ?? 0;
|
||||
var published = _published ?? true;
|
||||
var properties = _properties ?? new Dictionary<string, PropertyData[]>();
|
||||
var cultureInfos = _cultureInfos ?? new Dictionary<string, CultureVariation>();
|
||||
var segment = _segment ?? _name.ToLower().ReplaceNonAlphanumericChars('-');
|
||||
|
||||
var contentData = new ContentData(
|
||||
_name,
|
||||
segment,
|
||||
versionId,
|
||||
now,
|
||||
writerId,
|
||||
templateId,
|
||||
published,
|
||||
properties,
|
||||
cultureInfos);
|
||||
|
||||
return contentData;
|
||||
}
|
||||
|
||||
public static ContentData CreateBasic(string name, DateTime? versionDate = null)
|
||||
=> new ContentDataBuilder()
|
||||
.WithName(name)
|
||||
.WithVersionDate(versionDate ?? DateTime.Now)
|
||||
.Build();
|
||||
|
||||
public static ContentData CreateVariant(string name, Dictionary<string, CultureVariation> cultureInfos, DateTime? versionDate = null, bool published = true)
|
||||
=> new ContentDataBuilder()
|
||||
.WithName(name)
|
||||
.WithVersionDate(versionDate ?? DateTime.Now)
|
||||
.WithCultureInfos(cultureInfos)
|
||||
.WithPublished(published)
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
96
tests/Umbraco.Tests.Common/Builders/ContentNodeKitBuilder.cs
Normal file
96
tests/Umbraco.Tests.Common/Builders/ContentNodeKitBuilder.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.Builders
|
||||
{
|
||||
public class ContentNodeKitBuilder : BuilderBase<ContentNodeKit>
|
||||
{
|
||||
private int _contentTypeId;
|
||||
private ContentNode _contentNode;
|
||||
private ContentData _draftData;
|
||||
private ContentData _publishedData;
|
||||
|
||||
public ContentNodeKitBuilder WithContentNode(ContentNode contentNode)
|
||||
{
|
||||
_contentNode = contentNode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentNodeKitBuilder WithContentNode(int id, Guid uid, int level, string path, int sortOrder, int parentContentId, DateTime createDate, int creatorId)
|
||||
{
|
||||
_contentNode = new ContentNode(id, uid, level, path, sortOrder, parentContentId, createDate, creatorId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentNodeKitBuilder WithContentTypeId(int contentTypeId)
|
||||
{
|
||||
_contentTypeId = contentTypeId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentNodeKitBuilder WithDraftData(ContentData draftData)
|
||||
{
|
||||
_draftData = draftData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContentNodeKitBuilder WithPublishedData(ContentData publishedData)
|
||||
{
|
||||
_publishedData = publishedData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override ContentNodeKit Build()
|
||||
{
|
||||
var data = new ContentNodeKit(_contentNode, _contentTypeId, _draftData, _publishedData);
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ContentNodeKit
|
||||
/// </summary>
|
||||
/// <param name="contentTypeId"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="sortOrder"></param>
|
||||
/// <param name="level">
|
||||
/// Optional. Will get calculated based on the path value if not specified.
|
||||
/// </param>
|
||||
/// <param name="parentContentId">
|
||||
/// Optional. Will get calculated based on the path value if not specified.
|
||||
/// </param>
|
||||
/// <param name="creatorId"></param>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="createDate"></param>
|
||||
/// <param name="draftData"></param>
|
||||
/// <param name="publishedData"></param>
|
||||
/// <returns></returns>
|
||||
public static ContentNodeKit CreateWithContent(
|
||||
int contentTypeId,
|
||||
int id,
|
||||
string path,
|
||||
int? sortOrder = null,
|
||||
int? level = null,
|
||||
int? parentContentId = null,
|
||||
int creatorId = -1,
|
||||
Guid? uid = null,
|
||||
DateTime? createDate = null,
|
||||
ContentData draftData = null,
|
||||
ContentData publishedData = null)
|
||||
{
|
||||
var pathParts = path.Split(',');
|
||||
if (pathParts.Length >= 2)
|
||||
{
|
||||
parentContentId ??= int.Parse(pathParts[^2]);
|
||||
}
|
||||
|
||||
return new ContentNodeKitBuilder()
|
||||
.WithContentTypeId(contentTypeId)
|
||||
.WithContentNode(id, uid ?? Guid.NewGuid(), level ?? pathParts.Length - 1, path, sortOrder ?? 0, parentContentId.Value, createDate ?? DateTime.Now, creatorId)
|
||||
.WithDraftData(draftData)
|
||||
.WithPublishedData(publishedData)
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Cms.Tests.Common.Builders.Interfaces
|
||||
{
|
||||
public interface IWithAllowAsRootBuilder
|
||||
{
|
||||
bool? AllowAsRoot { get; set; }
|
||||
}
|
||||
}
|
||||
36
tests/Umbraco.Tests.Common/Builders/PropertyDataBuilder.cs
Normal file
36
tests/Umbraco.Tests.Common/Builders/PropertyDataBuilder.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.Builders
|
||||
{
|
||||
public class PropertyDataBuilder : BuilderBase<Dictionary<string, PropertyData[]>>
|
||||
{
|
||||
private readonly Dictionary<string, List<PropertyData>> _properties = new();
|
||||
|
||||
public PropertyDataBuilder WithPropertyData(string alias, PropertyData propertyData)
|
||||
{
|
||||
if (!_properties.TryGetValue(alias, out List<PropertyData> propertyDataCollection))
|
||||
{
|
||||
propertyDataCollection = new List<PropertyData>();
|
||||
_properties[alias] = propertyDataCollection;
|
||||
}
|
||||
|
||||
propertyDataCollection.Add(propertyData);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public PropertyDataBuilder WithPropertyData(string alias, object value, string culture = null, string segment = null)
|
||||
=> WithPropertyData(alias, new PropertyData
|
||||
{
|
||||
Culture = culture ?? string.Empty,
|
||||
Segment = segment ?? string.Empty,
|
||||
Value = value
|
||||
});
|
||||
|
||||
public override Dictionary<string, PropertyData[]> Build()
|
||||
=> _properties.ToDictionary(x => x.Key, x => x.Value.ToArray());
|
||||
}
|
||||
}
|
||||
152
tests/Umbraco.Tests.Common/Published/PublishedContentXml.cs
Normal file
152
tests/Umbraco.Tests.Common/Published/PublishedContentXml.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.Published
|
||||
{
|
||||
public static class PublishedContentXml
|
||||
{
|
||||
// The content XML that was used for the old PublishContentCacheTests
|
||||
public static string PublishContentCacheTestsXml()
|
||||
=> @"<?xml version=""1.0"" encoding=""utf-8""?><!DOCTYPE root[
|
||||
<!ELEMENT Home ANY>
|
||||
<!ATTLIST Home id ID #REQUIRED>
|
||||
|
||||
]>
|
||||
<root id=""-1"">
|
||||
<Home id=""1046"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""2"" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" nodeName=""Home"" urlName=""home"" writerName=""admin"" creatorName=""admin"" path=""-1,1046"" isDoc=""""><content><![CDATA[]]></content>
|
||||
<Home id=""1173"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""1"" createDate=""2012-07-20T18:06:45"" updateDate=""2012-07-20T19:07:31"" nodeName=""Sub1"" urlName=""sub1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173"" isDoc=""""><content><![CDATA[]]></content>
|
||||
<Home id=""1174"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""1"" createDate=""2012-07-20T18:07:54"" updateDate=""2012-07-20T19:10:27"" nodeName=""Sub2"" urlName=""sub2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1174"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
<Home id=""1176"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""2"" createDate=""2012-07-20T18:08:08"" updateDate=""2012-07-20T19:10:52"" nodeName=""Sub 3"" urlName=""sub-3"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1176"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
</Home>
|
||||
<Home id=""1175"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""2"" createDate=""2012-07-20T18:08:01"" updateDate=""2012-07-20T18:49:32"" nodeName=""Sub 2"" urlName=""sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1175"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
<Home id=""1177"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""2"" createDate=""2012-07-20T18:08:01"" updateDate=""2012-07-20T18:49:32"" nodeName=""Sub'Apostrophe"" urlName=""sub'apostrophe"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1177"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
</Home>
|
||||
<Home id=""1172"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""3"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""Test"" urlName=""test"" writerName=""admin"" creatorName=""admin"" path=""-1,1172"" isDoc="""" />
|
||||
</root>";
|
||||
|
||||
// The content XML that was used in the old BaseWebTest class
|
||||
public static string BaseWebTestXml(int templateId)
|
||||
=> @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<!DOCTYPE root[
|
||||
<!ELEMENT Home ANY>
|
||||
<!ATTLIST Home id ID #REQUIRED>
|
||||
<!ELEMENT CustomDocument ANY>
|
||||
<!ATTLIST CustomDocument id ID #REQUIRED>
|
||||
]>
|
||||
<root id=""-1"">
|
||||
<Home id=""1046"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""1"" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" nodeName=""Home"" urlName=""home"" writerName=""admin"" creatorName=""admin"" path=""-1,1046"" isDoc="""">
|
||||
<content><![CDATA[]]></content>
|
||||
<umbracoUrlAlias><![CDATA[this/is/my/alias, anotheralias]]></umbracoUrlAlias>
|
||||
<umbracoNaviHide>1</umbracoNaviHide>
|
||||
<Home id=""1173"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-20T18:06:45"" updateDate=""2012-07-20T19:07:31"" nodeName=""Sub1"" urlName=""sub1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173"" isDoc="""">
|
||||
<content><![CDATA[<div>This is some content</div>]]></content>
|
||||
<umbracoUrlAlias><![CDATA[page2/alias, 2ndpagealias]]></umbracoUrlAlias>
|
||||
<Home id=""1174"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-20T18:07:54"" updateDate=""2012-07-20T19:10:27"" nodeName=""Sub2"" urlName=""sub2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1174"" isDoc="""">
|
||||
<content><![CDATA[]]></content>
|
||||
<umbracoUrlAlias><![CDATA[only/one/alias]]></umbracoUrlAlias>
|
||||
<creatorName><![CDATA[Custom data with same property name as the member name]]></creatorName>
|
||||
</Home>
|
||||
<Home id=""1176"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""3"" createDate=""2012-07-20T18:08:08"" updateDate=""2012-07-20T19:10:52"" nodeName=""Sub 3"" urlName=""sub-3"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1176"" isDoc="""">
|
||||
<content><![CDATA[]]></content>
|
||||
</Home>
|
||||
<CustomDocument id=""1177"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""custom sub 1"" urlName=""custom-sub-1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1177"" isDoc="""" />
|
||||
<CustomDocument id=""1178"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-16T14:23:35"" nodeName=""custom sub 2"" urlName=""custom-sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1178"" isDoc="""" />
|
||||
<CustomDocument id=""1179"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-16T14:23:35"" nodeName=""custom sub 3 with accént character"" urlName=""custom-sub-3-with-accént-character"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1179"" isDoc="""" />
|
||||
<CustomDocument id=""1180"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-16T14:23:35"" nodeName=""custom sub 4 with æøå"" urlName=""custom-sub-4-with-æøå"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1180"" isDoc="""" />
|
||||
</Home>
|
||||
<Home id=""1175"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""3"" createDate=""2012-07-20T18:08:01"" updateDate=""2012-07-20T18:49:32"" nodeName=""Sub 2"" urlName=""sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1175"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
</Home>
|
||||
<CustomDocument id=""1172"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""Test"" urlName=""test-page"" writerName=""admin"" creatorName=""admin"" path=""-1,1172"" isDoc="""" />
|
||||
</root>";
|
||||
|
||||
// The content XML that was used in the old TestWithDatabase class
|
||||
public static string TestWithDatabaseXml(int templateId)
|
||||
=> @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<!DOCTYPE root[
|
||||
<!ELEMENT Home ANY>
|
||||
<!ATTLIST Home id ID #REQUIRED>
|
||||
<!ELEMENT CustomDocument ANY>
|
||||
<!ATTLIST CustomDocument id ID #REQUIRED>
|
||||
]>
|
||||
<root id=""-1"">
|
||||
<Home id=""1046"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""1"" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" nodeName=""Home"" urlName=""home"" writerName=""admin"" creatorName=""admin"" path=""-1,1046"" isDoc="""">
|
||||
<content><![CDATA[]]></content>
|
||||
<umbracoUrlAlias><![CDATA[this/is/my/alias, anotheralias]]></umbracoUrlAlias>
|
||||
<umbracoNaviHide>1</umbracoNaviHide>
|
||||
<Home id=""1173"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-20T18:06:45"" updateDate=""2012-07-20T19:07:31"" nodeName=""Sub1"" urlName=""sub1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173"" isDoc="""">
|
||||
<content><![CDATA[<div>This is some content</div>]]></content>
|
||||
<umbracoUrlAlias><![CDATA[page2/alias, 2ndpagealias]]></umbracoUrlAlias>
|
||||
<Home id=""1174"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-20T18:07:54"" updateDate=""2012-07-20T19:10:27"" nodeName=""Sub2"" urlName=""sub2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1174"" isDoc="""">
|
||||
<content><![CDATA[]]></content>
|
||||
<umbracoUrlAlias><![CDATA[only/one/alias]]></umbracoUrlAlias>
|
||||
<creatorName><![CDATA[Custom data with same property name as the member name]]></creatorName>
|
||||
</Home>
|
||||
<Home id=""1176"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""3"" createDate=""2012-07-20T18:08:08"" updateDate=""2012-07-20T19:10:52"" nodeName=""Sub 3"" urlName=""sub-3"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1176"" isDoc="""">
|
||||
<content><![CDATA[]]></content>
|
||||
</Home>
|
||||
<CustomDocument id=""1177"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""custom sub 1"" urlName=""custom-sub-1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1177"" isDoc="""" />
|
||||
<CustomDocument id=""1178"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-16T14:23:35"" nodeName=""custom sub 2"" urlName=""custom-sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1178"" isDoc="""" />
|
||||
</Home>
|
||||
<Home id=""1175"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""3"" createDate=""2012-07-20T18:08:01"" updateDate=""2012-07-20T18:49:32"" nodeName=""Sub 2"" urlName=""sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1175"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
</Home>
|
||||
<CustomDocument id=""1172"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""Test"" urlName=""test-page"" writerName=""admin"" creatorName=""admin"" path=""-1,1172"" isDoc="""" />
|
||||
</root>";
|
||||
|
||||
// The content XML that was used in the old PublishedContentTest class
|
||||
public static string PublishedContentTestXml(int templateId, Guid node1173Guid)
|
||||
=> @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<!DOCTYPE root[
|
||||
<!ELEMENT Home ANY>
|
||||
<!ATTLIST Home id ID #REQUIRED>
|
||||
<!ELEMENT CustomDocument ANY>
|
||||
<!ATTLIST CustomDocument id ID #REQUIRED>
|
||||
]>
|
||||
<root id=""-1"">
|
||||
<Home id=""1046"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""1"" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" nodeName=""Home"" urlName=""home"" writerName=""admin"" creatorName=""admin"" path=""-1,1046"" isDoc="""">
|
||||
<content><![CDATA[]]></content>
|
||||
<umbracoUrlAlias><![CDATA[this/is/my/alias, anotheralias]]></umbracoUrlAlias>
|
||||
<umbracoNaviHide>1</umbracoNaviHide>
|
||||
<testRecursive><![CDATA[This is the recursive val]]></testRecursive>
|
||||
<Home id=""1173"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""1"" createDate=""2012-07-20T18:06:45"" updateDate=""2012-07-20T19:07:31"" nodeName=""Sub1"" urlName=""sub1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173"" isDoc="""" key=""" + node1173Guid + @""">
|
||||
<content><![CDATA[<div>This is some content</div>]]></content>
|
||||
<umbracoUrlAlias><![CDATA[page2/alias, 2ndpagealias]]></umbracoUrlAlias>
|
||||
<testRecursive><![CDATA[]]></testRecursive>
|
||||
<Home id=""1174"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""1"" createDate=""2012-07-20T18:07:54"" updateDate=""2012-07-20T19:10:27"" nodeName=""Sub2"" urlName=""sub2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1174"" isDoc="""">
|
||||
<content><![CDATA[]]></content>
|
||||
<umbracoUrlAlias><![CDATA[only/one/alias]]></umbracoUrlAlias>
|
||||
<creatorName><![CDATA[Custom data with same property name as the member name]]></creatorName>
|
||||
<testRecursive><![CDATA[]]></testRecursive>
|
||||
</Home>
|
||||
<CustomDocument id=""117"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2018-07-18T10:06:37"" updateDate=""2018-07-18T10:06:37"" nodeName=""custom sub 1"" urlName=""custom-sub-1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,117"" isDoc="""">
|
||||
<umbracoNaviHide>0</umbracoNaviHide>
|
||||
</CustomDocument>
|
||||
<CustomDocument id=""1177"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""3"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""custom sub 1"" urlName=""custom-sub-1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1177"" isDoc="""">
|
||||
<umbracoNaviHide>0</umbracoNaviHide>
|
||||
</CustomDocument>
|
||||
<CustomDocument id=""1178"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-16T14:23:35"" nodeName=""custom sub 2"" urlName=""custom-sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1178"" isDoc="""">
|
||||
<umbracoNaviHide>0</umbracoNaviHide>
|
||||
<CustomDocument id=""1179"" parentID=""1178"" level=""4"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""1"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""custom sub sub 1"" urlName=""custom-sub-sub-1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1178,1179"" isDoc="""" />
|
||||
</CustomDocument>
|
||||
<Home id=""1176"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""5"" createDate=""2012-07-20T18:08:08"" updateDate=""2012-07-20T19:10:52"" nodeName=""Sub 3"" urlName=""sub-3"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1176"" isDoc="""" key=""CDB83BBC-A83B-4BA6-93B8-AADEF67D3C09"">
|
||||
<content><![CDATA[]]></content>
|
||||
<umbracoNaviHide>1</umbracoNaviHide>
|
||||
</Home>
|
||||
</Home>
|
||||
<Home id=""1175"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-20T18:08:01"" updateDate=""2012-07-20T18:49:32"" nodeName=""Sub 2"" urlName=""sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1175"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
<CustomDocument id=""4444"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""3"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""Test"" urlName=""test-page"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,4444"" isDoc="""">
|
||||
<selectedNodes><![CDATA[1172,1176,1173]]></selectedNodes>
|
||||
</CustomDocument>
|
||||
</Home>
|
||||
<CustomDocument id=""1172"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""Test"" urlName=""test-page"" writerName=""admin"" creatorName=""admin"" path=""-1,1172"" isDoc="""" />
|
||||
</root>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using System.Linq;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using System;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Moq;
|
||||
using Umbraco.Cms.Infrastructure.Serialization;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.Published
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts legacy Umbraco XML structures to NuCache <see cref="ContentNodeKit"/> collections
|
||||
/// to populate a test implementation of <see cref="INuCacheContentService"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does not support variant data because the XML structure doesn't support variant data.
|
||||
/// </remarks>
|
||||
public static class PublishedContentXmlAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate a collection of <see cref="ContentNodeKit"/> based on legacy umbraco XML
|
||||
/// </summary>
|
||||
/// <param name="xml">The legacy umbraco XML</param>
|
||||
/// <param name="shortStringHelper"></param>
|
||||
/// <param name="contentTypes">Dynamically generates a list of <see cref="ContentType"/>s based on the XML data</param>
|
||||
/// <param name="dataTypes">Dynamically generates a list of <see cref="DataType"/> for tests</param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<ContentNodeKit> GetContentNodeKits(
|
||||
string xml,
|
||||
IShortStringHelper shortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes)
|
||||
{
|
||||
// use the label data type for all data for these tests except in the case
|
||||
// where a property is named 'content', in which case use the RTE.
|
||||
var serializer = new ConfigurationEditorJsonSerializer();
|
||||
var labelDataType = new DataType(new VoidEditor("Label", Mock.Of<IDataValueEditorFactory>()), serializer) { Id = 3 };
|
||||
var rteDataType = new DataType(new VoidEditor("RTE", Mock.Of<IDataValueEditorFactory>()), serializer) { Id = 4 };
|
||||
dataTypes = new[] { labelDataType, rteDataType };
|
||||
|
||||
var kitsAndXml = new List<(ContentNodeKit kit, XElement node)>();
|
||||
|
||||
var xDoc = XDocument.Parse(xml);
|
||||
IEnumerable<XElement> nodes = xDoc.XPathSelectElements("//*[@isDoc]");
|
||||
foreach (XElement node in nodes)
|
||||
{
|
||||
var id = node.AttributeValue<int>("id");
|
||||
Guid key = node.AttributeValue<Guid?>("key") ?? id.ToGuid();
|
||||
|
||||
var propertyElements = node.Elements().Where(x => x.Attribute("id") == null);
|
||||
var properties = new Dictionary<string, PropertyData[]>();
|
||||
foreach(XElement propertyElement in propertyElements)
|
||||
{
|
||||
properties[propertyElement.Name.LocalName] = new[]
|
||||
{
|
||||
// TODO: builder?
|
||||
new PropertyData
|
||||
{
|
||||
Culture = string.Empty,
|
||||
Segment = string.Empty,
|
||||
Value = propertyElement.Value
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var contentData = new ContentDataBuilder()
|
||||
.WithName(node.AttributeValue<string>("nodeName"))
|
||||
.WithProperties(properties)
|
||||
.WithPublished(true)
|
||||
.WithTemplateId(node.AttributeValue<int>("template"))
|
||||
.WithUrlSegment(node.AttributeValue<string>("urlName"))
|
||||
.WithVersionDate(node.AttributeValue<DateTime>("updateDate"))
|
||||
.WithWriterId(node.AttributeValue<int>("writerID"))
|
||||
.Build();
|
||||
|
||||
ContentNodeKit kit = ContentNodeKitBuilder.CreateWithContent(
|
||||
node.AttributeValue<int>("nodeType"),
|
||||
id,
|
||||
node.AttributeValue<string>("path"),
|
||||
node.AttributeValue<int>("sortOrder"),
|
||||
node.AttributeValue<int>("level"),
|
||||
node.AttributeValue<int>("parentID"),
|
||||
node.AttributeValue<int>("creatorID"),
|
||||
key,
|
||||
node.AttributeValue<DateTime>("createDate"),
|
||||
contentData,
|
||||
contentData);
|
||||
|
||||
kitsAndXml.Add((kit, node));
|
||||
}
|
||||
|
||||
// put together the unique content types
|
||||
var contentTypesIdToType = new Dictionary<int, ContentType>();
|
||||
foreach((ContentNodeKit kit, XElement node) in kitsAndXml)
|
||||
{
|
||||
if (!contentTypesIdToType.TryGetValue(kit.ContentTypeId, out ContentType contentType))
|
||||
{
|
||||
contentType = new ContentType(shortStringHelper, -1)
|
||||
{
|
||||
Id = kit.ContentTypeId,
|
||||
Alias = node.Name.LocalName
|
||||
};
|
||||
SetContentTypeProperties(shortStringHelper, labelDataType, rteDataType, kit, contentType);
|
||||
contentTypesIdToType[kit.ContentTypeId] = contentType;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we've already created it but might need to add properties
|
||||
SetContentTypeProperties(shortStringHelper, labelDataType, rteDataType, kit, contentType);
|
||||
}
|
||||
}
|
||||
|
||||
contentTypes = contentTypesIdToType.Values.ToArray();
|
||||
|
||||
return kitsAndXml.Select(x => x.kit);
|
||||
}
|
||||
|
||||
private static void SetContentTypeProperties(IShortStringHelper shortStringHelper, DataType labelDataType, DataType rteDataType, ContentNodeKit kit, ContentType contentType)
|
||||
{
|
||||
foreach (KeyValuePair<string, PropertyData[]> property in kit.DraftData.Properties)
|
||||
{
|
||||
var propertyType = new PropertyType(shortStringHelper, labelDataType, property.Key);
|
||||
|
||||
if (!contentType.PropertyTypeExists(propertyType.Alias))
|
||||
{
|
||||
if (propertyType.Alias == "content")
|
||||
{
|
||||
propertyType.DataTypeId = rteDataType.Id;
|
||||
}
|
||||
contentType.AddPropertyType(propertyType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.Published
|
||||
{
|
||||
|
||||
public class PublishedSnapshotTestObjects
|
||||
{
|
||||
[PublishedModel("element1")]
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Serialization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.TestHelpers.PublishedContent
|
||||
{
|
||||
|
||||
public class AutoPublishedContentType : PublishedContentType
|
||||
{
|
||||
private static readonly IPublishedPropertyType Default;
|
||||
|
||||
static AutoPublishedContentType()
|
||||
{
|
||||
var configurationEditorJsonSerializer = new ConfigurationEditorJsonSerializer();
|
||||
var jsonSerializer = new JsonNetSerializer();
|
||||
var dataTypeServiceMock = new Mock<IDataTypeService>();
|
||||
|
||||
var dataType = new DataType(
|
||||
new VoidEditor(
|
||||
Mock.Of<IDataValueEditorFactory>()),
|
||||
configurationEditorJsonSerializer)
|
||||
{
|
||||
Id = 666
|
||||
};
|
||||
dataTypeServiceMock.Setup(x => x.GetAll()).Returns(dataType.Yield);
|
||||
|
||||
var factory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), new PropertyValueConverterCollection(() => Enumerable.Empty<IPropertyValueConverter>()), dataTypeServiceMock.Object);
|
||||
Default = factory.CreatePropertyType("*", 666);
|
||||
}
|
||||
|
||||
public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable<PublishedPropertyType> propertyTypes)
|
||||
: base(key, id, alias, PublishedItemType.Content, Enumerable.Empty<string>(), propertyTypes, ContentVariation.Nothing)
|
||||
{
|
||||
}
|
||||
|
||||
public AutoPublishedContentType(Guid key, int id, string alias, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes)
|
||||
: base(key, id, alias, PublishedItemType.Content, Enumerable.Empty<string>(), propertyTypes, ContentVariation.Nothing)
|
||||
{
|
||||
}
|
||||
|
||||
public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable<string> compositionAliases, IEnumerable<PublishedPropertyType> propertyTypes)
|
||||
: base(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing)
|
||||
{
|
||||
}
|
||||
|
||||
public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable<string> compositionAliases, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes)
|
||||
: base(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing)
|
||||
{
|
||||
}
|
||||
|
||||
public override IPublishedPropertyType GetPropertyType(string alias)
|
||||
{
|
||||
IPublishedPropertyType propertyType = base.GetPropertyType(alias);
|
||||
return propertyType ?? Default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Moq;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.TestHelpers.PublishedContent
|
||||
{
|
||||
[PublishedModel("ContentType2")]
|
||||
public class ContentType2 : PublishedContentModel
|
||||
{
|
||||
public ContentType2(IPublishedContent content, IPublishedValueFallback fallback)
|
||||
: base(content, fallback)
|
||||
{
|
||||
}
|
||||
|
||||
public int Prop1 => this.Value<int>(Mock.Of<IPublishedValueFallback>(), "prop1");
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.TestHelpers.PublishedContent
|
||||
{
|
||||
[PublishedModel("ContentType2Sub")]
|
||||
public class ContentType2Sub : ContentType2
|
||||
{
|
||||
public ContentType2Sub(IPublishedContent content, IPublishedValueFallback fallback)
|
||||
: base(content, fallback)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Cms.Core.PublishedCache.Internal;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.TestHelpers.PublishedContent
|
||||
{
|
||||
public class InternalPublishedPropertyWithLanguageVariants : InternalPublishedProperty
|
||||
{
|
||||
private readonly IDictionary<string, object> _solidSourceValues = new Dictionary<string, object>();
|
||||
private readonly IDictionary<string, object> _solidValues = new Dictionary<string, object>();
|
||||
private readonly IDictionary<string, object> _solidXPathValues = new Dictionary<string, object>();
|
||||
|
||||
public override object GetSourceValue(string culture = null, string segment = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return base.GetSourceValue(culture, segment);
|
||||
}
|
||||
|
||||
return _solidSourceValues.ContainsKey(culture) ? _solidSourceValues[culture] : null;
|
||||
}
|
||||
|
||||
public override object GetValue(string culture = null, string segment = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return base.GetValue(culture, segment);
|
||||
}
|
||||
|
||||
return _solidValues.ContainsKey(culture) ? _solidValues[culture] : null;
|
||||
}
|
||||
|
||||
public override object GetXPathValue(string culture = null, string segment = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return base.GetXPathValue(culture, segment);
|
||||
}
|
||||
|
||||
return _solidXPathValues.ContainsKey(culture) ? _solidXPathValues[culture] : null;
|
||||
}
|
||||
|
||||
public override bool HasValue(string culture = null, string segment = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return base.HasValue(culture, segment);
|
||||
}
|
||||
|
||||
return _solidSourceValues.ContainsKey(culture);
|
||||
}
|
||||
|
||||
public void SetSourceValue(string culture, object value, bool defaultValue = false)
|
||||
{
|
||||
_solidSourceValues.Add(culture, value);
|
||||
if (defaultValue)
|
||||
{
|
||||
SolidSourceValue = value;
|
||||
SolidHasValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetValue(string culture, object value, bool defaultValue = false)
|
||||
{
|
||||
_solidValues.Add(culture, value);
|
||||
if (defaultValue)
|
||||
{
|
||||
SolidValue = value;
|
||||
SolidHasValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetXPathValue(string culture, object value, bool defaultValue = false)
|
||||
{
|
||||
_solidXPathValues.Add(culture, value);
|
||||
if (defaultValue)
|
||||
{
|
||||
SolidXPathValue = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Moq;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.TestHelpers.PublishedContent
|
||||
{
|
||||
public class PublishedContentStrong1 : PublishedContentModel
|
||||
{
|
||||
public PublishedContentStrong1(IPublishedContent content, IPublishedValueFallback fallback)
|
||||
: base(content, fallback)
|
||||
{
|
||||
}
|
||||
|
||||
public int StrongValue => this.Value<int>(Mock.Of<IPublishedValueFallback>(), "strongValue");
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Moq;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.TestHelpers.PublishedContent
|
||||
{
|
||||
public class PublishedContentStrong1Sub : PublishedContentStrong1
|
||||
{
|
||||
public PublishedContentStrong1Sub(IPublishedContent content, IPublishedValueFallback fallback)
|
||||
: base(content, fallback)
|
||||
{
|
||||
}
|
||||
|
||||
public int AnotherValue => this.Value<int>(Mock.Of<IPublishedValueFallback>(), "anotherValue");
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Moq;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.TestHelpers.PublishedContent
|
||||
{
|
||||
public class PublishedContentStrong2 : PublishedContentModel
|
||||
{
|
||||
public PublishedContentStrong2(IPublishedContent content, IPublishedValueFallback fallback)
|
||||
: base(content, fallback)
|
||||
{
|
||||
}
|
||||
|
||||
public int StrongValue => this.Value<int>(Mock.Of<IPublishedValueFallback>(), "strongValue");
|
||||
}
|
||||
}
|
||||
12
tests/Umbraco.Tests.Common/TestLastChanceFinder.cs
Normal file
12
tests/Umbraco.Tests.Common/TestLastChanceFinder.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common
|
||||
{
|
||||
public class TestLastChanceFinder : IContentLastChanceFinder
|
||||
{
|
||||
public bool TryFindContent(IPublishedRequestBuilder frequest) => false;
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,14 @@ namespace Umbraco.Cms.Tests.Common
|
||||
{
|
||||
public class TestPublishedSnapshotAccessor : IPublishedSnapshotAccessor
|
||||
{
|
||||
private IPublishedSnapshot _snapshot = null;
|
||||
|
||||
public bool TryGetPublishedSnapshot(out IPublishedSnapshot publishedSnapshot)
|
||||
{
|
||||
publishedSnapshot = null;
|
||||
return false;
|
||||
publishedSnapshot = _snapshot;
|
||||
return _snapshot != null;
|
||||
}
|
||||
|
||||
public void SetCurrent(IPublishedSnapshot snapshot) => _snapshot = snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,9 +52,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
|
||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||
{
|
||||
InMemoryConfiguration[Constants.Configuration.ConfigNuCache + ":" + nameof(NuCacheSettings.NuCacheSerializerType)] = NuCacheSerializerType.JSON.ToString();
|
||||
builder.AddNuCache();
|
||||
builder.Services.AddUnique<IServerMessenger, ScopedRepositoryTests.LocalServerMessenger>();
|
||||
builder.Services.PostConfigure<NuCacheSettings>(options =>
|
||||
{
|
||||
options.NuCacheSerializerType = NuCacheSerializerType.JSON;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void AssertJsonStartsWith(int id, string expected)
|
||||
|
||||
@@ -6,7 +6,13 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
@@ -14,6 +20,12 @@ using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.Importing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Media;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
{
|
||||
@@ -22,6 +34,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
public class EntityXmlSerializerTests : UmbracoIntegrationTest
|
||||
{
|
||||
private IEntityXmlSerializer Serializer => GetRequiredService<IEntityXmlSerializer>();
|
||||
private IContentService ContentService => GetRequiredService<IContentService>();
|
||||
private IMediaService MediaService => GetRequiredService<IMediaService>();
|
||||
private IUserService UserService => GetRequiredService<IUserService>();
|
||||
private IMediaTypeService MediaTypeService => GetRequiredService<IMediaTypeService>();
|
||||
private IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();
|
||||
private IDataValueEditorFactory DataValueEditorFactory => GetRequiredService<IDataValueEditorFactory>();
|
||||
private ILocalizedTextService TextService => GetRequiredService<ILocalizedTextService>();
|
||||
private IFileService FileService => GetRequiredService<IFileService>();
|
||||
|
||||
[Test]
|
||||
public void Can_Export_Macro()
|
||||
@@ -89,6 +109,123 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
Assert.That(xml.ToString(), Is.EqualTo(languageItemsElement.ToString()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Generate_Xml_Representation_Of_Content()
|
||||
{
|
||||
// Arrange
|
||||
var template = TemplateBuilder.CreateTextPageTemplate();
|
||||
FileService.SaveTemplate(template); // else, FK violation on contentType!
|
||||
var contentType = ContentTypeBuilder.CreateTextPageContentType(
|
||||
defaultTemplateId: template.Id);
|
||||
ContentTypeService.Save(contentType);
|
||||
|
||||
var content = ContentBuilder.CreateTextpageContent(contentType, "Root Home", -1);
|
||||
ContentService.Save(content, Constants.Security.SuperUserId);
|
||||
|
||||
var nodeName = content.ContentType.Alias.ToSafeAlias(ShortStringHelper);
|
||||
var urlName = content.GetUrlSegment(ShortStringHelper, new[] { new DefaultUrlSegmentProvider(ShortStringHelper) });
|
||||
|
||||
// Act
|
||||
XElement element = content.ToXml(Serializer);
|
||||
|
||||
// Assert
|
||||
Assert.That(element, Is.Not.Null);
|
||||
Assert.That(element.Name.LocalName, Is.EqualTo(nodeName));
|
||||
Assert.AreEqual(content.Id.ToString(), (string)element.Attribute("id"));
|
||||
Assert.AreEqual(content.ParentId.ToString(), (string)element.Attribute("parentID"));
|
||||
Assert.AreEqual(content.Level.ToString(), (string)element.Attribute("level"));
|
||||
Assert.AreEqual(content.CreatorId.ToString(), (string)element.Attribute("creatorID"));
|
||||
Assert.AreEqual(content.SortOrder.ToString(), (string)element.Attribute("sortOrder"));
|
||||
Assert.AreEqual(content.CreateDate.ToString("s"), (string)element.Attribute("createDate"));
|
||||
Assert.AreEqual(content.UpdateDate.ToString("s"), (string)element.Attribute("updateDate"));
|
||||
Assert.AreEqual(content.Name, (string)element.Attribute("nodeName"));
|
||||
Assert.AreEqual(urlName, (string)element.Attribute("urlName"));
|
||||
Assert.AreEqual(content.Path, (string)element.Attribute("path"));
|
||||
Assert.AreEqual("", (string)element.Attribute("isDoc"));
|
||||
Assert.AreEqual(content.ContentType.Id.ToString(), (string)element.Attribute("nodeType"));
|
||||
Assert.AreEqual(content.GetCreatorProfile(UserService).Name, (string)element.Attribute("creatorName"));
|
||||
Assert.AreEqual(content.GetWriterProfile(UserService).Name, (string)element.Attribute("writerName"));
|
||||
Assert.AreEqual(content.WriterId.ToString(), (string)element.Attribute("writerID"));
|
||||
Assert.AreEqual(content.TemplateId.ToString(), (string)element.Attribute("template"));
|
||||
|
||||
Assert.AreEqual(content.Properties["title"].GetValue().ToString(), element.Elements("title").Single().Value);
|
||||
Assert.AreEqual(content.Properties["bodyText"].GetValue().ToString(), element.Elements("bodyText").Single().Value);
|
||||
Assert.AreEqual(content.Properties["keywords"].GetValue().ToString(), element.Elements("keywords").Single().Value);
|
||||
Assert.AreEqual(content.Properties["description"].GetValue().ToString(), element.Elements("description").Single().Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Generate_Xml_Representation_Of_Media()
|
||||
{
|
||||
// Arrange
|
||||
var mediaType = MediaTypeBuilder.CreateImageMediaType("image2");
|
||||
|
||||
MediaTypeService.Save(mediaType);
|
||||
|
||||
// reference, so static ctor runs, so event handlers register
|
||||
// and then, this will reset the width, height... because the file does not exist, of course ;-(
|
||||
var loggerFactory = NullLoggerFactory.Instance;
|
||||
var scheme = Mock.Of<IMediaPathScheme>();
|
||||
var contentSettings = new ContentSettings();
|
||||
|
||||
var mediaFileManager = new MediaFileManager(
|
||||
Mock.Of<IFileSystem>(),
|
||||
scheme,
|
||||
loggerFactory.CreateLogger<MediaFileManager>(),
|
||||
ShortStringHelper,
|
||||
Services,
|
||||
Options.Create(new ContentSettings()));
|
||||
|
||||
var ignored = new FileUploadPropertyEditor(
|
||||
DataValueEditorFactory,
|
||||
mediaFileManager,
|
||||
Options.Create(contentSettings),
|
||||
TextService,
|
||||
Services.GetRequiredService<UploadAutoFillProperties>(),
|
||||
ContentService,
|
||||
IOHelper);
|
||||
|
||||
var media = MediaBuilder.CreateMediaImage(mediaType, -1);
|
||||
media.WriterId = -1; // else it's zero and that's not a user and it breaks the tests
|
||||
MediaService.Save(media, Constants.Security.SuperUserId);
|
||||
|
||||
// so we have to force-reset these values because the property editor has cleared them
|
||||
media.SetValue(Constants.Conventions.Media.Width, "200");
|
||||
media.SetValue(Constants.Conventions.Media.Height, "200");
|
||||
media.SetValue(Constants.Conventions.Media.Bytes, "100");
|
||||
media.SetValue(Constants.Conventions.Media.Extension, "png");
|
||||
|
||||
var nodeName = media.ContentType.Alias.ToSafeAlias(ShortStringHelper);
|
||||
var urlName = media.GetUrlSegment(ShortStringHelper, new[] { new DefaultUrlSegmentProvider(ShortStringHelper) });
|
||||
|
||||
// Act
|
||||
XElement element = media.ToXml(Serializer);
|
||||
|
||||
// Assert
|
||||
Assert.That(element, Is.Not.Null);
|
||||
Assert.That(element.Name.LocalName, Is.EqualTo(nodeName));
|
||||
Assert.AreEqual(media.Id.ToString(), (string)element.Attribute("id"));
|
||||
Assert.AreEqual(media.ParentId.ToString(), (string)element.Attribute("parentID"));
|
||||
Assert.AreEqual(media.Level.ToString(), (string)element.Attribute("level"));
|
||||
Assert.AreEqual(media.SortOrder.ToString(), (string)element.Attribute("sortOrder"));
|
||||
Assert.AreEqual(media.CreateDate.ToString("s"), (string)element.Attribute("createDate"));
|
||||
Assert.AreEqual(media.UpdateDate.ToString("s"), (string)element.Attribute("updateDate"));
|
||||
Assert.AreEqual(media.Name, (string)element.Attribute("nodeName"));
|
||||
Assert.AreEqual(urlName, (string)element.Attribute("urlName"));
|
||||
Assert.AreEqual(media.Path, (string)element.Attribute("path"));
|
||||
Assert.AreEqual("", (string)element.Attribute("isDoc"));
|
||||
Assert.AreEqual(media.ContentType.Id.ToString(), (string)element.Attribute("nodeType"));
|
||||
Assert.AreEqual(media.GetCreatorProfile(UserService).Name, (string)element.Attribute("writerName"));
|
||||
Assert.AreEqual(media.CreatorId.ToString(), (string)element.Attribute("writerID"));
|
||||
Assert.IsNull(element.Attribute("template"));
|
||||
|
||||
Assert.AreEqual(media.Properties[Constants.Conventions.Media.File].GetValue().ToString(), element.Elements(Constants.Conventions.Media.File).Single().Value);
|
||||
Assert.AreEqual(media.Properties[Constants.Conventions.Media.Width].GetValue().ToString(), element.Elements(Constants.Conventions.Media.Width).Single().Value);
|
||||
Assert.AreEqual(media.Properties[Constants.Conventions.Media.Height].GetValue().ToString(), element.Elements(Constants.Conventions.Media.Height).Single().Value);
|
||||
Assert.AreEqual(media.Properties[Constants.Conventions.Media.Bytes].GetValue().ToString(), element.Elements(Constants.Conventions.Media.Bytes).Single().Value);
|
||||
Assert.AreEqual(media.Properties[Constants.Conventions.Media.Extension].GetValue().ToString(), element.Elements(Constants.Conventions.Media.Extension).Single().Value);
|
||||
}
|
||||
|
||||
private void CreateDictionaryData()
|
||||
{
|
||||
ILocalizationService localizationService = GetRequiredService<ILocalizationService>();
|
||||
@@ -104,7 +241,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
.Build();
|
||||
localizationService.Save(languageEnGb);
|
||||
|
||||
var parentItem = new DictionaryItem("Parent") {Key = Guid.Parse("28f2e02a-8c66-4fcd-85e3-8524d551c0d3")};
|
||||
var parentItem = new DictionaryItem("Parent") { Key = Guid.Parse("28f2e02a-8c66-4fcd-85e3-8524d551c0d3") };
|
||||
var parentTranslations = new List<IDictionaryTranslation>
|
||||
{
|
||||
new DictionaryTranslation(languageNbNo, "ForelderVerdi"),
|
||||
@@ -113,7 +250,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services
|
||||
parentItem.Translations = parentTranslations;
|
||||
localizationService.Save(parentItem);
|
||||
|
||||
var childItem = new DictionaryItem(parentItem.Key, "Child"){Key = Guid.Parse("e7dba0a9-d517-4ba4-8e18-2764d392c611")};
|
||||
var childItem = new DictionaryItem(parentItem.Key, "Child") { Key = Guid.Parse("e7dba0a9-d517-4ba4-8e18-2764d392c611") };
|
||||
var childTranslations = new List<IDictionaryTranslation>
|
||||
{
|
||||
new DictionaryTranslation(languageNbNo, "BarnVerdi"),
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>ImportResources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<None Remove="Umbraco.Web.BackOffice\UrlAndDomains\package.xml" />
|
||||
<EmbeddedResource Include="Umbraco.Web.BackOffice\UrlAndDomains\package.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Packaging;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Testing;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.UrlAndDomains
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Mapper = true, WithApplication = true,
|
||||
Logger = UmbracoTestOptions.Logger.Console)]
|
||||
public class DomainAndUrlsTests : UmbracoIntegrationTest
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
XDocument xml = PackageMigrationResource.GetEmbeddedPackageDataManifest(GetType());
|
||||
IPackagingService packagingService = GetRequiredService<IPackagingService>();
|
||||
InstallationSummary = packagingService.InstallCompiledPackageData(xml);
|
||||
|
||||
Root = InstallationSummary.ContentInstalled.First();
|
||||
ContentService.SaveAndPublish(Root);
|
||||
|
||||
var cultures = new List<string>();
|
||||
cultures.Add(GetRequiredService<ILocalizationService>().GetDefaultLanguageIsoCode());
|
||||
|
||||
foreach (ILanguage language in InstallationSummary.LanguagesInstalled)
|
||||
{
|
||||
cultures.Add(language.IsoCode);
|
||||
}
|
||||
|
||||
Cultures = cultures.ToArray();
|
||||
|
||||
IHttpContextAccessor httpContextAccessor = GetRequiredService<IHttpContextAccessor>();
|
||||
|
||||
httpContextAccessor.HttpContext = new DefaultHttpContext
|
||||
{
|
||||
Request =
|
||||
{
|
||||
Scheme = "https",
|
||||
Host = new HostString("localhost"),
|
||||
Path = "/",
|
||||
QueryString = new QueryString(string.Empty)
|
||||
}
|
||||
};
|
||||
|
||||
//Like the request middleware we specify the VariationContext to the default language.
|
||||
_variationContextAccessor.VariationContext = new VariationContext(Cultures[0]);
|
||||
GetRequiredService<IUmbracoContextFactory>().EnsureUmbracoContext();
|
||||
}
|
||||
|
||||
private IContentService ContentService => GetRequiredService<IContentService>();
|
||||
|
||||
public InstallationSummary InstallationSummary { get; set; }
|
||||
|
||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddUnique<IVariationContextAccessor>(_variationContextAccessor);
|
||||
builder.AddNuCache();
|
||||
}
|
||||
|
||||
private readonly TestVariationContextAccessor _variationContextAccessor = new TestVariationContextAccessor();
|
||||
|
||||
public IContent Root { get; set; }
|
||||
public string[] Cultures { get; set; }
|
||||
|
||||
|
||||
[Test]
|
||||
public void Having_three_cultures_and_set_domain_on_all_of_them()
|
||||
{
|
||||
foreach (var culture in Cultures)
|
||||
{
|
||||
SetDomainOnContent(Root, culture, GetDomainUrlFromCultureCode(culture));
|
||||
}
|
||||
|
||||
IEnumerable<UrlInfo> rootUrls = GetContentUrlsAsync(Root);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(6, rootUrls.Count());
|
||||
foreach (var culture in Cultures)
|
||||
{
|
||||
var domain = GetDomainUrlFromCultureCode(culture);
|
||||
Assert.IsTrue(rootUrls.Any(x => x.Text == domain));
|
||||
Assert.IsTrue(rootUrls.Any(x => x.Text == "https://localhost" + domain));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Having_three_cultures_but_set_domain_on_a_non_default_language()
|
||||
{
|
||||
var culture = Cultures[1];
|
||||
var domain = GetDomainUrlFromCultureCode(culture);
|
||||
SetDomainOnContent(Root, culture, domain);
|
||||
|
||||
IEnumerable<UrlInfo> rootUrls = GetContentUrlsAsync(Root);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(4, rootUrls.Count());
|
||||
|
||||
//We expect two for the domain that is setup
|
||||
Assert.IsTrue(rootUrls.Any(x => x.IsUrl && x.Text == domain && x.Culture == culture));
|
||||
Assert.IsTrue(rootUrls.Any(x => x.IsUrl && x.Text == "https://localhost" + domain && x.Culture == culture));
|
||||
|
||||
//We expect the default language to be routable on the default path "/"
|
||||
Assert.IsTrue(rootUrls.Any(x=>x.IsUrl && x.Text == "/" && x.Culture == Cultures[0]));
|
||||
|
||||
//We dont expect non-default languages without a domain to be routable
|
||||
Assert.IsTrue(rootUrls.Any(x=>x.IsUrl == false && x.Culture == Cultures[2]));
|
||||
});
|
||||
}
|
||||
|
||||
private static string GetDomainUrlFromCultureCode(string culture) =>
|
||||
"/" + culture.Replace("-", string.Empty).ToLower() + "/";
|
||||
|
||||
private void SetDomainOnContent(IContent content, string cultureIsoCode, string domain)
|
||||
{
|
||||
IDomainService domainService = GetRequiredService<IDomainService>();
|
||||
var langId = GetRequiredService<ILocalizationService>().GetLanguageIdByIsoCode(cultureIsoCode);
|
||||
domainService.Save(
|
||||
new UmbracoDomain(domain) { RootContentId = content.Id, LanguageId = langId });
|
||||
}
|
||||
|
||||
private IEnumerable<UrlInfo> GetContentUrlsAsync(IContent root) =>
|
||||
root.GetContentUrlsAsync(
|
||||
GetRequiredService<IPublishedRouter>(),
|
||||
GetRequiredService<IUmbracoContextAccessor>().GetRequiredUmbracoContext(),
|
||||
GetRequiredService<ILocalizationService>(),
|
||||
GetRequiredService<ILocalizedTextService>(),
|
||||
ContentService,
|
||||
GetRequiredService<IVariationContextAccessor>(),
|
||||
GetRequiredService<ILogger<IContent>>(),
|
||||
GetRequiredService<UriUtility>(),
|
||||
GetRequiredService<IPublishedUrlProvider>()
|
||||
).GetAwaiter().GetResult();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<umbPackage>
|
||||
<info>
|
||||
<package>
|
||||
<name>Test</name>
|
||||
</package>
|
||||
</info>
|
||||
<Documents>
|
||||
<DocumentSet importMode="root">
|
||||
<textPageLang id="5818" key="092b28ba-f1ce-4469-9573-ed716cfe32bc" parentID="-1" level="1" creatorID="-1" sortOrder="0" createDate="2021-10-13T15:39:32" updateDate="2021-10-13T15:39:32" nodeName="svSE" urlName="svse" path="-1,5818" isDoc="" nodeName-sv-se="svSE" nodeName-da="daDK" nodeName-en-us="enUS" nodeType="4130" nodeTypeAlias="textPageLang" creatorName="Bjarke Berg" writerName="Bjarke Berg" writerID="−1" template="4129" isPublished="true" />
|
||||
</DocumentSet>
|
||||
</Documents>
|
||||
<DocumentTypes>
|
||||
<DocumentType>
|
||||
<Info>
|
||||
<Name>TextPageLang</Name>
|
||||
<Alias>textPageLang</Alias>
|
||||
<Key>d4a44303-bf68-4f34-9f87-6668ff9e9cdc</Key>
|
||||
<Icon>icon-document</Icon>
|
||||
<Thumbnail>folder.png</Thumbnail>
|
||||
<Description />
|
||||
<AllowAtRoot>True</AllowAtRoot>
|
||||
<IsListView>False</IsListView>
|
||||
<IsElement>False</IsElement>
|
||||
<Variations>Culture</Variations>
|
||||
<Compositions />
|
||||
<AllowedTemplates>
|
||||
<Template>TextPageLang</Template>
|
||||
</AllowedTemplates>
|
||||
<DefaultTemplate>TextPageLang</DefaultTemplate>
|
||||
</Info>
|
||||
<Structure>
|
||||
<DocumentType>textPage</DocumentType>
|
||||
<DocumentType>textPageLang</DocumentType>
|
||||
</Structure>
|
||||
<GenericProperties>
|
||||
<GenericProperty>
|
||||
<Name>Grid</Name>
|
||||
<Alias>grid</Alias>
|
||||
<Key>74dcad47-03e6-4190-a7d5-480bd1d96783</Key>
|
||||
<Type>Umbraco.Grid</Type>
|
||||
<Definition>9ccf22c3-34c5-4e58-b365-a2d806c00540</Definition>
|
||||
<Tab Alias="content">Content</Tab>
|
||||
<SortOrder>0</SortOrder>
|
||||
<Mandatory>False</Mandatory>
|
||||
<LabelOnTop>False</LabelOnTop>
|
||||
<Variations>Culture</Variations>
|
||||
</GenericProperty>
|
||||
</GenericProperties>
|
||||
<Tabs>
|
||||
<Tab>
|
||||
<Id>2074</Id>
|
||||
<Key>1cd3a297-a821-49e9-98d8-5c21ddd2aeaf</Key>
|
||||
<Type>Group</Type>
|
||||
<Caption>Content</Caption>
|
||||
<Alias>content</Alias>
|
||||
<SortOrder>0</SortOrder>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</DocumentType>
|
||||
</DocumentTypes>
|
||||
<MediaTypes />
|
||||
<Templates />
|
||||
<Stylesheets />
|
||||
<Scripts />
|
||||
<PartialViews />
|
||||
<Macros />
|
||||
<MacroPartialViews />
|
||||
<DictionaryItems />
|
||||
<Languages>
|
||||
<Language Id="1003" CultureAlias="da" FriendlyName="dansk" />
|
||||
<Language Id="3" CultureAlias="sv-SE" FriendlyName="svensk (Sverige)" />
|
||||
<Language Id="1" CultureAlias="en-US" FriendlyName="English (United States)" />
|
||||
</Languages>
|
||||
<DataTypes />
|
||||
<MediaItems />
|
||||
</umbPackage>
|
||||
@@ -0,0 +1,278 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Core.Sync;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
|
||||
using Umbraco.Cms.Infrastructure.Serialization;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.TestHelpers
|
||||
{
|
||||
[TestFixture]
|
||||
public class PublishedSnapshotServiceTestBase
|
||||
{
|
||||
[SetUp]
|
||||
public virtual void Setup()
|
||||
{
|
||||
VariationContextAccessor = new TestVariationContextAccessor();
|
||||
PublishedSnapshotAccessor = new TestPublishedSnapshotAccessor();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown() => SnapshotService?.Dispose();
|
||||
|
||||
protected IShortStringHelper ShortStringHelper { get; } = TestHelper.ShortStringHelper;
|
||||
protected virtual IPublishedModelFactory PublishedModelFactory { get; } = new NoopPublishedModelFactory();
|
||||
protected IContentTypeService ContentTypeService { get; private set; }
|
||||
protected IMediaTypeService MediaTypeService { get; private set; }
|
||||
protected IDataTypeService DataTypeService { get; private set; }
|
||||
protected IDomainService DomainService { get; private set; }
|
||||
protected IPublishedValueFallback PublishedValueFallback { get; private set; }
|
||||
protected IPublishedSnapshotService SnapshotService { get; private set; }
|
||||
protected IVariationContextAccessor VariationContextAccessor { get; private set; }
|
||||
protected TestPublishedSnapshotAccessor PublishedSnapshotAccessor { get; private set; }
|
||||
protected TestNuCacheContentService NuCacheContentService { get; private set; }
|
||||
protected PublishedContentTypeFactory PublishedContentTypeFactory { get; private set; }
|
||||
protected GlobalSettings GlobalSettings { get; } = new();
|
||||
|
||||
protected virtual PropertyValueConverterCollection PropertyValueConverterCollection =>
|
||||
new(() => new[] { new TestSimpleTinyMceValueConverter() });
|
||||
|
||||
protected IPublishedContent GetContent(int id)
|
||||
{
|
||||
IPublishedSnapshot snapshot = GetPublishedSnapshot();
|
||||
IPublishedContent doc = snapshot.Content.GetById(id);
|
||||
Assert.IsNotNull(doc);
|
||||
return doc;
|
||||
}
|
||||
|
||||
protected IPublishedContent GetMedia(int id)
|
||||
{
|
||||
IPublishedSnapshot snapshot = GetPublishedSnapshot();
|
||||
IPublishedContent doc = snapshot.Media.GetById(id);
|
||||
Assert.IsNotNull(doc);
|
||||
return doc;
|
||||
}
|
||||
|
||||
protected UrlProvider GetUrlProvider(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
RequestHandlerSettings requestHandlerSettings,
|
||||
WebRoutingSettings webRoutingSettings,
|
||||
out UriUtility uriUtility)
|
||||
{
|
||||
uriUtility = new UriUtility(Mock.Of<IHostingEnvironment>());
|
||||
var urlProvider = new DefaultUrlProvider(
|
||||
Options.Create(requestHandlerSettings),
|
||||
Mock.Of<ILogger<DefaultUrlProvider>>(),
|
||||
new SiteDomainMapper(),
|
||||
umbracoContextAccessor,
|
||||
uriUtility,
|
||||
Mock.Of<ILocalizationService>(x=>x.GetDefaultLanguageIsoCode() == GlobalSettings.DefaultUILanguage)
|
||||
);
|
||||
|
||||
var publishedUrlProvider = new UrlProvider(
|
||||
umbracoContextAccessor,
|
||||
Options.Create(webRoutingSettings),
|
||||
new UrlProviderCollection(() => new[] { urlProvider }),
|
||||
new MediaUrlProviderCollection(() => Enumerable.Empty<IMediaUrlProvider>()),
|
||||
Mock.Of<IVariationContextAccessor>());
|
||||
|
||||
return publishedUrlProvider;
|
||||
}
|
||||
|
||||
protected static PublishedRouter CreatePublishedRouter(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IEnumerable<IContentFinder> contentFinders = null,
|
||||
IPublishedUrlProvider publishedUrlProvider = null) => new(Options.Create(new WebRoutingSettings()),
|
||||
new ContentFinderCollection(() => contentFinders ?? Enumerable.Empty<IContentFinder>()),
|
||||
new TestLastChanceFinder(), new TestVariationContextAccessor(), Mock.Of<IProfilingLogger>(),
|
||||
Mock.Of<ILogger<PublishedRouter>>(), publishedUrlProvider ?? Mock.Of<IPublishedUrlProvider>(),
|
||||
Mock.Of<IRequestAccessor>(), Mock.Of<IPublishedValueFallback>(), Mock.Of<IFileService>(),
|
||||
Mock.Of<IContentTypeService>(), umbracoContextAccessor, Mock.Of<IEventAggregator>());
|
||||
|
||||
protected IUmbracoContextAccessor GetUmbracoContextAccessor(string urlAsString)
|
||||
{
|
||||
IPublishedSnapshot snapshot = GetPublishedSnapshot();
|
||||
|
||||
var uri = new Uri(urlAsString.Contains(Uri.SchemeDelimiter)
|
||||
? urlAsString
|
||||
: $"http://example.com{urlAsString}");
|
||||
|
||||
IUmbracoContext umbracoContext = Mock.Of<IUmbracoContext>(
|
||||
x => x.CleanedUmbracoUrl == uri
|
||||
&& x.Content == snapshot.Content
|
||||
&& x.PublishedSnapshot == snapshot);
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
|
||||
return umbracoContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used as a property editor for any test property that has an editor alias called "Umbraco.Void.RTE"
|
||||
/// </summary>
|
||||
private class TestSimpleTinyMceValueConverter : SimpleTinyMceValueConverter
|
||||
{
|
||||
public override bool IsConverter(IPublishedPropertyType propertyType)
|
||||
=> propertyType.EditorAlias == "Umbraco.Void.RTE";
|
||||
}
|
||||
|
||||
protected static DataType[] GetDefaultDataTypes()
|
||||
{
|
||||
var serializer = new ConfigurationEditorJsonSerializer();
|
||||
|
||||
// create data types, property types and content types
|
||||
var dataType =
|
||||
new DataType(new VoidEditor("Editor", Mock.Of<IDataValueEditorFactory>()), serializer) { Id = 3 };
|
||||
|
||||
return new[] { dataType };
|
||||
}
|
||||
|
||||
protected virtual ServiceContext CreateServiceContext(IContentType[] contentTypes, IMediaType[] mediaTypes,
|
||||
IDataType[] dataTypes)
|
||||
{
|
||||
var contentTypeService = new Mock<IContentTypeService>();
|
||||
contentTypeService.Setup(x => x.GetAll()).Returns(contentTypes);
|
||||
contentTypeService.Setup(x => x.GetAll(It.IsAny<int[]>())).Returns(contentTypes);
|
||||
contentTypeService.Setup(x => x.Get(It.IsAny<string>()))
|
||||
.Returns((string alias) => contentTypes.FirstOrDefault(x => x.Alias.InvariantEquals(alias)));
|
||||
|
||||
var mediaTypeService = new Mock<IMediaTypeService>();
|
||||
mediaTypeService.Setup(x => x.GetAll()).Returns(mediaTypes);
|
||||
mediaTypeService.Setup(x => x.GetAll(It.IsAny<int[]>())).Returns(mediaTypes);
|
||||
mediaTypeService.Setup(x => x.Get(It.IsAny<string>()))
|
||||
.Returns((string alias) => mediaTypes.FirstOrDefault(x => x.Alias.InvariantEquals(alias)));
|
||||
|
||||
var contentTypeServiceBaseFactory = new Mock<IContentTypeBaseServiceProvider>();
|
||||
contentTypeServiceBaseFactory.Setup(x => x.For(It.IsAny<IContentBase>()))
|
||||
.Returns(contentTypeService.Object);
|
||||
|
||||
var dataTypeServiceMock = new Mock<IDataTypeService>();
|
||||
dataTypeServiceMock.Setup(x => x.GetAll()).Returns(dataTypes);
|
||||
|
||||
return ServiceContext.CreatePartial(
|
||||
dataTypeService: dataTypeServiceMock.Object,
|
||||
memberTypeService: Mock.Of<IMemberTypeService>(),
|
||||
memberService: Mock.Of<IMemberService>(),
|
||||
contentTypeService: contentTypeService.Object,
|
||||
mediaTypeService: mediaTypeService.Object,
|
||||
localizationService: Mock.Of<ILocalizationService>(),
|
||||
domainService: Mock.Of<IDomainService>(),
|
||||
fileService: Mock.Of<IFileService>()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a published snapshot and set the accessor to resolve the created one
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected IPublishedSnapshot GetPublishedSnapshot()
|
||||
{
|
||||
IPublishedSnapshot snapshot = SnapshotService.CreatePublishedSnapshot(null);
|
||||
PublishedSnapshotAccessor.SetCurrent(snapshot);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="IPublishedSnapshotService'" /> with a source of data
|
||||
/// </summary>
|
||||
/// <param name="contentNodeKits"></param>
|
||||
/// <param name="contentTypes"></param>
|
||||
protected void InitializedCache(
|
||||
IEnumerable<ContentNodeKit> contentNodeKits,
|
||||
IContentType[] contentTypes,
|
||||
IDataType[] dataTypes = null,
|
||||
IEnumerable<ContentNodeKit> mediaNodeKits = null,
|
||||
IMediaType[] mediaTypes = null)
|
||||
{
|
||||
// create a data source for NuCache
|
||||
NuCacheContentService = new TestNuCacheContentService(contentNodeKits, mediaNodeKits);
|
||||
|
||||
IRuntimeState runtime = Mock.Of<IRuntimeState>();
|
||||
Mock.Get(runtime).Setup(x => x.Level).Returns(RuntimeLevel.Run);
|
||||
|
||||
// create a service context
|
||||
ServiceContext serviceContext = CreateServiceContext(
|
||||
contentTypes ?? Array.Empty<IContentType>(),
|
||||
mediaTypes ?? Array.Empty<IMediaType>(),
|
||||
dataTypes ?? GetDefaultDataTypes());
|
||||
|
||||
DataTypeService = serviceContext.DataTypeService;
|
||||
ContentTypeService = serviceContext.ContentTypeService;
|
||||
MediaTypeService = serviceContext.MediaTypeService;
|
||||
DomainService = serviceContext.DomainService;
|
||||
|
||||
// create a scope provider
|
||||
IScopeProvider scopeProvider = Mock.Of<IScopeProvider>();
|
||||
Mock.Get(scopeProvider)
|
||||
.Setup(x => x.CreateScope(
|
||||
It.IsAny<IsolationLevel>(),
|
||||
It.IsAny<RepositoryCacheMode>(),
|
||||
It.IsAny<IEventDispatcher>(),
|
||||
It.IsAny<IScopedNotificationPublisher>(),
|
||||
It.IsAny<bool?>(),
|
||||
It.IsAny<bool>(),
|
||||
It.IsAny<bool>()))
|
||||
.Returns(Mock.Of<IScope>);
|
||||
|
||||
// create a published content type factory
|
||||
PublishedContentTypeFactory = new PublishedContentTypeFactory(
|
||||
PublishedModelFactory,
|
||||
PropertyValueConverterCollection,
|
||||
DataTypeService);
|
||||
|
||||
ITypeFinder typeFinder = TestHelper.GetTypeFinder();
|
||||
|
||||
var nuCacheSettings = new NuCacheSettings();
|
||||
|
||||
// at last, create the complete NuCache snapshot service!
|
||||
var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
|
||||
SnapshotService = new PublishedSnapshotService(
|
||||
options,
|
||||
Mock.Of<ISyncBootStateAccessor>(x => x.GetSyncBootState() == SyncBootState.WarmBoot),
|
||||
new SimpleMainDom(),
|
||||
serviceContext,
|
||||
PublishedContentTypeFactory,
|
||||
PublishedSnapshotAccessor,
|
||||
VariationContextAccessor,
|
||||
Mock.Of<IProfilingLogger>(),
|
||||
NullLoggerFactory.Instance,
|
||||
scopeProvider,
|
||||
NuCacheContentService,
|
||||
new TestDefaultCultureAccessor(),
|
||||
Options.Create(GlobalSettings),
|
||||
PublishedModelFactory,
|
||||
TestHelper.GetHostingEnvironment(),
|
||||
Options.Create(nuCacheSettings),
|
||||
//ContentNestedDataSerializerFactory,
|
||||
new ContentDataSerializer(new DictionaryOfPropertyDataSerializer()));
|
||||
|
||||
// invariant is the current default
|
||||
VariationContextAccessor.VariationContext = new VariationContext();
|
||||
|
||||
PublishedValueFallback = new PublishedValueFallback(serviceContext, VariationContextAccessor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.Persistence;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.TestHelpers
|
||||
{
|
||||
public class TestNuCacheContentService : INuCacheContentService
|
||||
{
|
||||
private IPublishedModelFactory PublishedModelFactory { get; } = new NoopPublishedModelFactory();
|
||||
|
||||
public TestNuCacheContentService(params ContentNodeKit[] kits)
|
||||
: this((IEnumerable<ContentNodeKit>)kits)
|
||||
{ }
|
||||
|
||||
public TestNuCacheContentService(IEnumerable<ContentNodeKit> contentKits, IEnumerable<ContentNodeKit> mediaKits = null)
|
||||
{
|
||||
ContentKits = contentKits?.ToDictionary(x => x.Node.Id, x => x) ?? new Dictionary<int, ContentNodeKit>();
|
||||
MediaKits = mediaKits?.ToDictionary(x => x.Node.Id, x => x) ?? new Dictionary<int, ContentNodeKit>();
|
||||
}
|
||||
|
||||
public Dictionary<int, ContentNodeKit> ContentKits { get; }
|
||||
public Dictionary<int, ContentNodeKit> MediaKits { get; }
|
||||
|
||||
// note: it is important to clone the returned kits, as the inner
|
||||
// ContentNode is directly reused and modified by the snapshot service
|
||||
public ContentNodeKit GetContentSource(int id)
|
||||
=> ContentKits.TryGetValue(id, out ContentNodeKit kit) ? kit.Clone(PublishedModelFactory) : default;
|
||||
|
||||
public IEnumerable<ContentNodeKit> GetAllContentSources()
|
||||
=> ContentKits.Values
|
||||
.OrderBy(x => x.Node.Level)
|
||||
.ThenBy(x => x.Node.ParentContentId)
|
||||
.ThenBy(x => x.Node.SortOrder)
|
||||
.Select(x => x.Clone(PublishedModelFactory));
|
||||
|
||||
public IEnumerable<ContentNodeKit> GetBranchContentSources(int id)
|
||||
=> ContentKits.Values
|
||||
.Where(x => x.Node.Path.EndsWith("," + id) || x.Node.Path.Contains("," + id + ","))
|
||||
.OrderBy(x => x.Node.Level)
|
||||
.ThenBy(x => x.Node.ParentContentId)
|
||||
.ThenBy(x => x.Node.SortOrder)
|
||||
.Select(x => x.Clone(PublishedModelFactory));
|
||||
|
||||
public IEnumerable<ContentNodeKit> GetTypeContentSources(IEnumerable<int> ids)
|
||||
=> ContentKits.Values
|
||||
.Where(x => ids.Contains(x.ContentTypeId))
|
||||
.OrderBy(x => x.Node.Level)
|
||||
.ThenBy(x => x.Node.ParentContentId)
|
||||
.ThenBy(x => x.Node.SortOrder)
|
||||
.Select(x => x.Clone(PublishedModelFactory));
|
||||
|
||||
public ContentNodeKit GetMediaSource(int id)
|
||||
=> MediaKits.TryGetValue(id, out ContentNodeKit kit) ? kit.Clone(PublishedModelFactory) : default;
|
||||
|
||||
public IEnumerable<ContentNodeKit> GetAllMediaSources()
|
||||
=> MediaKits.Values
|
||||
.OrderBy(x => x.Node.Level)
|
||||
.ThenBy(x => x.Node.ParentContentId)
|
||||
.ThenBy(x => x.Node.SortOrder)
|
||||
.Select(x => x.Clone(PublishedModelFactory));
|
||||
|
||||
public IEnumerable<ContentNodeKit> GetBranchMediaSources(int id)
|
||||
=> MediaKits.Values
|
||||
.Where(x => x.Node.Path.EndsWith("," + id) || x.Node.Path.Contains("," + id + ","))
|
||||
.OrderBy(x => x.Node.Level)
|
||||
.ThenBy(x => x.Node.ParentContentId)
|
||||
.ThenBy(x => x.Node.SortOrder)
|
||||
.Select(x => x.Clone(PublishedModelFactory));
|
||||
|
||||
public IEnumerable<ContentNodeKit> GetTypeMediaSources(IEnumerable<int> ids)
|
||||
=> MediaKits.Values
|
||||
.Where(x => ids.Contains(x.ContentTypeId))
|
||||
.OrderBy(x => x.Node.Level)
|
||||
.ThenBy(x => x.Node.ParentContentId)
|
||||
.ThenBy(x => x.Node.SortOrder)
|
||||
.Select(x => x.Clone(PublishedModelFactory));
|
||||
|
||||
public void DeleteContentItem(IContentBase item) => throw new NotImplementedException();
|
||||
public void DeleteContentItems(IEnumerable<IContentBase> items) => throw new NotImplementedException();
|
||||
public void RefreshContent(IContent content) => throw new NotImplementedException();
|
||||
|
||||
public void RebuildDatabaseCacheIfSerializerChanged() => throw new NotImplementedException();
|
||||
public void RefreshMedia(IMedia media) => throw new NotImplementedException();
|
||||
public void RefreshMember(IMember member) => throw new NotImplementedException();
|
||||
public void Rebuild(IReadOnlyCollection<int> contentTypeIds = null, IReadOnlyCollection<int> mediaTypeIds = null, IReadOnlyCollection<int> memberTypeIds = null) => throw new NotImplementedException();
|
||||
|
||||
public bool VerifyContentDbCache() => throw new NotImplementedException();
|
||||
public bool VerifyMediaDbCache() => throw new NotImplementedException();
|
||||
public bool VerifyMemberDbCache() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Collections;
|
||||
|
||||
namespace Umbraco.Tests.Collections
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Collections
|
||||
{
|
||||
[TestFixture]
|
||||
public class StackQueueTests
|
||||
@@ -16,7 +16,7 @@ namespace Umbraco.Tests.Collections
|
||||
}
|
||||
|
||||
var expected = 0;
|
||||
while(sq.Count > 0)
|
||||
while (sq.Count > 0)
|
||||
{
|
||||
var next = sq.Dequeue();
|
||||
Assert.AreEqual(expected, next);
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
// TODO: We should be able to decouple this from the base db tests since we're just mocking the services now
|
||||
|
||||
[TestFixture]
|
||||
public class ContentFinderByAliasTests : UrlRoutingTestBase
|
||||
{
|
||||
|
||||
[TestCase("/this/is/my/alias", 1001)]
|
||||
[TestCase("/anotheralias", 1001)]
|
||||
[TestCase("/page2/alias", 10011)]
|
||||
[TestCase("/2ndpagealias", 10011)]
|
||||
[TestCase("/only/one/alias", 100111)]
|
||||
[TestCase("/ONLY/one/Alias", 100111)]
|
||||
[TestCase("/alias43", 100121)]
|
||||
public async Task Lookup_By_Url_Alias(string urlAsString, int nodeMatch)
|
||||
{
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(urlAsString);
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
|
||||
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
var lookup =
|
||||
new ContentFinderByUrlAlias(Mock.Of<ILogger<ContentFinderByUrlAlias>>(), Mock.Of<IPublishedValueFallback>(), VariationContextAccessor, umbracoContextAccessor);
|
||||
|
||||
var result = lookup.TryFindContent(frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(frequest.PublishedContent.Id, nodeMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -8,40 +9,18 @@ using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class ContentFinderByAliasWithDomainsTests : ContentFinderByAliasTests
|
||||
public class ContentFinderByAliasWithDomainsTests : UrlRoutingTestBase
|
||||
{
|
||||
private PublishedContentType _publishedContentType;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
var properties = new[]
|
||||
{
|
||||
new PublishedPropertyType(
|
||||
propertyTypeAlias:"umbracoUrlAlias",
|
||||
dataTypeId: Constants.DataTypes.Textbox,
|
||||
isUserProperty:false,
|
||||
variations: ContentVariation.Nothing,
|
||||
propertyValueConverters:new PropertyValueConverterCollection(Enumerable.Empty<IPropertyValueConverter>()),
|
||||
contentType:Mock.Of<IPublishedContentType>(),
|
||||
publishedModelFactory:Mock.Of<IPublishedModelFactory>(),
|
||||
factory:Mock.Of<IPublishedContentTypeFactory>()
|
||||
)
|
||||
};
|
||||
_publishedContentType = new PublishedContentType(Guid.NewGuid(), 0, "Doc", PublishedItemType.Content, Enumerable.Empty<string>(), properties, ContentVariation.Nothing);
|
||||
}
|
||||
|
||||
protected override PublishedContentType GetPublishedContentTypeByAlias(string alias)
|
||||
{
|
||||
if (alias == "Doc") return _publishedContentType;
|
||||
return null;
|
||||
}
|
||||
|
||||
[TestCase("http://domain1.com/this/is/my/alias", "de-DE", -1001)] // alias to domain's page fails - no alias on domain's home
|
||||
[TestCase("http://domain1.com/page2/alias", "de-DE", 10011)] // alias to sub-page works
|
||||
@@ -56,10 +35,11 @@ namespace Umbraco.Tests.Routing
|
||||
public async Task Lookup_By_Url_Alias_And_Domain(string inputUrl, string expectedCulture, int expectedNode)
|
||||
{
|
||||
//SetDomains1();
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(inputUrl);
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
|
||||
var umbracoContext = GetUmbracoContext(inputUrl);
|
||||
var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext));
|
||||
var request = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
var request = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
// must lookup domain
|
||||
publishedRouter.FindDomain(request);
|
||||
|
||||
@@ -68,7 +48,7 @@ namespace Umbraco.Tests.Routing
|
||||
Assert.AreEqual(expectedCulture, request.Culture);
|
||||
}
|
||||
|
||||
var finder = new ContentFinderByUrlAlias(LoggerFactory.CreateLogger<ContentFinderByUrlAlias>(), Mock.Of<IPublishedValueFallback>(), VariationContextAccessor, GetUmbracoContextAccessor(umbracoContext));
|
||||
var finder = new ContentFinderByUrlAlias(Mock.Of<ILogger<ContentFinderByUrlAlias>>(), Mock.Of<IPublishedValueFallback>(), VariationContextAccessor, umbracoContextAccessor);
|
||||
var result = finder.TryFindContent(request);
|
||||
|
||||
if (expectedNode > 0)
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class ContentFinderByIdTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
|
||||
string xml = PublishedContentXml.BaseWebTestXml(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
}
|
||||
|
||||
[TestCase("/1046", 1046)]
|
||||
public async Task Lookup_By_Id(string urlAsString, int nodeMatch)
|
||||
{
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(urlAsString);
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
var webRoutingSettings = new WebRoutingSettings();
|
||||
var lookup = new ContentFinderByIdPath(Options.Create(webRoutingSettings), Mock.Of<ILogger<ContentFinderByIdPath>>(), Mock.Of<IRequestAccessor>(), umbracoContextAccessor);
|
||||
|
||||
|
||||
var result = lookup.TryFindContent(frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(frequest.PublishedContent.Id, nodeMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class ContentFinderByPageIdQueryTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
|
||||
string xml = PublishedContentXml.BaseWebTestXml(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
}
|
||||
|
||||
[TestCase("/?umbPageId=1046", 1046)]
|
||||
[TestCase("/?UMBPAGEID=1046", 1046)]
|
||||
[TestCase("/default.aspx?umbPageId=1046", 1046)] // TODO: Should this match??
|
||||
[TestCase("/some/other/page?umbPageId=1046", 1046)] // TODO: Should this match??
|
||||
[TestCase("/some/other/page.aspx?umbPageId=1046", 1046)] // TODO: Should this match??
|
||||
public async Task Lookup_By_Page_Id(string urlAsString, int nodeMatch)
|
||||
{
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(urlAsString);
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
|
||||
var queryStrings = HttpUtility.ParseQueryString(umbracoContext.CleanedUmbracoUrl.Query);
|
||||
|
||||
var mockRequestAccessor = new Mock<IRequestAccessor>();
|
||||
mockRequestAccessor.Setup(x => x.GetRequestValue("umbPageID"))
|
||||
.Returns(queryStrings["umbPageID"]);
|
||||
|
||||
var lookup = new ContentFinderByPageIdQuery(mockRequestAccessor.Object, umbracoContextAccessor);
|
||||
|
||||
var result = lookup.TryFindContent(frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(frequest.PublishedContent.Id, nodeMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class ContentFinderByUrlAndTemplateTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
private IFileService _fileService;
|
||||
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
|
||||
string xml = PublishedContentXml.BaseWebTestXml(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
}
|
||||
|
||||
protected override ServiceContext CreateServiceContext(IContentType[] contentTypes, IMediaType[] mediaTypes, IDataType[] dataTypes)
|
||||
{
|
||||
var serviceContext = base.CreateServiceContext(contentTypes, mediaTypes, dataTypes);
|
||||
|
||||
var fileService = Mock.Get(serviceContext.FileService);
|
||||
fileService.Setup(x => x.GetTemplate(It.IsAny<string>()))
|
||||
.Returns((string alias) => new Template(ShortStringHelper, alias, alias));
|
||||
|
||||
_fileService = fileService.Object;
|
||||
|
||||
return serviceContext;
|
||||
}
|
||||
|
||||
[TestCase("/blah")]
|
||||
[TestCase("/home/Sub1/blah")]
|
||||
[TestCase("/Home/Sub1/Blah")] //different cases
|
||||
public async Task Match_Document_By_Url_With_Template(string urlAsString)
|
||||
{
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(urlAsString);
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
|
||||
var webRoutingSettings = new WebRoutingSettings();
|
||||
var lookup = new ContentFinderByUrlAndTemplate(
|
||||
Mock.Of<ILogger<ContentFinderByUrlAndTemplate>>(),
|
||||
_fileService,
|
||||
ContentTypeService,
|
||||
umbracoContextAccessor,
|
||||
Microsoft.Extensions.Options.Options.Create(webRoutingSettings));
|
||||
|
||||
var result = lookup.TryFindContent(frequest);
|
||||
|
||||
IPublishedRequest request = frequest.Build();
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.IsNotNull(frequest.PublishedContent);
|
||||
var templateAlias = request.GetTemplateAlias();
|
||||
Assert.IsNotNull(templateAlias);
|
||||
Assert.AreEqual("blah".ToUpperInvariant(), templateAlias.ToUpperInvariant());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class ContentFinderByUrlTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
private async Task<(ContentFinderByUrl finder, IPublishedRequestBuilder frequest)> GetContentFinder(string urlString)
|
||||
{
|
||||
string xml = PublishedContentXml.BaseWebTestXml(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(urlString);
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
var lookup = new ContentFinderByUrl(Mock.Of<ILogger<ContentFinderByUrl>>(), umbracoContextAccessor);
|
||||
return (lookup, frequest);
|
||||
}
|
||||
|
||||
[TestCase("/", 1046)]
|
||||
[TestCase("/Sub1", 1173)]
|
||||
[TestCase("/sub1", 1173)]
|
||||
[TestCase("/home/sub1", -1)] // should fail
|
||||
|
||||
// these two are special. getNiceUrl(1046) returns "/" but getNiceUrl(1172) cannot also return "/" so
|
||||
// we've made it return "/test-page" => we have to support that URL back in the lookup...
|
||||
[TestCase("/home", 1046)]
|
||||
[TestCase("/test-page", 1172)]
|
||||
public async Task Match_Document_By_Url_Hide_Top_Level(string urlString, int expectedId)
|
||||
{
|
||||
GlobalSettings.HideTopLevelNodeFromPath = true;
|
||||
|
||||
var lookup = await GetContentFinder(urlString);
|
||||
|
||||
Assert.IsTrue(GlobalSettings.HideTopLevelNodeFromPath);
|
||||
|
||||
// FIXME: debugging - going further down, the routes cache is NOT empty?!
|
||||
if (urlString == "/home/sub1")
|
||||
System.Diagnostics.Debugger.Break();
|
||||
|
||||
var result = lookup.finder.TryFindContent(lookup.frequest);
|
||||
|
||||
if (expectedId > 0)
|
||||
{
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(expectedId, lookup.frequest.PublishedContent.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("/", 1046)]
|
||||
[TestCase("/home", 1046)]
|
||||
[TestCase("/home/Sub1", 1173)]
|
||||
[TestCase("/Home/Sub1", 1173)] //different cases
|
||||
public async Task Match_Document_By_Url(string urlString, int expectedId)
|
||||
{
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var lookup = await GetContentFinder(urlString);
|
||||
|
||||
Assert.IsFalse(GlobalSettings.HideTopLevelNodeFromPath);
|
||||
|
||||
var result = lookup.finder.TryFindContent(lookup.frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(expectedId, lookup.frequest.PublishedContent.Id);
|
||||
}
|
||||
/// <summary>
|
||||
/// This test handles requests with special characters in the URL.
|
||||
/// </summary>
|
||||
/// <param name="urlString"></param>
|
||||
/// <param name="expectedId"></param>
|
||||
[TestCase("/", 1046)]
|
||||
[TestCase("/home/sub1/custom-sub-3-with-accént-character", 1179)]
|
||||
[TestCase("/home/sub1/custom-sub-4-with-æøå", 1180)]
|
||||
public async Task Match_Document_By_Url_With_Special_Characters(string urlString, int expectedId)
|
||||
{
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var lookup = await GetContentFinder(urlString);
|
||||
|
||||
var result = lookup.finder.TryFindContent(lookup.frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(expectedId, lookup.frequest.PublishedContent.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This test handles requests with a hostname associated.
|
||||
/// The logic for handling this goes through the DomainHelper and is a bit different
|
||||
/// from what happens in a normal request - so it has a separate test with a mocked
|
||||
/// hostname added.
|
||||
/// </summary>
|
||||
/// <param name="urlString"></param>
|
||||
/// <param name="expectedId"></param>
|
||||
[TestCase("/", 1046)]
|
||||
[TestCase("/home/sub1/custom-sub-3-with-accént-character", 1179)]
|
||||
[TestCase("/home/sub1/custom-sub-4-with-æøå", 1180)]
|
||||
public async Task Match_Document_By_Url_With_Special_Characters_Using_Hostname(string urlString, int expectedId)
|
||||
{
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var lookup = await GetContentFinder(urlString);
|
||||
|
||||
lookup.frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite", -1, "en-US", false), new Uri("http://mysite/")));
|
||||
|
||||
var result = lookup.finder.TryFindContent(lookup.frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(expectedId, lookup.frequest.PublishedContent.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This test handles requests with a hostname with special characters associated.
|
||||
/// The logic for handling this goes through the DomainHelper and is a bit different
|
||||
/// from what happens in a normal request - so it has a separate test with a mocked
|
||||
/// hostname added.
|
||||
/// </summary>
|
||||
/// <param name="urlString"></param>
|
||||
/// <param name="expectedId"></param>
|
||||
[TestCase("/æøå/", 1046)]
|
||||
[TestCase("/æøå/home/sub1", 1173)]
|
||||
[TestCase("/æøå/home/sub1/custom-sub-3-with-accént-character", 1179)]
|
||||
[TestCase("/æøå/home/sub1/custom-sub-4-with-æøå", 1180)]
|
||||
public async Task Match_Document_By_Url_With_Special_Characters_In_Hostname(string urlString, int expectedId)
|
||||
{
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var lookup = await GetContentFinder(urlString);
|
||||
|
||||
lookup.frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite/æøå", -1, "en-US", false), new Uri("http://mysite/æøå")));
|
||||
|
||||
var result = lookup.finder.TryFindContent(lookup.frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(expectedId, lookup.frequest.PublishedContent.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,47 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
|
||||
[TestFixture]
|
||||
public class ContentFinderByUrlWithDomainsTests : UrlRoutingTestBase
|
||||
{
|
||||
void SetDomains3()
|
||||
private void SetDomains3()
|
||||
{
|
||||
SetupDomainServiceMock(new[]
|
||||
{
|
||||
new UmbracoDomain("domain1.com/") {Id = 1, LanguageId = LangDeId, RootContentId = 1001, LanguageIsoCode = "de-DE"}
|
||||
});
|
||||
var domainService = Mock.Get(DomainService);
|
||||
|
||||
domainService.Setup(service => service.GetAll(It.IsAny<bool>()))
|
||||
.Returns((bool incWildcards) => new[]
|
||||
{
|
||||
new UmbracoDomain("domain1.com/") {Id = 1, LanguageId = LangDeId, RootContentId = 1001, LanguageIsoCode = "de-DE"}
|
||||
});
|
||||
}
|
||||
|
||||
void SetDomains4()
|
||||
private void SetDomains4()
|
||||
{
|
||||
SetupDomainServiceMock(new[]
|
||||
{
|
||||
new UmbracoDomain("domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("domain1.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("domain1.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"},
|
||||
new UmbracoDomain("http://domain3.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1003, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain3.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10031, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain3.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10032, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
var domainService = Mock.Get(DomainService);
|
||||
|
||||
domainService.Setup(service => service.GetAll(It.IsAny<bool>()))
|
||||
.Returns((bool incWildcards) => new[]
|
||||
{
|
||||
new UmbracoDomain("domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("domain1.com/en") {Id = 2, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("domain1.com/fr") {Id = 3, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"},
|
||||
new UmbracoDomain("http://domain3.com/") {Id = 4, LanguageId = LangEngId, RootContentId = 1003, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain3.com/en") {Id = 5, LanguageId = LangEngId, RootContentId = 10031, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain3.com/fr") {Id = 6, LanguageId = LangFrId, RootContentId = 10032, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
}
|
||||
|
||||
protected override string GetXmlContent(int templateId)
|
||||
{
|
||||
return @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
=> @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<!DOCTYPE root[
|
||||
<!ELEMENT Doc ANY>
|
||||
<!ATTLIST Doc id ID #REQUIRED>
|
||||
@@ -115,7 +120,6 @@ namespace Umbraco.Tests.Routing
|
||||
</Doc>
|
||||
</Doc>
|
||||
</root>";
|
||||
}
|
||||
|
||||
[TestCase("http://domain1.com/", 1001)]
|
||||
[TestCase("http://domain1.com/1001-1", 10011)]
|
||||
@@ -125,16 +129,17 @@ namespace Umbraco.Tests.Routing
|
||||
{
|
||||
SetDomains3();
|
||||
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = true };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = true;
|
||||
|
||||
var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettings);
|
||||
var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory);
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(url);
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
|
||||
// must lookup domain else lookup by URL fails
|
||||
publishedRouter.FindDomain(frequest);
|
||||
|
||||
var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger<ContentFinderByUrl>(), GetUmbracoContextAccessor(umbracoContext));
|
||||
var lookup = new ContentFinderByUrl(Mock.Of<ILogger<ContentFinderByUrl>>(), umbracoContextAccessor);
|
||||
var result = lookup.TryFindContent(frequest);
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(expectedId, frequest.PublishedContent.Id);
|
||||
@@ -164,19 +169,20 @@ namespace Umbraco.Tests.Routing
|
||||
SetDomains4();
|
||||
|
||||
// defaults depend on test environment
|
||||
expectedCulture = expectedCulture ?? System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
|
||||
expectedCulture ??= System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
|
||||
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = true };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = true;
|
||||
|
||||
var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettings);
|
||||
var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory);
|
||||
var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(url);
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
|
||||
// must lookup domain else lookup by URL fails
|
||||
publishedRouter.FindDomain(frequest);
|
||||
Assert.AreEqual(expectedCulture, frequest.Culture);
|
||||
|
||||
var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger<ContentFinderByUrl>(), GetUmbracoContextAccessor(umbracoContext));
|
||||
var lookup = new ContentFinderByUrl(Mock.Of<ILogger<ContentFinderByUrl>>(), umbracoContextAccessor);
|
||||
var result = lookup.TryFindContent(frequest);
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(expectedId, frequest.PublishedContent.Id);
|
||||
@@ -1,124 +1,151 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
internal class DomainsAndCulturesTests : UrlRoutingTestBase
|
||||
public class DomainsAndCulturesTests : UrlRoutingTestBase
|
||||
{
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
|
||||
Builder.Services.AddTransient<ISiteDomainMapper, SiteDomainMapper>();
|
||||
}
|
||||
|
||||
private void SetDomains1()
|
||||
{
|
||||
SetupDomainServiceMock(new[]
|
||||
{
|
||||
new UmbracoDomain("domain1.com/")
|
||||
var domainService = Mock.Get(DomainService);
|
||||
|
||||
domainService.Setup(service => service.GetAll(It.IsAny<bool>()))
|
||||
.Returns((bool incWildcards) => new[]
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangDeId,
|
||||
RootContentId = 1001,
|
||||
LanguageIsoCode = "de-DE"
|
||||
},
|
||||
new UmbracoDomain("domain1.com/en")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangEngId,
|
||||
RootContentId = 10011,
|
||||
LanguageIsoCode = "en-US"
|
||||
},
|
||||
new UmbracoDomain("domain1.com/fr")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangFrId,
|
||||
RootContentId = 10012,
|
||||
LanguageIsoCode = "fr-FR"
|
||||
}
|
||||
});
|
||||
new UmbracoDomain("domain1.com/")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangDeId,
|
||||
RootContentId = 1001,
|
||||
LanguageIsoCode = "de-DE"
|
||||
},
|
||||
new UmbracoDomain("domain1.com/en")
|
||||
{
|
||||
Id = 2,
|
||||
LanguageId = LangEngId,
|
||||
RootContentId = 10011,
|
||||
LanguageIsoCode = "en-US"
|
||||
},
|
||||
new UmbracoDomain("domain1.com/fr")
|
||||
{
|
||||
Id = 3,
|
||||
LanguageId = LangFrId,
|
||||
RootContentId = 10012,
|
||||
LanguageIsoCode = "fr-FR"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void SetDomains2()
|
||||
{
|
||||
SetupDomainServiceMock(new[]
|
||||
{
|
||||
new UmbracoDomain("domain1.com/")
|
||||
var domainService = Mock.Get(DomainService);
|
||||
|
||||
domainService.Setup(service => service.GetAll(It.IsAny<bool>()))
|
||||
.Returns((bool incWildcards) => new[]
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangDeId,
|
||||
RootContentId = 1001,
|
||||
LanguageIsoCode = "de-DE"
|
||||
},
|
||||
new UmbracoDomain("domain1.com/en")
|
||||
new UmbracoDomain("domain1.com/")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangDeId,
|
||||
RootContentId = 1001,
|
||||
LanguageIsoCode = "de-DE"
|
||||
},
|
||||
new UmbracoDomain("domain1.com/en")
|
||||
{
|
||||
Id = 2,
|
||||
LanguageId = LangEngId,
|
||||
RootContentId = 10011,
|
||||
LanguageIsoCode = "en-US"
|
||||
},
|
||||
new UmbracoDomain("domain1.com/fr")
|
||||
{
|
||||
Id = 3,
|
||||
LanguageId = LangFrId,
|
||||
RootContentId = 10012,
|
||||
LanguageIsoCode = "fr-FR"
|
||||
},
|
||||
new UmbracoDomain("*1001")
|
||||
{
|
||||
Id = 4,
|
||||
LanguageId = LangDeId,
|
||||
RootContentId = 1001,
|
||||
LanguageIsoCode = "de-DE"
|
||||
},
|
||||
new UmbracoDomain("*10011")
|
||||
{
|
||||
Id = 5,
|
||||
LanguageId = LangCzId,
|
||||
RootContentId = 10011,
|
||||
LanguageIsoCode = "cs-CZ"
|
||||
},
|
||||
new UmbracoDomain("*100112")
|
||||
{
|
||||
Id = 6,
|
||||
LanguageId = LangNlId,
|
||||
RootContentId = 100112,
|
||||
LanguageIsoCode = "nl-NL"
|
||||
},
|
||||
new UmbracoDomain("*1001122")
|
||||
{
|
||||
Id = 7,
|
||||
LanguageId = LangDkId,
|
||||
RootContentId = 1001122,
|
||||
LanguageIsoCode = "da-DK"
|
||||
},
|
||||
new UmbracoDomain("*10012")
|
||||
{
|
||||
Id = 8,
|
||||
LanguageId = LangNlId,
|
||||
RootContentId = 10012,
|
||||
LanguageIsoCode = "nl-NL"
|
||||
},
|
||||
new UmbracoDomain("*10031")
|
||||
{
|
||||
Id = 9,
|
||||
LanguageId = LangNlId,
|
||||
RootContentId =10031,
|
||||
LanguageIsoCode = "nl-NL"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// domains such as "/en" are natively supported, and when instanciating
|
||||
// DomainAndUri for them, the host will come from the current request
|
||||
//
|
||||
private void SetDomains3()
|
||||
{
|
||||
var domainService = Mock.Get(DomainService);
|
||||
|
||||
domainService.Setup(service => service.GetAll(It.IsAny<bool>()))
|
||||
.Returns((bool incWildcards) => new[]
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangEngId,
|
||||
RootContentId = 10011,
|
||||
LanguageIsoCode = "en-US"
|
||||
},
|
||||
new UmbracoDomain("domain1.com/fr")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangFrId,
|
||||
RootContentId = 10012,
|
||||
LanguageIsoCode = "fr-FR"
|
||||
},
|
||||
new UmbracoDomain("*1001")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangDeId,
|
||||
RootContentId = 1001,
|
||||
LanguageIsoCode = "de-DE"
|
||||
},
|
||||
new UmbracoDomain("*10011")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangCzId,
|
||||
RootContentId = 10011,
|
||||
LanguageIsoCode = "cs-CZ"
|
||||
},
|
||||
new UmbracoDomain("*100112")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangNlId,
|
||||
RootContentId = 100112,
|
||||
LanguageIsoCode = "nl-NL"
|
||||
},
|
||||
new UmbracoDomain("*1001122")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangDkId,
|
||||
RootContentId = 1001122,
|
||||
LanguageIsoCode = "da-DK"
|
||||
},
|
||||
new UmbracoDomain("*10012")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangNlId,
|
||||
RootContentId = 10012,
|
||||
LanguageIsoCode = "nl-NL"
|
||||
},
|
||||
new UmbracoDomain("*10031")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangNlId,
|
||||
RootContentId =10031,
|
||||
LanguageIsoCode = "nl-NL"
|
||||
}
|
||||
});
|
||||
new UmbracoDomain("/en")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangEngId,
|
||||
RootContentId = 10011,
|
||||
LanguageIsoCode = "en-US"
|
||||
},
|
||||
new UmbracoDomain("/fr")
|
||||
{
|
||||
Id = 2,
|
||||
LanguageId = LangFrId,
|
||||
RootContentId = 10012,
|
||||
LanguageIsoCode = "fr-FR"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override string GetXmlContent(int templateId)
|
||||
{
|
||||
return @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
=> @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<!DOCTYPE root[
|
||||
<!ELEMENT Doc ANY>
|
||||
<!ATTLIST Doc id ID #REQUIRED>
|
||||
@@ -250,7 +277,6 @@ namespace Umbraco.Tests.Routing
|
||||
</Doc>
|
||||
</Doc>
|
||||
</root>";
|
||||
}
|
||||
|
||||
#region Cases
|
||||
[TestCase("http://domain1.com/", "de-DE", 1001)]
|
||||
@@ -265,18 +291,19 @@ namespace Umbraco.Tests.Routing
|
||||
{
|
||||
SetDomains1();
|
||||
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings);
|
||||
var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory);
|
||||
var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(inputUrl);
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
|
||||
// lookup domain
|
||||
publishedRouter.FindDomain(frequest);
|
||||
|
||||
Assert.AreEqual(expectedCulture, frequest.Culture);
|
||||
|
||||
var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger<ContentFinderByUrl>(), GetUmbracoContextAccessor(umbracoContext));
|
||||
var finder = new ContentFinderByUrl(Mock.Of<ILogger<ContentFinderByUrl>>(), umbracoContextAccessor);
|
||||
var result = finder.TryFindContent(frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
@@ -311,19 +338,20 @@ namespace Umbraco.Tests.Routing
|
||||
SetDomains2();
|
||||
|
||||
// defaults depend on test environment
|
||||
expectedCulture = expectedCulture ?? System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
|
||||
expectedCulture ??= System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
|
||||
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings);
|
||||
var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory);
|
||||
var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(inputUrl);
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
|
||||
// lookup domain
|
||||
publishedRouter.FindDomain(frequest);
|
||||
|
||||
// find document
|
||||
var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger<ContentFinderByUrl>(), GetUmbracoContextAccessor(umbracoContext));
|
||||
var finder = new ContentFinderByUrl(Mock.Of<ILogger<ContentFinderByUrl>>(), umbracoContextAccessor);
|
||||
var result = finder.TryFindContent(frequest);
|
||||
|
||||
// apply wildcard domain
|
||||
@@ -333,29 +361,7 @@ namespace Umbraco.Tests.Routing
|
||||
Assert.AreEqual(expectedCulture, frequest.Culture);
|
||||
Assert.AreEqual(frequest.PublishedContent.Id, expectedNode);
|
||||
}
|
||||
// domains such as "/en" are natively supported, and when instanciating
|
||||
// DomainAndUri for them, the host will come from the current request
|
||||
//
|
||||
private void SetDomains3()
|
||||
{
|
||||
SetupDomainServiceMock(new[]
|
||||
{
|
||||
new UmbracoDomain("/en")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangEngId,
|
||||
RootContentId = 10011,
|
||||
LanguageIsoCode = "en-US"
|
||||
},
|
||||
new UmbracoDomain("/fr")
|
||||
{
|
||||
Id = 1,
|
||||
LanguageId = LangFrId,
|
||||
RootContentId = 10012,
|
||||
LanguageIsoCode = "fr-FR"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#region Cases
|
||||
[TestCase("http://domain1.com/en", "en-US", 10011)]
|
||||
@@ -367,10 +373,13 @@ namespace Umbraco.Tests.Routing
|
||||
{
|
||||
SetDomains3();
|
||||
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false };
|
||||
var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings);
|
||||
var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory);
|
||||
var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(inputUrl);
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
|
||||
// lookup domain
|
||||
publishedRouter.FindDomain(frequest);
|
||||
@@ -378,7 +387,7 @@ namespace Umbraco.Tests.Routing
|
||||
|
||||
Assert.AreEqual(expectedCulture, frequest.Culture);
|
||||
|
||||
var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger<ContentFinderByUrl>(), GetUmbracoContextAccessor(umbracoContext));
|
||||
var finder = new ContentFinderByUrl(Mock.Of<ILogger<ContentFinderByUrl>>(), umbracoContextAccessor);
|
||||
var result = finder.TryFindContent(frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
@@ -0,0 +1,216 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class GetContentUrlsTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
private WebRoutingSettings _webRoutingSettings;
|
||||
private RequestHandlerSettings _requestHandlerSettings;
|
||||
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
|
||||
_webRoutingSettings = new WebRoutingSettings();
|
||||
_requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
string xml = PublishedContentXml.BaseWebTestXml(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
}
|
||||
|
||||
private ILocalizedTextService GetTextService()
|
||||
{
|
||||
var textService = new Mock<ILocalizedTextService>();
|
||||
textService.Setup(x => x.Localize(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<CultureInfo>(),
|
||||
It.IsAny<IDictionary<string, string>>()
|
||||
))
|
||||
.Returns((string key, string alias, CultureInfo culture, IDictionary<string, string> args)
|
||||
=> $"{key}/{alias}");
|
||||
|
||||
return textService.Object;
|
||||
}
|
||||
|
||||
private ILocalizationService GetLangService(params string[] isoCodes)
|
||||
{
|
||||
var allLangs = isoCodes
|
||||
.Select(CultureInfo.GetCultureInfo)
|
||||
.Select(culture => new Language(GlobalSettings, culture.Name)
|
||||
{
|
||||
CultureName = culture.DisplayName,
|
||||
IsDefault = true,
|
||||
IsMandatory = true
|
||||
}).ToArray();
|
||||
|
||||
|
||||
var langServiceMock = new Mock<ILocalizationService>();
|
||||
langServiceMock.Setup(x => x.GetAllLanguages()).Returns(allLangs);
|
||||
langServiceMock.Setup(x => x.GetDefaultLanguageIsoCode()).Returns(allLangs.First(x=>x.IsDefault).IsoCode);
|
||||
|
||||
return langServiceMock.Object;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Content_Not_Published()
|
||||
{
|
||||
var contentType = ContentTypeBuilder.CreateBasicContentType();
|
||||
var content = ContentBuilder.CreateBasicContent(contentType);
|
||||
content.Id = 1046; // FIXME: we are using this ID only because it's built into the test XML published cache
|
||||
content.Path = "-1,1046";
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("http://localhost:8000");
|
||||
var publishedRouter = CreatePublishedRouter(
|
||||
umbracoContextAccessor,
|
||||
new[] { new ContentFinderByUrl(Mock.Of<ILogger<ContentFinderByUrl>>(), umbracoContextAccessor) });
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, _requestHandlerSettings, _webRoutingSettings, out UriUtility uriUtility);
|
||||
|
||||
var urls = (await content.GetContentUrlsAsync(
|
||||
publishedRouter,
|
||||
umbracoContext,
|
||||
GetLangService("en-US", "fr-FR"),
|
||||
GetTextService(),
|
||||
Mock.Of<IContentService>(),
|
||||
VariationContextAccessor,
|
||||
Mock.Of<ILogger<IContent>>(),
|
||||
uriUtility,
|
||||
urlProvider)).ToList();
|
||||
|
||||
Assert.AreEqual(1, urls.Count);
|
||||
Assert.AreEqual("content/itemNotPublished", urls[0].Text);
|
||||
Assert.IsFalse(urls[0].IsUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Invariant_Root_Content_Published_No_Domains()
|
||||
{
|
||||
var contentType = ContentTypeBuilder.CreateBasicContentType();
|
||||
var content = ContentBuilder.CreateBasicContent(contentType);
|
||||
content.Id = 1046; // FIXME: we are using this ID only because it's built into the test XML published cache
|
||||
content.Path = "-1,1046";
|
||||
content.Published = true;
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("http://localhost:8000");
|
||||
var publishedRouter = CreatePublishedRouter(
|
||||
umbracoContextAccessor,
|
||||
new[] { new ContentFinderByUrl(Mock.Of<ILogger<ContentFinderByUrl>>(), umbracoContextAccessor) });
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, _requestHandlerSettings, _webRoutingSettings, out UriUtility uriUtility);
|
||||
|
||||
var urls = (await content.GetContentUrlsAsync(
|
||||
publishedRouter,
|
||||
umbracoContext,
|
||||
GetLangService("en-US", "fr-FR"),
|
||||
GetTextService(),
|
||||
Mock.Of<IContentService>(),
|
||||
VariationContextAccessor,
|
||||
Mock.Of<ILogger<IContent>>(),
|
||||
uriUtility,
|
||||
urlProvider)).ToList();
|
||||
|
||||
|
||||
Assert.AreEqual(2, urls.Count);
|
||||
|
||||
var enUrl = urls.First(x => x.Culture == "en-US");
|
||||
|
||||
Assert.AreEqual("/home/", enUrl.Text);
|
||||
Assert.AreEqual("en-US", enUrl.Culture);
|
||||
Assert.IsTrue(enUrl.IsUrl);
|
||||
|
||||
var frUrl = urls.First(x => x.Culture == "fr-FR");
|
||||
|
||||
Assert.IsFalse(frUrl.IsUrl);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Invariant_Child_Content_Published_No_Domains()
|
||||
{
|
||||
var contentType = ContentTypeBuilder.CreateBasicContentType();
|
||||
var parent = ContentBuilder.CreateBasicContent(contentType);
|
||||
parent.Id = 1046; // FIXME: we are using this ID only because it's built into the test XML published cache
|
||||
parent.Name = "home";
|
||||
parent.Path = "-1,1046";
|
||||
parent.Published = true;
|
||||
var child = ContentBuilder.CreateBasicContent(contentType);
|
||||
child.Name = "sub1";
|
||||
child.Id = 1173; // FIXME: we are using this ID only because it's built into the test XML published cache
|
||||
child.Path = "-1,1046,1173";
|
||||
child.Published = true;
|
||||
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("http://localhost:8000");
|
||||
var publishedRouter = CreatePublishedRouter(
|
||||
umbracoContextAccessor,
|
||||
new[] { new ContentFinderByUrl(Mock.Of<ILogger<ContentFinderByUrl>>(), umbracoContextAccessor) });
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
|
||||
|
||||
var localizationService = GetLangService("en-US", "fr-FR");
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, _requestHandlerSettings, _webRoutingSettings, out UriUtility uriUtility);
|
||||
|
||||
var urls = (await child.GetContentUrlsAsync(
|
||||
publishedRouter,
|
||||
umbracoContext,
|
||||
localizationService,
|
||||
GetTextService(),
|
||||
Mock.Of<IContentService>(),
|
||||
VariationContextAccessor,
|
||||
Mock.Of<ILogger<IContent>>(),
|
||||
uriUtility,
|
||||
urlProvider)).ToList();
|
||||
|
||||
Assert.AreEqual(2, urls.Count);
|
||||
|
||||
var enUrl = urls.First(x => x.Culture == "en-US");
|
||||
|
||||
Assert.AreEqual("/home/sub1/", enUrl.Text);
|
||||
Assert.AreEqual("en-US", enUrl.Culture);
|
||||
Assert.IsTrue(enUrl.IsUrl);
|
||||
|
||||
var frUrl = urls.First(x => x.Culture == "fr-FR");
|
||||
|
||||
Assert.IsFalse(frUrl.IsUrl);
|
||||
}
|
||||
|
||||
// TODO: We need a lot of tests here, the above was just to get started with being able to unit test this method
|
||||
// * variant URLs without domains assigned, what happens?
|
||||
// * variant URLs with domains assigned, but also having more languages installed than there are domains/cultures assigned
|
||||
// * variant URLs with an ancestor culture unpublished
|
||||
// * invariant URLs with ancestors as variants
|
||||
// * ... probably a lot more
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class PublishedRouterTests
|
||||
{
|
||||
private PublishedRouter CreatePublishedRouter(IUmbracoContextAccessor umbracoContextAccessor)
|
||||
=> new PublishedRouter(
|
||||
Microsoft.Extensions.Options.Options.Create(new WebRoutingSettings()),
|
||||
new ContentFinderCollection(() => Enumerable.Empty<IContentFinder>()),
|
||||
new TestLastChanceFinder(),
|
||||
new TestVariationContextAccessor(),
|
||||
Mock.Of<IProfilingLogger>(),
|
||||
Mock.Of<ILogger<PublishedRouter>>(),
|
||||
Mock.Of<IPublishedUrlProvider>(),
|
||||
Mock.Of<IRequestAccessor>(),
|
||||
Mock.Of<IPublishedValueFallback>(),
|
||||
Mock.Of<IFileService>(),
|
||||
Mock.Of<IContentTypeService>(),
|
||||
umbracoContextAccessor,
|
||||
Mock.Of<IEventAggregator>());
|
||||
|
||||
private IUmbracoContextAccessor GetUmbracoContextAccessor()
|
||||
{
|
||||
var uri = new Uri("http://example.com");
|
||||
var umbracoContext = Mock.Of<IUmbracoContext>(x => x.CleanedUmbracoUrl == uri);
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
|
||||
return umbracoContextAccessor;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ConfigureRequest_Returns_False_Without_HasPublishedContent()
|
||||
{
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor();
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var request = await publishedRouter.CreateRequestAsync(umbracoContextAccessor.GetRequiredUmbracoContext().CleanedUmbracoUrl);
|
||||
var result = publishedRouter.BuildRequest(request);
|
||||
|
||||
Assert.IsFalse(result.Success());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ConfigureRequest_Returns_False_When_IsRedirect()
|
||||
{
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor();
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var request = await publishedRouter.CreateRequestAsync(umbracoContextAccessor.GetRequiredUmbracoContext().CleanedUmbracoUrl);
|
||||
var content = GetPublishedContentMock();
|
||||
request.SetPublishedContent(content.Object);
|
||||
request.SetCulture("en-AU");
|
||||
request.SetRedirect("/hello");
|
||||
var result = publishedRouter.BuildRequest(request);
|
||||
|
||||
Assert.IsFalse(result.Success());
|
||||
}
|
||||
|
||||
private Mock<IPublishedContent> GetPublishedContentMock()
|
||||
{
|
||||
var pc = new Mock<IPublishedContent>();
|
||||
pc.Setup(content => content.Id).Returns(1);
|
||||
pc.Setup(content => content.Name).Returns("test");
|
||||
pc.Setup(content => content.CreateDate).Returns(DateTime.Now);
|
||||
pc.Setup(content => content.UpdateDate).Returns(DateTime.Now);
|
||||
pc.Setup(content => content.Path).Returns("-1,1");
|
||||
pc.Setup(content => content.Parent).Returns(() => null);
|
||||
pc.Setup(content => content.Properties).Returns(new Collection<IPublishedProperty>());
|
||||
pc.Setup(content => content.ContentType).Returns(new PublishedContentType(Guid.NewGuid(), 22, "anything", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing));
|
||||
return pc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class UrlProviderWithHideTopLevelNodeFromPathTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
|
||||
string xml = PublishedContentXml.BaseWebTestXml(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
GlobalSettings.HideTopLevelNodeFromPath = true;
|
||||
}
|
||||
|
||||
[TestCase(1046, "/")]
|
||||
[TestCase(1173, "/sub1/")]
|
||||
[TestCase(1174, "/sub1/sub2/")]
|
||||
[TestCase(1176, "/sub1/sub-3/")]
|
||||
[TestCase(1177, "/sub1/custom-sub-1/")]
|
||||
[TestCase(1178, "/sub1/custom-sub-2/")]
|
||||
[TestCase(1175, "/sub-2/")]
|
||||
[TestCase(1172, "/test-page/")] // not hidden because not first root
|
||||
public void Get_Url_Hiding_Top_Level(int nodeId, string niceUrlMatch)
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("/test");
|
||||
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
var result = urlProvider.GetUrl(nodeId);
|
||||
Assert.AreEqual(niceUrlMatch, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class UrlProviderWithoutHideTopLevelNodeFromPathTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
private const string CacheKeyPrefix = "NuCache.ContentCache.RouteByContent";
|
||||
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
|
||||
|
||||
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
}
|
||||
|
||||
private void PopulateCache(string culture = "fr-FR")
|
||||
{
|
||||
var dataTypes = GetDefaultDataTypes();
|
||||
var propertyDataTypes = new Dictionary<string, IDataType>
|
||||
{
|
||||
// we only have one data type for this test which will be resolved with string empty.
|
||||
[string.Empty] = dataTypes[0]
|
||||
};
|
||||
var contentType1 = new ContentType(ShortStringHelper, -1);
|
||||
|
||||
ContentData rootData = new ContentDataBuilder()
|
||||
.WithName("Page" + Guid.NewGuid())
|
||||
.WithCultureInfos(new Dictionary<string, CultureVariation>
|
||||
{
|
||||
[culture] = new CultureVariation
|
||||
{
|
||||
Name = "root",
|
||||
IsDraft = true,
|
||||
Date = DateTime.Now,
|
||||
UrlSegment = "root"
|
||||
},
|
||||
})
|
||||
.Build(ShortStringHelper, propertyDataTypes, contentType1, "alias");
|
||||
|
||||
ContentNodeKit root = ContentNodeKitBuilder.CreateWithContent(
|
||||
contentType1.Id,
|
||||
9876, $"-1,9876",
|
||||
draftData: rootData,
|
||||
publishedData: rootData);
|
||||
|
||||
ContentData parentData = new ContentDataBuilder()
|
||||
.WithName("Page" + Guid.NewGuid())
|
||||
.WithCultureInfos(new Dictionary<string, CultureVariation>
|
||||
{
|
||||
[culture] = new CultureVariation
|
||||
{
|
||||
Name = "home",
|
||||
IsDraft = true,
|
||||
Date = DateTime.Now,
|
||||
UrlSegment = "home"
|
||||
},
|
||||
})
|
||||
.Build();
|
||||
|
||||
ContentNodeKit parent = ContentNodeKitBuilder.CreateWithContent(
|
||||
contentType1.Id,
|
||||
5432, $"-1,9876,5432",
|
||||
parentContentId: 9876,
|
||||
draftData: parentData,
|
||||
publishedData: parentData);
|
||||
|
||||
ContentData contentData = new ContentDataBuilder()
|
||||
.WithName("Page" + Guid.NewGuid())
|
||||
.WithCultureInfos(new Dictionary<string, CultureVariation>
|
||||
{
|
||||
[culture] = new CultureVariation
|
||||
{
|
||||
Name = "name-fr2",
|
||||
IsDraft = true,
|
||||
Date = DateTime.Now,
|
||||
UrlSegment = "test-fr"
|
||||
},
|
||||
})
|
||||
.Build();
|
||||
|
||||
ContentNodeKit content = ContentNodeKitBuilder.CreateWithContent(
|
||||
contentType1.Id,
|
||||
1234, $"-1,9876,5432,1234",
|
||||
parentContentId: 5432,
|
||||
draftData: contentData,
|
||||
publishedData: contentData);
|
||||
|
||||
InitializedCache(new[] { root, parent, content }, new[] { contentType1 }, dataTypes: dataTypes);
|
||||
}
|
||||
|
||||
private void SetDomains1()
|
||||
{
|
||||
var domainService = Mock.Get(DomainService);
|
||||
|
||||
domainService.Setup(service => service.GetAll(It.IsAny<bool>()))
|
||||
.Returns((bool incWildcards) => new[]
|
||||
{
|
||||
new UmbracoDomain("http://example.us/") {Id = 1, RootContentId = 9876, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://example.fr/") {Id = 2, RootContentId = 9876, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This checks that when we retrieve a NiceUrl for multiple items that there are no issues with cache overlap
|
||||
/// and that they are all cached correctly.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Ensure_Cache_Is_Correct()
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = false };
|
||||
|
||||
string xml = PublishedContentXml.BaseWebTestXml(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("/test");
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
var samples = new Dictionary<int, string> {
|
||||
{ 1046, "/home" },
|
||||
{ 1173, "/home/sub1" },
|
||||
{ 1174, "/home/sub1/sub2" },
|
||||
{ 1176, "/home/sub1/sub-3" },
|
||||
{ 1177, "/home/sub1/custom-sub-1" },
|
||||
{ 1178, "/home/sub1/custom-sub-2" },
|
||||
{ 1175, "/home/sub-2" },
|
||||
{ 1172, "/test-page" }
|
||||
};
|
||||
|
||||
foreach (var sample in samples)
|
||||
{
|
||||
var result = urlProvider.GetUrl(sample.Key);
|
||||
Assert.AreEqual(sample.Value, result);
|
||||
}
|
||||
|
||||
var randomSample = new KeyValuePair<int, string>(1177, "/home/sub1/custom-sub-1");
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var result = urlProvider.GetUrl(randomSample.Key);
|
||||
Assert.AreEqual(randomSample.Value, result);
|
||||
}
|
||||
|
||||
|
||||
var cache = (FastDictionaryAppCache)umbracoContext.PublishedSnapshot.ElementsCache;
|
||||
var cachedRoutes = cache.Keys.Where(x => x.StartsWith(CacheKeyPrefix)).ToList();
|
||||
Assert.AreEqual(8, cachedRoutes.Count);
|
||||
|
||||
foreach (var sample in samples)
|
||||
{
|
||||
var cacheKey = $"{CacheKeyPrefix}[P:{sample.Key}]";
|
||||
var found = (string)cache.Get(cacheKey);
|
||||
Assert.IsNotNull(found);
|
||||
Assert.AreEqual(sample.Value, found);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(1046, "/home/")]
|
||||
[TestCase(1173, "/home/sub1/")]
|
||||
[TestCase(1174, "/home/sub1/sub2/")]
|
||||
[TestCase(1176, "/home/sub1/sub-3/")]
|
||||
[TestCase(1177, "/home/sub1/custom-sub-1/")]
|
||||
[TestCase(1178, "/home/sub1/custom-sub-2/")]
|
||||
[TestCase(1175, "/home/sub-2/")]
|
||||
[TestCase(1172, "/test-page/")]
|
||||
public void Get_Url_Not_Hiding_Top_Level(int nodeId, string niceUrlMatch)
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
|
||||
string xml = PublishedContentXml.BaseWebTestXml(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("/test");
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
var result = urlProvider.GetUrl(nodeId);
|
||||
Assert.AreEqual(niceUrlMatch, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("fr-FR", ExpectedResult = "#")] // Non default cultures cannot return urls
|
||||
[TestCase("en-US", ExpectedResult = "/root/home/test-fr/")] // Default culture can return urls
|
||||
public string Get_Url_For_Culture_Variant_Without_Domains_Non_Current_Url(string culture)
|
||||
{
|
||||
const string currentUri = "http://example.us/test";
|
||||
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
|
||||
PopulateCache(culture);
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(currentUri);
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
|
||||
//even though we are asking for a specific culture URL, there are no domains assigned so all that can be returned is a normal relative URL.
|
||||
var url = urlProvider.GetUrl(1234, culture: culture);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This tests DefaultUrlProvider.GetUrl with a specific culture when the current URL is the culture specific domain
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Get_Url_For_Culture_Variant_With_Current_Url()
|
||||
{
|
||||
const string currentUri = "http://example.fr/test";
|
||||
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
|
||||
PopulateCache();
|
||||
|
||||
SetDomains1();
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(currentUri);
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
var url = urlProvider.GetUrl(1234, culture: "fr-FR");
|
||||
|
||||
Assert.AreEqual("/home/test-fr/", url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This tests DefaultUrlProvider.GetUrl with a specific culture when the current URL is not the culture specific domain
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Get_Url_For_Culture_Variant_Non_Current_Url()
|
||||
{
|
||||
const string currentUri = "http://example.us/test";
|
||||
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
|
||||
PopulateCache();
|
||||
|
||||
SetDomains1();
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(currentUri);
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
var url = urlProvider.GetUrl(1234, culture: "fr-FR");
|
||||
|
||||
//the current uri is not the culture specific domain we want, so the result is an absolute path to the culture specific domain
|
||||
Assert.AreEqual("http://example.fr/home/test-fr/", url);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Url_Relative_Or_Absolute()
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
|
||||
string xml = PublishedContentXml.BaseWebTestXml(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("http://example.com/test");
|
||||
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
Assert.AreEqual("/home/sub1/custom-sub-1/", urlProvider.GetUrl(1177));
|
||||
|
||||
urlProvider.Mode = UrlMode.Absolute;
|
||||
Assert.AreEqual("http://example.com/home/sub1/custom-sub-1/", urlProvider.GetUrl(1177));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Url_Unpublished()
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings();
|
||||
|
||||
string xml = PublishedContentXml.BaseWebTestXml(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("http://example.com/test");
|
||||
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
//mock the Umbraco settings that we need
|
||||
|
||||
Assert.AreEqual("#", urlProvider.GetUrl(999999));
|
||||
|
||||
urlProvider.Mode = UrlMode.Absolute;
|
||||
|
||||
Assert.AreEqual("#", urlProvider.GetUrl(999999));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
// TODO: We should be able to decouple this from the base db tests since we're just mocking the services now
|
||||
|
||||
[TestFixture]
|
||||
public class ContentFinderByAliasTests : UrlRoutingTestBase
|
||||
public abstract class UrlRoutingTestBase : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
private PublishedContentType _publishedContentType;
|
||||
|
||||
protected override void Initialize()
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Initialize();
|
||||
base.Setup();
|
||||
|
||||
string xml = GetXmlContent(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
var properties = new[]
|
||||
{
|
||||
new PublishedPropertyType("umbracoUrlAlias", Constants.DataTypes.Textbox, false, ContentVariation.Nothing,
|
||||
new PropertyValueConverterCollection(Enumerable.Empty<IPropertyValueConverter>()),
|
||||
Mock.Of<IPublishedModelFactory>(),
|
||||
Mock.Of<IPublishedContentTypeFactory>()),
|
||||
};
|
||||
_publishedContentType = new PublishedContentType(Guid.NewGuid(), 0, "Doc", PublishedItemType.Content, Enumerable.Empty<string>(), properties, ContentVariation.Nothing);
|
||||
}
|
||||
|
||||
protected override PublishedContentType GetPublishedContentTypeByAlias(string alias)
|
||||
// Sets up the mock domain service
|
||||
protected override ServiceContext CreateServiceContext(IContentType[] contentTypes, IMediaType[] mediaTypes, IDataType[] dataTypes)
|
||||
{
|
||||
if (alias == "Doc") return _publishedContentType;
|
||||
return null;
|
||||
var serviceContext = base.CreateServiceContext(contentTypes, mediaTypes, dataTypes);
|
||||
|
||||
//setup mock domain service
|
||||
var domainService = Mock.Get(serviceContext.DomainService);
|
||||
domainService.Setup(service => service.GetAll(It.IsAny<bool>()))
|
||||
.Returns((bool incWildcards) => new[]
|
||||
{
|
||||
new UmbracoDomain("domain1.com/"){Id = 1, LanguageId = LangDeId, RootContentId = 1001, LanguageIsoCode = "de-DE"},
|
||||
new UmbracoDomain("domain1.com/en"){Id = 2, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("domain1.com/fr"){Id = 3, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
|
||||
return serviceContext;
|
||||
}
|
||||
|
||||
[TestCase("/this/is/my/alias", 1001)]
|
||||
[TestCase("/anotheralias", 1001)]
|
||||
[TestCase("/page2/alias", 10011)]
|
||||
[TestCase("/2ndpagealias", 10011)]
|
||||
[TestCase("/only/one/alias", 100111)]
|
||||
[TestCase("/ONLY/one/Alias", 100111)]
|
||||
[TestCase("/alias43", 100121)]
|
||||
public async Task Lookup_By_Url_Alias(string urlAsString, int nodeMatch)
|
||||
{
|
||||
var umbracoContext = GetUmbracoContext(urlAsString);
|
||||
var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext));
|
||||
var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
var lookup =
|
||||
new ContentFinderByUrlAlias(LoggerFactory.CreateLogger<ContentFinderByUrlAlias>(), Mock.Of<IPublishedValueFallback>(), VariationContextAccessor, GetUmbracoContextAccessor(umbracoContext));
|
||||
|
||||
var result = lookup.TryFindContent(frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(frequest.PublishedContent.Id, nodeMatch);
|
||||
}
|
||||
|
||||
protected override string GetXmlContent(int templateId)
|
||||
{
|
||||
return @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
protected virtual string GetXmlContent(int templateId)
|
||||
=> @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<!DOCTYPE root[
|
||||
<!ELEMENT Doc ANY>
|
||||
<!ATTLIST Doc id ID #REQUIRED>
|
||||
@@ -147,7 +134,12 @@ namespace Umbraco.Tests.Routing
|
||||
</Doc>
|
||||
</Doc>
|
||||
</root>";
|
||||
}
|
||||
|
||||
public const int LangDeId = 333;
|
||||
public const int LangEngId = 334;
|
||||
public const int LangFrId = 335;
|
||||
public const int LangCzId = 336;
|
||||
public const int LangNlId = 337;
|
||||
public const int LangDkId = 338;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
@@ -13,77 +14,83 @@ using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Tests.LegacyXmlPublishedCache;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class UrlsProviderWithDomainsTests : UrlRoutingTestBase
|
||||
{
|
||||
private IUmbracoContextAccessor UmbracoContextAccessor { get; } = new TestUmbracoContextAccessor();
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
private const string CacheKeyPrefix = "NuCache.ContentCache.RouteByContent";
|
||||
|
||||
Builder.Services.AddUnique(Mock.Of<IDomainService>());
|
||||
Builder.Services.AddTransient<ISiteDomainMapper, SiteDomainMapper>();
|
||||
private void SetDomains1()
|
||||
{
|
||||
var domainService = Mock.Get(DomainService);
|
||||
|
||||
domainService.Setup(service => service.GetAll(It.IsAny<bool>()))
|
||||
.Returns((bool incWildcards) => new[]
|
||||
{
|
||||
new UmbracoDomain("domain1.com") {Id = 1, LanguageId = LangFrId, RootContentId = 1001, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
}
|
||||
|
||||
void SetDomains1()
|
||||
private void SetDomains2()
|
||||
{
|
||||
SetupDomainServiceMock(new[]
|
||||
{
|
||||
new UmbracoDomain("domain1.com") {Id = 1, LanguageId = LangFrId, RootContentId = 1001, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
var domainService = Mock.Get(DomainService);
|
||||
|
||||
domainService.Setup(service => service.GetAll(It.IsAny<bool>()))
|
||||
.Returns((bool incWildcards) => new[]
|
||||
{
|
||||
new UmbracoDomain("http://domain1.com/foo") {Id = 1, LanguageId = LangFrId, RootContentId = 1001, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
}
|
||||
|
||||
void SetDomains2()
|
||||
private void SetDomains3()
|
||||
{
|
||||
SetupDomainServiceMock(new[]
|
||||
{
|
||||
new UmbracoDomain("http://domain1.com/foo") {Id = 1, LanguageId = LangFrId, RootContentId = 1001, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
var domainService = Mock.Get(DomainService);
|
||||
|
||||
domainService.Setup(service => service.GetAll(It.IsAny<bool>()))
|
||||
.Returns((bool incWildcards) => new[]
|
||||
{
|
||||
new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangFrId, RootContentId = 10011, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
}
|
||||
|
||||
void SetDomains3()
|
||||
private void SetDomains4()
|
||||
{
|
||||
SetupDomainServiceMock(new[]
|
||||
{
|
||||
new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangFrId, RootContentId = 10011, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
var domainService = Mock.Get(DomainService);
|
||||
|
||||
domainService.Setup(service => service.GetAll(It.IsAny<bool>()))
|
||||
.Returns((bool incWildcards) => new[]
|
||||
{
|
||||
new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain1.com/en") {Id = 2, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain1.com/fr") {Id = 3, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"},
|
||||
new UmbracoDomain("http://domain3.com/") {Id = 4, LanguageId = LangEngId, RootContentId = 1003, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain3.com/en") {Id = 5, LanguageId = LangEngId, RootContentId = 10031, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain3.com/fr") {Id = 6, LanguageId = LangFrId, RootContentId = 10032, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
}
|
||||
|
||||
void SetDomains4()
|
||||
private void SetDomains5()
|
||||
{
|
||||
SetupDomainServiceMock(new[]
|
||||
{
|
||||
new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain1.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain1.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"},
|
||||
new UmbracoDomain("http://domain3.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1003, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain3.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10031, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain3.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10032, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
}
|
||||
var domainService = Mock.Get(DomainService);
|
||||
|
||||
void SetDomains5()
|
||||
{
|
||||
SetupDomainServiceMock(new[]
|
||||
{
|
||||
new UmbracoDomain("http://domain1.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain1a.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain1b.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain1.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"},
|
||||
new UmbracoDomain("http://domain1a.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"},
|
||||
new UmbracoDomain("http://domain1b.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"},
|
||||
new UmbracoDomain("http://domain3.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10031, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain3.com/fr") {Id = 1, LanguageId = LangFrId, RootContentId = 10032, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
domainService.Setup(service => service.GetAll(It.IsAny<bool>()))
|
||||
.Returns((bool incWildcards) => new[]
|
||||
{
|
||||
new UmbracoDomain("http://domain1.com/en") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain1a.com/en") {Id = 2, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain1b.com/en") {Id = 3, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain1.com/fr") {Id = 4, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"},
|
||||
new UmbracoDomain("http://domain1a.com/fr") {Id = 5, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"},
|
||||
new UmbracoDomain("http://domain1b.com/fr") {Id = 6, LanguageId = LangFrId, RootContentId = 10012, LanguageIsoCode = "fr-FR"},
|
||||
new UmbracoDomain("http://domain3.com/en") {Id = 7, LanguageId = LangEngId, RootContentId = 10031, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain3.com/fr") {Id = 8, LanguageId = LangFrId, RootContentId = 10032, LanguageIsoCode = "fr-FR"}
|
||||
});
|
||||
}
|
||||
|
||||
protected override string GetXmlContent(int templateId)
|
||||
{
|
||||
return @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
=> @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<!DOCTYPE root[
|
||||
<!ELEMENT Doc ANY>
|
||||
<!ATTLIST Doc id ID #REQUIRED>
|
||||
@@ -162,7 +169,6 @@ namespace Umbraco.Tests.Routing
|
||||
</Doc>
|
||||
</Doc>
|
||||
</root>";
|
||||
}
|
||||
|
||||
// with one simple domain "domain1.com"
|
||||
// basic tests
|
||||
@@ -179,21 +185,18 @@ namespace Umbraco.Tests.Routing
|
||||
[TestCase(10011, "https://domain1.com", false, "/1001-1/")]
|
||||
public void Get_Url_SimpleDomain(int nodeId, string currentUrl, bool absolute, string expected)
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false };
|
||||
|
||||
var umbracoContext = GetUmbracoContext("/test", 1111, globalSettings: globalSettings);
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
|
||||
var urlProvider = new DefaultUrlProvider(Microsoft.Extensions.Options.Options.Create(requestHandlerSettings),
|
||||
LoggerFactory.CreateLogger<DefaultUrlProvider>(),
|
||||
new SiteDomainMapper(), umbracoContextAccessor, UriUtility);
|
||||
var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider);
|
||||
|
||||
SetDomains1();
|
||||
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("/test");
|
||||
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
var currentUri = new Uri(currentUrl);
|
||||
var mode = absolute ? UrlMode.Absolute : UrlMode.Auto;
|
||||
var result = publishedUrlProvider.GetUrl(nodeId, mode, current: currentUri);
|
||||
var result = urlProvider.GetUrl(nodeId, mode, current: currentUri);
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
@@ -212,21 +215,18 @@ namespace Umbraco.Tests.Routing
|
||||
[TestCase(10011, "https://domain1.com", false, "http://domain1.com/foo/1001-1/")]
|
||||
public void Get_Url_SimpleWithSchemeAndPath(int nodeId, string currentUrl, bool absolute, string expected)
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false };
|
||||
|
||||
var umbracoContext = GetUmbracoContext("/test", 1111, globalSettings: globalSettings);
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
|
||||
var urlProvider = new DefaultUrlProvider(Microsoft.Extensions.Options.Options.Create(requestHandlerSettings),
|
||||
LoggerFactory.CreateLogger<DefaultUrlProvider>(),
|
||||
new SiteDomainMapper(), umbracoContextAccessor, UriUtility);
|
||||
var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider);
|
||||
|
||||
SetDomains2();
|
||||
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("/test");
|
||||
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
var currentUri = new Uri(currentUrl);
|
||||
var mode = absolute ? UrlMode.Absolute : UrlMode.Auto;
|
||||
var result = publishedUrlProvider.GetUrl(nodeId, mode, current : currentUri);
|
||||
var result = urlProvider.GetUrl(nodeId, mode, current: currentUri);
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
@@ -237,21 +237,18 @@ namespace Umbraco.Tests.Routing
|
||||
[TestCase(1002, "http://domain1.com", false, "/1002/")]
|
||||
public void Get_Url_DeepDomain(int nodeId, string currentUrl, bool absolute, string expected)
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false };
|
||||
|
||||
var umbracoContext = GetUmbracoContext("/test", 1111, globalSettings: globalSettings);
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
|
||||
var urlProvider = new DefaultUrlProvider(Microsoft.Extensions.Options.Options.Create(requestHandlerSettings),
|
||||
LoggerFactory.CreateLogger<DefaultUrlProvider>(),
|
||||
new SiteDomainMapper(), umbracoContextAccessor, UriUtility);
|
||||
var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider);
|
||||
|
||||
SetDomains3();
|
||||
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("/test");
|
||||
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
var currentUri = new Uri(currentUrl);
|
||||
var mode = absolute ? UrlMode.Absolute : UrlMode.Auto;
|
||||
var result = publishedUrlProvider.GetUrl(nodeId, mode, current : currentUri);
|
||||
var result = urlProvider.GetUrl(nodeId, mode, current: currentUri);
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
@@ -268,152 +265,130 @@ namespace Umbraco.Tests.Routing
|
||||
[TestCase(100321, "http://domain3.com", false, "/fr/1003-2-1/")]
|
||||
public void Get_Url_NestedDomains(int nodeId, string currentUrl, bool absolute, string expected)
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false };
|
||||
|
||||
var umbracoContext = GetUmbracoContext("/test", 1111, globalSettings: globalSettings);
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
|
||||
var urlProvider = new DefaultUrlProvider(
|
||||
Microsoft.Extensions.Options.Options.Create(requestHandlerSettings),
|
||||
LoggerFactory.CreateLogger<DefaultUrlProvider>(),
|
||||
new SiteDomainMapper(), umbracoContextAccessor, UriUtility);
|
||||
var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider);
|
||||
|
||||
SetDomains4();
|
||||
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("/test");
|
||||
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
|
||||
var currentUri = new Uri(currentUrl);
|
||||
var mode = absolute ? UrlMode.Absolute : UrlMode.Auto;
|
||||
var result = publishedUrlProvider.GetUrl(nodeId, mode, current : currentUri);
|
||||
var result = urlProvider.GetUrl(nodeId, mode, current: currentUri);
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Url_DomainsAndCache()
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false };
|
||||
|
||||
var umbracoContext = GetUmbracoContext("/test", 1111, globalSettings: globalSettings);
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
|
||||
var urlProvider = new DefaultUrlProvider(
|
||||
Microsoft.Extensions.Options.Options.Create(requestHandlerSettings),
|
||||
LoggerFactory.CreateLogger<DefaultUrlProvider>(),
|
||||
new SiteDomainMapper(), umbracoContextAccessor, UriUtility);
|
||||
var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider);
|
||||
|
||||
SetDomains4();
|
||||
|
||||
string ignore;
|
||||
ignore = publishedUrlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = publishedUrlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = publishedUrlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = publishedUrlProvider.GetUrl(10012, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = publishedUrlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = publishedUrlProvider.GetUrl(10013, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = publishedUrlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = publishedUrlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain2.com"));
|
||||
ignore = publishedUrlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain2.com"));
|
||||
ignore = publishedUrlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain2.com"));
|
||||
ignore = publishedUrlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain2.com"));
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var cache = umbracoContext.Content as PublishedContentCache;
|
||||
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
|
||||
var cachedRoutes = cache.RoutesCache.GetCachedRoutes();
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("/test");
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
|
||||
|
||||
string ignore;
|
||||
ignore = urlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = urlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = urlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = urlProvider.GetUrl(10012, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = urlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = urlProvider.GetUrl(10013, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = urlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain1.com"));
|
||||
ignore = urlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain2.com"));
|
||||
ignore = urlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain2.com"));
|
||||
ignore = urlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain2.com"));
|
||||
ignore = urlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain2.com"));
|
||||
|
||||
|
||||
var cache = (FastDictionaryAppCache)umbracoContext.PublishedSnapshot.ElementsCache;
|
||||
var cachedRoutes = cache.Keys.Where(x => x.StartsWith(CacheKeyPrefix)).ToList();
|
||||
Assert.AreEqual(7, cachedRoutes.Count);
|
||||
|
||||
var cachedIds = cache.RoutesCache.GetCachedIds();
|
||||
Assert.AreEqual(0, cachedIds.Count);
|
||||
//var cachedIds = cache.RoutesCache.GetCachedIds();
|
||||
//Assert.AreEqual(0, cachedIds.Count);
|
||||
|
||||
CheckRoute(cachedRoutes, cachedIds, 1001, "1001/");
|
||||
CheckRoute(cachedRoutes, cachedIds, 10011, "10011/");
|
||||
CheckRoute(cachedRoutes, cachedIds, 100111, "10011/1001-1-1");
|
||||
CheckRoute(cachedRoutes, cachedIds, 10012, "10012/");
|
||||
CheckRoute(cachedRoutes, cachedIds, 100121, "10012/1001-2-1");
|
||||
CheckRoute(cachedRoutes, cachedIds, 10013, "1001/1001-3");
|
||||
CheckRoute(cachedRoutes, cachedIds, 1002, "/1002");
|
||||
CheckRoute(cache, 1001, "1001/");
|
||||
CheckRoute(cache, 10011, "10011/");
|
||||
CheckRoute(cache, 100111, "10011/1001-1-1");
|
||||
CheckRoute(cache, 10012, "10012/");
|
||||
CheckRoute(cache, 100121, "10012/1001-2-1");
|
||||
CheckRoute(cache, 10013, "1001/1001-3");
|
||||
CheckRoute(cache, 1002, "/1002");
|
||||
|
||||
// use the cache
|
||||
Assert.AreEqual("/", publishedUrlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
Assert.AreEqual("/en/", publishedUrlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
Assert.AreEqual("/en/1001-1-1/", publishedUrlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
Assert.AreEqual("/fr/", publishedUrlProvider.GetUrl(10012, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
Assert.AreEqual("/fr/1001-2-1/", publishedUrlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
Assert.AreEqual("/1001-3/", publishedUrlProvider.GetUrl(10013, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
Assert.AreEqual("/1002/", publishedUrlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
Assert.AreEqual("/", urlProvider.GetUrl(1001, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
Assert.AreEqual("/en/", urlProvider.GetUrl(10011, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
Assert.AreEqual("/en/1001-1-1/", urlProvider.GetUrl(100111, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
Assert.AreEqual("/fr/", urlProvider.GetUrl(10012, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
Assert.AreEqual("/fr/1001-2-1/", urlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
Assert.AreEqual("/1001-3/", urlProvider.GetUrl(10013, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
Assert.AreEqual("/1002/", urlProvider.GetUrl(1002, UrlMode.Auto, current: new Uri("http://domain1.com")));
|
||||
|
||||
Assert.AreEqual("http://domain1.com/fr/1001-2-1/", publishedUrlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain2.com")));
|
||||
Assert.AreEqual("http://domain1.com/fr/1001-2-1/", urlProvider.GetUrl(100121, UrlMode.Auto, current: new Uri("http://domain2.com")));
|
||||
}
|
||||
|
||||
private static void CheckRoute(IDictionary<int, string> routes, IDictionary<string, int> ids, int id, string route)
|
||||
private static void CheckRoute(FastDictionaryAppCache routes, int id, string route)
|
||||
{
|
||||
Assert.IsTrue(routes.ContainsKey(id));
|
||||
Assert.AreEqual(route, routes[id]);
|
||||
Assert.IsFalse(ids.ContainsKey(route));
|
||||
var cacheKey = $"{CacheKeyPrefix}[P:{id}]";
|
||||
var found = (string)routes.Get(cacheKey);
|
||||
Assert.IsNotNull(found);
|
||||
Assert.AreEqual(route, found);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Url_Relative_Or_Absolute()
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false };
|
||||
|
||||
var umbracoContext = GetUmbracoContext("http://domain1.com/test", 1111, globalSettings: globalSettings);
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
|
||||
var urlProvider = new DefaultUrlProvider(
|
||||
Microsoft.Extensions.Options.Options.Create(requestHandlerSettings),
|
||||
LoggerFactory.CreateLogger<DefaultUrlProvider>(),
|
||||
new SiteDomainMapper(), umbracoContextAccessor, UriUtility);
|
||||
var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider);
|
||||
|
||||
SetDomains4();
|
||||
|
||||
Assert.AreEqual("/en/1001-1-1/", publishedUrlProvider.GetUrl(100111));
|
||||
Assert.AreEqual("http://domain3.com/en/1003-1-1/", publishedUrlProvider.GetUrl(100311));
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
publishedUrlProvider.Mode = UrlMode.Absolute;
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("http://domain1.com/test");
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
Assert.AreEqual("http://domain1.com/en/1001-1-1/", publishedUrlProvider.GetUrl(100111));
|
||||
Assert.AreEqual("http://domain3.com/en/1003-1-1/", publishedUrlProvider.GetUrl(100311));
|
||||
|
||||
Assert.AreEqual("/en/1001-1-1/", urlProvider.GetUrl(100111));
|
||||
Assert.AreEqual("http://domain3.com/en/1003-1-1/", urlProvider.GetUrl(100311));
|
||||
|
||||
urlProvider.Mode = UrlMode.Absolute;
|
||||
|
||||
Assert.AreEqual("http://domain1.com/en/1001-1-1/", urlProvider.GetUrl(100111));
|
||||
Assert.AreEqual("http://domain3.com/en/1003-1-1/", urlProvider.GetUrl(100311));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Url_Alternate()
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false };
|
||||
|
||||
var umbracoContext = GetUmbracoContext("http://domain1.com/en/test", 1111, globalSettings: globalSettings);
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext);
|
||||
var urlProvider = new DefaultUrlProvider(
|
||||
Microsoft.Extensions.Options.Options.Create(requestHandlerSettings),
|
||||
LoggerFactory.CreateLogger<DefaultUrlProvider>(),
|
||||
new SiteDomainMapper(), umbracoContextAccessor, UriUtility);
|
||||
var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider);
|
||||
|
||||
SetDomains5();
|
||||
|
||||
var url = publishedUrlProvider.GetUrl(100111, UrlMode.Absolute);
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor("http://domain1.com/en/test");
|
||||
UrlProvider urlProvider = GetUrlProvider(umbracoContextAccessor, requestHandlerSettings, new WebRoutingSettings(), out UriUtility uriUtility);
|
||||
|
||||
|
||||
var url = urlProvider.GetUrl(100111, UrlMode.Absolute);
|
||||
Assert.AreEqual("http://domain1.com/en/1001-1-1/", url);
|
||||
|
||||
var result = publishedUrlProvider.GetOtherUrls(100111).ToArray();
|
||||
var result = urlProvider.GetOtherUrls(100111).ToArray();
|
||||
|
||||
foreach (var x in result) Console.WriteLine(x);
|
||||
foreach (var x in result)
|
||||
Console.WriteLine(x);
|
||||
|
||||
Assert.AreEqual(2, result.Length);
|
||||
Assert.AreEqual(result[0].Text, "http://domain1b.com/en/1001-1-1/");
|
||||
Assert.AreEqual(result[1].Text, "http://domain1a.com/en/1001-1-1/");
|
||||
}
|
||||
|
||||
private IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext, DefaultUrlProvider urlProvider)
|
||||
{
|
||||
var webRoutingSettings = new WebRoutingSettings();
|
||||
return new UrlProvider(
|
||||
new TestUmbracoContextAccessor(umbracoContext),
|
||||
Microsoft.Extensions.Options.Options.Create(webRoutingSettings),
|
||||
new UrlProviderCollection(new []{urlProvider}),
|
||||
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
|
||||
Mock.Of<IVariationContextAccessor>()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,19 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Tests.LegacyXmlPublishedCache;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class UrlsWithNestedDomains : UrlRoutingTestBase
|
||||
@@ -25,61 +27,60 @@ namespace Umbraco.Tests.Routing
|
||||
// using the closest domain to the node - here we test that if we request
|
||||
// a non-canonical route, it is not cached / the cache is not polluted
|
||||
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
Builder.Services.AddUnique(Mock.Of<IDomainService>());
|
||||
Builder.Services.AddTransient<ISiteDomainMapper, SiteDomainMapper>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DoNotPolluteCache()
|
||||
{
|
||||
var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true };
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
SetDomains1();
|
||||
|
||||
const string url = "http://domain1.com/1001-1/1001-1-1";
|
||||
|
||||
// get the nice URL for 100111
|
||||
var umbracoContext = GetUmbracoContext(url, 9999, globalSettings: globalSettings);
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(umbracoContext);
|
||||
var umbracoContextAccessor = GetUmbracoContextAccessor(url);
|
||||
var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
|
||||
var urlProvider = new DefaultUrlProvider(
|
||||
Microsoft.Extensions.Options.Options.Create(requestHandlerSettings),
|
||||
LoggerFactory.CreateLogger<DefaultUrlProvider>(),
|
||||
new SiteDomainMapper(), umbracoContextAccessor, UriUtility);
|
||||
Mock.Of<ILogger<DefaultUrlProvider>>(),
|
||||
new SiteDomainMapper(),
|
||||
umbracoContextAccessor,
|
||||
new UriUtility(Mock.Of<IHostingEnvironment>()),
|
||||
Mock.Of<ILocalizationService>());
|
||||
var publishedUrlProvider = GetPublishedUrlProvider(umbracoContext, urlProvider);
|
||||
|
||||
Assert.AreEqual("http://domain2.com/1001-1-1/", publishedUrlProvider.GetUrl(100111, UrlMode.Absolute));
|
||||
string absUrl = publishedUrlProvider.GetUrl(100111, UrlMode.Absolute);
|
||||
Assert.AreEqual("http://domain2.com/1001-1-1/", absUrl);
|
||||
|
||||
const string cacheKeyPrefix = "NuCache.ContentCache.RouteByContent";
|
||||
|
||||
// check that the proper route has been cached
|
||||
var cache = umbracoContext.Content as PublishedContentCache;
|
||||
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
|
||||
var cachedRoutes = cache.RoutesCache.GetCachedRoutes();
|
||||
Assert.AreEqual("10011/1001-1-1", cachedRoutes[100111]);
|
||||
var cache = (FastDictionaryAppCache)umbracoContext.PublishedSnapshot.ElementsCache;
|
||||
|
||||
var cachedRoutes = cache.Keys.Where(x => x.StartsWith(cacheKeyPrefix)).ToList();
|
||||
var cacheKey = $"{cacheKeyPrefix}[P:100111]";
|
||||
Assert.AreEqual("10011/1001-1-1", cache.Get(cacheKey));
|
||||
|
||||
// route a rogue URL
|
||||
var publishedRouter = CreatePublishedRouter(umbracoContextAccessor);
|
||||
var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl);
|
||||
|
||||
publishedRouter.FindDomain(frequest);
|
||||
Assert.IsTrue(frequest.HasDomain());
|
||||
|
||||
// check that it's been routed
|
||||
var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger<ContentFinderByUrl>(), GetUmbracoContextAccessor(umbracoContext));
|
||||
var lookup = new ContentFinderByUrl(Mock.Of<ILogger<ContentFinderByUrl>>(), umbracoContextAccessor);
|
||||
var result = lookup.TryFindContent(frequest);
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(100111, frequest.PublishedContent.Id);
|
||||
|
||||
// has the cache been polluted?
|
||||
cachedRoutes = cache.RoutesCache.GetCachedRoutes();
|
||||
Assert.AreEqual("10011/1001-1-1", cachedRoutes[100111]); // no
|
||||
//Assert.AreEqual("1001/1001-1/1001-1-1", cachedRoutes[100111]); // yes
|
||||
cachedRoutes = cache.Keys.Where(x => x.StartsWith(cacheKeyPrefix)).ToList();
|
||||
Assert.AreEqual("10011/1001-1-1", cache.Get(cacheKey)); // no
|
||||
|
||||
// what's the nice URL now?
|
||||
Assert.AreEqual("http://domain2.com/1001-1-1/", publishedUrlProvider.GetUrl(100111)); // good
|
||||
//Assert.AreEqual("http://domain1.com/1001-1/1001-1-1", routingContext.NiceUrlProvider.GetNiceUrl(100111, true)); // bad
|
||||
}
|
||||
|
||||
private IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext, object urlProvider)
|
||||
@@ -87,14 +88,16 @@ namespace Umbraco.Tests.Routing
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void SetDomains1()
|
||||
private void SetDomains1()
|
||||
{
|
||||
SetupDomainServiceMock(new[]
|
||||
{
|
||||
new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain2.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}
|
||||
});
|
||||
var domainService = Mock.Get(DomainService);
|
||||
|
||||
domainService.Setup(service => service.GetAll(It.IsAny<bool>()))
|
||||
.Returns((bool incWildcards) => new[]
|
||||
{
|
||||
new UmbracoDomain("http://domain1.com/") {Id = 1, LanguageId = LangEngId, RootContentId = 1001, LanguageIsoCode = "en-US"},
|
||||
new UmbracoDomain("http://domain2.com/") {Id = 2, LanguageId = LangEngId, RootContentId = 10011, LanguageIsoCode = "en-US"}
|
||||
});
|
||||
}
|
||||
|
||||
private IPublishedUrlProvider GetPublishedUrlProvider(IUmbracoContext umbracoContext, DefaultUrlProvider urlProvider)
|
||||
@@ -103,15 +106,14 @@ namespace Umbraco.Tests.Routing
|
||||
return new UrlProvider(
|
||||
new TestUmbracoContextAccessor(umbracoContext),
|
||||
Microsoft.Extensions.Options.Options.Create(webRoutingSettings),
|
||||
new UrlProviderCollection(new []{urlProvider}),
|
||||
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
|
||||
new UrlProviderCollection(() => new[] { urlProvider }),
|
||||
new MediaUrlProviderCollection(() => Enumerable.Empty<IMediaUrlProvider>()),
|
||||
Mock.Of<IVariationContextAccessor>()
|
||||
);
|
||||
}
|
||||
|
||||
protected override string GetXmlContent(int templateId)
|
||||
{
|
||||
return @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
=> @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<!DOCTYPE root[
|
||||
<!ELEMENT Doc ANY>
|
||||
<!ATTLIST Doc id ID #REQUIRED>
|
||||
@@ -190,6 +192,5 @@ namespace Umbraco.Tests.Routing
|
||||
</Doc>
|
||||
</Doc>
|
||||
</root>";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using Moq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Web.PublishedCache.NuCache.DataSource;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
|
||||
|
||||
namespace Umbraco.Tests.PublishedContent
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache
|
||||
{
|
||||
[TestFixture]
|
||||
public class ContentSerializationTests
|
||||
@@ -77,9 +77,12 @@ namespace Umbraco.Tests.PublishedContent
|
||||
{
|
||||
public override int Compare(CultureVariation x, CultureVariation y)
|
||||
{
|
||||
if (x == null && y == null) return 0;
|
||||
if (x == null && y != null) return -1;
|
||||
if (x != null && y == null) return 1;
|
||||
if (x == null && y == null)
|
||||
return 0;
|
||||
if (x == null && y != null)
|
||||
return -1;
|
||||
if (x != null && y == null)
|
||||
return 1;
|
||||
|
||||
return x.Date.CompareTo(y.Date) | x.IsDraft.CompareTo(y.IsDraft) | x.Name.CompareTo(y.Name) | x.UrlSegment.CompareTo(y.UrlSegment);
|
||||
}
|
||||
@@ -89,9 +92,12 @@ namespace Umbraco.Tests.PublishedContent
|
||||
{
|
||||
public override int Compare(PropertyData x, PropertyData y)
|
||||
{
|
||||
if (x == null && y == null) return 0;
|
||||
if (x == null && y != null) return -1;
|
||||
if (x != null && y == null) return 1;
|
||||
if (x == null && y == null)
|
||||
return 0;
|
||||
if (x == null && y != null)
|
||||
return -1;
|
||||
if (x != null && y == null)
|
||||
return 1;
|
||||
|
||||
var xVal = x.Value?.ToString() ?? string.Empty;
|
||||
var yVal = y.Value?.ToString() ?? string.Empty;
|
||||
@@ -0,0 +1,85 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache
|
||||
{
|
||||
[TestFixture]
|
||||
public class PublishContentCacheTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
private IPublishedContentCache _cache;
|
||||
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
|
||||
string xml = PublishedContentXml.PublishContentCacheTestsXml();
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
// configure the Home content type to be composed of another for tests.
|
||||
var compositionType = new ContentType(TestHelper.ShortStringHelper, -1)
|
||||
{
|
||||
Alias = "MyCompositionAlias"
|
||||
};
|
||||
contentTypes.First(x => x.Alias == "Home").AddContentType(compositionType);
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
_cache = GetPublishedSnapshot().Content;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Has_Content()
|
||||
{
|
||||
Assert.IsTrue(_cache.HasContent());
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void Get_Root_Docs()
|
||||
{
|
||||
var result = _cache.GetAtRoot();
|
||||
Assert.AreEqual(2, result.Count());
|
||||
Assert.AreEqual(1046, result.ElementAt(0).Id);
|
||||
Assert.AreEqual(1172, result.ElementAt(1).Id);
|
||||
}
|
||||
|
||||
|
||||
[TestCase("/", 1046)]
|
||||
[TestCase("/home", 1046)]
|
||||
[TestCase("/Home", 1046)] //test different cases
|
||||
[TestCase("/home/sub1", 1173)]
|
||||
[TestCase("/Home/sub1", 1173)]
|
||||
[TestCase("/home/Sub1", 1173)] //test different cases
|
||||
[TestCase("/home/Sub'Apostrophe", 1177)]
|
||||
public void Get_Node_By_Route(string route, int nodeId)
|
||||
{
|
||||
var result = _cache.GetByRoute(route, false);
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(nodeId, result.Id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[TestCase("/", 1046)]
|
||||
[TestCase("/sub1", 1173)]
|
||||
[TestCase("/Sub1", 1173)]
|
||||
public void Get_Node_By_Route_Hiding_Top_Level_Nodes(string route, int nodeId)
|
||||
{
|
||||
var result = _cache.GetByRoute(route, true);
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(nodeId, result.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Unit tests for IPublishedContent and extensions
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class PublishedContentDataTableTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
private readonly DataType[] _dataTypes = GetDefaultDataTypes();
|
||||
|
||||
private static ContentType CreateContentType(string name, IDataType dataType, IReadOnlyDictionary<string, string> propertyAliasesAndNames)
|
||||
{
|
||||
var contentType = new ContentType(TestHelper.ShortStringHelper, -1)
|
||||
{
|
||||
Alias = name,
|
||||
Name = name,
|
||||
Key = Guid.NewGuid(),
|
||||
Id = name.GetHashCode()
|
||||
};
|
||||
foreach(var prop in propertyAliasesAndNames)
|
||||
{
|
||||
contentType.AddPropertyType(new PropertyType(TestHelper.ShortStringHelper, dataType, prop.Key)
|
||||
{
|
||||
Name = prop.Value
|
||||
});
|
||||
}
|
||||
|
||||
return contentType;
|
||||
}
|
||||
|
||||
private IEnumerable<ContentNodeKit> CreateCache(
|
||||
bool createChildren,
|
||||
IDataType dataType,
|
||||
out ContentType[] contentTypes)
|
||||
{
|
||||
var result = new List<ContentNodeKit>();
|
||||
var valueCounter = 1;
|
||||
var parentId = 3;
|
||||
|
||||
var properties = new Dictionary<string, string>
|
||||
{
|
||||
["property1"] = "Property 1",
|
||||
["property2"] = "Property 2",
|
||||
};
|
||||
|
||||
ContentType parentContentType = CreateContentType("Parent", dataType, new Dictionary<string, string>(properties)
|
||||
{
|
||||
["property3"] = "Property 3"
|
||||
});
|
||||
ContentType childContentType = CreateContentType("Child", dataType, new Dictionary<string, string>(properties)
|
||||
{
|
||||
["property4"] = "Property 4"
|
||||
});
|
||||
ContentType child2ContentType = CreateContentType("Child2", dataType, new Dictionary<string, string>(properties)
|
||||
{
|
||||
["property4"] = "Property 4"
|
||||
});
|
||||
|
||||
contentTypes = new[] { parentContentType, childContentType, child2ContentType };
|
||||
|
||||
ContentData parentData = new ContentDataBuilder()
|
||||
.WithName("Page" + Guid.NewGuid())
|
||||
.WithProperties(new PropertyDataBuilder()
|
||||
.WithPropertyData("property1", "value" + valueCounter)
|
||||
.WithPropertyData("property2", "value" + (valueCounter + 1))
|
||||
.WithPropertyData("property3", "value" + (valueCounter + 2))
|
||||
.Build())
|
||||
.Build();
|
||||
|
||||
ContentNodeKit parent = ContentNodeKitBuilder.CreateWithContent(
|
||||
parentContentType.Id,
|
||||
parentId, $"-1,{parentId}",
|
||||
draftData: parentData,
|
||||
publishedData: parentData);
|
||||
|
||||
result.Add(parent);
|
||||
|
||||
if (createChildren)
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
valueCounter += 3;
|
||||
var childId = parentId + i + 1;
|
||||
|
||||
ContentData childData = new ContentDataBuilder()
|
||||
.WithName("Page" + Guid.NewGuid())
|
||||
.WithProperties(new PropertyDataBuilder()
|
||||
.WithPropertyData("property1", "value" + valueCounter)
|
||||
.WithPropertyData("property2", "value" + (valueCounter + 1))
|
||||
.WithPropertyData("property4", "value" + (valueCounter + 2))
|
||||
.Build())
|
||||
.Build();
|
||||
|
||||
ContentNodeKit child = ContentNodeKitBuilder.CreateWithContent(
|
||||
i > 0 ? childContentType.Id : child2ContentType.Id,
|
||||
childId, $"-1,{parentId},{childId}", i,
|
||||
draftData: childData,
|
||||
publishedData: childData);
|
||||
|
||||
result.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void To_DataTable()
|
||||
{
|
||||
var cache = CreateCache(true, _dataTypes[0], out ContentType[] contentTypes);
|
||||
InitializedCache(cache, contentTypes, dataTypes: _dataTypes);
|
||||
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var root = snapshot.Content.GetAtRoot().First();
|
||||
|
||||
var dt = root.ChildrenAsTable(
|
||||
VariationContextAccessor,
|
||||
ContentTypeService,
|
||||
MediaTypeService,
|
||||
Mock.Of<IMemberTypeService>(),
|
||||
Mock.Of<IPublishedUrlProvider>());
|
||||
|
||||
Assert.AreEqual(11, dt.Columns.Count);
|
||||
Assert.AreEqual(3, dt.Rows.Count);
|
||||
Assert.AreEqual("value4", dt.Rows[0]["Property 1"]);
|
||||
Assert.AreEqual("value5", dt.Rows[0]["Property 2"]);
|
||||
Assert.AreEqual("value6", dt.Rows[0]["Property 4"]);
|
||||
Assert.AreEqual("value7", dt.Rows[1]["Property 1"]);
|
||||
Assert.AreEqual("value8", dt.Rows[1]["Property 2"]);
|
||||
Assert.AreEqual("value9", dt.Rows[1]["Property 4"]);
|
||||
Assert.AreEqual("value10", dt.Rows[2]["Property 1"]);
|
||||
Assert.AreEqual("value11", dt.Rows[2]["Property 2"]);
|
||||
Assert.AreEqual("value12", dt.Rows[2]["Property 4"]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void To_DataTable_With_Filter()
|
||||
{
|
||||
var cache = CreateCache(true, _dataTypes[0], out ContentType[] contentTypes);
|
||||
InitializedCache(cache, contentTypes, dataTypes: _dataTypes);
|
||||
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var root = snapshot.Content.GetAtRoot().First();
|
||||
|
||||
var dt = root.ChildrenAsTable(
|
||||
VariationContextAccessor,
|
||||
ContentTypeService,
|
||||
MediaTypeService,
|
||||
Mock.Of<IMemberTypeService>(),
|
||||
Mock.Of<IPublishedUrlProvider>(),
|
||||
"Child");
|
||||
|
||||
Assert.AreEqual(11, dt.Columns.Count);
|
||||
Assert.AreEqual(2, dt.Rows.Count);
|
||||
Assert.AreEqual("value7", dt.Rows[0]["Property 1"]);
|
||||
Assert.AreEqual("value8", dt.Rows[0]["Property 2"]);
|
||||
Assert.AreEqual("value9", dt.Rows[0]["Property 4"]);
|
||||
Assert.AreEqual("value10", dt.Rows[1]["Property 1"]);
|
||||
Assert.AreEqual("value11", dt.Rows[1]["Property 2"]);
|
||||
Assert.AreEqual("value12", dt.Rows[1]["Property 4"]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void To_DataTable_No_Rows()
|
||||
{
|
||||
var cache = CreateCache(false, _dataTypes[0], out ContentType[] contentTypes);
|
||||
InitializedCache(cache, contentTypes, dataTypes: _dataTypes);
|
||||
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var root = snapshot.Content.GetAtRoot().First();
|
||||
|
||||
var dt = root.ChildrenAsTable(
|
||||
VariationContextAccessor,
|
||||
ContentTypeService,
|
||||
MediaTypeService,
|
||||
Mock.Of<IMemberTypeService>(),
|
||||
Mock.Of<IPublishedUrlProvider>());
|
||||
|
||||
//will return an empty data table
|
||||
Assert.AreEqual(0, dt.Columns.Count);
|
||||
Assert.AreEqual(0, dt.Rows.Count);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache
|
||||
{
|
||||
[TestFixture]
|
||||
public class PublishedContentExtensionTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
private const string XmlContent = @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<!DOCTYPE root[
|
||||
<!ELEMENT inherited ANY>
|
||||
<!ATTLIST inherited id ID #REQUIRED>
|
||||
]>
|
||||
<root id=""-1"">
|
||||
<inherited id=""1100"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1"" sortOrder=""1"" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" nodeName=""Home"" urlName=""home"" writerName=""admin"" creatorName=""admin"" path=""-1,1046"" isDoc=""""/>
|
||||
</root>";
|
||||
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
XmlContent,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
// configure inheritance for content types
|
||||
var baseType = new ContentType(TestHelper.ShortStringHelper, -1) { Alias = "Base" };
|
||||
contentTypes[0].AddContentType(baseType);
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsDocumentType_NonRecursive_ActualType_ReturnsTrue()
|
||||
{
|
||||
var publishedContent = GetContent(1100);
|
||||
Assert.That(publishedContent.IsDocumentType("Inherited", false));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsDocumentType_NonRecursive_BaseType_ReturnsFalse()
|
||||
{
|
||||
var publishedContent = GetContent(1100);
|
||||
Assert.That(publishedContent.IsDocumentType("Base", false), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsDocumentType_Recursive_ActualType_ReturnsTrue()
|
||||
{
|
||||
var publishedContent = GetContent(1100);
|
||||
Assert.That(publishedContent.IsDocumentType("Inherited", true));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsDocumentType_Recursive_BaseType_ReturnsTrue()
|
||||
{
|
||||
var publishedContent = GetContent(1100);
|
||||
Assert.That(publishedContent.IsDocumentType("Base", true));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsDocumentType_Recursive_InvalidBaseType_ReturnsFalse()
|
||||
{
|
||||
var publishedContent = GetContent(1100);
|
||||
Assert.That(publishedContent.IsDocumentType("invalidbase", true), Is.False);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache
|
||||
{
|
||||
[TestFixture]
|
||||
public class PublishedContentLanguageVariantTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
|
||||
var dataTypes = GetDefaultDataTypes();
|
||||
var cache = CreateCache(dataTypes, out ContentType[] contentTypes);
|
||||
|
||||
InitializedCache(cache, contentTypes, dataTypes: dataTypes);
|
||||
}
|
||||
|
||||
protected override PropertyValueConverterCollection PropertyValueConverterCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
PropertyValueConverterCollection collection = base.PropertyValueConverterCollection;
|
||||
return new PropertyValueConverterCollection(() => collection.Append(new TestNoValueValueConverter()));
|
||||
}
|
||||
}
|
||||
|
||||
private class TestNoValueValueConverter : SimpleTinyMceValueConverter
|
||||
{
|
||||
public override bool IsConverter(IPublishedPropertyType propertyType)
|
||||
=> propertyType.Alias == "noprop";
|
||||
|
||||
// for this test, we return false for IsValue for this property
|
||||
public override bool? IsValue(object value, PropertyValueLevel level) => false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to mock localization service
|
||||
/// </summary>
|
||||
/// <param name="contentTypes"></param>
|
||||
/// <param name="dataTypes"></param>
|
||||
/// <returns></returns>
|
||||
protected override ServiceContext CreateServiceContext(IContentType[] contentTypes, IMediaType[] mediaTypes, IDataType[] dataTypes)
|
||||
{
|
||||
var serviceContext = base.CreateServiceContext(contentTypes, mediaTypes, dataTypes);
|
||||
|
||||
var localizationService = Mock.Get(serviceContext.LocalizationService);
|
||||
|
||||
var languages = new List<Language>
|
||||
{
|
||||
new Language(GlobalSettings, "en-US") { Id = 1, CultureName = "English", IsDefault = true },
|
||||
new Language(GlobalSettings, "fr") { Id = 2, CultureName = "French" },
|
||||
new Language(GlobalSettings, "es") { Id = 3, CultureName = "Spanish", FallbackLanguageId = 1 },
|
||||
new Language(GlobalSettings, "it") { Id = 4, CultureName = "Italian", FallbackLanguageId = 3 },
|
||||
new Language(GlobalSettings, "de") { Id = 5, CultureName = "German" },
|
||||
new Language(GlobalSettings, "da") { Id = 6, CultureName = "Danish", FallbackLanguageId = 8 },
|
||||
new Language(GlobalSettings, "sv") { Id = 7, CultureName = "Swedish", FallbackLanguageId = 6 },
|
||||
new Language(GlobalSettings, "no") { Id = 8, CultureName = "Norweigan", FallbackLanguageId = 7 },
|
||||
new Language(GlobalSettings, "nl") { Id = 9, CultureName = "Dutch", FallbackLanguageId = 1 }
|
||||
};
|
||||
|
||||
localizationService.Setup(x => x.GetAllLanguages()).Returns(languages);
|
||||
localizationService.Setup(x => x.GetLanguageById(It.IsAny<int>()))
|
||||
.Returns((int id) => languages.SingleOrDefault(y => y.Id == id));
|
||||
localizationService.Setup(x => x.GetLanguageByIsoCode(It.IsAny<string>()))
|
||||
.Returns((string c) => languages.SingleOrDefault(y => y.IsoCode == c));
|
||||
|
||||
return serviceContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a content cache
|
||||
/// </summary>
|
||||
/// <param name="dataTypes"></param>
|
||||
/// <param name="contentTypes"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Builds a content hierarchy of 3 nodes, each has a different set of cultural properties.
|
||||
/// The first 2 share the same content type, the last one is a different content type.
|
||||
/// NOTE: The content items themselves are 'Invariant' but their properties are 'Variant' by culture.
|
||||
/// Normally in Umbraco this is prohibited but our APIs and database do actually support that behavior.
|
||||
/// It is simpler to have these tests run this way, else we would need to use WithCultureInfos
|
||||
/// for each item and pass in name values for all cultures we are supporting and then specify the
|
||||
/// default VariationContextAccessor.VariationContext value to be a default culture instead of "".
|
||||
/// </remarks>
|
||||
private IEnumerable<ContentNodeKit> CreateCache(IDataType[] dataTypes, out ContentType[] contentTypes)
|
||||
{
|
||||
var result = new List<ContentNodeKit>();
|
||||
|
||||
var propertyDataTypes = new Dictionary<string, IDataType>
|
||||
{
|
||||
// we only have one data type for this test which will be resolved with string empty.
|
||||
[string.Empty] = dataTypes[0]
|
||||
};
|
||||
|
||||
var contentType1 = new ContentType(ShortStringHelper, -1);
|
||||
|
||||
ContentData item1Data = new ContentDataBuilder()
|
||||
.WithName("Content 1")
|
||||
.WithProperties(new PropertyDataBuilder()
|
||||
.WithPropertyData("welcomeText", "Welcome")
|
||||
.WithPropertyData("welcomeText", "Welcome", "en-US")
|
||||
.WithPropertyData("welcomeText", "Willkommen", "de")
|
||||
.WithPropertyData("welcomeText", "Welkom", "nl")
|
||||
.WithPropertyData("welcomeText2", "Welcome")
|
||||
.WithPropertyData("welcomeText2", "Welcome", "en-US")
|
||||
.WithPropertyData("noprop", "xxx")
|
||||
.Build())
|
||||
// build with a dynamically created content type
|
||||
.Build(ShortStringHelper, propertyDataTypes, contentType1, "ContentType1");
|
||||
|
||||
ContentNodeKit item1 = ContentNodeKitBuilder.CreateWithContent(
|
||||
contentType1.Id,
|
||||
1, "-1,1",
|
||||
draftData: item1Data,
|
||||
publishedData: item1Data);
|
||||
|
||||
result.Add(item1);
|
||||
|
||||
ContentData item2Data = new ContentDataBuilder()
|
||||
.WithName("Content 2")
|
||||
.WithProperties(new PropertyDataBuilder()
|
||||
.WithPropertyData("welcomeText", "Welcome")
|
||||
.WithPropertyData("welcomeText", "Welcome", "en-US")
|
||||
.WithPropertyData("noprop", "xxx")
|
||||
.Build())
|
||||
// build while dynamically updating the same content type
|
||||
.Build(ShortStringHelper, propertyDataTypes, contentType1);
|
||||
|
||||
ContentNodeKit item2 = ContentNodeKitBuilder.CreateWithContent(
|
||||
contentType1.Id,
|
||||
2, "-1,1,2",
|
||||
parentContentId: 1,
|
||||
draftData: item2Data,
|
||||
publishedData: item2Data);
|
||||
|
||||
result.Add(item2);
|
||||
|
||||
var contentType2 = new ContentType(ShortStringHelper, -1);
|
||||
|
||||
ContentData item3Data = new ContentDataBuilder()
|
||||
.WithName("Content 3")
|
||||
.WithProperties(new PropertyDataBuilder()
|
||||
.WithPropertyData("prop3", "Oxxo")
|
||||
.WithPropertyData("prop3", "Oxxo", "en-US")
|
||||
.Build())
|
||||
// build with a dynamically created content type
|
||||
.Build(ShortStringHelper, propertyDataTypes, contentType2, "ContentType2");
|
||||
|
||||
ContentNodeKit item3 = ContentNodeKitBuilder.CreateWithContent(
|
||||
contentType2.Id,
|
||||
3, "-1,1,2,3",
|
||||
parentContentId: 2,
|
||||
draftData: item3Data,
|
||||
publishedData: item3Data);
|
||||
|
||||
result.Add(item3);
|
||||
|
||||
contentTypes = new[] { contentType1, contentType2 };
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_For_Populated_Requested_Language()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First();
|
||||
var value = content.Value(Mock.Of<IPublishedValueFallback>(), "welcomeText", "en-US");
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_For_Populated_Requested_Non_Default_Language()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First();
|
||||
var value = content.Value(Mock.Of<IPublishedValueFallback>(), "welcomeText", "de");
|
||||
Assert.AreEqual("Willkommen", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_Without_Fallback()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First();
|
||||
var value = content.Value(Mock.Of<IPublishedValueFallback>(), "welcomeText", "fr");
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Unless_Requested()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First();
|
||||
var value = content.Value(Mock.Of<IPublishedValueFallback>(), "welcomeText", "es");
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First();
|
||||
var value = content.Value(PublishedValueFallback, "welcomeText", "es", fallback: Fallback.ToLanguage);
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Over_Two_Levels()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First();
|
||||
var value = content.Value(PublishedValueFallback, "welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors));
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Do_Not_GetContent_For_Unpopulated_Requested_Language_With_Fallback_Over_That_Loops()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First();
|
||||
var value = content.Value(Mock.Of<IPublishedValueFallback>(), "welcomeText", "no", fallback: Fallback.ToLanguage);
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Do_Not_Get_Content_Recursively_Unless_Requested()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First().Children.First();
|
||||
var value = content.Value(Mock.Of<IPublishedValueFallback>(), "welcomeText2");
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_Recursively()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First().Children.First();
|
||||
var value = content.Value(PublishedValueFallback, "welcomeText2", fallback: Fallback.ToAncestors);
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Do_Not_Get_Content_Recursively_Unless_Requested2()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First().Children.First().Children.First();
|
||||
Assert.IsNull(content.GetProperty("welcomeText2"));
|
||||
var value = content.Value(Mock.Of<IPublishedValueFallback>(), "welcomeText2");
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_Recursively2()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First().Children.First().Children.First();
|
||||
Assert.IsNull(content.GetProperty("welcomeText2"));
|
||||
var value = content.Value(PublishedValueFallback, "welcomeText2", fallback: Fallback.ToAncestors);
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_Recursively3()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First().Children.First().Children.First();
|
||||
Assert.IsNull(content.GetProperty("noprop"));
|
||||
var value = content.Value(PublishedValueFallback, "noprop", fallback: Fallback.ToAncestors);
|
||||
// property has no value - based on the converter
|
||||
// but we still get the value (ie, the converter would do something)
|
||||
Assert.AreEqual("xxx", value.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_With_Recursive_Priority()
|
||||
{
|
||||
VariationContextAccessor.VariationContext = new VariationContext("nl");
|
||||
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First().Children.First();
|
||||
|
||||
var value = content.Value(PublishedValueFallback, "welcomeText", "nl", fallback: Fallback.To(Fallback.Ancestors, Fallback.Language));
|
||||
|
||||
// No Dutch value is directly assigned. Check has fallen back to Dutch value from parent.
|
||||
Assert.AreEqual("Welkom", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_With_Fallback_Language_Priority()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First().Children.First();
|
||||
|
||||
var value = content.Value(PublishedValueFallback, "welcomeText", "nl", fallback: Fallback.ToLanguage);
|
||||
|
||||
// No Dutch value is directly assigned. Check has fallen back to English value from language variant.
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Throws_For_Non_Supported_Fallback()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First().Children.First();
|
||||
|
||||
Assert.Throws<NotSupportedException>(() => content.Value(PublishedValueFallback, "welcomeText", "nl", fallback: Fallback.To(999)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Fallback_To_Default_Value()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var content = snapshot.Content.GetAtRoot().First().Children.First();
|
||||
|
||||
// no Dutch value is assigned, so getting null
|
||||
var value = content.Value(PublishedValueFallback, "welcomeText", "nl");
|
||||
Assert.IsNull(value);
|
||||
|
||||
// even if we 'just' provide a default value
|
||||
value = content.Value(PublishedValueFallback, "welcomeText", "nl", defaultValue: "woop");
|
||||
Assert.IsNull(value);
|
||||
|
||||
// but it works with proper fallback settings
|
||||
value = content.Value(PublishedValueFallback, "welcomeText", "nl", fallback: Fallback.ToDefaultValue, defaultValue: "woop");
|
||||
Assert.AreEqual("woop", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,193 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
using Umbraco.Cms.Core.Media;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Core.Templates;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Infrastructure.Serialization;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Web.Composing;
|
||||
|
||||
namespace Umbraco.Tests.PublishedContent
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests the methods on IPublishedContent using the DefaultPublishedContentStore
|
||||
/// </summary>
|
||||
|
||||
[TestFixture]
|
||||
[UmbracoTest(TypeLoader = UmbracoTestOptions.TypeLoader.PerFixture)]
|
||||
public class PublishedContentTests : PublishedContentTestBase
|
||||
public class PublishedContentTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
_publishedSnapshotAccessorMock = new Mock<IPublishedSnapshotAccessor>();
|
||||
Builder.Services.AddUnique<IPublishedSnapshotAccessor>(_publishedSnapshotAccessorMock.Object);
|
||||
|
||||
Builder.Services.AddUnique<IPublishedModelFactory>(f => new PublishedModelFactory(f.GetRequiredService<TypeLoader>().GetTypes<PublishedContentModel>(), f.GetRequiredService<IPublishedValueFallback>()));
|
||||
Builder.Services.AddUnique<IPublishedContentTypeFactory, PublishedContentTypeFactory>();
|
||||
Builder.Services.AddUnique<IPublishedValueFallback, PublishedValueFallback>();
|
||||
|
||||
var loggerFactory = NullLoggerFactory.Instance;
|
||||
var mediaService = Mock.Of<IMediaService>();
|
||||
var contentTypeBaseServiceProvider = Mock.Of<IContentTypeBaseServiceProvider>();
|
||||
var umbracoContextAccessor = Mock.Of<IUmbracoContextAccessor>();
|
||||
var backOfficeSecurityAccessor = Mock.Of<IBackOfficeSecurityAccessor>();
|
||||
var publishedUrlProvider = Mock.Of<IPublishedUrlProvider>();
|
||||
var imageSourceParser = new HtmlImageSourceParser(publishedUrlProvider);
|
||||
var serializer = new ConfigurationEditorJsonSerializer();
|
||||
var mediaFileService = new MediaFileManager(Mock.Of<IFileSystem>(), Mock.Of<IMediaPathScheme>(),
|
||||
loggerFactory.CreateLogger<MediaFileManager>(), Mock.Of<IShortStringHelper>());
|
||||
var pastedImages = new RichTextEditorPastedImages(umbracoContextAccessor, loggerFactory.CreateLogger<RichTextEditorPastedImages>(), HostingEnvironment, mediaService, contentTypeBaseServiceProvider, mediaFileService, ShortStringHelper, publishedUrlProvider, serializer);
|
||||
var linkParser = new HtmlLocalLinkParser(umbracoContextAccessor, publishedUrlProvider);
|
||||
|
||||
var dataTypeService = new TestObjects.TestDataTypeService(
|
||||
new DataType(new VoidEditor(DataValueEditorFactory), serializer) { Id = 1 },
|
||||
new DataType(new TrueFalsePropertyEditor(DataValueEditorFactory, IOHelper), serializer) { Id = 1001 },
|
||||
new DataType(new RichTextPropertyEditor(DataValueEditorFactory, backOfficeSecurityAccessor, imageSourceParser, linkParser, pastedImages, IOHelper, Mock.Of<IImageUrlGenerator>()), serializer) { Id = 1002 },
|
||||
new DataType(new IntegerPropertyEditor(DataValueEditorFactory), serializer) { Id = 1003 },
|
||||
new DataType(new TextboxPropertyEditor(DataValueEditorFactory, IOHelper), serializer) { Id = 1004 },
|
||||
new DataType(new MediaPickerPropertyEditor(DataValueEditorFactory, IOHelper), serializer) { Id = 1005 });
|
||||
Builder.Services.AddUnique<IDataTypeService>(f => dataTypeService);
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
var factory = Factory.GetRequiredService<IPublishedContentTypeFactory>() as PublishedContentTypeFactory;
|
||||
|
||||
// need to specify a custom callback for unit tests
|
||||
// AutoPublishedContentTypes generates properties automatically
|
||||
// when they are requested, but we must declare those that we
|
||||
// explicitely want to be here...
|
||||
|
||||
IEnumerable<IPublishedPropertyType> CreatePropertyTypes(IPublishedContentType contentType)
|
||||
{
|
||||
// AutoPublishedContentType will auto-generate other properties
|
||||
yield return factory.CreatePropertyType(contentType, "umbracoNaviHide", 1001);
|
||||
yield return factory.CreatePropertyType(contentType, "selectedNodes", 1);
|
||||
yield return factory.CreatePropertyType(contentType, "umbracoUrlAlias", 1);
|
||||
yield return factory.CreatePropertyType(contentType, "content", 1002);
|
||||
yield return factory.CreatePropertyType(contentType, "testRecursive", 1);
|
||||
}
|
||||
|
||||
var compositionAliases = new[] { "MyCompositionAlias" };
|
||||
var anythingType = new AutoPublishedContentType(Guid.NewGuid(), 0, "anything", compositionAliases, CreatePropertyTypes);
|
||||
var homeType = new AutoPublishedContentType(Guid.NewGuid(), 0, "home", compositionAliases, CreatePropertyTypes);
|
||||
ContentTypesCache.GetPublishedContentTypeByAlias = alias => alias.InvariantEquals("home") ? homeType : anythingType;
|
||||
}
|
||||
|
||||
|
||||
protected override TypeLoader CreateTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, ILogger<TypeLoader> logger, IProfilingLogger profilingLogger , IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
var baseLoader = base.CreateTypeLoader(ioHelper, typeFinder, runtimeCache, logger, profilingLogger , hostingEnvironment);
|
||||
|
||||
return new TypeLoader(typeFinder, runtimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), logger, profilingLogger , false,
|
||||
// this is so the model factory looks into the test assembly
|
||||
baseLoader.AssembliesToScan
|
||||
.Union(new[] { typeof(PublishedContentTests).Assembly })
|
||||
.ToList());
|
||||
}
|
||||
|
||||
private readonly Guid _node1173Guid = Guid.NewGuid();
|
||||
private Mock<IPublishedSnapshotAccessor> _publishedSnapshotAccessorMock;
|
||||
private PublishedModelFactory _publishedModelFactory;
|
||||
private DataType[] _dataTypes;
|
||||
|
||||
protected override string GetXmlContent(int templateId)
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
return @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<!DOCTYPE root[
|
||||
<!ELEMENT Home ANY>
|
||||
<!ATTLIST Home id ID #REQUIRED>
|
||||
<!ELEMENT CustomDocument ANY>
|
||||
<!ATTLIST CustomDocument id ID #REQUIRED>
|
||||
]>
|
||||
<root id=""-1"">
|
||||
<Home id=""1046"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""1"" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" nodeName=""Home"" urlName=""home"" writerName=""admin"" creatorName=""admin"" path=""-1,1046"" isDoc="""">
|
||||
<content><![CDATA[]]></content>
|
||||
<umbracoUrlAlias><![CDATA[this/is/my/alias, anotheralias]]></umbracoUrlAlias>
|
||||
<umbracoNaviHide>1</umbracoNaviHide>
|
||||
<testRecursive><![CDATA[This is the recursive val]]></testRecursive>
|
||||
<Home id=""1173"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""1"" createDate=""2012-07-20T18:06:45"" updateDate=""2012-07-20T19:07:31"" nodeName=""Sub1"" urlName=""sub1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173"" isDoc="""" key=""" + _node1173Guid + @""">
|
||||
<content><![CDATA[<div>This is some content</div>]]></content>
|
||||
<umbracoUrlAlias><![CDATA[page2/alias, 2ndpagealias]]></umbracoUrlAlias>
|
||||
<testRecursive><![CDATA[]]></testRecursive>
|
||||
<Home id=""1174"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""1"" createDate=""2012-07-20T18:07:54"" updateDate=""2012-07-20T19:10:27"" nodeName=""Sub2"" urlName=""sub2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1174"" isDoc="""">
|
||||
<content><![CDATA[]]></content>
|
||||
<umbracoUrlAlias><![CDATA[only/one/alias]]></umbracoUrlAlias>
|
||||
<creatorName><![CDATA[Custom data with same property name as the member name]]></creatorName>
|
||||
<testRecursive><![CDATA[]]></testRecursive>
|
||||
</Home>
|
||||
<CustomDocument id=""117"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2018-07-18T10:06:37"" updateDate=""2018-07-18T10:06:37"" nodeName=""custom sub 1"" urlName=""custom-sub-1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,117"" isDoc="""" />
|
||||
<CustomDocument id=""1177"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""3"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""custom sub 1"" urlName=""custom-sub-1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1177"" isDoc="""" />
|
||||
<CustomDocument id=""1178"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-16T14:23:35"" nodeName=""custom sub 2"" urlName=""custom-sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1178"" isDoc="""">
|
||||
<CustomDocument id=""1179"" parentID=""1178"" level=""4"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""1"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""custom sub sub 1"" urlName=""custom-sub-sub-1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1178,1179"" isDoc="""" />
|
||||
</CustomDocument>
|
||||
<Home id=""1176"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""5"" createDate=""2012-07-20T18:08:08"" updateDate=""2012-07-20T19:10:52"" nodeName=""Sub 3"" urlName=""sub-3"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1176"" isDoc="""" key=""CDB83BBC-A83B-4BA6-93B8-AADEF67D3C09"">
|
||||
<content><![CDATA[]]></content>
|
||||
<umbracoNaviHide>1</umbracoNaviHide>
|
||||
</Home>
|
||||
</Home>
|
||||
<Home id=""1175"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-20T18:08:01"" updateDate=""2012-07-20T18:49:32"" nodeName=""Sub 2"" urlName=""sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1175"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
<CustomDocument id=""4444"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""3"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""Test"" urlName=""test-page"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,4444"" isDoc="""">
|
||||
<selectedNodes><![CDATA[1172,1176,1173]]></selectedNodes>
|
||||
</CustomDocument>
|
||||
</Home>
|
||||
<CustomDocument id=""1172"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""Test"" urlName=""test-page"" writerName=""admin"" creatorName=""admin"" path=""-1,1172"" isDoc="""" />
|
||||
</root>";
|
||||
base.Setup();
|
||||
|
||||
string xml = PublishedContentXml.PublishedContentTestXml(1234, _node1173Guid);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
_dataTypes = dataTypes;
|
||||
|
||||
// configure the Home content type to be composed of another for tests.
|
||||
var compositionType = new ContentType(TestHelper.ShortStringHelper, -1)
|
||||
{
|
||||
Alias = "MyCompositionAlias"
|
||||
};
|
||||
contentTypes.First(x => x.Alias == "Home").AddContentType(compositionType);
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
}
|
||||
|
||||
internal IPublishedContent GetNode(int id)
|
||||
// override to specify our own factory with custom types
|
||||
protected override IPublishedModelFactory PublishedModelFactory
|
||||
=> _publishedModelFactory ??= new PublishedModelFactory(
|
||||
new[] { typeof(Home), typeof(Anything), typeof(CustomDocument) },
|
||||
PublishedValueFallback);
|
||||
|
||||
[PublishedModel("Home")]
|
||||
internal class Home : PublishedContentModel
|
||||
{
|
||||
var ctx = GetUmbracoContext("/test");
|
||||
var doc = ctx.Content.GetById(id);
|
||||
Assert.IsNotNull(doc);
|
||||
return doc;
|
||||
public Home(IPublishedContent content, IPublishedValueFallback fallback)
|
||||
: base(content, fallback)
|
||||
{ }
|
||||
|
||||
public bool UmbracoNaviHide => this.Value<bool>(Mock.Of<IPublishedValueFallback>(), "umbracoNaviHide");
|
||||
}
|
||||
|
||||
[PublishedModel("anything")]
|
||||
internal class Anything : PublishedContentModel
|
||||
{
|
||||
public Anything(IPublishedContent content, IPublishedValueFallback fallback)
|
||||
: base(content, fallback)
|
||||
{ }
|
||||
}
|
||||
|
||||
[PublishedModel("CustomDocument")]
|
||||
internal class CustomDocument : PublishedContentModel
|
||||
{
|
||||
public CustomDocument(IPublishedContent content, IPublishedValueFallback fallback)
|
||||
: base(content, fallback)
|
||||
{ }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetNodeByIds()
|
||||
{
|
||||
var ctx = GetUmbracoContext("/test");
|
||||
var contentById = ctx.Content.GetById(1173);
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
|
||||
var contentById = snapshot.Content.GetById(1173);
|
||||
Assert.IsNotNull(contentById);
|
||||
var contentByGuid = ctx.Content.GetById(_node1173Guid);
|
||||
var contentByGuid = snapshot.Content.GetById(_node1173Guid);
|
||||
Assert.IsNotNull(contentByGuid);
|
||||
Assert.AreEqual(contentById.Id, contentByGuid.Id);
|
||||
Assert.AreEqual(contentById.Key, contentByGuid.Key);
|
||||
|
||||
contentById = ctx.Content.GetById(666);
|
||||
contentById = snapshot.Content.GetById(666);
|
||||
Assert.IsNull(contentById);
|
||||
contentByGuid = ctx.Content.GetById(Guid.NewGuid());
|
||||
contentByGuid = snapshot.Content.GetById(Guid.NewGuid());
|
||||
Assert.IsNull(contentByGuid);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Is_Last_From_Where_Filter_Dynamic_Linq()
|
||||
{
|
||||
var doc = GetNode(1173);
|
||||
var doc = GetContent(1173);
|
||||
|
||||
var items = doc.Children(VariationContextAccessor).Where(x => x.IsVisible(Mock.Of<IPublishedValueFallback>())).ToIndexedArray();
|
||||
|
||||
@@ -195,11 +110,11 @@ namespace Umbraco.Tests.PublishedContent
|
||||
{
|
||||
if (item.Content.Id != 1178)
|
||||
{
|
||||
Assert.IsFalse(item.IsLast());
|
||||
Assert.IsFalse(item.IsLast(), $"The item {item.Content.Id} is last");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsTrue(item.IsLast());
|
||||
Assert.IsTrue(item.IsLast(), $"The item {item.Content.Id} is not last");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,7 +122,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Is_Last_From_Where_Filter()
|
||||
{
|
||||
var doc = GetNode(1173);
|
||||
var doc = GetContent(1173);
|
||||
|
||||
var items = doc
|
||||
.Children(VariationContextAccessor)
|
||||
@@ -243,30 +158,14 @@ namespace Umbraco.Tests.PublishedContent
|
||||
}
|
||||
}
|
||||
|
||||
[PublishedModel("Home")]
|
||||
internal class Home : PublishedContentModel
|
||||
{
|
||||
public Home(IPublishedContent content, IPublishedValueFallback fallback)
|
||||
: base(content, fallback)
|
||||
{}
|
||||
}
|
||||
|
||||
[PublishedModel("anything")]
|
||||
internal class Anything : PublishedContentModel
|
||||
{
|
||||
public Anything(IPublishedContent content, IPublishedValueFallback fallback)
|
||||
: base(content, fallback)
|
||||
{ }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Is_Last_From_Where_Filter2()
|
||||
{
|
||||
var doc = GetNode(1173);
|
||||
var doc = GetContent(1173);
|
||||
var ct = doc.ContentType;
|
||||
|
||||
var items = doc.Children(VariationContextAccessor)
|
||||
.Select(x => x.CreateModel(Current.PublishedModelFactory)) // linq, returns IEnumerable<IPublishedContent>
|
||||
.Select(x => x.CreateModel(PublishedModelFactory)) // linq, returns IEnumerable<IPublishedContent>
|
||||
|
||||
// only way around this is to make sure every IEnumerable<T> extension
|
||||
// explicitely returns a PublishedContentSet, not an IEnumerable<T>
|
||||
@@ -295,7 +194,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Is_Last_From_Take()
|
||||
{
|
||||
var doc = GetNode(1173);
|
||||
var doc = GetContent(1173);
|
||||
|
||||
var items = doc.Children(VariationContextAccessor).Take(4).ToIndexedArray();
|
||||
|
||||
@@ -315,7 +214,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Is_Last_From_Skip()
|
||||
{
|
||||
var doc = GetNode(1173);
|
||||
var doc = GetContent(1173);
|
||||
|
||||
foreach (var d in doc.Children(VariationContextAccessor).Skip(1).ToIndexedArray())
|
||||
{
|
||||
@@ -333,10 +232,10 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Is_Last_From_Concat()
|
||||
{
|
||||
var doc = GetNode(1173);
|
||||
var doc = GetContent(1173);
|
||||
|
||||
var items = doc.Children(VariationContextAccessor)
|
||||
.Concat(new[] { GetNode(1175), GetNode(4444) })
|
||||
.Concat(new[] { GetContent(1175), GetContent(4444) })
|
||||
.ToIndexedArray();
|
||||
|
||||
foreach (var item in items)
|
||||
@@ -355,7 +254,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Descendants_Ordered_Properly()
|
||||
{
|
||||
var doc = GetNode(1046);
|
||||
var doc = GetContent(1046);
|
||||
|
||||
var expected = new[] { 1046, 1173, 1174, 117, 1177, 1178, 1179, 1176, 1175, 4444, 1172 };
|
||||
var exindex = 0;
|
||||
@@ -370,9 +269,11 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Get_Property_Value_Recursive()
|
||||
{
|
||||
var doc = GetNode(1174);
|
||||
var rVal = doc.Value(Factory.GetRequiredService<IPublishedValueFallback>(), "testRecursive", fallback: Fallback.ToAncestors);
|
||||
var nullVal = doc.Value(Factory.GetRequiredService<IPublishedValueFallback>(), "DoNotFindThis", fallback: Fallback.ToAncestors);
|
||||
// TODO: We need to use a different fallback?
|
||||
|
||||
var doc = GetContent(1174);
|
||||
var rVal = doc.Value(PublishedValueFallback, "testRecursive", fallback: Fallback.ToAncestors);
|
||||
var nullVal = doc.Value(PublishedValueFallback, "DoNotFindThis", fallback: Fallback.ToAncestors);
|
||||
Assert.AreEqual("This is the recursive val", rVal);
|
||||
Assert.AreEqual(null, nullVal);
|
||||
}
|
||||
@@ -380,17 +281,17 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Get_Property_Value_Uses_Converter()
|
||||
{
|
||||
var doc = GetNode(1173);
|
||||
var doc = GetContent(1173);
|
||||
|
||||
var propVal = doc.Value(Mock.Of<IPublishedValueFallback>(), "content");
|
||||
var propVal = doc.Value(PublishedValueFallback, "content");
|
||||
Assert.IsInstanceOf(typeof(IHtmlEncodedString), propVal);
|
||||
Assert.AreEqual("<div>This is some content</div>", propVal.ToString());
|
||||
|
||||
var propVal2 = doc.Value<IHtmlEncodedString>(Mock.Of<IPublishedValueFallback>(), "content");
|
||||
var propVal2 = doc.Value<IHtmlEncodedString>(PublishedValueFallback, "content");
|
||||
Assert.IsInstanceOf(typeof(IHtmlEncodedString), propVal2);
|
||||
Assert.AreEqual("<div>This is some content</div>", propVal2.ToString());
|
||||
|
||||
var propVal3 = doc.Value(Mock.Of<IPublishedValueFallback>(), "Content");
|
||||
var propVal3 = doc.Value(PublishedValueFallback, "Content");
|
||||
Assert.IsInstanceOf(typeof(IHtmlEncodedString), propVal3);
|
||||
Assert.AreEqual("<div>This is some content</div>", propVal3.ToString());
|
||||
}
|
||||
@@ -398,12 +299,12 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Complex_Linq()
|
||||
{
|
||||
var doc = GetNode(1173);
|
||||
var doc = GetContent(1173);
|
||||
|
||||
var result = doc.Ancestors().OrderBy(x => x.Level)
|
||||
.Single()
|
||||
.Descendants(Mock.Of<IVariationContextAccessor>())
|
||||
.FirstOrDefault(x => x.Value<string>(Mock.Of<IPublishedValueFallback>(), "selectedNodes", defaultValue: "").Split(',').Contains("1173"));
|
||||
.FirstOrDefault(x => x.Value<string>(PublishedValueFallback, "selectedNodes", fallback: Fallback.ToDefaultValue, defaultValue: "").Split(',').Contains("1173"));
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
}
|
||||
@@ -411,16 +312,16 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Children_GroupBy_DocumentTypeAlias()
|
||||
{
|
||||
var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { });
|
||||
var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { });
|
||||
var contentTypes = new Dictionary<string, PublishedContentType>
|
||||
{
|
||||
{ home.Alias, home },
|
||||
{ custom.Alias, custom }
|
||||
};
|
||||
ContentTypesCache.GetPublishedContentTypeByAlias = alias => contentTypes[alias];
|
||||
//var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { });
|
||||
//var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { });
|
||||
//var contentTypes = new Dictionary<string, PublishedContentType>
|
||||
//{
|
||||
// { home.Alias, home },
|
||||
// { custom.Alias, custom }
|
||||
//};
|
||||
//ContentTypesCache.GetPublishedContentTypeByAlias = alias => contentTypes[alias];
|
||||
|
||||
var doc = GetNode(1046);
|
||||
var doc = GetContent(1046);
|
||||
|
||||
var found1 = doc.Children(VariationContextAccessor).GroupBy(x => x.ContentType.Alias).ToArray();
|
||||
|
||||
@@ -432,16 +333,16 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Children_Where_DocumentTypeAlias()
|
||||
{
|
||||
var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { });
|
||||
var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { });
|
||||
var contentTypes = new Dictionary<string, PublishedContentType>
|
||||
{
|
||||
{ home.Alias, home },
|
||||
{ custom.Alias, custom }
|
||||
};
|
||||
ContentTypesCache.GetPublishedContentTypeByAlias = alias => contentTypes[alias];
|
||||
//var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { });
|
||||
//var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { });
|
||||
//var contentTypes = new Dictionary<string, PublishedContentType>
|
||||
//{
|
||||
// { home.Alias, home },
|
||||
// { custom.Alias, custom }
|
||||
//};
|
||||
//ContentTypesCache.GetPublishedContentTypeByAlias = alias => contentTypes[alias];
|
||||
|
||||
var doc = GetNode(1046);
|
||||
var doc = GetContent(1046);
|
||||
|
||||
var found1 = doc.Children(VariationContextAccessor).Where(x => x.ContentType.Alias == "CustomDocument");
|
||||
var found2 = doc.Children(VariationContextAccessor).Where(x => x.ContentType.Alias == "Home");
|
||||
@@ -453,7 +354,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Children_Order_By_Update_Date()
|
||||
{
|
||||
var doc = GetNode(1173);
|
||||
var doc = GetContent(1173);
|
||||
|
||||
var ordered = doc.Children(VariationContextAccessor).OrderBy(x => x.UpdateDate);
|
||||
|
||||
@@ -468,12 +369,12 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void FirstChild()
|
||||
{
|
||||
var doc = GetNode(1173); // has child nodes
|
||||
var doc = GetContent(1173); // has child nodes
|
||||
Assert.IsNotNull(doc.FirstChild(Mock.Of<IVariationContextAccessor>()));
|
||||
Assert.IsNotNull(doc.FirstChild(Mock.Of<IVariationContextAccessor>(), x => true));
|
||||
Assert.IsNotNull(doc.FirstChild<IPublishedContent>(Mock.Of<IVariationContextAccessor>()));
|
||||
|
||||
doc = GetNode(1175); // does not have child nodes
|
||||
doc = GetContent(1175); // does not have child nodes
|
||||
Assert.IsNull(doc.FirstChild(Mock.Of<IVariationContextAccessor>()));
|
||||
Assert.IsNull(doc.FirstChild(Mock.Of<IVariationContextAccessor>(), x => true));
|
||||
Assert.IsNull(doc.FirstChild<IPublishedContent>(Mock.Of<IVariationContextAccessor>()));
|
||||
@@ -482,7 +383,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void FirstChildAsT()
|
||||
{
|
||||
var doc = GetNode(1046); // has child nodes
|
||||
var doc = GetContent(1046); // has child nodes
|
||||
|
||||
var model = doc.FirstChild<Home>(Mock.Of<IVariationContextAccessor>(), x => true); // predicate
|
||||
|
||||
@@ -491,7 +392,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Assert.IsInstanceOf<Home>(model);
|
||||
Assert.IsInstanceOf<IPublishedContent>(model);
|
||||
|
||||
doc = GetNode(1175); // does not have child nodes
|
||||
doc = GetContent(1175); // does not have child nodes
|
||||
Assert.IsNull(doc.FirstChild<Anything>(Mock.Of<IVariationContextAccessor>()));
|
||||
Assert.IsNull(doc.FirstChild<Anything>(Mock.Of<IVariationContextAccessor>(), x => true));
|
||||
}
|
||||
@@ -499,7 +400,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void IsComposedOf()
|
||||
{
|
||||
var doc = GetNode(1173);
|
||||
var doc = GetContent(1173);
|
||||
|
||||
var isComposedOf = doc.IsComposedOf("MyCompositionAlias");
|
||||
|
||||
@@ -509,7 +410,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void HasProperty()
|
||||
{
|
||||
var doc = GetNode(1173);
|
||||
var doc = GetContent(1173);
|
||||
|
||||
var hasProp = doc.HasProperty(Constants.Conventions.Content.UrlAlias);
|
||||
|
||||
@@ -519,7 +420,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void HasValue()
|
||||
{
|
||||
var doc = GetNode(1173);
|
||||
var doc = GetContent(1173);
|
||||
|
||||
var hasValue = doc.HasValue(Mock.Of<IPublishedValueFallback>(), Constants.Conventions.Content.UrlAlias);
|
||||
var noValue = doc.HasValue(Mock.Of<IPublishedValueFallback>(), "blahblahblah");
|
||||
@@ -531,7 +432,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Ancestors_Where_Visible()
|
||||
{
|
||||
var doc = GetNode(1174);
|
||||
var doc = GetContent(1174);
|
||||
|
||||
var whereVisible = doc.Ancestors().Where(x => x.IsVisible(Mock.Of<IPublishedValueFallback>()));
|
||||
Assert.AreEqual(1, whereVisible.Count());
|
||||
@@ -541,8 +442,8 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Visible()
|
||||
{
|
||||
var hidden = GetNode(1046);
|
||||
var visible = GetNode(1173);
|
||||
var hidden = GetContent(1046);
|
||||
var visible = GetContent(1173);
|
||||
|
||||
Assert.IsFalse(hidden.IsVisible(Mock.Of<IPublishedValueFallback>()));
|
||||
Assert.IsTrue(visible.IsVisible(Mock.Of<IPublishedValueFallback>()));
|
||||
@@ -551,7 +452,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Ancestor_Or_Self()
|
||||
{
|
||||
var doc = GetNode(1173);
|
||||
var doc = GetContent(1173);
|
||||
|
||||
var result = doc.AncestorOrSelf();
|
||||
|
||||
@@ -564,7 +465,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void U4_4559()
|
||||
{
|
||||
var doc = GetNode(1174);
|
||||
var doc = GetContent(1174);
|
||||
var result = doc.AncestorOrSelf(1);
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(1046, result.Id);
|
||||
@@ -573,27 +474,27 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Ancestors_Or_Self()
|
||||
{
|
||||
var doc = GetNode(1174);
|
||||
var doc = GetContent(1174);
|
||||
|
||||
var result = doc.AncestorsOrSelf().ToArray();
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
Assert.AreEqual(3, result.Length);
|
||||
Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1174, 1173, 1046 }));
|
||||
Assert.IsTrue(result.Select(x => x.Id).ContainsAll(new [] { 1174, 1173, 1046 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Ancestors()
|
||||
{
|
||||
var doc = GetNode(1174);
|
||||
var doc = GetContent(1174);
|
||||
|
||||
var result = doc.Ancestors().ToArray();
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
Assert.AreEqual(2, result.Length);
|
||||
Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1173, 1046 }));
|
||||
Assert.IsTrue(result.Select(x => x.Id).ContainsAll(new [] { 1173, 1046 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -607,12 +508,12 @@ namespace Umbraco.Tests.PublishedContent
|
||||
// -- Custom Doc4: 117 (parent 1173)
|
||||
// - Custom Doc3: 1172 (no parent)
|
||||
|
||||
var home = GetNode(1173);
|
||||
var root = GetNode(1046);
|
||||
var customDoc = GetNode(1178);
|
||||
var customDoc2 = GetNode(1179);
|
||||
var customDoc3 = GetNode(1172);
|
||||
var customDoc4 = GetNode(117);
|
||||
var home = GetContent(1173);
|
||||
var root = GetContent(1046);
|
||||
var customDoc = GetContent(1178);
|
||||
var customDoc2 = GetContent(1179);
|
||||
var customDoc3 = GetContent(1172);
|
||||
var customDoc4 = GetContent(117);
|
||||
|
||||
Assert.IsTrue(root.IsAncestor(customDoc4));
|
||||
Assert.IsFalse(root.IsAncestor(customDoc3));
|
||||
@@ -656,12 +557,12 @@ namespace Umbraco.Tests.PublishedContent
|
||||
// -- Custom Doc4: 117 (parent 1173)
|
||||
// - Custom Doc3: 1172 (no parent)
|
||||
|
||||
var home = GetNode(1173);
|
||||
var root = GetNode(1046);
|
||||
var customDoc = GetNode(1178);
|
||||
var customDoc2 = GetNode(1179);
|
||||
var customDoc3 = GetNode(1172);
|
||||
var customDoc4 = GetNode(117);
|
||||
var home = GetContent(1173);
|
||||
var root = GetContent(1046);
|
||||
var customDoc = GetContent(1178);
|
||||
var customDoc2 = GetContent(1179);
|
||||
var customDoc3 = GetContent(1172);
|
||||
var customDoc4 = GetContent(117);
|
||||
|
||||
Assert.IsTrue(root.IsAncestorOrSelf(customDoc4));
|
||||
Assert.IsFalse(root.IsAncestorOrSelf(customDoc3));
|
||||
@@ -699,27 +600,27 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void Descendants_Or_Self()
|
||||
{
|
||||
var doc = GetNode(1046);
|
||||
var doc = GetContent(1046);
|
||||
|
||||
var result = doc.DescendantsOrSelf(Mock.Of<IVariationContextAccessor>()).ToArray();
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
Assert.AreEqual(10, result.Count());
|
||||
Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1046, 1173, 1174, 1176, 1175 }));
|
||||
Assert.IsTrue(result.Select(x => x.Id).ContainsAll(new [] { 1046, 1173, 1174, 1176, 1175 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Descendants()
|
||||
{
|
||||
var doc = GetNode(1046);
|
||||
var doc = GetContent(1046);
|
||||
|
||||
var result = doc.Descendants(Mock.Of<IVariationContextAccessor>()).ToArray();
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
|
||||
Assert.AreEqual(9, result.Count());
|
||||
Assert.IsTrue(result.Select(x => ((dynamic)x).GetId()).ContainsAll(new dynamic[] { 1173, 1174, 1176, 1175, 4444 }));
|
||||
Assert.IsTrue(result.Select(x => x.Id).ContainsAll(new [] { 1173, 1174, 1176, 1175, 4444 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -733,12 +634,12 @@ namespace Umbraco.Tests.PublishedContent
|
||||
// -- Custom Doc4: 117 (parent 1173)
|
||||
// - Custom Doc3: 1172 (no parent)
|
||||
|
||||
var home = GetNode(1173);
|
||||
var root = GetNode(1046);
|
||||
var customDoc = GetNode(1178);
|
||||
var customDoc2 = GetNode(1179);
|
||||
var customDoc3 = GetNode(1172);
|
||||
var customDoc4 = GetNode(117);
|
||||
var home = GetContent(1173);
|
||||
var root = GetContent(1046);
|
||||
var customDoc = GetContent(1178);
|
||||
var customDoc2 = GetContent(1179);
|
||||
var customDoc3 = GetContent(1172);
|
||||
var customDoc4 = GetContent(117);
|
||||
|
||||
Assert.IsFalse(root.IsDescendant(root));
|
||||
Assert.IsFalse(root.IsDescendant(home));
|
||||
@@ -782,12 +683,12 @@ namespace Umbraco.Tests.PublishedContent
|
||||
// -- Custom Doc4: 117 (parent 1173)
|
||||
// - Custom Doc3: 1172 (no parent)
|
||||
|
||||
var home = GetNode(1173);
|
||||
var root = GetNode(1046);
|
||||
var customDoc = GetNode(1178);
|
||||
var customDoc2 = GetNode(1179);
|
||||
var customDoc3 = GetNode(1172);
|
||||
var customDoc4 = GetNode(117);
|
||||
var home = GetContent(1173);
|
||||
var root = GetContent(1046);
|
||||
var customDoc = GetContent(1178);
|
||||
var customDoc2 = GetContent(1179);
|
||||
var customDoc3 = GetContent(1172);
|
||||
var customDoc4 = GetContent(117);
|
||||
|
||||
Assert.IsTrue(root.IsDescendantOrSelf(root));
|
||||
Assert.IsFalse(root.IsDescendantOrSelf(home));
|
||||
@@ -830,39 +731,40 @@ namespace Umbraco.Tests.PublishedContent
|
||||
// --- Level1.1.2: 117 (parent 1173)
|
||||
// --- Level1.1.3: 1177 (parent 1173)
|
||||
// --- Level1.1.4: 1178 (parent 1173)
|
||||
// ---- Level1.1.4.1: 1179 (parent 1178)
|
||||
// --- Level1.1.5: 1176 (parent 1173)
|
||||
// -- Level1.2: 1175 (parent 1046)
|
||||
// -- Level1.3: 4444 (parent 1046)
|
||||
var root = GetNode(1046);
|
||||
var level1_1 = GetNode(1173);
|
||||
var level1_1_1 = GetNode(1174);
|
||||
var level1_1_2 = GetNode(117);
|
||||
var level1_1_3 = GetNode(1177);
|
||||
var level1_1_4 = GetNode(1178);
|
||||
var level1_1_5 = GetNode(1176);
|
||||
var level1_2 = GetNode(1175);
|
||||
var level1_3 = GetNode(4444);
|
||||
// - Root : 1172 (no parent)
|
||||
|
||||
_publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot(It.IsAny<string>())).Returns(new []{root});
|
||||
var root = GetContent(1046);
|
||||
var level1_1 = GetContent(1173);
|
||||
var level1_1_1 = GetContent(1174);
|
||||
var level1_1_2 = GetContent(117);
|
||||
var level1_1_3 = GetContent(1177);
|
||||
var level1_1_4 = GetContent(1178);
|
||||
var level1_1_5 = GetContent(1176);
|
||||
var level1_2 = GetContent(1175);
|
||||
var level1_3 = GetContent(4444);
|
||||
var root2 = GetContent(1172);
|
||||
|
||||
var variationContextAccessor = Factory.GetRequiredService<IVariationContextAccessor>();
|
||||
var publishedSnapshot = _publishedSnapshotAccessorMock.Object.PublishedSnapshot;
|
||||
var publishedSnapshot = GetPublishedSnapshot();
|
||||
|
||||
CollectionAssertAreEqual(new []{root}, root.SiblingsAndSelf(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { root, root2 }, root.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor));
|
||||
|
||||
CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_1.SiblingsAndSelf(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_2.SiblingsAndSelf(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual( new []{level1_1, level1_2, level1_3}, level1_3.SiblingsAndSelf(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1, level1_2, level1_3 }, level1_1.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1, level1_2, level1_3 }, level1_2.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1, level1_2, level1_3 }, level1_3.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor));
|
||||
|
||||
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_1.SiblingsAndSelf(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_2.SiblingsAndSelf(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_3.SiblingsAndSelf(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_4.SiblingsAndSelf(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_5.SiblingsAndSelf(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5 }, level1_1_1.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5 }, level1_1_2.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5 }, level1_1_3.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5 }, level1_1_4.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_4, level1_1_5 }, level1_1_5.SiblingsAndSelf(publishedSnapshot, VariationContextAccessor));
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Test]
|
||||
public void Siblings()
|
||||
{
|
||||
// Structure:
|
||||
@@ -872,40 +774,41 @@ namespace Umbraco.Tests.PublishedContent
|
||||
// --- Level1.1.2: 117 (parent 1173)
|
||||
// --- Level1.1.3: 1177 (parent 1173)
|
||||
// --- Level1.1.4: 1178 (parent 1173)
|
||||
// ---- Level1.1.4.1: 1179 (parent 1178)
|
||||
// --- Level1.1.5: 1176 (parent 1173)
|
||||
// -- Level1.2: 1175 (parent 1046)
|
||||
// -- Level1.3: 4444 (parent 1046)
|
||||
var root = GetNode(1046);
|
||||
var level1_1 = GetNode(1173);
|
||||
var level1_1_1 = GetNode(1174);
|
||||
var level1_1_2 = GetNode(117);
|
||||
var level1_1_3 = GetNode(1177);
|
||||
var level1_1_4 = GetNode(1178);
|
||||
var level1_1_5 = GetNode(1176);
|
||||
var level1_2 = GetNode(1175);
|
||||
var level1_3 = GetNode(4444);
|
||||
// - Root : 1172 (no parent)
|
||||
|
||||
_publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot.Content.GetAtRoot(It.IsAny<string>())).Returns(new []{root});
|
||||
var root = GetContent(1046);
|
||||
var level1_1 = GetContent(1173);
|
||||
var level1_1_1 = GetContent(1174);
|
||||
var level1_1_2 = GetContent(117);
|
||||
var level1_1_3 = GetContent(1177);
|
||||
var level1_1_4 = GetContent(1178);
|
||||
var level1_1_5 = GetContent(1176);
|
||||
var level1_2 = GetContent(1175);
|
||||
var level1_3 = GetContent(4444);
|
||||
var root2 = GetContent(1172);
|
||||
|
||||
var variationContextAccessor = Factory.GetRequiredService<IVariationContextAccessor>();
|
||||
var publishedSnapshot = _publishedSnapshotAccessorMock.Object.PublishedSnapshot;
|
||||
var publishedSnapshot = GetPublishedSnapshot();
|
||||
|
||||
CollectionAssertAreEqual(new IPublishedContent[0], root.Siblings(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { root2 }, root.Siblings(publishedSnapshot, VariationContextAccessor));
|
||||
|
||||
CollectionAssertAreEqual( new []{level1_2, level1_3}, level1_1.Siblings(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual( new []{level1_1, level1_3}, level1_2.Siblings(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual( new []{level1_1, level1_2}, level1_3.Siblings(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_2, level1_3 }, level1_1.Siblings(publishedSnapshot, VariationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1, level1_3 }, level1_2.Siblings(publishedSnapshot, VariationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1, level1_2 }, level1_3.Siblings(publishedSnapshot, VariationContextAccessor));
|
||||
|
||||
CollectionAssertAreEqual( new []{ level1_1_2, level1_1_3, level1_1_4, level1_1_5}, level1_1_1.Siblings(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual( new []{level1_1_1, level1_1_3, level1_1_4, level1_1_5}, level1_1_2.Siblings(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_4, level1_1_5}, level1_1_3.Siblings(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_5}, level1_1_4.Siblings(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual( new []{level1_1_1, level1_1_2, level1_1_3, level1_1_4}, level1_1_5.Siblings(publishedSnapshot, variationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1_2, level1_1_3, level1_1_4, level1_1_5 }, level1_1_1.Siblings(publishedSnapshot, VariationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1_1, level1_1_3, level1_1_4, level1_1_5 }, level1_1_2.Siblings(publishedSnapshot, VariationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_4, level1_1_5 }, level1_1_3.Siblings(publishedSnapshot, VariationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_5 }, level1_1_4.Siblings(publishedSnapshot, VariationContextAccessor));
|
||||
CollectionAssertAreEqual(new[] { level1_1_1, level1_1_2, level1_1_3, level1_1_4 }, level1_1_5.Siblings(publishedSnapshot, VariationContextAccessor));
|
||||
|
||||
}
|
||||
|
||||
private void CollectionAssertAreEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual)
|
||||
where T: IPublishedContent
|
||||
where T : IPublishedContent
|
||||
{
|
||||
var e = expected.Select(x => x.Id);
|
||||
var a = actual.Select(x => x.Id);
|
||||
@@ -915,37 +818,26 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void FragmentProperty()
|
||||
{
|
||||
var factory = Factory.GetRequiredService<IPublishedContentTypeFactory>() as PublishedContentTypeFactory;
|
||||
|
||||
IEnumerable<IPublishedPropertyType> CreatePropertyTypes(IPublishedContentType contentType)
|
||||
{
|
||||
yield return factory.CreatePropertyType(contentType, "detached", 1003);
|
||||
yield return PublishedContentTypeFactory.CreatePropertyType(contentType, "detached", _dataTypes[0].Id);
|
||||
}
|
||||
|
||||
var ct = factory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes);
|
||||
var ct = PublishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes);
|
||||
var pt = ct.GetPropertyType("detached");
|
||||
var prop = new PublishedElementPropertyBase(pt, null, false, PropertyCacheLevel.None, 5548);
|
||||
Assert.IsInstanceOf<int>(prop.GetValue());
|
||||
Assert.AreEqual(5548, prop.GetValue());
|
||||
}
|
||||
|
||||
public void Fragment1()
|
||||
{
|
||||
var type = ContentTypesCache.Get(PublishedItemType.Content, "detachedSomething");
|
||||
var values = new Dictionary<string, object>();
|
||||
var f = new PublishedElement(type, Guid.NewGuid(), values, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Fragment2()
|
||||
{
|
||||
var factory = Factory.GetRequiredService<IPublishedContentTypeFactory>() as PublishedContentTypeFactory;
|
||||
|
||||
IEnumerable<IPublishedPropertyType> CreatePropertyTypes(IPublishedContentType contentType)
|
||||
{
|
||||
yield return factory.CreatePropertyType(contentType, "legend", 1004);
|
||||
yield return factory.CreatePropertyType(contentType, "image", 1005);
|
||||
yield return factory.CreatePropertyType(contentType, "size", 1003);
|
||||
yield return PublishedContentTypeFactory.CreatePropertyType(contentType, "legend", _dataTypes[0].Id);
|
||||
yield return PublishedContentTypeFactory.CreatePropertyType(contentType, "image", _dataTypes[0].Id);
|
||||
yield return PublishedContentTypeFactory.CreatePropertyType(contentType, "size", _dataTypes[0].Id);
|
||||
}
|
||||
|
||||
const string val1 = "boom bam";
|
||||
@@ -954,7 +846,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
|
||||
var guid = Guid.NewGuid();
|
||||
|
||||
var ct = factory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes);
|
||||
var ct = PublishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes);
|
||||
|
||||
var c = new ImageWithLegendModel(ct, guid, new Dictionary<string, object>
|
||||
{
|
||||
@@ -967,6 +859,87 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Assert.AreEqual(val3, c.Size);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void First()
|
||||
{
|
||||
var publishedSnapshot = GetPublishedSnapshot();
|
||||
var content = publishedSnapshot.Content.GetAtRoot().First();
|
||||
Assert.AreEqual("Home", content.Name(VariationContextAccessor));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Distinct()
|
||||
{
|
||||
var items = GetContent(1173)
|
||||
.Children(VariationContextAccessor)
|
||||
.Distinct()
|
||||
.Distinct()
|
||||
.ToIndexedArray();
|
||||
|
||||
Assert.AreEqual(5, items.Length);
|
||||
|
||||
IndexedArrayItem<IPublishedContent> item = items[0];
|
||||
Assert.AreEqual(1174, item.Content.Id);
|
||||
Assert.IsTrue(item.IsFirst());
|
||||
Assert.IsFalse(item.IsLast());
|
||||
|
||||
item = items[^1];
|
||||
Assert.AreEqual(1176, item.Content.Id);
|
||||
Assert.IsFalse(item.IsFirst());
|
||||
Assert.IsTrue(item.IsLast());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OfType1()
|
||||
{
|
||||
var publishedSnapshot = GetPublishedSnapshot();
|
||||
var items = publishedSnapshot.Content.GetAtRoot()
|
||||
.OfType<Home>()
|
||||
.Distinct()
|
||||
.ToIndexedArray();
|
||||
Assert.AreEqual(1, items.Length);
|
||||
Assert.IsInstanceOf<Home>(items.First().Content);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OfType2()
|
||||
{
|
||||
var publishedSnapshot = GetPublishedSnapshot();
|
||||
var content = publishedSnapshot.Content.GetAtRoot()
|
||||
.OfType<CustomDocument>()
|
||||
.Distinct()
|
||||
.ToIndexedArray();
|
||||
Assert.AreEqual(1, content.Length);
|
||||
Assert.IsInstanceOf<CustomDocument>(content.First().Content);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OfType()
|
||||
{
|
||||
var content = GetContent(1173)
|
||||
.Children(VariationContextAccessor)
|
||||
.OfType<Home>()
|
||||
.First(x => x.UmbracoNaviHide == true);
|
||||
Assert.AreEqual(1176, content.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Position()
|
||||
{
|
||||
var items = GetContent(1173).Children(VariationContextAccessor)
|
||||
.Where(x => x.Value<int?>(Mock.Of<IPublishedValueFallback>(), "umbracoNaviHide") == 0)
|
||||
.ToIndexedArray();
|
||||
|
||||
Assert.AreEqual(3, items.Length);
|
||||
|
||||
Assert.IsTrue(items.First().IsFirst());
|
||||
Assert.IsFalse(items.First().IsLast());
|
||||
Assert.IsFalse(items.Skip(1).First().IsFirst());
|
||||
Assert.IsFalse(items.Skip(1).First().IsLast());
|
||||
Assert.IsFalse(items.Skip(2).First().IsFirst());
|
||||
Assert.IsTrue(items.Skip(2).First().IsLast());
|
||||
}
|
||||
|
||||
class ImageWithLegendModel : PublishedElement
|
||||
{
|
||||
public ImageWithLegendModel(IPublishedContentType contentType, Guid fragmentKey, Dictionary<string, object> values, bool previewing)
|
||||
@@ -980,5 +953,31 @@ namespace Umbraco.Tests.PublishedContent
|
||||
|
||||
public int Size => this.Value<int>(Mock.Of<IPublishedValueFallback>(), "size");
|
||||
}
|
||||
|
||||
//[PublishedModel("ContentType2")]
|
||||
//public class ContentType2 : PublishedContentModel
|
||||
//{
|
||||
// #region Plumbing
|
||||
|
||||
// public ContentType2(IPublishedContent content, IPublishedValueFallback fallback)
|
||||
// : base(content, fallback)
|
||||
// { }
|
||||
|
||||
// #endregion
|
||||
|
||||
// public int Prop1 => this.Value<int>(Mock.Of<IPublishedValueFallback>(), "prop1");
|
||||
//}
|
||||
|
||||
//[PublishedModel("ContentType2Sub")]
|
||||
//public class ContentType2Sub : ContentType2
|
||||
//{
|
||||
// #region Plumbing
|
||||
|
||||
// public ContentType2Sub(IPublishedContent content, IPublishedValueFallback fallback)
|
||||
// : base(content, fallback)
|
||||
// { }
|
||||
|
||||
// #endregion
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
|
||||
using Umbraco.Cms.Infrastructure.Serialization;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests the typed extension methods on IPublishedContent using the DefaultPublishedMediaStore
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class PublishedMediaTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
|
||||
var dataTypes = GetDefaultDataTypes().ToList();
|
||||
var serializer = new ConfigurationEditorJsonSerializer();
|
||||
var rteDataType = new DataType(new VoidEditor("RTE", Mock.Of<IDataValueEditorFactory>()), serializer) { Id = 4 };
|
||||
dataTypes.Add(rteDataType);
|
||||
_dataTypes = dataTypes.ToArray();
|
||||
|
||||
_propertyDataTypes = new()
|
||||
{
|
||||
// defaults will just use the first one
|
||||
[string.Empty] = _dataTypes[0],
|
||||
|
||||
// content uses the RTE
|
||||
["content"] = _dataTypes[1]
|
||||
};
|
||||
}
|
||||
|
||||
private Dictionary<string, IDataType> _propertyDataTypes;
|
||||
private DataType[] _dataTypes;
|
||||
|
||||
private ContentNodeKit CreateRoot(out MediaType mediaType)
|
||||
{
|
||||
mediaType = new MediaType(ShortStringHelper, -1);
|
||||
|
||||
ContentData item1Data = new ContentDataBuilder()
|
||||
.WithName("Content 1")
|
||||
.WithProperties(new PropertyDataBuilder()
|
||||
.WithPropertyData("content", "<div>This is some content</div>")
|
||||
.Build())
|
||||
// build with a dynamically created media type
|
||||
.Build(ShortStringHelper, _propertyDataTypes, mediaType, "image2");
|
||||
|
||||
ContentNodeKit item1 = ContentNodeKitBuilder.CreateWithContent(
|
||||
mediaType.Id,
|
||||
1, "-1,1",
|
||||
draftData: item1Data,
|
||||
publishedData: item1Data);
|
||||
|
||||
return item1;
|
||||
}
|
||||
|
||||
private IEnumerable<ContentNodeKit> CreateChildren(
|
||||
int startId,
|
||||
ContentNodeKit parent,
|
||||
IMediaType mediaType,
|
||||
int count)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var id = startId + i + 1;
|
||||
|
||||
ContentData item1Data = new ContentDataBuilder()
|
||||
.WithName("Child " + id)
|
||||
.WithProperties(new PropertyDataBuilder()
|
||||
.WithPropertyData("content", "<div>This is some content</div>")
|
||||
.Build())
|
||||
.Build();
|
||||
|
||||
var parentPath = parent.Node.Path;
|
||||
|
||||
ContentNodeKit item1 = ContentNodeKitBuilder.CreateWithContent(
|
||||
mediaType.Id,
|
||||
id, $"{parentPath},{id}",
|
||||
draftData: item1Data,
|
||||
publishedData: item1Data);
|
||||
|
||||
yield return item1;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeWithHierarchy(
|
||||
out int rootId,
|
||||
out IReadOnlyList<ContentNodeKit> firstLevelChildren,
|
||||
out IReadOnlyList<ContentNodeKit> secondLevelChildren)
|
||||
{
|
||||
var cache = new List<ContentNodeKit>();
|
||||
var root = CreateRoot(out MediaType mediaType);
|
||||
firstLevelChildren = CreateChildren(10, root, mediaType, 3).ToList();
|
||||
secondLevelChildren = CreateChildren(20, firstLevelChildren[0], mediaType, 3).ToList();
|
||||
cache.Add(root);
|
||||
cache.AddRange(firstLevelChildren);
|
||||
cache.AddRange(secondLevelChildren);
|
||||
InitializedCache(null, null, _dataTypes, cache, new[] { mediaType });
|
||||
rootId = root.Node.Id;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Property_Value_Uses_Converter()
|
||||
{
|
||||
var cache = CreateRoot(out MediaType mediaType);
|
||||
InitializedCache(null, null, _dataTypes.ToArray(), new[] { cache }, new[] { mediaType });
|
||||
|
||||
var publishedMedia = GetMedia(1);
|
||||
|
||||
var propVal = publishedMedia.Value(PublishedValueFallback, "content");
|
||||
Assert.IsInstanceOf<IHtmlEncodedString>(propVal);
|
||||
Assert.AreEqual("<div>This is some content</div>", propVal.ToString());
|
||||
|
||||
var propVal2 = publishedMedia.Value<IHtmlEncodedString>(PublishedValueFallback, "content");
|
||||
Assert.IsInstanceOf<IHtmlEncodedString>(propVal2);
|
||||
Assert.AreEqual("<div>This is some content</div>", propVal2.ToString());
|
||||
|
||||
var propVal3 = publishedMedia.Value(PublishedValueFallback, "Content");
|
||||
Assert.IsInstanceOf<IHtmlEncodedString>(propVal3);
|
||||
Assert.AreEqual("<div>This is some content</div>", propVal3.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Children()
|
||||
{
|
||||
InitializeWithHierarchy(
|
||||
out var rootId,
|
||||
out IReadOnlyList<ContentNodeKit> firstLevelChildren,
|
||||
out IReadOnlyList<ContentNodeKit> secondLevelChildren);
|
||||
|
||||
var publishedMedia = GetMedia(rootId);
|
||||
|
||||
var rootChildren = publishedMedia.Children(VariationContextAccessor);
|
||||
Assert.IsTrue(rootChildren.Select(x => x.Id).ContainsAll(firstLevelChildren.Select(x => x.Node.Id)));
|
||||
|
||||
var publishedChild1 = GetMedia(firstLevelChildren[0].Node.Id);
|
||||
var subChildren = publishedChild1.Children(VariationContextAccessor);
|
||||
Assert.IsTrue(subChildren.Select(x => x.Id).ContainsAll(secondLevelChildren.Select(x => x.Node.Id)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Descendants()
|
||||
{
|
||||
InitializeWithHierarchy(
|
||||
out var rootId,
|
||||
out IReadOnlyList<ContentNodeKit> firstLevelChildren,
|
||||
out IReadOnlyList<ContentNodeKit> secondLevelChildren);
|
||||
|
||||
var publishedMedia = GetMedia(rootId);
|
||||
var rootDescendants = publishedMedia.Descendants(VariationContextAccessor);
|
||||
|
||||
var descendentIds = firstLevelChildren.Select(x => x.Node.Id).Concat(secondLevelChildren.Select(x => x.Node.Id));
|
||||
|
||||
Assert.IsTrue(rootDescendants.Select(x => x.Id).ContainsAll(descendentIds));
|
||||
|
||||
var publishedChild1 = GetMedia(firstLevelChildren[0].Node.Id);
|
||||
var subDescendants = publishedChild1.Descendants(VariationContextAccessor);
|
||||
Assert.IsTrue(subDescendants.Select(x => x.Id).ContainsAll(secondLevelChildren.Select(x => x.Node.Id)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DescendantsOrSelf()
|
||||
{
|
||||
InitializeWithHierarchy(
|
||||
out var rootId,
|
||||
out IReadOnlyList<ContentNodeKit> firstLevelChildren,
|
||||
out IReadOnlyList<ContentNodeKit> secondLevelChildren);
|
||||
|
||||
var publishedMedia = GetMedia(rootId);
|
||||
var rootDescendantsOrSelf = publishedMedia.DescendantsOrSelf(VariationContextAccessor);
|
||||
var descendentAndSelfIds = firstLevelChildren.Select(x => x.Node.Id)
|
||||
.Concat(secondLevelChildren.Select(x => x.Node.Id))
|
||||
.Append(rootId);
|
||||
|
||||
Assert.IsTrue(rootDescendantsOrSelf.Select(x => x.Id).ContainsAll(descendentAndSelfIds));
|
||||
|
||||
var publishedChild1 = GetMedia(firstLevelChildren[0].Node.Id);
|
||||
var subDescendantsOrSelf = publishedChild1.DescendantsOrSelf(VariationContextAccessor);
|
||||
Assert.IsTrue(subDescendantsOrSelf.Select(x => x.Id).ContainsAll(
|
||||
secondLevelChildren.Select(x => x.Node.Id).Append(firstLevelChildren[0].Node.Id)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Parent()
|
||||
{
|
||||
InitializeWithHierarchy(
|
||||
out var rootId,
|
||||
out IReadOnlyList<ContentNodeKit> firstLevelChildren,
|
||||
out IReadOnlyList<ContentNodeKit> secondLevelChildren);
|
||||
|
||||
var publishedMedia = GetMedia(rootId);
|
||||
Assert.AreEqual(null, publishedMedia.Parent);
|
||||
|
||||
var publishedChild1 = GetMedia(firstLevelChildren[0].Node.Id);
|
||||
Assert.AreEqual(publishedMedia.Id, publishedChild1.Parent.Id);
|
||||
|
||||
var publishedSubChild1 = GetMedia(secondLevelChildren[0].Node.Id);
|
||||
Assert.AreEqual(firstLevelChildren[0].Node.Id, publishedSubChild1.Parent.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Ancestors()
|
||||
{
|
||||
InitializeWithHierarchy(
|
||||
out var rootId,
|
||||
out IReadOnlyList<ContentNodeKit> firstLevelChildren,
|
||||
out IReadOnlyList<ContentNodeKit> secondLevelChildren);
|
||||
|
||||
var publishedSubChild1 = GetMedia(secondLevelChildren[0].Node.Id);
|
||||
Assert.IsTrue(publishedSubChild1.Ancestors().Select(x => x.Id)
|
||||
.ContainsAll(new[] { firstLevelChildren[0].Node.Id, rootId }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AncestorsOrSelf()
|
||||
{
|
||||
InitializeWithHierarchy(
|
||||
out var rootId,
|
||||
out IReadOnlyList<ContentNodeKit> firstLevelChildren,
|
||||
out IReadOnlyList<ContentNodeKit> secondLevelChildren);
|
||||
|
||||
var publishedSubChild1 = GetMedia(secondLevelChildren[0].Node.Id);
|
||||
Assert.IsTrue(publishedSubChild1.AncestorsOrSelf().Select(x => x.Id)
|
||||
.ContainsAll(new[] { secondLevelChildren[0].Node.Id, firstLevelChildren[0].Node.Id, rootId }));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services.Changes;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache
|
||||
{
|
||||
|
||||
|
||||
[TestFixture]
|
||||
public class PublishedSnapshotServiceContentTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
private ContentType _contentType;
|
||||
private PropertyType _propertyType;
|
||||
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
|
||||
_propertyType = new PropertyType(TestHelper.ShortStringHelper, "Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.Culture };
|
||||
_contentType = new ContentType(TestHelper.ShortStringHelper, -1) { Id = 2, Alias = "alias-ct", Variations = ContentVariation.Culture };
|
||||
_contentType.AddPropertyType(_propertyType);
|
||||
|
||||
var contentTypes = new[]
|
||||
{
|
||||
_contentType
|
||||
};
|
||||
|
||||
InitializedCache(new[] { CreateKit() }, contentTypes);
|
||||
}
|
||||
|
||||
private ContentNodeKit CreateKit()
|
||||
{
|
||||
var draftData = new ContentDataBuilder()
|
||||
.WithName("It Works2!")
|
||||
.WithPublished(false)
|
||||
.WithProperties(new Dictionary<string, PropertyData[]>
|
||||
{
|
||||
["prop"] = new[]
|
||||
{
|
||||
new PropertyData { Culture = "", Segment = "", Value = "val2" },
|
||||
new PropertyData { Culture = "fr-FR", Segment = "", Value = "val-fr2" },
|
||||
new PropertyData { Culture = "en-UK", Segment = "", Value = "val-uk2" },
|
||||
new PropertyData { Culture = "dk-DA", Segment = "", Value = "val-da2" },
|
||||
new PropertyData { Culture = "de-DE", Segment = "", Value = "val-de2" }
|
||||
}
|
||||
})
|
||||
.WithCultureInfos(new Dictionary<string, CultureVariation>
|
||||
{
|
||||
// draft data = everything, and IsDraft indicates what's edited
|
||||
["fr-FR"] = new CultureVariation { Name = "name-fr2", IsDraft = true, Date = new DateTime(2018, 01, 03, 01, 00, 00) },
|
||||
["en-UK"] = new CultureVariation { Name = "name-uk2", IsDraft = true, Date = new DateTime(2018, 01, 04, 01, 00, 00) },
|
||||
["dk-DA"] = new CultureVariation { Name = "name-da2", IsDraft = true, Date = new DateTime(2018, 01, 05, 01, 00, 00) },
|
||||
["de-DE"] = new CultureVariation { Name = "name-de1", IsDraft = false, Date = new DateTime(2018, 01, 02, 01, 00, 00) }
|
||||
})
|
||||
.Build();
|
||||
|
||||
var publishedData = new ContentDataBuilder()
|
||||
.WithName("It Works1!")
|
||||
.WithPublished(true)
|
||||
.WithProperties(new Dictionary<string, PropertyData[]>
|
||||
{
|
||||
["prop"] = new[]
|
||||
{
|
||||
new PropertyData { Culture = "", Segment = "", Value = "val1" },
|
||||
new PropertyData { Culture = "fr-FR", Segment = "", Value = "val-fr1" },
|
||||
new PropertyData { Culture = "en-UK", Segment = "", Value = "val-uk1" }
|
||||
}
|
||||
})
|
||||
.WithCultureInfos(new Dictionary<string, CultureVariation>
|
||||
{
|
||||
// published data = only what's actually published, and IsDraft has to be false
|
||||
["fr-FR"] = new CultureVariation { Name = "name-fr1", IsDraft = false, Date = new DateTime(2018, 01, 01, 01, 00, 00) },
|
||||
["en-UK"] = new CultureVariation { Name = "name-uk1", IsDraft = false, Date = new DateTime(2018, 01, 02, 01, 00, 00) },
|
||||
["de-DE"] = new CultureVariation { Name = "name-de1", IsDraft = false, Date = new DateTime(2018, 01, 02, 01, 00, 00) }
|
||||
})
|
||||
.Build();
|
||||
|
||||
var kit = ContentNodeKitBuilder.CreateWithContent(
|
||||
2,
|
||||
1, "-1,1", 0,
|
||||
draftData: draftData,
|
||||
publishedData: publishedData);
|
||||
|
||||
return kit;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Verifies_Variant_Data()
|
||||
{
|
||||
// this test implements a full standalone NuCache (based upon a test IDataSource, does not
|
||||
// use any local db files, does not rely on any database) - and tests variations
|
||||
|
||||
// get a snapshot, get a published content
|
||||
IPublishedSnapshot snapshot = GetPublishedSnapshot();
|
||||
IPublishedContent publishedContent = snapshot.Content.GetById(1);
|
||||
|
||||
Assert.IsNotNull(publishedContent);
|
||||
Assert.AreEqual("val1", publishedContent.Value<string>(Mock.Of<IPublishedValueFallback>(), "prop"));
|
||||
Assert.AreEqual("val-fr1", publishedContent.Value<string>(Mock.Of<IPublishedValueFallback>(), "prop", "fr-FR"));
|
||||
Assert.AreEqual("val-uk1", publishedContent.Value<string>(Mock.Of<IPublishedValueFallback>(), "prop", "en-UK"));
|
||||
|
||||
Assert.IsNull(publishedContent.Name(VariationContextAccessor)); // no invariant name for varying content
|
||||
Assert.AreEqual("name-fr1", publishedContent.Name(VariationContextAccessor, "fr-FR"));
|
||||
Assert.AreEqual("name-uk1", publishedContent.Name(VariationContextAccessor, "en-UK"));
|
||||
|
||||
var draftContent = snapshot.Content.GetById(true, 1);
|
||||
Assert.AreEqual("val2", draftContent.Value<string>(Mock.Of<IPublishedValueFallback>(), "prop"));
|
||||
Assert.AreEqual("val-fr2", draftContent.Value<string>(Mock.Of<IPublishedValueFallback>(), "prop", "fr-FR"));
|
||||
Assert.AreEqual("val-uk2", draftContent.Value<string>(Mock.Of<IPublishedValueFallback>(), "prop", "en-UK"));
|
||||
|
||||
Assert.IsNull(draftContent.Name(VariationContextAccessor)); // no invariant name for varying content
|
||||
Assert.AreEqual("name-fr2", draftContent.Name(VariationContextAccessor, "fr-FR"));
|
||||
Assert.AreEqual("name-uk2", draftContent.Name(VariationContextAccessor, "en-UK"));
|
||||
|
||||
// now french is default
|
||||
VariationContextAccessor.VariationContext = new VariationContext("fr-FR");
|
||||
Assert.AreEqual("val-fr1", publishedContent.Value<string>(Mock.Of<IPublishedValueFallback>(), "prop"));
|
||||
Assert.AreEqual("name-fr1", publishedContent.Name(VariationContextAccessor));
|
||||
Assert.AreEqual(new DateTime(2018, 01, 01, 01, 00, 00), publishedContent.CultureDate(VariationContextAccessor));
|
||||
|
||||
// now uk is default
|
||||
VariationContextAccessor.VariationContext = new VariationContext("en-UK");
|
||||
Assert.AreEqual("val-uk1", publishedContent.Value<string>(Mock.Of<IPublishedValueFallback>(), "prop"));
|
||||
Assert.AreEqual("name-uk1", publishedContent.Name(VariationContextAccessor));
|
||||
Assert.AreEqual(new DateTime(2018, 01, 02, 01, 00, 00), publishedContent.CultureDate(VariationContextAccessor));
|
||||
|
||||
// invariant needs to be retrieved explicitly, when it's not default
|
||||
Assert.AreEqual("val1", publishedContent.Value<string>(Mock.Of<IPublishedValueFallback>(), "prop", culture: ""));
|
||||
|
||||
// but,
|
||||
// if the content type / property type does not vary, then it's all invariant again
|
||||
// modify the content type and property type, notify the snapshot service
|
||||
_contentType.Variations = ContentVariation.Nothing;
|
||||
_propertyType.Variations = ContentVariation.Nothing;
|
||||
SnapshotService.Notify(new[] { new ContentTypeCacheRefresher.JsonPayload("IContentType", publishedContent.ContentType.Id, ContentTypeChangeTypes.RefreshMain) });
|
||||
|
||||
// get a new snapshot (nothing changed in the old one), get the published content again
|
||||
var anotherSnapshot = SnapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
var againContent = anotherSnapshot.Content.GetById(1);
|
||||
|
||||
Assert.AreEqual(ContentVariation.Nothing, againContent.ContentType.Variations);
|
||||
Assert.AreEqual(ContentVariation.Nothing, againContent.ContentType.GetPropertyType("prop").Variations);
|
||||
|
||||
// now, "no culture" means "invariant"
|
||||
Assert.AreEqual("It Works1!", againContent.Name(VariationContextAccessor));
|
||||
Assert.AreEqual("val1", againContent.Value<string>(Mock.Of<IPublishedValueFallback>(), "prop"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Verifies_Published_And_Draft_Content()
|
||||
{
|
||||
// get the published published content
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
var c1 = snapshot.Content.GetById(1);
|
||||
|
||||
// published content = nothing is draft here
|
||||
Assert.IsFalse(c1.IsDraft("fr-FR"));
|
||||
Assert.IsFalse(c1.IsDraft("en-UK"));
|
||||
Assert.IsFalse(c1.IsDraft("dk-DA"));
|
||||
Assert.IsFalse(c1.IsDraft("de-DE"));
|
||||
|
||||
// and only those with published name, are published
|
||||
Assert.IsTrue(c1.IsPublished("fr-FR"));
|
||||
Assert.IsTrue(c1.IsPublished("en-UK"));
|
||||
Assert.IsFalse(c1.IsDraft("dk-DA"));
|
||||
Assert.IsTrue(c1.IsPublished("de-DE"));
|
||||
|
||||
// get the draft published content
|
||||
var c2 = snapshot.Content.GetById(true, 1);
|
||||
|
||||
// draft content = we have drafts
|
||||
Assert.IsTrue(c2.IsDraft("fr-FR"));
|
||||
Assert.IsTrue(c2.IsDraft("en-UK"));
|
||||
Assert.IsTrue(c2.IsDraft("dk-DA"));
|
||||
Assert.IsFalse(c2.IsDraft("de-DE")); // except for the one that does not
|
||||
|
||||
// and only those with published name, are published
|
||||
Assert.IsTrue(c2.IsPublished("fr-FR"));
|
||||
Assert.IsTrue(c2.IsPublished("en-UK"));
|
||||
Assert.IsFalse(c2.IsPublished("dk-DA"));
|
||||
Assert.IsTrue(c2.IsPublished("de-DE"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache
|
||||
{
|
||||
|
||||
[TestFixture]
|
||||
public class RootNodeTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
[SetUp]
|
||||
public override void Setup()
|
||||
{
|
||||
base.Setup();
|
||||
|
||||
string xml = PublishedContentXml.TestWithDatabaseXml(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PublishedContentHasNoRootNode()
|
||||
{
|
||||
var snapshot = GetPublishedSnapshot();
|
||||
|
||||
// there is no content node with ID -1
|
||||
var content = snapshot.Content.GetById(-1);
|
||||
Assert.IsNull(content);
|
||||
|
||||
// content at root has null parent
|
||||
content = snapshot.Content.GetById(1046);
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(1, content.Level);
|
||||
Assert.IsNull(content.Parent);
|
||||
|
||||
// non-existing content is null
|
||||
content = snapshot.Content.GetById(666);
|
||||
Assert.IsNull(content);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,23 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Tests.LegacyXmlPublishedCache;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Cms.Infrastructure.PublishedCache;
|
||||
using Umbraco.Cms.Tests.Common.Published;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
|
||||
|
||||
namespace Umbraco.Tests.Routing
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PublishedCache
|
||||
{
|
||||
// purpose: test the values returned by PublishedContentCache.GetRouteById
|
||||
// and .GetByRoute (no caching at all, just routing nice URLs) including all
|
||||
// the quirks due to hideTopLevelFromPath and backward compatibility.
|
||||
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
|
||||
public class UrlRoutesTests : TestWithDatabaseBase
|
||||
public class UrlRoutesTests : PublishedSnapshotServiceTestBase
|
||||
{
|
||||
#region Test Setup
|
||||
|
||||
protected override string GetXmlContent(int templateId)
|
||||
{
|
||||
return @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
private static string GetXmlContent(int templateId)
|
||||
=> @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<!DOCTYPE root[
|
||||
<!ELEMENT Doc ANY>
|
||||
<!ATTLIST Doc id ID #REQUIRED>
|
||||
@@ -48,17 +46,6 @@ namespace Umbraco.Tests.Routing
|
||||
</Doc>
|
||||
</Doc>
|
||||
</root>";
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (FirstTestInFixture)
|
||||
ServiceContext.ContentTypeService.Save(new ContentType(ShortStringHelper, -1) { Alias = "Doc", Name = "name" });
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/*
|
||||
* Just so it's documented somewhere, as of jan. 2017, routes obey the following pseudo-code:
|
||||
@@ -193,11 +180,19 @@ DetermineRouteById(id):
|
||||
[TestCase(2006, false, "/x/b/e")]
|
||||
public void GetRouteByIdNoHide(int id, bool hide, string expected)
|
||||
{
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = hide };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = hide;
|
||||
|
||||
var umbracoContext = GetUmbracoContext("/test", 0, globalSettings: globalSettings);
|
||||
var cache = umbracoContext.Content as PublishedContentCache;
|
||||
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
|
||||
string xml = GetXmlContent(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
var cache = GetPublishedSnapshot().Content;
|
||||
|
||||
var route = cache.GetRouteById(false, id);
|
||||
Assert.AreEqual(expected, route);
|
||||
@@ -216,12 +211,19 @@ DetermineRouteById(id):
|
||||
[TestCase(2006, true, "/b/e")] // risky!
|
||||
public void GetRouteByIdHide(int id, bool hide, string expected)
|
||||
{
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = hide };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = hide;
|
||||
|
||||
var snapshotService = CreatePublishedSnapshotService(globalSettings);
|
||||
var umbracoContext = GetUmbracoContext("/test", 0, globalSettings: globalSettings, snapshotService: snapshotService);
|
||||
var cache = umbracoContext.Content as PublishedContentCache;
|
||||
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
|
||||
string xml = GetXmlContent(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
var cache = GetPublishedSnapshot().Content;
|
||||
|
||||
var route = cache.GetRouteById(false, id);
|
||||
Assert.AreEqual(expected, route);
|
||||
@@ -230,27 +232,23 @@ DetermineRouteById(id):
|
||||
[Test]
|
||||
public void GetRouteByIdCache()
|
||||
{
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
string xml = GetXmlContent(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
var cache = GetPublishedSnapshot().Content;
|
||||
|
||||
var snapshotService = CreatePublishedSnapshotService(globalSettings);
|
||||
var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings, snapshotService: snapshotService);
|
||||
var cache = umbracoContext.Content as PublishedContentCache;
|
||||
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
|
||||
|
||||
var route = cache.GetRouteById(false, 1000);
|
||||
Assert.AreEqual("/a", route);
|
||||
|
||||
// GetRouteById registers a non-trusted route, which is cached for
|
||||
// id -> route queries (fast GetUrl) but *not* for route -> id
|
||||
// queries (safe inbound routing)
|
||||
|
||||
var cachedRoutes = cache.RoutesCache.GetCachedRoutes();
|
||||
Assert.AreEqual(1, cachedRoutes.Count);
|
||||
Assert.IsTrue(cachedRoutes.ContainsKey(1000));
|
||||
Assert.AreEqual("/a", cachedRoutes[1000]);
|
||||
|
||||
var cachedIds = cache.RoutesCache.GetCachedIds();
|
||||
Assert.AreEqual(0, cachedIds.Count);
|
||||
}
|
||||
|
||||
[TestCase("/", false, 1000)]
|
||||
@@ -261,12 +259,19 @@ DetermineRouteById(id):
|
||||
[TestCase("/x", false, 2000)]
|
||||
public void GetByRouteNoHide(string route, bool hide, int expected)
|
||||
{
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = hide };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = hide;
|
||||
|
||||
var snapshotService = CreatePublishedSnapshotService(globalSettings);
|
||||
var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings, snapshotService: snapshotService);
|
||||
var cache = umbracoContext.Content as PublishedContentCache;
|
||||
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
|
||||
string xml = GetXmlContent(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
var cache = GetPublishedSnapshot().Content;
|
||||
|
||||
const bool preview = false; // make sure we don't cache - but HOW? should be some sort of switch?!
|
||||
var content = cache.GetByRoute(preview, route);
|
||||
@@ -292,12 +297,19 @@ DetermineRouteById(id):
|
||||
[TestCase("/b/c", true, 1002)] // (hence the 2005 collision)
|
||||
public void GetByRouteHide(string route, bool hide, int expected)
|
||||
{
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = hide };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = hide;
|
||||
|
||||
var snapshotService = CreatePublishedSnapshotService(globalSettings);
|
||||
var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings, snapshotService: snapshotService);
|
||||
var cache = umbracoContext.Content as PublishedContentCache;
|
||||
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
|
||||
string xml = GetXmlContent(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
var cache = GetPublishedSnapshot().Content;
|
||||
|
||||
const bool preview = false; // make sure we don't cache - but HOW? should be some sort of switch?!
|
||||
var content = cache.GetByRoute(preview, route);
|
||||
@@ -315,30 +327,23 @@ DetermineRouteById(id):
|
||||
[Test]
|
||||
public void GetByRouteCache()
|
||||
{
|
||||
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false };
|
||||
GlobalSettings.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var snapshotService = CreatePublishedSnapshotService(globalSettings);
|
||||
var umbracoContext = GetUmbracoContext("/test", 0, globalSettings:globalSettings, snapshotService:snapshotService);
|
||||
var cache = umbracoContext.Content as PublishedContentCache;
|
||||
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
|
||||
string xml = GetXmlContent(1234);
|
||||
|
||||
IEnumerable<ContentNodeKit> kits = PublishedContentXmlAdapter.GetContentNodeKits(
|
||||
xml,
|
||||
TestHelper.ShortStringHelper,
|
||||
out ContentType[] contentTypes,
|
||||
out DataType[] dataTypes).ToList();
|
||||
|
||||
InitializedCache(kits, contentTypes, dataTypes: dataTypes);
|
||||
|
||||
var cache = GetPublishedSnapshot().Content;
|
||||
|
||||
var content = cache.GetByRoute(false, "/a/b/c");
|
||||
Assert.IsNotNull(content);
|
||||
Assert.AreEqual(1002, content.Id);
|
||||
|
||||
// GetByRoute registers a trusted route, which is cached both for
|
||||
// id -> route queries (fast GetUrl) and for route -> id queries
|
||||
// (fast inbound routing)
|
||||
|
||||
var cachedRoutes = cache.RoutesCache.GetCachedRoutes();
|
||||
Assert.AreEqual(1, cachedRoutes.Count);
|
||||
Assert.IsTrue(cachedRoutes.ContainsKey(1002));
|
||||
Assert.AreEqual("/a/b/c", cachedRoutes[1002]);
|
||||
|
||||
var cachedIds = cache.RoutesCache.GetCachedIds();
|
||||
Assert.AreEqual(1, cachedIds.Count);
|
||||
Assert.IsTrue(cachedIds.ContainsKey("/a/b/c"));
|
||||
Assert.AreEqual(1002, cachedIds["/a/b/c"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Serialization;
|
||||
using Umbraco.Core.Serialization;
|
||||
using Umbraco.Cms.Infrastructure.Serialization;
|
||||
|
||||
namespace Umbraco.Tests.Serialization
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Serialization
|
||||
{
|
||||
[TestFixture]
|
||||
public class AutoInterningStringConverterTests
|
||||
@@ -61,7 +58,7 @@ namespace Umbraco.Tests.Serialization
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonConverter(typeof(AutoInterningStringKeyCaseInsensitiveDictionaryConverter<int>))]
|
||||
public Dictionary<string, int> Values = new Dictionary<string, int>();
|
||||
public Dictionary<string, int> Values { get; set; } = new Dictionary<string, int>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Tests.LegacyXmlPublishedCache;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Tests.Cache.PublishedCache
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
|
||||
public class PublishContentCacheTests : BaseWebTest
|
||||
{
|
||||
private FakeHttpContextFactory _httpContextFactory;
|
||||
private IUmbracoContext _umbracoContext;
|
||||
private IPublishedContentCache _cache;
|
||||
private XmlDocument _xml;
|
||||
|
||||
private string GetXml()
|
||||
{
|
||||
return @"<?xml version=""1.0"" encoding=""utf-8""?><!DOCTYPE root[
|
||||
<!ELEMENT Home ANY>
|
||||
<!ATTLIST Home id ID #REQUIRED>
|
||||
|
||||
]>
|
||||
<root id=""-1"">
|
||||
<Home id=""1046"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""2"" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" nodeName=""Home"" urlName=""home"" writerName=""admin"" creatorName=""admin"" path=""-1,1046"" isDoc=""""><content><![CDATA[]]></content>
|
||||
<Home id=""1173"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""1"" createDate=""2012-07-20T18:06:45"" updateDate=""2012-07-20T19:07:31"" nodeName=""Sub1"" urlName=""sub1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173"" isDoc=""""><content><![CDATA[]]></content>
|
||||
<Home id=""1174"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""1"" createDate=""2012-07-20T18:07:54"" updateDate=""2012-07-20T19:10:27"" nodeName=""Sub2"" urlName=""sub2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1174"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
<Home id=""1176"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""2"" createDate=""2012-07-20T18:08:08"" updateDate=""2012-07-20T19:10:52"" nodeName=""Sub 3"" urlName=""sub-3"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1176"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
</Home>
|
||||
<Home id=""1175"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""2"" createDate=""2012-07-20T18:08:01"" updateDate=""2012-07-20T18:49:32"" nodeName=""Sub 2"" urlName=""sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1175"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
<Home id=""1177"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""2"" createDate=""2012-07-20T18:08:01"" updateDate=""2012-07-20T18:49:32"" nodeName=""Sub'Apostrophe"" urlName=""sub'apostrophe"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1177"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
</Home>
|
||||
<Home id=""1172"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""1045"" sortOrder=""3"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""Test"" urlName=""test"" writerName=""admin"" creatorName=""admin"" path=""-1,1172"" isDoc="""" />
|
||||
</root>";
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_httpContextFactory = new FakeHttpContextFactory("~/Home");
|
||||
|
||||
var globalSettings = new GlobalSettings();
|
||||
var umbracoContextAccessor = Factory.GetRequiredService<IUmbracoContextAccessor>();
|
||||
|
||||
_xml = new XmlDocument();
|
||||
_xml.LoadXml(GetXml());
|
||||
var xmlStore = new XmlStore(() => _xml, null, null, null, HostingEnvironment);
|
||||
var appCache = new DictionaryAppCache();
|
||||
var domainCache = new DomainCache(Mock.Of<IDomainService>(), DefaultCultureAccessor);
|
||||
var publishedShapshot = new PublishedSnapshot(
|
||||
new PublishedContentCache(xmlStore, domainCache, appCache, globalSettings, ContentTypesCache, null, VariationContextAccessor, null),
|
||||
new PublishedMediaCache(xmlStore, Mock.Of<IMediaService>(), Mock.Of<IUserService>(), appCache, ContentTypesCache, Factory.GetRequiredService<IEntityXmlSerializer>(), umbracoContextAccessor, VariationContextAccessor),
|
||||
new PublishedMemberCache(ContentTypesCache, VariationContextAccessor),
|
||||
domainCache);
|
||||
var publishedSnapshotService = new Mock<IPublishedSnapshotService>();
|
||||
publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny<string>())).Returns(publishedShapshot);
|
||||
|
||||
var httpContext = _httpContextFactory.HttpContext;
|
||||
var httpContextAccessor = TestHelper.GetHttpContextAccessor(httpContext);
|
||||
_umbracoContext = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
publishedSnapshotService.Object,
|
||||
Mock.Of<IBackOfficeSecurity>(),
|
||||
globalSettings,
|
||||
HostingEnvironment,
|
||||
new TestVariationContextAccessor(),
|
||||
UriUtility,
|
||||
new AspNetCookieManager(httpContextAccessor));
|
||||
|
||||
_cache = _umbracoContext.Content;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Has_Content()
|
||||
{
|
||||
Assert.IsTrue(_cache.HasContent());
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void Get_Root_Docs()
|
||||
{
|
||||
var result = _cache.GetAtRoot();
|
||||
Assert.AreEqual(2, result.Count());
|
||||
Assert.AreEqual(1046, result.ElementAt(0).Id);
|
||||
Assert.AreEqual(1172, result.ElementAt(1).Id);
|
||||
}
|
||||
|
||||
|
||||
[TestCase("/", 1046)]
|
||||
[TestCase("/home", 1046)]
|
||||
[TestCase("/Home", 1046)] //test different cases
|
||||
[TestCase("/home/sub1", 1173)]
|
||||
[TestCase("/Home/sub1", 1173)]
|
||||
[TestCase("/home/Sub1", 1173)] //test different cases
|
||||
[TestCase("/home/Sub'Apostrophe", 1177)]
|
||||
public void Get_Node_By_Route(string route, int nodeId)
|
||||
{
|
||||
var result = _cache.GetByRoute(route, false);
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(nodeId, result.Id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[TestCase("/", 1046)]
|
||||
[TestCase("/sub1", 1173)]
|
||||
[TestCase("/Sub1", 1173)]
|
||||
public void Get_Node_By_Route_Hiding_Top_Level_Nodes(string route, int nodeId)
|
||||
{
|
||||
var result = _cache.GetByRoute(route, true);
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(nodeId, result.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,413 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using Examine;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Tests.LegacyXmlPublishedCache;
|
||||
using Umbraco.Tests.PublishedContent;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
using Current = Umbraco.Web.Composing.Current;
|
||||
|
||||
namespace Umbraco.Tests.Cache.PublishedCache
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
|
||||
public class PublishMediaCacheTests : BaseWebTest
|
||||
{
|
||||
private Dictionary<string, PublishedContentType> _mediaTypes;
|
||||
private int _testWriterAndCreatorId;
|
||||
|
||||
private IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
|
||||
Builder.WithCollectionBuilder<UrlSegmentProviderCollectionBuilder>()
|
||||
.Clear()
|
||||
.Append<DefaultUrlSegmentProvider>();
|
||||
|
||||
_umbracoContextAccessor = Current.UmbracoContextAccessor;
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
var type = new AutoPublishedContentType(Guid.NewGuid(), 22, "myType", new PublishedPropertyType[] { });
|
||||
var image = new AutoPublishedContentType(Guid.NewGuid(), 23, "Image", new PublishedPropertyType[] { });
|
||||
var testMediaType = new AutoPublishedContentType(Guid.NewGuid(), 24, "TestMediaType", new PublishedPropertyType[] { });
|
||||
_mediaTypes = new Dictionary<string, PublishedContentType>
|
||||
{
|
||||
{ type.Alias, type },
|
||||
{ image.Alias, image },
|
||||
{ testMediaType.Alias, testMediaType }
|
||||
};
|
||||
ContentTypesCache.GetPublishedContentTypeByAlias = alias => _mediaTypes[alias];
|
||||
|
||||
_testWriterAndCreatorId = ServiceContext.UserService.CreateUserWithIdentity("Shannon", "test").Id;
|
||||
}
|
||||
|
||||
private IMediaType MakeNewMediaType(IUser user, string text, int parentId = -1)
|
||||
{
|
||||
var mt = new MediaType(ShortStringHelper, parentId) { Name = text, Alias = text, Thumbnail = "icon-folder", Icon = "icon-folder" };
|
||||
ServiceContext.MediaTypeService.Save(mt);
|
||||
return mt;
|
||||
}
|
||||
|
||||
private IMedia MakeNewMedia(string name, IMediaType mediaType, IUser user, int parentId)
|
||||
{
|
||||
var m = ServiceContext.MediaService.CreateMediaWithIdentity(name, parentId, mediaType.Alias);
|
||||
return m;
|
||||
}
|
||||
|
||||
//NOTE: This is "Without_Examine" too
|
||||
[Test]
|
||||
public void Get_Root_Docs()
|
||||
{
|
||||
var user = ServiceContext.UserService.GetUserById(0);
|
||||
var mType = MakeNewMediaType(user, "TestMediaType");
|
||||
var mRoot1 = MakeNewMedia("MediaRoot1", mType, user, -1);
|
||||
var mRoot2 = MakeNewMedia("MediaRoot2", mType, user, -1);
|
||||
var mChild1 = MakeNewMedia("Child1", mType, user, mRoot1.Id);
|
||||
var mChild2 = MakeNewMedia("Child2", mType, user, mRoot2.Id);
|
||||
|
||||
var ctx = GetUmbracoContext("/test");
|
||||
var cache = new PublishedMediaCache(new XmlStore((XmlDocument) null, null, null, null, HostingEnvironment), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService<IEntityXmlSerializer>(), Factory.GetRequiredService<IUmbracoContextAccessor>(), VariationContextAccessor);
|
||||
var roots = cache.GetAtRoot();
|
||||
Assert.AreEqual(2, roots.Count());
|
||||
Assert.IsTrue(roots.Select(x => x.Id).ContainsAll(new[] {mRoot1.Id, mRoot2.Id}));
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Item_Without_Examine()
|
||||
{
|
||||
var user = ServiceContext.UserService.GetUserById(0);
|
||||
var mType = MakeNewMediaType(user, "TestMediaType");
|
||||
_mediaTypes[mType.Alias] = new PublishedContentType(mType, null);
|
||||
var mRoot = MakeNewMedia("MediaRoot", mType, user, -1);
|
||||
var mChild1 = MakeNewMedia("Child1", mType, user, mRoot.Id);
|
||||
|
||||
//var publishedMedia = PublishedMediaTests.GetNode(mRoot.Id, GetUmbracoContext("/test", 1234));
|
||||
var umbracoContext = GetUmbracoContext("/test");
|
||||
var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null, HostingEnvironment), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService<IEntityXmlSerializer>(), Factory.GetRequiredService<IUmbracoContextAccessor>(), VariationContextAccessor);
|
||||
var publishedMedia = cache.GetById(mRoot.Id);
|
||||
Assert.IsNotNull(publishedMedia);
|
||||
|
||||
Assert.AreEqual(mRoot.Id, publishedMedia.Id);
|
||||
Assert.AreEqual(mRoot.CreateDate.ToString("dd/MM/yyyy HH:mm:ss"), publishedMedia.CreateDate.ToString("dd/MM/yyyy HH:mm:ss"));
|
||||
Assert.AreEqual(mRoot.CreatorId, publishedMedia.CreatorId);
|
||||
//Assert.AreEqual(mRoot.User.Name, publishedMedia.CreatorName);
|
||||
Assert.AreEqual(mRoot.ContentType.Alias, publishedMedia.ContentType.Alias);
|
||||
Assert.AreEqual(mRoot.ContentType.Id, publishedMedia.ContentType.Id);
|
||||
Assert.AreEqual(mRoot.Level, publishedMedia.Level);
|
||||
Assert.AreEqual(mRoot.Name, publishedMedia.Name);
|
||||
Assert.AreEqual(mRoot.Path, publishedMedia.Path);
|
||||
Assert.AreEqual(mRoot.SortOrder, publishedMedia.SortOrder);
|
||||
Assert.IsNull(publishedMedia.Parent);
|
||||
}
|
||||
|
||||
[TestCase("id")]
|
||||
[TestCase("__NodeId")]
|
||||
public void DictionaryDocument_Id_Keys(string key)
|
||||
{
|
||||
var dicDoc = GetDictionaryDocument(idKey: key);
|
||||
DoAssert(dicDoc);
|
||||
}
|
||||
|
||||
[TestCase("template")]
|
||||
[TestCase("templateId")]
|
||||
public void DictionaryDocument_Template_Keys(string key)
|
||||
{
|
||||
var dicDoc = GetDictionaryDocument(templateKey: key);
|
||||
DoAssert(dicDoc);
|
||||
}
|
||||
|
||||
[TestCase("nodeName")]
|
||||
public void DictionaryDocument_NodeName_Keys(string key)
|
||||
{
|
||||
var dicDoc = GetDictionaryDocument(nodeNameKey: key);
|
||||
DoAssert(dicDoc);
|
||||
}
|
||||
|
||||
[TestCase("nodeTypeAlias")]
|
||||
[TestCase("__NodeTypeAlias")]
|
||||
public void DictionaryDocument_NodeTypeAlias_Keys(string key)
|
||||
{
|
||||
var dicDoc = GetDictionaryDocument(nodeTypeAliasKey: key);
|
||||
DoAssert(dicDoc);
|
||||
}
|
||||
|
||||
[TestCase("path")]
|
||||
[TestCase("__Path")]
|
||||
public void DictionaryDocument_Path_Keys(string key)
|
||||
{
|
||||
var dicDoc = GetDictionaryDocument(pathKey: key);
|
||||
DoAssert(dicDoc);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DictionaryDocument_Key()
|
||||
{
|
||||
var key = Guid.NewGuid();
|
||||
var dicDoc = GetDictionaryDocument(keyVal: key);
|
||||
DoAssert(dicDoc, keyVal: key);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DictionaryDocument_Get_Children()
|
||||
{
|
||||
var child1 = GetDictionaryDocument(idVal: 222333);
|
||||
var child2 = GetDictionaryDocument(idVal: 444555);
|
||||
|
||||
var dicDoc = GetDictionaryDocument(children: new List<IPublishedContent>()
|
||||
{
|
||||
child1, child2
|
||||
});
|
||||
|
||||
Assert.AreEqual(2, dicDoc.Children.Count());
|
||||
Assert.AreEqual(222333, dicDoc.Children.ElementAt(0).Id);
|
||||
Assert.AreEqual(444555, dicDoc.Children.ElementAt(1).Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Convert_From_Search_Result()
|
||||
{
|
||||
var ctx = GetUmbracoContext("/test");
|
||||
var key = Guid.NewGuid();
|
||||
|
||||
var fields = new Dictionary<string, string>
|
||||
{
|
||||
{"__IndexType", "media"},
|
||||
{"__NodeId", "1234"},
|
||||
{"__NodeTypeAlias", Constants.Conventions.MediaTypes.Image},
|
||||
{"__Path", "-1,1234"},
|
||||
{"__nodeName", "Test"},
|
||||
{"id", "1234"},
|
||||
{"key", key.ToString()},
|
||||
{"urlName", "/media/test.jpg"},
|
||||
{"nodeType", "0"},
|
||||
{"sortOrder", "0"},
|
||||
{"level", "2"},
|
||||
{"nodeName", "Test"},
|
||||
{"nodeTypeAlias", Constants.Conventions.MediaTypes.Image},
|
||||
{"parentID", "-1"},
|
||||
{"path", "-1,1234"},
|
||||
{"updateDate", DateTime.Parse("2012-07-16T10:34:09").Ticks.ToString()},
|
||||
{"createDate", DateTime.Parse("2012-07-17T10:34:09").Ticks.ToString()},
|
||||
{"creatorID", _testWriterAndCreatorId.ToString()},
|
||||
{"creatorName", "Shannon"}
|
||||
};
|
||||
|
||||
var result = new SearchResult("1234", 1, () => fields.ToDictionary(x => x.Key, x => new List<string> { x.Value }));
|
||||
|
||||
var store = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null, HostingEnvironment), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService<IEntityXmlSerializer>(), Factory.GetRequiredService<IUmbracoContextAccessor>(), VariationContextAccessor);
|
||||
var doc = store.CreateFromCacheValues(store.ConvertFromSearchResult(result));
|
||||
|
||||
DoAssert(doc, 1234, key, null, 0, "/media/test.jpg", "Image", 23, "Shannon", "Shannon", "-1,1234", DateTime.Parse("2012-07-17T10:34:09"), DateTime.Parse("2012-07-16T10:34:09"), 2);
|
||||
Assert.AreEqual(null, doc.Parent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Convert_From_XPath_Navigator()
|
||||
{
|
||||
var ctx = GetUmbracoContext("/test");
|
||||
var key = Guid.NewGuid();
|
||||
|
||||
var xmlDoc = GetMediaXml();
|
||||
((XmlElement)xmlDoc.DocumentElement.FirstChild).SetAttribute("key", key.ToString());
|
||||
var navigator = xmlDoc.SelectSingleNode("/root/Image").CreateNavigator();
|
||||
var cache = new PublishedMediaCache(new XmlStore((XmlDocument)null, null, null, null, HostingEnvironment), ServiceContext.MediaService, ServiceContext.UserService, new DictionaryAppCache(), ContentTypesCache, Factory.GetRequiredService<IEntityXmlSerializer>(), Factory.GetRequiredService<IUmbracoContextAccessor>(),VariationContextAccessor);
|
||||
var doc = cache.CreateFromCacheValues(cache.ConvertFromXPathNavigator(navigator, true));
|
||||
|
||||
DoAssert(doc, 2000, key, null, 2, "image1", "Image", 23, "Shannon", "Shannon", "-1,2000", DateTime.Parse("2012-06-12T14:13:17"), DateTime.Parse("2012-07-20T18:50:43"), 1);
|
||||
Assert.AreEqual(null, doc.Parent);
|
||||
Assert.AreEqual(2, doc.Children.Count());
|
||||
Assert.AreEqual(2001, doc.Children.ElementAt(0).Id);
|
||||
Assert.AreEqual(2002, doc.Children.ElementAt(1).Id);
|
||||
}
|
||||
|
||||
private XmlDocument GetMediaXml()
|
||||
{
|
||||
var xml = @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<!DOCTYPE root[
|
||||
<!ELEMENT Home ANY>
|
||||
<!ATTLIST Home id ID #REQUIRED>
|
||||
<!ELEMENT CustomDocument ANY>
|
||||
<!ATTLIST CustomDocument id ID #REQUIRED>
|
||||
]>
|
||||
<root id=""-1"">
|
||||
<Image id=""2000"" parentID=""-1"" level=""1"" writerID=""[WriterId]"" creatorID=""[CreatorId]"" nodeType=""2044"" template=""0"" sortOrder=""2"" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" nodeName=""Image1"" urlName=""image1"" path=""-1,2000"" isDoc="""">
|
||||
<file><![CDATA[/media/1234/image1.png]]></file>
|
||||
<Image id=""2001"" parentID=""2000"" level=""2"" writerID=""[WriterId]"" creatorID=""[CreatorId]"" nodeType=""2044"" template=""0"" sortOrder=""2"" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" nodeName=""Image1"" urlName=""image1"" path=""-1,2000,2001"" isDoc="""">
|
||||
<file><![CDATA[/media/1234/image1.png]]></file>
|
||||
</Image>
|
||||
<Image id=""2002"" parentID=""2000"" level=""2"" writerID=""[WriterId]"" creatorID=""[CreatorId]"" nodeType=""2044"" template=""0"" sortOrder=""2"" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" nodeName=""Image1"" urlName=""image1"" path=""-1,2000,2002"" isDoc="""">
|
||||
<file><![CDATA[/media/1234/image1.png]]></file>
|
||||
</Image>
|
||||
</Image>
|
||||
</root>";
|
||||
xml = xml.Replace("[WriterId]", _testWriterAndCreatorId.ToString());
|
||||
xml = xml.Replace("[CreatorId]", _testWriterAndCreatorId.ToString());
|
||||
|
||||
var xmlDoc = new XmlDocument();
|
||||
xmlDoc.LoadXml(xml);
|
||||
return xmlDoc;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> GetDictionary(
|
||||
int id,
|
||||
Guid key,
|
||||
int parentId,
|
||||
string idKey,
|
||||
string templateKey,
|
||||
string nodeNameKey,
|
||||
string nodeTypeAliasKey,
|
||||
string pathKey)
|
||||
{
|
||||
return new Dictionary<string, string>()
|
||||
{
|
||||
{idKey, id.ToString()},
|
||||
{"key", key.ToString()},
|
||||
{templateKey, "0"},
|
||||
{"sortOrder", "44"},
|
||||
{nodeNameKey, "Testing"},
|
||||
{"urlName", "testing"},
|
||||
{nodeTypeAliasKey, "myType"},
|
||||
{"nodeType", "22"},
|
||||
{"writerID", _testWriterAndCreatorId.ToString()},
|
||||
{"creatorID", _testWriterAndCreatorId.ToString()},
|
||||
{pathKey, "1,2,3,4,5"},
|
||||
{"createDate", "2012-01-02"},
|
||||
{"updateDate", "2012-01-02"},
|
||||
{"level", "3"},
|
||||
{"parentID", parentId.ToString()}
|
||||
};
|
||||
}
|
||||
|
||||
private DictionaryPublishedContent GetDictionaryDocument(
|
||||
string idKey = "id",
|
||||
string templateKey = "template",
|
||||
string nodeNameKey = "nodeName",
|
||||
string nodeTypeAliasKey = "nodeTypeAlias",
|
||||
string pathKey = "path",
|
||||
int idVal = 1234,
|
||||
Guid keyVal = default(Guid),
|
||||
int parentIdVal = 321,
|
||||
IEnumerable<IPublishedContent> children = null)
|
||||
{
|
||||
if (children == null)
|
||||
children = new List<IPublishedContent>();
|
||||
var dicDoc = new DictionaryPublishedContent(
|
||||
//the dictionary
|
||||
GetDictionary(idVal, keyVal, parentIdVal, idKey, templateKey, nodeNameKey, nodeTypeAliasKey, pathKey),
|
||||
//callback to get the parent
|
||||
d => new DictionaryPublishedContent(
|
||||
// the dictionary
|
||||
GetDictionary(parentIdVal, default(Guid), -1, idKey, templateKey, nodeNameKey, nodeTypeAliasKey, pathKey),
|
||||
// callback to get the parent: there is no parent
|
||||
a => null,
|
||||
// callback to get the children: we're not going to test this so ignore
|
||||
(dd, n) => new List<IPublishedContent>(),
|
||||
// callback to get a property
|
||||
(dd, a) => dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(a)),
|
||||
null, // cache provider
|
||||
VariationContextAccessor,
|
||||
ContentTypesCache,
|
||||
// no xpath
|
||||
null,
|
||||
// not from examine
|
||||
false),
|
||||
//callback to get the children
|
||||
(dd, n) => children,
|
||||
// callback to get a property
|
||||
(dd, a) => dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(a)),
|
||||
null, // cache provider
|
||||
VariationContextAccessor,
|
||||
ContentTypesCache,
|
||||
// no xpath
|
||||
null,
|
||||
// not from examine
|
||||
false);
|
||||
return dicDoc;
|
||||
}
|
||||
|
||||
private void DoAssert(
|
||||
DictionaryPublishedContent dicDoc,
|
||||
int idVal = 1234,
|
||||
Guid keyVal = default(Guid),
|
||||
int? templateIdVal = null,
|
||||
int sortOrderVal = 44,
|
||||
string urlNameVal = "testing",
|
||||
string nodeTypeAliasVal = "myType",
|
||||
int nodeTypeIdVal = 22,
|
||||
string writerNameVal = "Shannon",
|
||||
string creatorNameVal = "Shannon",
|
||||
string pathVal = "1,2,3,4,5",
|
||||
DateTime? createDateVal = null,
|
||||
DateTime? updateDateVal = null,
|
||||
int levelVal = 3,
|
||||
int parentIdVal = 321)
|
||||
{
|
||||
if (!createDateVal.HasValue)
|
||||
createDateVal = DateTime.Parse("2012-01-02");
|
||||
if (!updateDateVal.HasValue)
|
||||
updateDateVal = DateTime.Parse("2012-01-02");
|
||||
|
||||
DoAssert((IPublishedContent)dicDoc, idVal, keyVal, templateIdVal, sortOrderVal, urlNameVal, nodeTypeAliasVal, nodeTypeIdVal, writerNameVal,
|
||||
creatorNameVal, pathVal, createDateVal, updateDateVal, levelVal);
|
||||
|
||||
//now validate the parentId that has been parsed, this doesn't exist on the IPublishedContent
|
||||
Assert.AreEqual(parentIdVal, dicDoc.ParentId);
|
||||
}
|
||||
|
||||
private void DoAssert(
|
||||
IPublishedContent doc,
|
||||
int idVal = 1234,
|
||||
Guid keyVal = default(Guid),
|
||||
int? templateIdVal = null,
|
||||
int sortOrderVal = 44,
|
||||
string urlNameVal = "testing",
|
||||
string nodeTypeAliasVal = "myType",
|
||||
int nodeTypeIdVal = 22,
|
||||
string writerNameVal = "Shannon",
|
||||
string creatorNameVal = "Shannon",
|
||||
string pathVal = "1,2,3,4,5",
|
||||
DateTime? createDateVal = null,
|
||||
DateTime? updateDateVal = null,
|
||||
int levelVal = 3)
|
||||
{
|
||||
if (!createDateVal.HasValue)
|
||||
createDateVal = DateTime.Parse("2012-01-02");
|
||||
if (!updateDateVal.HasValue)
|
||||
updateDateVal = DateTime.Parse("2012-01-02");
|
||||
|
||||
Assert.AreEqual(idVal, doc.Id);
|
||||
Assert.AreEqual(keyVal, doc.Key);
|
||||
Assert.AreEqual(templateIdVal, doc.TemplateId);
|
||||
Assert.AreEqual(sortOrderVal, doc.SortOrder);
|
||||
Assert.AreEqual(urlNameVal, doc.UrlSegment);
|
||||
Assert.AreEqual(nodeTypeAliasVal, doc.ContentType.Alias);
|
||||
Assert.AreEqual(nodeTypeIdVal, doc.ContentType.Id);
|
||||
Assert.AreEqual(writerNameVal, doc.GetWriterName(ServiceContext.UserService));
|
||||
Assert.AreEqual(creatorNameVal, doc.GetCreatorName(ServiceContext.UserService));
|
||||
Assert.AreEqual(_testWriterAndCreatorId, doc.WriterId);
|
||||
Assert.AreEqual(_testWriterAndCreatorId, doc.CreatorId);
|
||||
Assert.AreEqual(pathVal, doc.Path);
|
||||
Assert.AreEqual(createDateVal.Value, doc.CreateDate);
|
||||
Assert.AreEqual(updateDateVal.Value, doc.UpdateDate);
|
||||
Assert.AreEqual(levelVal, doc.Level);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Infrastructure.Persistence;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
|
||||
namespace Umbraco.Tests.Issues
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true)]
|
||||
public class U9560 : TestWithDatabaseBase
|
||||
{
|
||||
[Test]
|
||||
public void Test()
|
||||
{
|
||||
// create a content type and some properties
|
||||
var contentType = new ContentType(ShortStringHelper, -1);
|
||||
contentType.Alias = "test";
|
||||
contentType.Name = "test";
|
||||
var propertyType = new PropertyType(ShortStringHelper, "test", ValueStorageType.Ntext, "prop") { Name = "Prop", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 };
|
||||
contentType.PropertyTypeCollection.Add(propertyType);
|
||||
ServiceContext.ContentTypeService.Save(contentType);
|
||||
|
||||
var aliasName = string.Empty;
|
||||
|
||||
// read fields, same as what we do with PetaPoco Fetch<dynamic>
|
||||
using (var db = Factory.GetRequiredService<IUmbracoDatabaseFactory>().CreateDatabase())
|
||||
{
|
||||
db.OpenSharedConnection();
|
||||
try
|
||||
{
|
||||
var conn = db.Connection;
|
||||
var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = "SELECT mandatory, dataTypeId, propertyTypeGroupId, contentTypeId, sortOrder, alias, name, validationRegExp, description from cmsPropertyType where id=" + propertyType.Id;
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
for (var i = 0; i < reader.FieldCount; i++)
|
||||
Console.WriteLine(reader.GetName(i));
|
||||
aliasName = reader.GetName(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
db.CloseSharedConnection();
|
||||
}
|
||||
}
|
||||
|
||||
// note that although the query is for 'alias' the field is named 'Alias'
|
||||
Assert.AreEqual("Alias", aliasName);
|
||||
|
||||
// try differently
|
||||
using (var db = Factory.GetRequiredService<IUmbracoDatabaseFactory>().CreateDatabase())
|
||||
{
|
||||
db.OpenSharedConnection();
|
||||
try
|
||||
{
|
||||
var conn = db.Connection;
|
||||
var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = "SELECT mandatory, dataTypeId, propertyTypeGroupId, contentTypeId, sortOrder, alias as alias, name, validationRegExp, description from cmsPropertyType where id=" + propertyType.Id;
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
for (var i = 0; i < reader.FieldCount; i++)
|
||||
Console.WriteLine(reader.GetName(i));
|
||||
aliasName = reader.GetName(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
db.CloseSharedConnection();
|
||||
}
|
||||
}
|
||||
|
||||
// and now it is OK
|
||||
Assert.AreEqual("alias", aliasName);
|
||||
|
||||
//// get the legacy content type
|
||||
//var legacyContentType = new umbraco.cms.businesslogic.ContentType(contentType.Id);
|
||||
//Assert.AreEqual("test", legacyContentType.Alias);
|
||||
|
||||
//// get the legacy properties
|
||||
//var legacyProperties = legacyContentType.PropertyTypes;
|
||||
|
||||
//// without the fix, due to some (swallowed) inner exception, we have no properties
|
||||
////Assert.IsNull(legacyProperties);
|
||||
|
||||
//// thanks to the fix, it works
|
||||
//Assert.IsNotNull(legacyProperties);
|
||||
//Assert.AreEqual(1, legacyProperties.Count);
|
||||
//Assert.AreEqual("prop", legacyProperties[0].Alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
{
|
||||
[TableName("cmsContentXml")]
|
||||
[PrimaryKey("nodeId", AutoIncrement = false)]
|
||||
[ExplicitColumns]
|
||||
internal class ContentXmlDto
|
||||
{
|
||||
[Column("nodeId")]
|
||||
[PrimaryKeyColumn(AutoIncrement = false)]
|
||||
[ForeignKey(typeof(ContentDto), Column = "nodeId")]
|
||||
public int NodeId { get; set; }
|
||||
|
||||
[Column("xml")]
|
||||
[SpecialDbType(SpecialDbTypes.NTEXT)]
|
||||
public string Xml { get; set; }
|
||||
|
||||
[Column("rv")]
|
||||
public long Rv { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Xml.XPath;
|
||||
using Examine;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Web.Composing;
|
||||
|
||||
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
{
|
||||
/// <summary>
|
||||
/// An IPublishedContent that is represented all by a dictionary.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a helper class and definitely not intended for public use, it expects that all of the values required
|
||||
/// to create an IPublishedContent exist in the dictionary by specific aliases.
|
||||
/// </remarks>
|
||||
internal class DictionaryPublishedContent : PublishedContentBase
|
||||
{
|
||||
// note: I'm not sure this class fully complies with IPublishedContent rules especially
|
||||
// I'm not sure that _properties contains all properties including those without a value,
|
||||
// neither that GetProperty will return a property without a value vs. null... @zpqrtbnk
|
||||
|
||||
// List of properties that will appear in the XML and do not match
|
||||
// anything in the ContentType, so they must be ignored.
|
||||
private static readonly string[] IgnoredKeys = { "version", "isDoc" };
|
||||
|
||||
public DictionaryPublishedContent(
|
||||
IReadOnlyDictionary<string, string> valueDictionary,
|
||||
Func<int, IPublishedContent> getParent,
|
||||
Func<int, XPathNavigator, IEnumerable<IPublishedContent>> getChildren,
|
||||
Func<DictionaryPublishedContent, string, IPublishedProperty> getProperty,
|
||||
IAppCache appCache,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
PublishedContentTypeCache contentTypeCache,
|
||||
XPathNavigator nav,
|
||||
bool fromExamine):base(variationContextAccessor)
|
||||
{
|
||||
if (valueDictionary == null) throw new ArgumentNullException(nameof(valueDictionary));
|
||||
if (getParent == null) throw new ArgumentNullException(nameof(getParent));
|
||||
if (getProperty == null) throw new ArgumentNullException(nameof(getProperty));
|
||||
|
||||
_getParent = new Lazy<IPublishedContent>(() => getParent(ParentId));
|
||||
_getChildren = new Lazy<IEnumerable<IPublishedContent>>(() => getChildren(Id, nav));
|
||||
_getProperty = getProperty;
|
||||
_appCache = appCache;
|
||||
|
||||
LoadedFromExamine = fromExamine;
|
||||
|
||||
ValidateAndSetProperty(valueDictionary, val => _id = Int32.Parse(val), "id", "nodeId", "__NodeId"); //should validate the int!
|
||||
ValidateAndSetProperty(valueDictionary, val => _key = Guid.Parse(val), "key", "__key", "__Key");
|
||||
//ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId");
|
||||
ValidateAndSetProperty(valueDictionary, val => _sortOrder = Int32.Parse(val), "sortOrder");
|
||||
ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName");
|
||||
ValidateAndSetProperty(valueDictionary, val => _urlName = val, "urlName");
|
||||
ValidateAndSetProperty(valueDictionary, val => _documentTypeAlias = val, "nodeTypeAlias", ExamineFieldNames.ItemTypeFieldName);
|
||||
ValidateAndSetProperty(valueDictionary, val => _documentTypeId = Int32.Parse(val), "nodeType");
|
||||
//ValidateAndSetProperty(valueDictionary, val => _writerId = int.Parse(val), "writerID");
|
||||
ValidateAndSetProperty(valueDictionary, val => _creatorId = Int32.Parse(val), "creatorID", "writerID"); //this is a bit of a hack fix for: U4-1132
|
||||
ValidateAndSetProperty(valueDictionary, val => _path = val, "path", "__Path");
|
||||
ValidateAndSetProperty(valueDictionary, val => _createDate = ParseDateTimeValue(val), "createDate");
|
||||
ValidateAndSetProperty(valueDictionary, val => _updateDate = ParseDateTimeValue(val), "updateDate");
|
||||
ValidateAndSetProperty(valueDictionary, val => _level = Int32.Parse(val), "level");
|
||||
ValidateAndSetProperty(valueDictionary, val =>
|
||||
{
|
||||
int pId;
|
||||
ParentId = -1;
|
||||
if (Int32.TryParse(val, out pId))
|
||||
{
|
||||
ParentId = pId;
|
||||
}
|
||||
}, "parentID");
|
||||
|
||||
_contentType = contentTypeCache.Get(PublishedItemType.Media, _documentTypeAlias);
|
||||
_properties = new Collection<IPublishedProperty>();
|
||||
|
||||
//handle content type properties
|
||||
//make sure we create them even if there's no value
|
||||
foreach (var propertyType in _contentType.PropertyTypes)
|
||||
{
|
||||
var alias = propertyType.Alias;
|
||||
_keysAdded.Add(alias);
|
||||
string value;
|
||||
const bool isPreviewing = false; // false :: never preview a media
|
||||
var property = valueDictionary.TryGetValue(alias, out value) == false || value == null
|
||||
? new XmlPublishedProperty(propertyType, this, isPreviewing)
|
||||
: new XmlPublishedProperty(propertyType, this, isPreviewing, value);
|
||||
_properties.Add(property);
|
||||
}
|
||||
|
||||
//loop through remaining values that haven't been applied
|
||||
foreach (var i in valueDictionary.Where(x =>
|
||||
_keysAdded.Contains(x.Key) == false // not already processed
|
||||
&& IgnoredKeys.Contains(x.Key) == false)) // not ignorable
|
||||
{
|
||||
if (i.Key.InvariantStartsWith("__"))
|
||||
{
|
||||
// no type for that one, dunno how to convert, drop it
|
||||
//IPublishedProperty property = new PropertyResult(i.Key, i.Value, PropertyResultType.CustomProperty);
|
||||
//_properties.Add(property);
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is a property that does not correspond to anything, ignore and log
|
||||
Current.Logger.LogWarning("Dropping property '{PropertyKey}' because it does not belong to the content type.", i.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime ParseDateTimeValue(string val)
|
||||
{
|
||||
if (LoadedFromExamine == false)
|
||||
return DateTime.Parse(val);
|
||||
|
||||
//we need to parse the date time using Lucene converters
|
||||
var ticks = Int64.Parse(val);
|
||||
return new DateTime(ticks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flag to get/set if this was loaded from examine cache
|
||||
/// </summary>
|
||||
internal bool LoadedFromExamine { get; }
|
||||
|
||||
//private readonly Func<DictionaryPublishedContent, IPublishedContent> _getParent;
|
||||
private readonly Lazy<IPublishedContent> _getParent;
|
||||
//private readonly Func<DictionaryPublishedContent, IEnumerable<IPublishedContent>> _getChildren;
|
||||
private readonly Lazy<IEnumerable<IPublishedContent>> _getChildren;
|
||||
private readonly Func<DictionaryPublishedContent, string, IPublishedProperty> _getProperty;
|
||||
private readonly IAppCache _appCache;
|
||||
|
||||
/// <summary>
|
||||
/// Returns 'Media' as the item type
|
||||
/// </summary>
|
||||
public override PublishedItemType ItemType => PublishedItemType.Media;
|
||||
|
||||
public override IPublishedContent Parent => _getParent.Value;
|
||||
|
||||
public int ParentId { get; private set; }
|
||||
|
||||
public override int Id => _id;
|
||||
|
||||
public override Guid Key => _key;
|
||||
|
||||
public override int? TemplateId => null;
|
||||
|
||||
public override int SortOrder => _sortOrder;
|
||||
|
||||
public override string Name => _name;
|
||||
|
||||
private static readonly Lazy<Dictionary<string, PublishedCultureInfo>> NoCultures = new Lazy<Dictionary<string, PublishedCultureInfo>>(() => new Dictionary<string, PublishedCultureInfo>());
|
||||
public override IReadOnlyDictionary<string, PublishedCultureInfo> Cultures => NoCultures.Value;
|
||||
|
||||
public override string UrlSegment => _urlName;
|
||||
|
||||
public override int WriterId => _creatorId;
|
||||
|
||||
public override int CreatorId => _creatorId;
|
||||
|
||||
public override string Path => _path;
|
||||
|
||||
public override DateTime CreateDate => _createDate;
|
||||
|
||||
public override DateTime UpdateDate => _updateDate;
|
||||
|
||||
public override int Level => _level;
|
||||
|
||||
public override bool IsDraft(string culture = null) => false;
|
||||
|
||||
public override bool IsPublished(string culture = null) => true;
|
||||
|
||||
public override IEnumerable<IPublishedProperty> Properties => _properties;
|
||||
|
||||
public override IEnumerable<IPublishedContent> Children => _getChildren.Value;
|
||||
|
||||
public override IEnumerable<IPublishedContent> ChildrenForAllCultures => Children;
|
||||
|
||||
public override IPublishedProperty GetProperty(string alias)
|
||||
{
|
||||
return _getProperty(this, alias);
|
||||
}
|
||||
|
||||
public override IPublishedContentType ContentType => _contentType;
|
||||
|
||||
private readonly List<string> _keysAdded = new List<string>();
|
||||
private int _id;
|
||||
private Guid _key;
|
||||
//private int _templateId;
|
||||
private int _sortOrder;
|
||||
private string _name;
|
||||
private string _urlName;
|
||||
private string _documentTypeAlias;
|
||||
private int _documentTypeId;
|
||||
//private int _writerId;
|
||||
private int _creatorId;
|
||||
private string _path;
|
||||
private DateTime _createDate;
|
||||
private DateTime _updateDate;
|
||||
//private Guid _version;
|
||||
private int _level;
|
||||
private readonly ICollection<IPublishedProperty> _properties;
|
||||
private readonly IPublishedContentType _contentType;
|
||||
|
||||
private void ValidateAndSetProperty(IReadOnlyDictionary<string, string> valueDictionary, Action<string> setProperty, params string[] potentialKeys)
|
||||
{
|
||||
var key = potentialKeys.FirstOrDefault(x => valueDictionary.ContainsKey(x) && valueDictionary[x] != null);
|
||||
if (key == null)
|
||||
{
|
||||
throw new FormatException("The valueDictionary is not formatted correctly and is missing any of the '" + String.Join(",", potentialKeys) + "' elements");
|
||||
}
|
||||
|
||||
setProperty(valueDictionary[key]);
|
||||
_keysAdded.Add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
{
|
||||
internal class DomainCache : IDomainCache
|
||||
{
|
||||
private readonly IDomainService _domainService;
|
||||
|
||||
public DomainCache(IDomainService domainService, IDefaultCultureAccessor defaultCultureAccessor)
|
||||
{
|
||||
_domainService = domainService;
|
||||
DefaultCulture = defaultCultureAccessor.DefaultCulture;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Domain> GetAll(bool includeWildcards) => _domainService.GetAll(includeWildcards)
|
||||
.Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false)
|
||||
.Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, x.LanguageIsoCode, x.IsWildcard));
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Domain> GetAssigned(int documentId, bool includeWildcards = false) => _domainService.GetAssignedDomains(documentId, includeWildcards)
|
||||
.Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false)
|
||||
.Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, x.LanguageIsoCode, x.IsWildcard));
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasAssigned(int documentId, bool includeWildcards = false)
|
||||
=> documentId > 0 && GetAssigned(documentId, includeWildcards).Any();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DefaultCulture { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,863 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Runtime;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages a queue of tasks and runs them in the background.
|
||||
/// </summary>
|
||||
/// <remarks>This class exists for logging purposes - the one you want to use is BackgroundTaskRunner{T}.</remarks>
|
||||
public abstract class BackgroundTaskRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a MainDom hook.
|
||||
/// </summary>
|
||||
public class MainDomHook
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MainDomHook"/> class.
|
||||
/// </summary>
|
||||
/// <param name="mainDom">The <see cref="IMainDom"/> object.</param>
|
||||
/// <param name="install">A method to execute when hooking into the main domain.</param>
|
||||
/// <param name="release">A method to execute when the main domain releases.</param>
|
||||
public MainDomHook(IMainDom mainDom, Action install, Action release)
|
||||
{
|
||||
MainDom = mainDom;
|
||||
Install = install;
|
||||
Release = release;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IMainDom"/> object.
|
||||
/// </summary>
|
||||
public IMainDom MainDom { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the method to execute when hooking into the main domain.
|
||||
/// </summary>
|
||||
public Action Install { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the method to execute when the main domain releases.
|
||||
/// </summary>
|
||||
public Action Release { get; }
|
||||
|
||||
internal bool Register()
|
||||
{
|
||||
if (MainDom != null)
|
||||
{
|
||||
return MainDom.Register(Install, Release);
|
||||
}
|
||||
|
||||
// tests
|
||||
Install?.Invoke();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages a queue of tasks of type <typeparamref name="T"/> and runs them in the background.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the managed tasks.</typeparam>
|
||||
/// <remarks>The task runner is web-aware and will ensure that it shuts down correctly when the AppDomain
|
||||
/// shuts down (ie is unloaded).</remarks>
|
||||
public class BackgroundTaskRunner<T> : BackgroundTaskRunner, IBackgroundTaskRunner<T>
|
||||
where T : class, IBackgroundTask
|
||||
{
|
||||
// do not remove this comment!
|
||||
//
|
||||
// if you plan to do anything on this class, first go and read
|
||||
// http://blog.stephencleary.com/2012/12/dont-block-in-asynchronous-code.html
|
||||
// http://stackoverflow.com/questions/19481964/calling-taskcompletionsource-setresult-in-a-non-blocking-manner
|
||||
// http://stackoverflow.com/questions/21225361/is-there-anything-like-asynchronous-blockingcollectiont
|
||||
// and more, and more, and more
|
||||
// and remember: async is hard
|
||||
|
||||
private readonly string _logPrefix;
|
||||
private readonly BackgroundTaskRunnerOptions _options;
|
||||
private readonly ILogger<BackgroundTaskRunner<T>> _logger;
|
||||
private readonly IApplicationShutdownRegistry _applicationShutdownRegistry;
|
||||
private readonly object _locker = new object();
|
||||
|
||||
private readonly BufferBlock<T> _tasks = new BufferBlock<T>(new DataflowBlockOptions());
|
||||
|
||||
// in various places we are testing these vars outside a lock, so make them volatile
|
||||
private volatile bool _isRunning; // is running
|
||||
private volatile bool _completed; // does not accept tasks anymore, may still be running
|
||||
|
||||
private Task _runningTask; // the threading task that is currently executing background tasks
|
||||
private CancellationTokenSource _shutdownTokenSource; // used to cancel everything and shutdown
|
||||
private CancellationTokenSource _cancelTokenSource; // used to cancel the current task
|
||||
private CancellationToken _shutdownToken;
|
||||
|
||||
private bool _terminating; // ensures we raise that event only once
|
||||
private bool _terminated; // remember we've terminated
|
||||
private readonly TaskCompletionSource<int> _terminatedSource = new TaskCompletionSource<int>(); // enable awaiting termination
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">A logger.</param>
|
||||
/// <param name="applicationShutdownRegistry">The application shutdown registry</param>
|
||||
/// <param name="hook">An optional main domain hook.</param>
|
||||
public BackgroundTaskRunner(ILogger<BackgroundTaskRunner<T>> logger, IApplicationShutdownRegistry applicationShutdownRegistry, MainDomHook hook = null)
|
||||
: this(typeof(T).FullName, new BackgroundTaskRunnerOptions(), logger, applicationShutdownRegistry, hook)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the runner.</param>
|
||||
/// <param name="logger">A logger.</param>
|
||||
/// <param name="applicationShutdownRegistry">The application shutdown registry</param>
|
||||
/// <param name="hook">An optional main domain hook.</param>
|
||||
public BackgroundTaskRunner(string name, ILogger<BackgroundTaskRunner<T>> logger, IApplicationShutdownRegistry applicationShutdownRegistry, MainDomHook hook = null)
|
||||
: this(name, new BackgroundTaskRunnerOptions(), logger, applicationShutdownRegistry, hook)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class with a set of options.
|
||||
/// </summary>
|
||||
/// <param name="options">The set of options.</param>
|
||||
/// <param name="logger">A logger.</param>
|
||||
/// <param name="applicationShutdownRegistry">The application shutdown registry</param>
|
||||
/// <param name="hook">An optional main domain hook.</param>
|
||||
public BackgroundTaskRunner(BackgroundTaskRunnerOptions options, ILogger<BackgroundTaskRunner<T>> logger, IApplicationShutdownRegistry applicationShutdownRegistry, MainDomHook hook = null)
|
||||
: this(typeof(T).FullName, options, logger, applicationShutdownRegistry, hook)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class with a set of options.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the runner.</param>
|
||||
/// <param name="options">The set of options.</param>
|
||||
/// <param name="logger">A logger.</param>
|
||||
/// <param name="applicationShutdownRegistry">The application shutdown registry</param>
|
||||
/// <param name="hook">An optional main domain hook.</param>
|
||||
public BackgroundTaskRunner(string name, BackgroundTaskRunnerOptions options, ILogger<BackgroundTaskRunner<T>> logger, IApplicationShutdownRegistry applicationShutdownRegistry, MainDomHook hook = null)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_applicationShutdownRegistry = applicationShutdownRegistry;
|
||||
_logPrefix = "[" + name + "] ";
|
||||
|
||||
if (options.Hosted)
|
||||
_applicationShutdownRegistry.RegisterObject(this);
|
||||
|
||||
if (hook != null)
|
||||
_completed = _terminated = hook.Register() == false;
|
||||
|
||||
if (options.AutoStart && _terminated == false)
|
||||
StartUp();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of tasks in the queue.
|
||||
/// </summary>
|
||||
public int TaskCount => _tasks.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a threading task is currently running.
|
||||
/// </summary>
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the runner has completed and cannot accept tasks anymore.
|
||||
/// </summary>
|
||||
public bool IsCompleted => _completed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the running threading task as an immutable awaitable.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">There is no running task.</exception>
|
||||
/// <remarks>
|
||||
/// <para>Unless the AutoStart option is true, there will be no current threading task until
|
||||
/// a background task is added to the queue, and there will be no current threading task
|
||||
/// when the queue is empty. In which case this method returns null.</para>
|
||||
/// <para>The returned value can be awaited and that is all (eg no continuation).</para>
|
||||
/// </remarks>
|
||||
internal ThreadingTaskImmutable CurrentThreadingTask
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
return _runningTask == null ? null : new ThreadingTaskImmutable(_runningTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an awaitable used to await the runner running operation.
|
||||
/// </summary>
|
||||
/// <returns>An awaitable instance.</returns>
|
||||
/// <remarks>Used to wait until the runner is no longer running (IsRunning == false),
|
||||
/// though the runner could be started again afterwards by adding tasks to it. If
|
||||
/// the runner is not running, returns a completed awaitable.</remarks>
|
||||
public ThreadingTaskImmutable StoppedAwaitable
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
var task = _runningTask ?? Task.CompletedTask;
|
||||
return new ThreadingTaskImmutable(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an awaitable object that can be used to await for the runner to terminate.
|
||||
/// </summary>
|
||||
/// <returns>An awaitable object.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Used to wait until the runner has terminated.</para>
|
||||
/// <para>
|
||||
/// The only time the runner will be terminated is by the Hosting Environment when the application is being shutdown.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal ThreadingTaskImmutable TerminatedAwaitable
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
return new ThreadingTaskImmutable(_terminatedSource.Task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a task to the queue.
|
||||
/// </summary>
|
||||
/// <param name="task">The task to add.</param>
|
||||
/// <exception cref="InvalidOperationException">The task runner has completed.</exception>
|
||||
public void Add(T task)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
if (_completed)
|
||||
throw new InvalidOperationException("The task runner has completed.");
|
||||
|
||||
// add task
|
||||
_logger.LogDebug("{LogPrefix} Task Added {TaskType}", _logPrefix , task.GetType().FullName);
|
||||
_tasks.Post(task);
|
||||
|
||||
// start
|
||||
StartUpLocked();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add a task to the queue.
|
||||
/// </summary>
|
||||
/// <param name="task">The task to add.</param>
|
||||
/// <returns>true if the task could be added to the queue; otherwise false.</returns>
|
||||
/// <remarks>Returns false if the runner is completed.</remarks>
|
||||
public bool TryAdd(T task)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
if (_completed)
|
||||
{
|
||||
_logger.LogDebug("{LogPrefix} Task cannot be added {TaskType}, the task runner has already shutdown", _logPrefix, task.GetType().FullName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// add task
|
||||
_logger.LogDebug("{LogPrefix} Task added {TaskType}", _logPrefix, task.GetType().FullName);
|
||||
_tasks.Post(task);
|
||||
|
||||
// start
|
||||
StartUpLocked();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels to current task, if any.
|
||||
/// </summary>
|
||||
/// <remarks>Has no effect if the task runs synchronously, or does not want to cancel.</remarks>
|
||||
public void CancelCurrentBackgroundTask()
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
if (_completed)
|
||||
throw new InvalidOperationException("The task runner has completed.");
|
||||
_cancelTokenSource?.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the tasks runner, if not already running.
|
||||
/// </summary>
|
||||
/// <remarks>Is invoked each time a task is added, to ensure it is going to be processed.</remarks>
|
||||
/// <exception cref="InvalidOperationException">The task runner has completed.</exception>
|
||||
internal void StartUp()
|
||||
{
|
||||
if (_isRunning) return;
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
if (_completed)
|
||||
throw new InvalidOperationException("The task runner has completed.");
|
||||
|
||||
StartUpLocked();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the tasks runner, if not already running.
|
||||
/// </summary>
|
||||
/// <remarks>Must be invoked within lock(_locker) and with _isCompleted being false.</remarks>
|
||||
private void StartUpLocked()
|
||||
{
|
||||
// double check
|
||||
if (_isRunning) return;
|
||||
_isRunning = true;
|
||||
|
||||
// create a new token source since this is a new process
|
||||
_shutdownTokenSource = new CancellationTokenSource();
|
||||
_shutdownToken = _shutdownTokenSource.Token;
|
||||
using (ExecutionContext.SuppressFlow()) // Do not flow AsyncLocal to the child thread
|
||||
{
|
||||
_runningTask = Task.Run(async () => await Pump().ConfigureAwait(false), _shutdownToken);
|
||||
}
|
||||
|
||||
_logger.LogDebug("{LogPrefix} Starting", _logPrefix);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shuts the tasks runner down.
|
||||
/// </summary>
|
||||
/// <param name="force">True for force the runner to stop.</param>
|
||||
/// <param name="wait">True to wait until the runner has stopped.</param>
|
||||
/// <remarks>If <paramref name="force"/> is false, no more tasks can be queued but all queued tasks
|
||||
/// will run. If it is true, then only the current one (if any) will end and no other task will run.</remarks>
|
||||
public void Shutdown(bool force, bool wait)
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
_completed = true; // do not accept new tasks
|
||||
if (_isRunning == false) return; // done already
|
||||
}
|
||||
|
||||
var hasTasks = TaskCount > 0;
|
||||
|
||||
if (!force && hasTasks)
|
||||
{
|
||||
_logger.LogInformation("{LogPrefix} Waiting for tasks to complete", _logPrefix);
|
||||
}
|
||||
|
||||
// complete the queue
|
||||
// will stop waiting on the queue or on a latch
|
||||
_tasks.Complete();
|
||||
|
||||
if (force)
|
||||
{
|
||||
// we must bring everything down, now
|
||||
lock (_locker)
|
||||
{
|
||||
// was Complete() enough?
|
||||
// if _tasks.Complete() ended up triggering code to stop the runner and reset
|
||||
// the _isRunning flag, then there's no need to initiate a cancel on the cancelation token.
|
||||
if (_isRunning == false)
|
||||
return;
|
||||
}
|
||||
|
||||
// try to cancel running async tasks (cannot do much about sync tasks)
|
||||
// break latched tasks
|
||||
// stop processing the queue
|
||||
_shutdownTokenSource?.Cancel(false); // false is the default
|
||||
_shutdownTokenSource?.Dispose();
|
||||
_shutdownTokenSource = null;
|
||||
}
|
||||
|
||||
// tasks in the queue will be executed...
|
||||
if (!wait) return;
|
||||
|
||||
_runningTask?.Wait(CancellationToken.None); // wait for whatever is running to end...
|
||||
}
|
||||
|
||||
private async Task Pump()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// get the next task
|
||||
// if it returns null the runner is going down, stop
|
||||
var bgTask = await GetNextBackgroundTask(_shutdownToken);
|
||||
if (bgTask == null) return;
|
||||
|
||||
// set a cancellation source so that the current task can be cancelled
|
||||
// link from _shutdownToken so that we can use _cancelTokenSource for both
|
||||
lock (_locker)
|
||||
{
|
||||
_cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_shutdownToken);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// wait for latch should return the task
|
||||
// if it returns null it's either that the task has been cancelled
|
||||
// or the whole runner is going down - in both cases, continue,
|
||||
// and GetNextBackgroundTask will take care of shutdowns
|
||||
bgTask = await WaitForLatch(bgTask, _cancelTokenSource.Token);
|
||||
|
||||
if (bgTask != null)
|
||||
{
|
||||
// executes & be safe - RunAsync should NOT throw but only raise an event,
|
||||
// but... just make sure we never ever take everything down
|
||||
try
|
||||
{
|
||||
await RunAsync(bgTask, _cancelTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{LogPrefix} Task runner exception", _logPrefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// done
|
||||
lock (_locker)
|
||||
{
|
||||
// always dispose CancellationTokenSource when you are done using them
|
||||
// https://lowleveldesign.org/2015/11/30/catch-in-cancellationtokensource/
|
||||
_cancelTokenSource.Dispose();
|
||||
_cancelTokenSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gets the next background task from the buffer
|
||||
private async Task<T> GetNextBackgroundTask(CancellationToken token)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var task = await GetNextBackgroundTask2(token);
|
||||
if (task != null) return task;
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
// deal with race condition
|
||||
if (_shutdownToken.IsCancellationRequested == false && TaskCount > 0) continue;
|
||||
|
||||
// if we really have nothing to do, stop
|
||||
_logger.LogDebug("{LogPrefix} Stopping", _logPrefix);
|
||||
|
||||
if (_options.PreserveRunningTask == false)
|
||||
_runningTask = null;
|
||||
_isRunning = false;
|
||||
_shutdownToken = CancellationToken.None;
|
||||
}
|
||||
|
||||
OnEvent(Stopped, "Stopped");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<T> GetNextBackgroundTask2(CancellationToken shutdownToken)
|
||||
{
|
||||
// exit if canceling
|
||||
if (shutdownToken.IsCancellationRequested)
|
||||
return null;
|
||||
|
||||
// if KeepAlive is false then don't block, exit if there is
|
||||
// no task in the buffer - yes, there is a race condition, which
|
||||
// we'll take care of
|
||||
if (_options.KeepAlive == false && TaskCount == 0)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// A Task<TResult> that informs of whether and when more output is available. If, when the
|
||||
// task completes, its Result is true, more output is available in the source (though another
|
||||
// consumer of the source may retrieve the data). If it returns false, more output is not
|
||||
// and will never be available, due to the source completing prior to output being available.
|
||||
|
||||
var output = await _tasks.OutputAvailableAsync(shutdownToken); // block until output or cancelled
|
||||
if (output == false) return null;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// A task that represents the asynchronous receive operation. When an item value is successfully
|
||||
// received from the source, the returned task is completed and its Result returns the received
|
||||
// value. If an item value cannot be retrieved because the source is empty and completed, an
|
||||
// InvalidOperationException exception is thrown in the returned task.
|
||||
|
||||
// the source cannot be empty *and* completed here - we know we have output
|
||||
return await _tasks.ReceiveAsync(shutdownToken);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// if bgTask is not a latched background task, or if it is not latched, returns immediately
|
||||
// else waits for the latch, taking care of completion and shutdown and whatnot
|
||||
private async Task<T> WaitForLatch(T bgTask, CancellationToken token)
|
||||
{
|
||||
var latched = bgTask as ILatchedBackgroundTask;
|
||||
if (latched == null || latched.IsLatched == false) return bgTask;
|
||||
|
||||
// support canceling awaiting
|
||||
// read https://github.com/dotnet/corefx/issues/2704
|
||||
// read http://stackoverflow.com/questions/27238232/how-can-i-cancel-task-whenall
|
||||
var tokenTaskSource = new TaskCompletionSource<bool>();
|
||||
token.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tokenTaskSource);
|
||||
|
||||
// returns the task that completed
|
||||
// - latched.Latch completes when the latch releases
|
||||
// - _tasks.Completion completes when the runner completes
|
||||
// - tokenTaskSource.Task completes when this task, or the whole runner is cancelled
|
||||
var task = await Task.WhenAny(latched.Latch, _tasks.Completion, tokenTaskSource.Task);
|
||||
|
||||
// ok to run now
|
||||
if (task == latched.Latch)
|
||||
return bgTask;
|
||||
|
||||
// we are shutting down if the _tasks.Complete(); was called or the shutdown token was cancelled
|
||||
var isShuttingDown = _shutdownToken.IsCancellationRequested || task == _tasks.Completion;
|
||||
|
||||
// if shutting down, return the task only if it runs on shutdown
|
||||
if (isShuttingDown && latched.RunsOnShutdown)
|
||||
return bgTask;
|
||||
|
||||
// else, either it does not run on shutdown or it's been cancelled, dispose
|
||||
latched.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
// runs the background task, taking care of shutdown (as far as possible - cannot abort
|
||||
// a non-async Run for example, so we'll do our best)
|
||||
private async Task RunAsync(T bgTask, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnTaskStarting(new TaskEventArgs<T>(bgTask));
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
if (bgTask.IsAsync)
|
||||
{
|
||||
// configure await = false since we don't care about the context, we're on a background thread.
|
||||
await bgTask.RunAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
bgTask.Run();
|
||||
}
|
||||
}
|
||||
finally // ensure we disposed - unless latched again ie wants to re-run
|
||||
{
|
||||
if (!(bgTask is ILatchedBackgroundTask lbgTask) || lbgTask.IsLatched == false)
|
||||
{
|
||||
bgTask.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OnTaskError(new TaskEventArgs<T>(bgTask, e));
|
||||
throw;
|
||||
}
|
||||
|
||||
OnTaskCompleted(new TaskEventArgs<T>(bgTask));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
_logger.LogError(ex, "{LogPrefix} Task has failed", _logPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
// triggers when a background task starts
|
||||
public event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskStarting;
|
||||
|
||||
// triggers when a background task has completed
|
||||
public event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskCompleted;
|
||||
|
||||
// triggers when a background task throws
|
||||
public event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskError;
|
||||
|
||||
// triggers when a background task is cancelled
|
||||
public event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskCancelled;
|
||||
|
||||
// triggers when the runner stops (but could start again if a task is added to it)
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Stopped;
|
||||
|
||||
// triggers when the hosting environment requests that the runner terminates
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Terminating;
|
||||
|
||||
// triggers when the hosting environment has terminated (no task can be added, no task is running)
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Terminated;
|
||||
|
||||
private void OnEvent(TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> handler, string name)
|
||||
{
|
||||
OnEvent(handler, name, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void OnEvent<TArgs>(TypedEventHandler<BackgroundTaskRunner<T>, TArgs> handler, string name, TArgs e)
|
||||
{
|
||||
_logger.LogDebug("{LogPrefix} OnEvent {EventName}", _logPrefix, name);
|
||||
|
||||
if (handler == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{LogPrefix} {Name} exception occurred", _logPrefix, name);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnTaskError(TaskEventArgs<T> e)
|
||||
{
|
||||
OnEvent(TaskError, "TaskError", e);
|
||||
}
|
||||
|
||||
protected virtual void OnTaskStarting(TaskEventArgs<T> e)
|
||||
{
|
||||
OnEvent(TaskStarting, "TaskStarting", e);
|
||||
}
|
||||
|
||||
protected virtual void OnTaskCompleted(TaskEventArgs<T> e)
|
||||
{
|
||||
OnEvent(TaskCompleted, "TaskCompleted", e);
|
||||
}
|
||||
|
||||
protected virtual void OnTaskCancelled(TaskEventArgs<T> e)
|
||||
{
|
||||
OnEvent(TaskCancelled, "TaskCancelled", e);
|
||||
|
||||
// dispose it
|
||||
e.Task.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
private readonly object _disposalLocker = new object();
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
~BackgroundTaskRunner()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (IsDisposed || disposing == false)
|
||||
return;
|
||||
|
||||
lock (_disposalLocker)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
DisposeResources();
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void DisposeResources()
|
||||
{
|
||||
// just make sure we eventually go down
|
||||
Shutdown(true, false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IRegisteredObject.Stop
|
||||
|
||||
/// <summary>
|
||||
/// Used by IRegisteredObject.Stop and shutdown on threadpool threads to not block shutdown times.
|
||||
/// </summary>
|
||||
/// <param name="immediate"></param>
|
||||
/// <returns>
|
||||
/// An awaitable Task that is used to handle the shutdown.
|
||||
/// </returns>
|
||||
internal Task StopInternal(bool immediate)
|
||||
{
|
||||
// the first time the hosting environment requests that the runner terminates,
|
||||
// raise the Terminating event - that could be used to prevent any process that
|
||||
// would expect the runner to be available from starting.
|
||||
var onTerminating = false;
|
||||
lock (_locker)
|
||||
{
|
||||
if (_terminating == false)
|
||||
{
|
||||
_terminating = true;
|
||||
_logger.LogInformation("{LogPrefix} Terminating {Immediate}", _logPrefix, immediate ? immediate.ToString() : string.Empty);
|
||||
onTerminating = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (onTerminating)
|
||||
OnEvent(Terminating, "Terminating");
|
||||
|
||||
// Run the Stop commands on another thread since IRegisteredObject.Stop calls are called sequentially
|
||||
// with a single aspnet thread during shutdown and we don't want to delay other calls to IRegisteredObject.Stop.
|
||||
if (!immediate)
|
||||
{
|
||||
using (ExecutionContext.SuppressFlow())
|
||||
{
|
||||
return Task.Run(StopInitial, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_locker)
|
||||
{
|
||||
if (_terminated) return Task.CompletedTask;
|
||||
using (ExecutionContext.SuppressFlow())
|
||||
{
|
||||
return Task.Run(StopImmediate, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests a registered object to un-register.
|
||||
/// </summary>
|
||||
/// <param name="immediate">true to indicate the registered object should un-register from the hosting
|
||||
/// environment before returning; otherwise, false.</param>
|
||||
/// <remarks>
|
||||
/// <para>"When the application manager needs to stop a registered object, it will call the Stop method."</para>
|
||||
/// <para>The application manager will call the Stop method to ask a registered object to un-register. During
|
||||
/// processing of the Stop method, the registered object must call the applicationShutdownRegistry.UnregisterObject method.</para>
|
||||
/// </remarks>
|
||||
public void Stop(bool immediate) => StopInternal(immediate);
|
||||
|
||||
/// <summary>
|
||||
/// Called when immediate == false for IRegisteredObject.Stop(bool immediate)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Called on a threadpool thread
|
||||
/// </remarks>
|
||||
private void StopInitial()
|
||||
{
|
||||
// immediate == false when the app is trying to wind down, immediate == true will be called either:
|
||||
// after a call with immediate == false or if the app is not trying to wind down and needs to immediately stop.
|
||||
// So Stop may be called twice or sometimes only once.
|
||||
|
||||
try
|
||||
{
|
||||
Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait
|
||||
}
|
||||
finally
|
||||
{
|
||||
// raise the completed event only after the running threading task has completed
|
||||
lock (_locker)
|
||||
{
|
||||
if (_runningTask != null)
|
||||
{
|
||||
_runningTask.ContinueWith(
|
||||
_ => StopImmediate(),
|
||||
// Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html
|
||||
TaskScheduler.Default);
|
||||
}
|
||||
else
|
||||
{
|
||||
StopImmediate();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// If the shutdown token was not canceled in the Shutdown call above, it means there was still tasks
|
||||
// being processed, in which case we'll give it a couple seconds
|
||||
if (!_shutdownToken.IsCancellationRequested)
|
||||
{
|
||||
// If we are called with immediate == false, wind down above and then shutdown within 2 seconds,
|
||||
// we want to shut down the app as quick as possible, if we wait until immediate == true, this can
|
||||
// take a very long time since immediate will only be true when a new request is received on the new
|
||||
// appdomain (or another iis timeout occurs ... which can take some time).
|
||||
Thread.Sleep(2000); //we are already on a threadpool thread
|
||||
StopImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when immediate == true for IRegisteredObject.Stop(bool immediate)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Called on a threadpool thread
|
||||
/// </remarks>
|
||||
private void StopImmediate()
|
||||
{
|
||||
_logger.LogInformation("{LogPrefix} Canceling tasks", _logPrefix);
|
||||
try
|
||||
{
|
||||
Shutdown(true, true); // cancel all tasks, wait for the current one to end
|
||||
}
|
||||
finally
|
||||
{
|
||||
Terminate(true);
|
||||
}
|
||||
}
|
||||
|
||||
// called by Stop either immediately or eventually
|
||||
private void Terminate(bool immediate)
|
||||
{
|
||||
// signal the environment we have terminated
|
||||
// log
|
||||
// raise the Terminated event
|
||||
// complete the awaitable completion source, if any
|
||||
|
||||
if (immediate)
|
||||
{
|
||||
//only unregister when it's the final call, else we won't be notified of the final call
|
||||
_applicationShutdownRegistry.UnregisterObject(this);
|
||||
}
|
||||
|
||||
if (_terminated) return; // already taken care of
|
||||
|
||||
TaskCompletionSource<int> terminatedSource;
|
||||
lock (_locker)
|
||||
{
|
||||
_terminated = true;
|
||||
terminatedSource = _terminatedSource;
|
||||
}
|
||||
|
||||
_logger.LogInformation("{LogPrefix} Tasks {TaskStatus}, terminated",
|
||||
_logPrefix,
|
||||
immediate ? "cancelled" : "completed");
|
||||
|
||||
OnEvent(Terminated, "Terminated");
|
||||
|
||||
terminatedSource.TrySetResult(0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides options to the <see cref="BackgroundTaskRunner{T}"/> class.
|
||||
/// </summary>
|
||||
public class BackgroundTaskRunnerOptions
|
||||
{
|
||||
// TODO: Could add options for using a stack vs queue if required
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackgroundTaskRunnerOptions"/> class.
|
||||
/// </summary>
|
||||
public BackgroundTaskRunnerOptions()
|
||||
{
|
||||
LongRunning = false;
|
||||
KeepAlive = false;
|
||||
AutoStart = false;
|
||||
PreserveRunningTask = false;
|
||||
Hosted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the running task should be a long-running,
|
||||
/// coarse grained operation.
|
||||
/// </summary>
|
||||
public bool LongRunning { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the running task should block and wait
|
||||
/// on the queue, or end, when the queue is empty.
|
||||
/// </summary>
|
||||
public bool KeepAlive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the running task should start immediately
|
||||
/// or only once a task has been added to the queue.
|
||||
/// </summary>
|
||||
public bool AutoStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the running task should be preserved
|
||||
/// once completed, or reset to null. For unit tests.
|
||||
/// </summary>
|
||||
public bool PreserveRunningTask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the runner should register with (and be
|
||||
/// stopped by) the hosting. Otherwise, something else should take care of stopping
|
||||
/// the runner. True by default.
|
||||
/// </summary>
|
||||
public bool Hosted { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a background task.
|
||||
/// </summary>
|
||||
public interface IBackgroundTask : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs the background task.
|
||||
/// </summary>
|
||||
void Run();
|
||||
|
||||
/// <summary>
|
||||
/// Runs the task asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="token">A cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> instance representing the execution of the background task.</returns>
|
||||
/// <exception cref="NotImplementedException">The background task cannot run asynchronously.</exception>
|
||||
Task RunAsync(CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the background task can run asynchronously.
|
||||
/// </summary>
|
||||
bool IsAsync { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using Umbraco.Cms.Core;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a service managing a queue of tasks of type <typeparamref name="T"/> and running them in the background.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the managed tasks.</typeparam>
|
||||
/// <remarks>The interface is not complete and exists only to have the contravariance on T.</remarks>
|
||||
public interface IBackgroundTaskRunner<in T> : IDisposable, IRegisteredObject
|
||||
where T : class, IBackgroundTask
|
||||
{
|
||||
bool IsCompleted { get; }
|
||||
void Add(T task);
|
||||
bool TryAdd(T task);
|
||||
|
||||
// TODO: complete the interface?
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a latched background task.
|
||||
/// </summary>
|
||||
/// <remarks>Latched background tasks can suspend their execution until
|
||||
/// a condition is met. However if the tasks runner has to terminate,
|
||||
/// latched background tasks can be executed immediately, depending on
|
||||
/// the value returned by RunsOnShutdown.</remarks>
|
||||
public interface ILatchedBackgroundTask : IBackgroundTask
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a task on latch.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The task is not latched.</exception>
|
||||
Task Latch { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the task is latched.
|
||||
/// </summary>
|
||||
/// <remarks>Should return false as soon as the condition is met.</remarks>
|
||||
bool IsLatched { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the task can be executed immediately if the task runner has to terminate.
|
||||
/// </summary>
|
||||
bool RunsOnShutdown { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Cms.Core;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
public abstract class LatchedBackgroundTaskBase : DisposableObjectSlim, ILatchedBackgroundTask
|
||||
{
|
||||
private TaskCompletionSource<bool> _latch;
|
||||
|
||||
protected LatchedBackgroundTaskBase()
|
||||
{
|
||||
_latch = new TaskCompletionSource<bool>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements IBackgroundTask.Run().
|
||||
/// </summary>
|
||||
public virtual void Run()
|
||||
{
|
||||
throw new NotSupportedException("This task cannot run synchronously.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements IBackgroundTask.RunAsync().
|
||||
/// </summary>
|
||||
public virtual Task RunAsync(CancellationToken token)
|
||||
{
|
||||
throw new NotSupportedException("This task cannot run asynchronously.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the background task can run asynchronously.
|
||||
/// </summary>
|
||||
public abstract bool IsAsync { get; }
|
||||
|
||||
public Task Latch => _latch.Task;
|
||||
|
||||
public bool IsLatched => _latch.Task.IsCompleted == false;
|
||||
|
||||
protected void Release()
|
||||
{
|
||||
_latch.SetResult(true);
|
||||
}
|
||||
|
||||
protected void Reset()
|
||||
{
|
||||
_latch = new TaskCompletionSource<bool>();
|
||||
}
|
||||
|
||||
public virtual bool RunsOnShutdown => false;
|
||||
|
||||
// the task is going to be disposed after execution,
|
||||
// unless it is latched again, thus indicating it wants to
|
||||
// remain active
|
||||
|
||||
protected override void DisposeResources()
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides arguments for task runner events.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the task.</typeparam>
|
||||
public class TaskEventArgs<T> : EventArgs
|
||||
where T : IBackgroundTask
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TaskEventArgs{T}"/> class with a task.
|
||||
/// </summary>
|
||||
/// <param name="task">The task.</param>
|
||||
public TaskEventArgs(T task)
|
||||
{
|
||||
Task = task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TaskEventArgs{T}"/> class with a task and an exception.
|
||||
/// </summary>
|
||||
/// <param name="task">The task.</param>
|
||||
/// <param name="exception">An exception.</param>
|
||||
public TaskEventArgs(T task, Exception exception)
|
||||
{
|
||||
Task = task;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the task.
|
||||
/// </summary>
|
||||
public T Task { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception.
|
||||
/// </summary>
|
||||
public Exception Exception { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps a <see cref="Task"/> within an object that gives access to its GetAwaiter method and Status
|
||||
/// property while ensuring that it cannot be modified in any way.
|
||||
/// </summary>
|
||||
public class ThreadingTaskImmutable
|
||||
{
|
||||
private readonly Task _task;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ThreadingTaskImmutable"/> class with a Task.
|
||||
/// </summary>
|
||||
/// <param name="task">The task.</param>
|
||||
public ThreadingTaskImmutable(Task task)
|
||||
{
|
||||
if (task == null)
|
||||
throw new ArgumentNullException("task");
|
||||
_task = task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an awaiter used to await the task.
|
||||
/// </summary>
|
||||
/// <returns>An awaiter instance.</returns>
|
||||
public TaskAwaiter GetAwaiter()
|
||||
{
|
||||
return _task.GetAwaiter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TaskStatus of the task.
|
||||
/// </summary>
|
||||
/// <returns>The current TaskStatus of the task.</returns>
|
||||
public TaskStatus Status
|
||||
{
|
||||
get { return _task.Status; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Web.Composing;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
|
||||
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
{
|
||||
class PreviewContent
|
||||
{
|
||||
private readonly int _userId;
|
||||
private readonly Guid _previewSet;
|
||||
private string _previewSetPath;
|
||||
private XmlDocument _previewXml;
|
||||
private readonly XmlStore _xmlStore;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the XML document.
|
||||
/// </summary>
|
||||
/// <remarks>May return <c>null</c> if the preview content set is invalid.</remarks>
|
||||
public XmlDocument XmlContent
|
||||
{
|
||||
get
|
||||
{
|
||||
// null if invalid preview content
|
||||
if (_previewSetPath == null) return null;
|
||||
|
||||
// load if not loaded yet
|
||||
if (_previewXml != null)
|
||||
return _previewXml;
|
||||
|
||||
_previewXml = new XmlDocument();
|
||||
|
||||
try
|
||||
{
|
||||
_previewXml.Load(_previewSetPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Current.Logger.LogError(ex, "Could not load preview set {PreviewSet} for user {UserId}.", _previewSet, _userId);
|
||||
|
||||
ClearPreviewSet();
|
||||
|
||||
_previewXml = null;
|
||||
_previewSetPath = null; // do not try again
|
||||
}
|
||||
|
||||
return _previewXml;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the preview token.
|
||||
/// </summary>
|
||||
/// <remarks>To be stored in a cookie or wherever appropriate.</remarks>
|
||||
public string Token => _userId + ":" + _previewSet;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PreviewContent"/> class for a user.
|
||||
/// </summary>
|
||||
/// <param name="xmlStore">The underlying Xml store.</param>
|
||||
/// <param name="userId">The user identifier.</param>
|
||||
public PreviewContent(XmlStore xmlStore, int userId)
|
||||
{
|
||||
if (xmlStore == null)
|
||||
throw new ArgumentNullException(nameof(xmlStore));
|
||||
_xmlStore = xmlStore;
|
||||
|
||||
_userId = userId;
|
||||
_previewSet = Guid.NewGuid();
|
||||
_previewSetPath = GetPreviewSetPath(_userId, _previewSet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PreviewContent"/> with a preview token.
|
||||
/// </summary>
|
||||
/// <param name="xmlStore">The underlying Xml store.</param>
|
||||
/// <param name="token">The preview token.</param>
|
||||
public PreviewContent(XmlStore xmlStore, string token)
|
||||
{
|
||||
if (xmlStore == null)
|
||||
throw new ArgumentNullException(nameof(xmlStore));
|
||||
_xmlStore = xmlStore;
|
||||
|
||||
if (token.IsNullOrWhiteSpace())
|
||||
throw new ArgumentException("Null or empty token.", nameof(token));
|
||||
var parts = token.Split(':');
|
||||
if (parts.Length != 2)
|
||||
throw new ArgumentException("Invalid token.", nameof(token));
|
||||
|
||||
if (int.TryParse(parts[0], out _userId) == false)
|
||||
throw new ArgumentException("Invalid token.", nameof(token));
|
||||
if (Guid.TryParse(parts[1], out _previewSet) == false)
|
||||
throw new ArgumentException("Invalid token.", nameof(token));
|
||||
|
||||
_previewSetPath = GetPreviewSetPath(_userId, _previewSet);
|
||||
}
|
||||
|
||||
// creates and saves a new preview set
|
||||
// used in 2 places and each time includeSubs is true
|
||||
// have to use the Document class at the moment because IContent does not do ToXml...
|
||||
public void CreatePreviewSet(int contentId, bool includeSubs)
|
||||
{
|
||||
// note: always include subs
|
||||
_previewXml = _xmlStore.GetPreviewXml(contentId, includeSubs);
|
||||
|
||||
// make sure the preview folder exists
|
||||
var dir = new DirectoryInfo(TestHelper.IOHelper.MapPath(Constants.SystemDirectories.Preview));
|
||||
if (dir.Exists == false)
|
||||
dir.Create();
|
||||
|
||||
// clean old preview sets
|
||||
ClearPreviewDirectory(_userId, dir);
|
||||
|
||||
// save
|
||||
_previewXml.Save(_previewSetPath);
|
||||
}
|
||||
|
||||
// get the full path to the preview set
|
||||
private static string GetPreviewSetPath(int userId, Guid previewSet)
|
||||
{
|
||||
return TestHelper.IOHelper.MapPath(Path.Combine(Constants.SystemDirectories.Preview, userId + "_" + previewSet + ".config"));
|
||||
}
|
||||
|
||||
// deletes files for the user, and files accessed more than one hour ago
|
||||
private static void ClearPreviewDirectory(int userId, DirectoryInfo dir)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
var prefix = userId + "_";
|
||||
foreach (var file in dir.GetFiles("*.config")
|
||||
.Where(x => x.Name.StartsWith(prefix) || (now - x.LastAccessTime).TotalMinutes > 1))
|
||||
{
|
||||
DeletePreviewSetFile(userId, file);
|
||||
}
|
||||
}
|
||||
|
||||
// delete one preview set file in a safe way
|
||||
private static void DeletePreviewSetFile(int userId, FileSystemInfo file)
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Current.Logger.LogError(ex, "Couldn't delete preview set {FileName} for user {UserId}", file.Name, userId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the preview set in a safe way.
|
||||
/// </summary>
|
||||
public void ClearPreviewSet()
|
||||
{
|
||||
if (_previewSetPath == null) return;
|
||||
var previewSetFile = new FileInfo(_previewSetPath);
|
||||
DeletePreviewSetFile(_userId, previewSetFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
{
|
||||
[TableName("cmsPreviewXml")]
|
||||
[PrimaryKey("nodeId", AutoIncrement = false)]
|
||||
[ExplicitColumns]
|
||||
internal class PreviewXmlDto
|
||||
{
|
||||
[Column("nodeId")]
|
||||
[PrimaryKeyColumn(AutoIncrement = false)]
|
||||
[ForeignKey(typeof(ContentDto), Column = "nodeId")]
|
||||
public int NodeId { get; set; }
|
||||
|
||||
[Column("xml")]
|
||||
[SpecialDbType(SpecialDbTypes.NTEXT)]
|
||||
public string Xml { get; set; }
|
||||
|
||||
[Column("rv")]
|
||||
public long Rv { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,548 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.XPath;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Xml;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
|
||||
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
{
|
||||
internal class PublishedContentCache : PublishedCacheBase, IPublishedContentCache
|
||||
{
|
||||
private readonly IAppCache _appCache;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly RoutesCache _routesCache;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
private readonly IDomainCache _domainCache;
|
||||
private readonly PublishedContentTypeCache _contentTypeCache;
|
||||
|
||||
// initialize a PublishedContentCache instance with
|
||||
// an XmlStore containing the master xml
|
||||
// an IAppCache that should be at request-level
|
||||
// a RoutesCache - need to cleanup that one
|
||||
// a preview token string (or null if not previewing)
|
||||
public PublishedContentCache(
|
||||
XmlStore xmlStore, // an XmlStore containing the master xml
|
||||
IDomainCache domainCache, // an IDomainCache implementation
|
||||
IAppCache appCache, // an IAppCache that should be at request-level
|
||||
GlobalSettings globalSettings,
|
||||
PublishedContentTypeCache contentTypeCache, // a PublishedContentType cache
|
||||
RoutesCache routesCache, // a RoutesCache
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
string previewToken) // a preview token string (or null if not previewing)
|
||||
: base(previewToken.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
_appCache = appCache;
|
||||
_globalSettings = globalSettings;
|
||||
_routesCache = routesCache; // may be null for unit-testing
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
_contentTypeCache = contentTypeCache;
|
||||
_domainCache = domainCache;
|
||||
|
||||
_xmlStore = xmlStore;
|
||||
_xml = _xmlStore.Xml; // capture - because the cache has to remain consistent
|
||||
|
||||
if (previewToken.IsNullOrWhiteSpace() == false)
|
||||
_previewContent = new PreviewContent(_xmlStore, previewToken);
|
||||
}
|
||||
|
||||
#region Unit Tests
|
||||
|
||||
// for INTERNAL, UNIT TESTS use ONLY
|
||||
internal RoutesCache RoutesCache => _routesCache;
|
||||
|
||||
// for INTERNAL, UNIT TESTS use ONLY
|
||||
internal XmlStore XmlStore => _xmlStore;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Routes
|
||||
|
||||
public virtual IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string culture = null)
|
||||
{
|
||||
if (route == null) throw new ArgumentNullException(nameof(route));
|
||||
|
||||
// try to get from cache if not previewing
|
||||
var contentId = preview || _routesCache == null ? 0 : _routesCache.GetNodeId(route);
|
||||
|
||||
// if found id in cache then get corresponding content
|
||||
// and clear cache if not found - for whatever reason
|
||||
IPublishedContent content = null;
|
||||
if (contentId > 0)
|
||||
{
|
||||
content = GetById(preview, contentId);
|
||||
if (content == null)
|
||||
_routesCache?.ClearNode(contentId);
|
||||
}
|
||||
|
||||
// still have nothing? actually determine the id
|
||||
hideTopLevelNode = hideTopLevelNode ?? _globalSettings.HideTopLevelNodeFromPath; // default = settings
|
||||
content = content ?? DetermineIdByRoute(preview, route, hideTopLevelNode.Value);
|
||||
|
||||
// cache if we have a content and not previewing
|
||||
if (content != null && preview == false && _routesCache != null)
|
||||
AddToCacheIfDeepestRoute(content, route);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private void AddToCacheIfDeepestRoute(IPublishedContent content, string route)
|
||||
{
|
||||
var domainRootNodeId = route.StartsWith("/") ? -1 : int.Parse(route.Substring(0, route.IndexOf('/')));
|
||||
|
||||
// so we have a route that maps to a content... say "1234/path/to/content" - however, there could be a
|
||||
// domain set on "to" and route "4567/content" would also map to the same content - and due to how
|
||||
// URLs computing work (by walking the tree up to the first domain we find) it is that second route
|
||||
// that would be returned - the "deepest" route - and that is the route we want to cache, *not* the
|
||||
// longer one - so make sure we don't cache the wrong route
|
||||
|
||||
var deepest = DomainUtilities.ExistsDomainInPath(_domainCache.GetAll(false), content.Path, domainRootNodeId) == false;
|
||||
|
||||
if (deepest)
|
||||
_routesCache.Store(content.Id, route, true); // trusted route
|
||||
}
|
||||
|
||||
public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string culture = null)
|
||||
{
|
||||
return GetByRoute(PreviewDefault, route, hideTopLevelNode);
|
||||
}
|
||||
|
||||
public virtual string GetRouteById(bool preview, int contentId, string culture = null)
|
||||
{
|
||||
// try to get from cache if not previewing
|
||||
var route = preview || _routesCache == null ? null : _routesCache.GetRoute(contentId);
|
||||
|
||||
// if found in cache then return
|
||||
if (route != null)
|
||||
return route;
|
||||
|
||||
// else actually determine the route
|
||||
route = DetermineRouteById(preview, contentId);
|
||||
|
||||
// node not found
|
||||
if (route == null)
|
||||
return null;
|
||||
|
||||
// cache the route BUT do NOT trust it as it can be a colliding route
|
||||
// meaning if we GetRouteById again, we'll get it from cache, but it
|
||||
// won't be used for inbound routing
|
||||
if (preview == false)
|
||||
_routesCache.Store(contentId, route, false);
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
public string GetRouteById(int contentId, string culture = null)
|
||||
{
|
||||
return GetRouteById(PreviewDefault, contentId, culture);
|
||||
}
|
||||
|
||||
IPublishedContent DetermineIdByRoute(bool preview, string route, bool hideTopLevelNode)
|
||||
{
|
||||
//the route always needs to be lower case because we only store the urlName attribute in lower case
|
||||
route = route?.ToLowerInvariant() ?? throw new ArgumentNullException(nameof(route));
|
||||
|
||||
var pos = route.IndexOf('/');
|
||||
var path = pos == 0 ? route : route.Substring(pos);
|
||||
var startNodeId = pos == 0 ? 0 : int.Parse(route.Substring(0, pos));
|
||||
|
||||
//check if we can find the node in our xml cache
|
||||
var id = NavigateRoute(preview, startNodeId, path, hideTopLevelNode);
|
||||
return id > 0 ? GetById(preview, id) : null;
|
||||
}
|
||||
|
||||
private static XmlElement GetXmlElementChildWithLowestSortOrder(XmlNode element)
|
||||
{
|
||||
XmlElement elt = null;
|
||||
var min = int.MaxValue;
|
||||
foreach (var n in element.ChildNodes)
|
||||
{
|
||||
var e = n as XmlElement;
|
||||
if (e == null) continue;
|
||||
|
||||
var sortOrder = int.Parse(e.GetAttribute("sortOrder"));
|
||||
if (sortOrder >= min) continue;
|
||||
|
||||
min = sortOrder;
|
||||
elt = e;
|
||||
}
|
||||
return elt;
|
||||
}
|
||||
|
||||
private int NavigateRoute(bool preview, int startNodeId, string path, bool hideTopLevelNode)
|
||||
{
|
||||
var xml = GetXml(preview);
|
||||
XmlElement elt;
|
||||
|
||||
// empty path
|
||||
if (path == string.Empty || path == "/")
|
||||
{
|
||||
if (startNodeId > 0)
|
||||
{
|
||||
elt = xml.GetElementById(startNodeId.ToString(CultureInfo.InvariantCulture));
|
||||
return elt == null ? -1 : startNodeId;
|
||||
}
|
||||
|
||||
elt = GetXmlElementChildWithLowestSortOrder(xml.DocumentElement);
|
||||
return elt == null ? -1 : int.Parse(elt.GetAttribute("id"));
|
||||
}
|
||||
|
||||
// non-empty path
|
||||
elt = startNodeId <= 0
|
||||
? xml.DocumentElement
|
||||
: xml.GetElementById(startNodeId.ToString(CultureInfo.InvariantCulture));
|
||||
if (elt == null) return -1;
|
||||
|
||||
var urlParts = path.Split(SlashChar, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (hideTopLevelNode && startNodeId <= 0)
|
||||
{
|
||||
//Don't use OfType<T> or Cast<T>, this is critical code, all ChildNodes are XmlElement so explicitly cast
|
||||
// https://gist.github.com/Shazwazza/04e2e5642a316f4a87e52dada2901198
|
||||
foreach (var n in elt.ChildNodes)
|
||||
{
|
||||
var e = n as XmlElement;
|
||||
if (e == null) continue;
|
||||
|
||||
var id = NavigateElementRoute(e, urlParts);
|
||||
if (id > 0) return id;
|
||||
}
|
||||
|
||||
if (urlParts.Length > 1)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return NavigateElementRoute(elt, urlParts);
|
||||
}
|
||||
|
||||
private static int NavigateElementRoute(XmlElement elt, string[] urlParts)
|
||||
{
|
||||
var found = true;
|
||||
var i = 0;
|
||||
while (found && i < urlParts.Length)
|
||||
{
|
||||
found = false;
|
||||
//Don't use OfType<T> or Cast<T>, this is critical code, all ChildNodes are XmlElement so explicitly cast
|
||||
// https://gist.github.com/Shazwazza/04e2e5642a316f4a87e52dada2901198
|
||||
var sortOrder = -1;
|
||||
foreach (var o in elt.ChildNodes)
|
||||
{
|
||||
var child = o as XmlElement;
|
||||
if (child == null) continue;
|
||||
|
||||
var noNode = child.GetAttributeNode("isDoc") == null;
|
||||
if (noNode) continue;
|
||||
if (child.GetAttribute("urlName") != urlParts[i]) continue;
|
||||
|
||||
found = true;
|
||||
|
||||
var so = int.Parse(child.GetAttribute("sortOrder"));
|
||||
if (sortOrder >= 0 && so >= sortOrder) continue;
|
||||
|
||||
sortOrder = so;
|
||||
elt = child;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return found ? int.Parse(elt.GetAttribute("id")) : -1;
|
||||
}
|
||||
|
||||
string DetermineRouteById(bool preview, int contentId)
|
||||
{
|
||||
var node = GetById(preview, contentId);
|
||||
if (node == null) return null;
|
||||
|
||||
// walk up from that node until we hit a node with a domain,
|
||||
// or we reach the content root, collecting URLs in the way
|
||||
var pathParts = new List<string>();
|
||||
var n = node;
|
||||
var hasDomains = _domainCache.HasAssigned(n.Id);
|
||||
while (hasDomains == false && n != null) // n is null at root
|
||||
{
|
||||
// get the url
|
||||
var urlName = n.UrlSegment(TestHelper.VariationContextAccessor);
|
||||
pathParts.Add(urlName);
|
||||
|
||||
// move to parent node
|
||||
n = n.Parent;
|
||||
hasDomains = n != null && _domainCache.HasAssigned(n.Id);
|
||||
}
|
||||
|
||||
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
|
||||
if (hasDomains == false && _globalSettings.HideTopLevelNodeFromPath)
|
||||
{
|
||||
if (node.Parent == null)
|
||||
{
|
||||
var rootNode = GetByRoute(preview, "/", true);
|
||||
if (rootNode == null)
|
||||
throw new Exception("Failed to get node at /.");
|
||||
if (rootNode.Id == node.Id) // remove only if we're the default node
|
||||
pathParts.RemoveAt(pathParts.Count - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
pathParts.RemoveAt(pathParts.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// assemble the route
|
||||
pathParts.Reverse();
|
||||
var path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc
|
||||
var route = (n?.Id.ToString(CultureInfo.InvariantCulture) ?? "") + path;
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region XPath Strings
|
||||
|
||||
static class XPathStrings
|
||||
{
|
||||
public const string Root = "/root";
|
||||
public const string RootDocuments = "/root/* [@isDoc]";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Converters
|
||||
|
||||
private IPublishedContent ConvertToDocument(XmlNode xmlNode, bool isPreviewing)
|
||||
{
|
||||
return xmlNode == null ? null : XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache, _variationContextAccessor);
|
||||
}
|
||||
|
||||
private IEnumerable<IPublishedContent> ConvertToDocuments(XmlNodeList xmlNodes, bool isPreviewing)
|
||||
{
|
||||
return xmlNodes.Cast<XmlNode>()
|
||||
.Select(xmlNode => XmlPublishedContent.Get(xmlNode, isPreviewing, _appCache, _contentTypeCache, _variationContextAccessor));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Getters
|
||||
|
||||
public override IPublishedContent GetById(bool preview, int nodeId)
|
||||
{
|
||||
return ConvertToDocument(GetXml(preview).GetElementById(nodeId.ToString(CultureInfo.InvariantCulture)), preview);
|
||||
}
|
||||
|
||||
public override IPublishedContent GetById(bool preview, Guid nodeId)
|
||||
{
|
||||
// implement this, but in a more efficient way
|
||||
//const string xpath = "//* [@isDoc and @key=$guid]";
|
||||
//return GetSingleByXPath(preview, xpath, new[] { new XPathVariable("guid", nodeId.ToString()) });
|
||||
|
||||
var keyMatch = nodeId.ToString();
|
||||
|
||||
var nav = GetXml(preview).CreateNavigator();
|
||||
if (nav.MoveToFirstChild() == false) return null; // from / to /root
|
||||
if (nav.MoveToFirstChild() == false) return null; // from /root to /root/*
|
||||
|
||||
while (true)
|
||||
{
|
||||
var isDoc = false;
|
||||
string key = null;
|
||||
|
||||
if (nav.HasAttributes)
|
||||
{
|
||||
nav.MoveToFirstAttribute();
|
||||
do
|
||||
{
|
||||
if (nav.Name == "isDoc") isDoc = true;
|
||||
if (nav.Name == "key") key = nav.Value;
|
||||
if (isDoc && key != null) break;
|
||||
} while (nav.MoveToNextAttribute());
|
||||
nav.MoveToParent();
|
||||
}
|
||||
|
||||
if (isDoc == false || key != keyMatch)
|
||||
{
|
||||
if (isDoc && nav.MoveToFirstChild())
|
||||
continue;
|
||||
while (nav.MoveToNext(XPathNodeType.Element) == false)
|
||||
if (nav.MoveToParent() == false || nav.NodeType == XPathNodeType.Root) return null;
|
||||
continue;
|
||||
}
|
||||
|
||||
var elt = nav.UnderlyingObject as XmlNode;
|
||||
return ConvertToDocument(elt, preview);
|
||||
}
|
||||
}
|
||||
|
||||
public override IPublishedContent GetById(bool preview, Udi nodeId)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public override bool HasById(bool preview, int contentId)
|
||||
{
|
||||
return GetXml(preview).CreateNavigator().MoveToId(contentId.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public override IEnumerable<IPublishedContent> GetAtRoot(bool preview, string culture = null)
|
||||
{
|
||||
return ConvertToDocuments(GetXml(preview).SelectNodes(XPathStrings.RootDocuments), preview);
|
||||
}
|
||||
|
||||
public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars)
|
||||
{
|
||||
if (xpath == null) throw new ArgumentNullException(nameof(xpath));
|
||||
if (string.IsNullOrWhiteSpace(xpath)) return null;
|
||||
|
||||
var xml = GetXml(preview);
|
||||
var node = vars == null
|
||||
? xml.SelectSingleNode(xpath)
|
||||
: xml.SelectSingleNode(xpath, vars);
|
||||
return ConvertToDocument(node, preview);
|
||||
}
|
||||
|
||||
public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars)
|
||||
{
|
||||
if (xpath == null) throw new ArgumentNullException(nameof(xpath));
|
||||
|
||||
var xml = GetXml(preview);
|
||||
var node = vars == null
|
||||
? xml.SelectSingleNode(xpath)
|
||||
: xml.SelectSingleNode(xpath, vars);
|
||||
return ConvertToDocument(node, preview);
|
||||
}
|
||||
|
||||
public override IEnumerable<IPublishedContent> GetByXPath(bool preview, string xpath, XPathVariable[] vars)
|
||||
{
|
||||
if (xpath == null) throw new ArgumentNullException(nameof(xpath));
|
||||
if (string.IsNullOrWhiteSpace(xpath)) return Enumerable.Empty<IPublishedContent>();
|
||||
|
||||
var xml = GetXml(preview);
|
||||
var nodes = vars == null
|
||||
? xml.SelectNodes(xpath)
|
||||
: xml.SelectNodes(xpath, vars);
|
||||
return ConvertToDocuments(nodes, preview);
|
||||
}
|
||||
|
||||
public override IEnumerable<IPublishedContent> GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars)
|
||||
{
|
||||
if (xpath == null) throw new ArgumentNullException(nameof(xpath));
|
||||
|
||||
var xml = GetXml(preview);
|
||||
var nodes = vars == null
|
||||
? xml.SelectNodes(xpath)
|
||||
: xml.SelectNodes(xpath, vars);
|
||||
return ConvertToDocuments(nodes, preview);
|
||||
}
|
||||
|
||||
public override bool HasContent(bool preview)
|
||||
{
|
||||
var xml = GetXml(preview);
|
||||
var node = xml?.SelectSingleNode(XPathStrings.RootDocuments);
|
||||
return node != null;
|
||||
}
|
||||
|
||||
public override XPathNavigator CreateNavigator(bool preview)
|
||||
{
|
||||
var xml = GetXml(preview);
|
||||
return xml.CreateNavigator();
|
||||
}
|
||||
|
||||
public override XPathNavigator CreateNodeNavigator(int id, bool preview)
|
||||
{
|
||||
// hackish - backward compatibility ;-(
|
||||
|
||||
XPathNavigator navigator = null;
|
||||
|
||||
if (preview)
|
||||
{
|
||||
var node = _xmlStore.GetPreviewXmlNode(id);
|
||||
if (node != null)
|
||||
{
|
||||
navigator = node.CreateNavigator();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var node = GetXml(false).GetElementById(id.ToInvariantString());
|
||||
if (node != null)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
var clone = doc.ImportNode(node, false);
|
||||
var props = node.SelectNodes("./* [not(@id)]");
|
||||
if (props == null) throw new Exception("oops");
|
||||
foreach (var n in props.Cast<XmlNode>())
|
||||
clone.AppendChild(doc.ImportNode(n, true));
|
||||
navigator = node.CreateNavigator();
|
||||
}
|
||||
}
|
||||
|
||||
return navigator;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Legacy Xml
|
||||
|
||||
private readonly XmlStore _xmlStore;
|
||||
private XmlDocument _xml;
|
||||
private readonly PreviewContent _previewContent;
|
||||
|
||||
internal XmlDocument GetXml(bool preview)
|
||||
{
|
||||
// not trying to be thread-safe here, that's not the point
|
||||
|
||||
if (preview == false)
|
||||
{
|
||||
// if there's a current enlisted reader/writer, use its xml
|
||||
var tempXml = _xmlStore.TempXml;
|
||||
if (tempXml != null) return tempXml;
|
||||
return _xml;
|
||||
}
|
||||
|
||||
// Xml cache does not support retrieving preview content when not previewing
|
||||
if (_previewContent == null)
|
||||
throw new InvalidOperationException("Cannot retrieve preview content when not previewing.");
|
||||
|
||||
// PreviewContent tries to load the Xml once and if it fails,
|
||||
// it invalidates itself and always return null for XmlContent.
|
||||
var previewXml = _previewContent.XmlContent;
|
||||
return previewXml ?? _xml;
|
||||
}
|
||||
|
||||
internal void Resync(XmlDocument xml)
|
||||
{
|
||||
_xml = xml; // re-capture
|
||||
|
||||
// note: we're not resyncing "preview" because that would mean re-building the whole
|
||||
// preview set which is costly, so basically when previewing, there will be no resync.
|
||||
|
||||
// clear recursive properties cached by XmlPublishedContent.GetProperty
|
||||
// assume that nothing else is going to cache IPublishedProperty items (else would need to do ByKeySearch)
|
||||
// NOTE also clears all the media cache properties, which is OK (see media cache)
|
||||
_appCache.ClearOfType<IPublishedProperty>();
|
||||
//_appCache.ClearCacheByKeySearch("XmlPublishedCache.PublishedContentCache:RecursiveProperty-");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region XPathQuery
|
||||
|
||||
static readonly char[] SlashChar = { '/' };
|
||||
|
||||
#endregion
|
||||
|
||||
#region Content types
|
||||
|
||||
public override IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Content, id);
|
||||
|
||||
public override IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Content, alias);
|
||||
|
||||
public override IPublishedContentType GetContentType(Guid key) => _contentTypeCache.Get(PublishedItemType.Content, key);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,698 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.XPath;
|
||||
using Examine;
|
||||
using Examine.Search;
|
||||
using Lucene.Net.Store;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Core.Xml;
|
||||
using Umbraco.Cms.Infrastructure.Examine;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Web.Composing;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
|
||||
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
{
|
||||
/// <summary>
|
||||
/// An IPublishedMediaStore that first checks for the media in Examine, and then reverts to the database
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// NOTE: In the future if we want to properly cache all media this class can be extended or replaced when these classes/interfaces are exposed publicly.
|
||||
/// </remarks>
|
||||
internal class PublishedMediaCache : PublishedCacheBase, IPublishedMediaCache
|
||||
{
|
||||
private readonly IMediaService _mediaService;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
// by default these are null unless specified by the ctor dedicated to tests
|
||||
// when they are null the cache derives them from the ExamineManager, see
|
||||
// method GetExamineManagerSafe().
|
||||
//
|
||||
private readonly ISearcher _searchProvider;
|
||||
private readonly XmlStore _xmlStore;
|
||||
private readonly PublishedContentTypeCache _contentTypeCache;
|
||||
private readonly IEntityXmlSerializer _entitySerializer;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
private readonly IExamineManager _examineManager = new ExamineManager();
|
||||
|
||||
// must be specified by the ctor
|
||||
private readonly IAppCache _appCache;
|
||||
|
||||
public PublishedMediaCache(XmlStore xmlStore, IMediaService mediaService, IUserService userService,
|
||||
IAppCache appCache, PublishedContentTypeCache contentTypeCache, IEntityXmlSerializer entitySerializer,
|
||||
IUmbracoContextAccessor umbracoContextAccessor, IVariationContextAccessor variationContextAccessor)
|
||||
: base(false)
|
||||
{
|
||||
_mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService));
|
||||
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
|
||||
|
||||
_appCache = appCache;
|
||||
_xmlStore = xmlStore;
|
||||
_contentTypeCache = contentTypeCache;
|
||||
_entitySerializer = entitySerializer;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generally used for unit testing to use an explicit examine searcher
|
||||
/// </summary>
|
||||
/// <param name="mediaService"></param>
|
||||
/// <param name="userService"></param>
|
||||
/// <param name="searchProvider"></param>
|
||||
/// <param name="appCache"></param>
|
||||
/// <param name="contentTypeCache"></param>
|
||||
/// <param name="entitySerializer"></param>
|
||||
internal PublishedMediaCache(IMediaService mediaService, IUserService userService, ISearcher searchProvider, IAppCache appCache, PublishedContentTypeCache contentTypeCache, IEntityXmlSerializer entitySerializer, IUmbracoContextAccessor umbracoContextAccessor)
|
||||
: base(false)
|
||||
{
|
||||
_mediaService = mediaService ?? throw new ArgumentNullException(nameof(mediaService));
|
||||
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
|
||||
_searchProvider = searchProvider ?? throw new ArgumentNullException(nameof(searchProvider));
|
||||
_appCache = appCache;
|
||||
_contentTypeCache = contentTypeCache;
|
||||
_entitySerializer = entitySerializer;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
}
|
||||
|
||||
static PublishedMediaCache()
|
||||
{
|
||||
InitializeCacheConfig();
|
||||
}
|
||||
|
||||
public override IPublishedContent GetById(bool preview, int nodeId)
|
||||
{
|
||||
return GetUmbracoMedia(nodeId);
|
||||
}
|
||||
|
||||
public override IPublishedContent GetById(bool preview, Guid nodeId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override IPublishedContent GetById(bool preview, Udi nodeId)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public override bool HasById(bool preview, int contentId)
|
||||
{
|
||||
return GetUmbracoMedia(contentId) != null;
|
||||
}
|
||||
|
||||
public override IEnumerable<IPublishedContent> GetAtRoot(bool preview, string culture = null)
|
||||
{
|
||||
var searchProvider = GetSearchProviderSafe();
|
||||
|
||||
if (searchProvider != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// first check in Examine for the cache values
|
||||
// +(+parentID:-1) +__IndexType:media
|
||||
|
||||
var criteria = searchProvider.CreateQuery("media");
|
||||
var filter = criteria.ParentId(-1).Not().Field(UmbracoExamineFieldNames.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard());
|
||||
|
||||
var result = filter.Execute();
|
||||
if (result != null)
|
||||
return result.Select(x => CreateFromCacheValues(ConvertFromSearchResult(x)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is FileNotFoundException)
|
||||
{
|
||||
//Currently examine is throwing FileNotFound exceptions when we have a load balanced filestore and a node is published in umbraco
|
||||
//See this thread: http://examine.cdodeplex.com/discussions/264341
|
||||
//Catch the exception here for the time being, and just fallback to GetMedia
|
||||
// TODO: Need to fix examine in LB scenarios!
|
||||
Current.Logger.LogError(ex, "Could not load data from Examine index for media");
|
||||
}
|
||||
else if (ex is ObjectDisposedException)
|
||||
{
|
||||
//If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot
|
||||
//be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db.
|
||||
Current.Logger.LogError(ex, "Could not load data from Examine index for media, the app domain is most likely in a shutdown state");
|
||||
}
|
||||
else throw;
|
||||
}
|
||||
}
|
||||
|
||||
//something went wrong, fetch from the db
|
||||
|
||||
var rootMedia = _mediaService.GetRootMedia();
|
||||
return rootMedia.Select(m => GetUmbracoMedia(m.Id));
|
||||
}
|
||||
|
||||
public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars)
|
||||
{
|
||||
throw new NotImplementedException("PublishedMediaCache does not support XPath.");
|
||||
//var navigator = CreateNavigator(preview);
|
||||
//var iterator = navigator.Select(xpath, vars);
|
||||
//return GetSingleByXPath(iterator);
|
||||
}
|
||||
|
||||
public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars)
|
||||
{
|
||||
throw new NotImplementedException("PublishedMediaCache does not support XPath.");
|
||||
//var navigator = CreateNavigator(preview);
|
||||
//var iterator = navigator.Select(xpath, vars);
|
||||
//return GetSingleByXPath(iterator);
|
||||
}
|
||||
|
||||
private IPublishedContent GetSingleByXPath(XPathNodeIterator iterator)
|
||||
{
|
||||
throw new NotImplementedException("PublishedMediaCache does not support XPath.");
|
||||
//if (iterator.MoveNext() == false) return null;
|
||||
//var idAttr = iterator.Current.GetAttribute("id", "");
|
||||
//int id;
|
||||
//return int.TryParse(idAttr, out id) ? GetUmbracoMedia(id) : null;
|
||||
}
|
||||
|
||||
public override IEnumerable<IPublishedContent> GetByXPath(bool preview, string xpath, XPathVariable[] vars)
|
||||
{
|
||||
throw new NotImplementedException("PublishedMediaCache does not support XPath.");
|
||||
//var navigator = CreateNavigator(preview);
|
||||
//var iterator = navigator.Select(xpath, vars);
|
||||
//return GetByXPath(iterator);
|
||||
}
|
||||
|
||||
public override IEnumerable<IPublishedContent> GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars)
|
||||
{
|
||||
throw new NotImplementedException("PublishedMediaCache does not support XPath.");
|
||||
//var navigator = CreateNavigator(preview);
|
||||
//var iterator = navigator.Select(xpath, vars);
|
||||
//return GetByXPath(iterator);
|
||||
}
|
||||
|
||||
private IEnumerable<IPublishedContent> GetByXPath(XPathNodeIterator iterator)
|
||||
{
|
||||
while (iterator.MoveNext())
|
||||
{
|
||||
var idAttr = iterator.Current.GetAttribute("id", "");
|
||||
int id;
|
||||
if (int.TryParse(idAttr, out id))
|
||||
yield return GetUmbracoMedia(id);
|
||||
}
|
||||
}
|
||||
|
||||
public override XPathNavigator CreateNavigator(bool preview)
|
||||
{
|
||||
throw new NotImplementedException("PublishedMediaCache does not support XPath.");
|
||||
//var doc = _xmlStore.GetMediaXml();
|
||||
//return doc.CreateNavigator();
|
||||
}
|
||||
|
||||
public override XPathNavigator CreateNodeNavigator(int id, bool preview)
|
||||
{
|
||||
// preview is ignored for media cache
|
||||
|
||||
// this code is mostly used when replacing old media.ToXml() code, and that code
|
||||
// stored the XML attached to the media itself - so for some time in memory - so
|
||||
// unless we implement some sort of cache here, we're probably degrading perfs.
|
||||
|
||||
XPathNavigator navigator = null;
|
||||
var node = _xmlStore.GetMediaXmlNode(id);
|
||||
if (node != null)
|
||||
{
|
||||
navigator = node.CreateNavigator();
|
||||
}
|
||||
return navigator;
|
||||
}
|
||||
|
||||
public override bool HasContent(bool preview) { throw new NotImplementedException(); }
|
||||
|
||||
private ISearcher GetSearchProviderSafe()
|
||||
{
|
||||
if (_searchProvider != null)
|
||||
return _searchProvider;
|
||||
|
||||
try
|
||||
{
|
||||
return _examineManager.TryGetIndex(Constants.UmbracoIndexes.InternalIndexName, out var index) ? index.GetSearcher() : null;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
//Currently examine is throwing FileNotFound exceptions when we have a load balanced filestore and a node is published in umbraco
|
||||
//See this thread: http://examine.cdodeplex.com/discussions/264341
|
||||
//Catch the exception here for the time being, and just fallback to GetMedia
|
||||
// TODO: Need to fix examine in LB scenarios!
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
//This will occur when the search provider cannot be initialized. In newer examine versions the initialization is lazy and therefore
|
||||
// the manager will return the singleton without throwing initialization errors, however if examine isn't configured correctly a null
|
||||
// reference error will occur because the examine settings are null.
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
//If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot
|
||||
//be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db.
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private IPublishedContent GetUmbracoMedia(int id)
|
||||
{
|
||||
// this recreates an IPublishedContent and model each time
|
||||
// it is called, but at least it should NOT hit the database
|
||||
// nor Lucene each time, relying on the memory cache instead
|
||||
|
||||
if (id <= 0) return null; // fail fast
|
||||
|
||||
var cacheValues = GetCacheValues(id, GetUmbracoMediaCacheValues);
|
||||
|
||||
return cacheValues == null ? null : CreateFromCacheValues(cacheValues);
|
||||
}
|
||||
|
||||
private CacheValues GetUmbracoMediaCacheValues(int id)
|
||||
{
|
||||
var searchProvider = GetSearchProviderSafe();
|
||||
|
||||
if (searchProvider != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// first check in Examine as this is WAY faster
|
||||
//
|
||||
// the filter will create a query like this:
|
||||
// +(+__NodeId:3113 -__Path:-1,-21,*) +__IndexType:media
|
||||
//
|
||||
// note that since the use of the wildcard, it automatically escapes it in Lucene.
|
||||
|
||||
var criteria = searchProvider.CreateQuery("media");
|
||||
var filter = criteria.Id(id.ToInvariantString()).Not().Field(UmbracoExamineFieldNames.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard());
|
||||
|
||||
var result = filter.Execute().FirstOrDefault();
|
||||
if (result != null) return ConvertFromSearchResult(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is FileNotFoundException)
|
||||
{
|
||||
//Currently examine is throwing FileNotFound exceptions when we have a load balanced filestore and a node is published in umbraco
|
||||
//See this thread: http://examine.cdodeplex.com/discussions/264341
|
||||
//Catch the exception here for the time being, and just fallback to GetMedia
|
||||
// TODO: Need to fix examine in LB scenarios!
|
||||
Current.Logger.LogError(ex, "Could not load data from Examine index for media");
|
||||
}
|
||||
else if (ex is ObjectDisposedException)
|
||||
{
|
||||
//If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot
|
||||
//be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db.
|
||||
Current.Logger.LogError(ex, "Could not load data from Examine index for media, the app domain is most likely in a shutdown state");
|
||||
}
|
||||
else throw;
|
||||
}
|
||||
}
|
||||
|
||||
// don't log a warning here, as it can flood the log in case of eg a media picker referencing a media
|
||||
// that has been deleted, hence is not in the Examine index anymore (for a good reason). try to get
|
||||
// the media from the service, first
|
||||
var media = _mediaService.GetById(id);
|
||||
if (media == null || media.Trashed) return null; // not found, ok
|
||||
|
||||
// so, the media was not found in Examine's index *yet* it exists, which probably indicates that
|
||||
// the index is corrupted. Or not up-to-date. Log a warning, but only once, and only if seeing the
|
||||
// error more that a number of times.
|
||||
|
||||
var miss = Interlocked.CompareExchange(ref _examineIndexMiss, 0, 0); // volatile read
|
||||
if (miss < ExamineIndexMissMax && Interlocked.Increment(ref _examineIndexMiss) == ExamineIndexMissMax)
|
||||
Current.Logger.LogWarning("Failed ({ExamineIndexMissMax} times) to retrieve medias from Examine index and had to load"
|
||||
+ " them from DB. This may indicate that the Examine index is corrupted.", ExamineIndexMissMax);
|
||||
|
||||
return ConvertFromIMedia(media);
|
||||
}
|
||||
|
||||
private const int ExamineIndexMissMax = 10;
|
||||
private int _examineIndexMiss;
|
||||
|
||||
internal CacheValues ConvertFromXPathNodeIterator(XPathNodeIterator media, int id)
|
||||
{
|
||||
if (media?.Current != null)
|
||||
{
|
||||
return media.Current.Name.InvariantEquals("error")
|
||||
? null
|
||||
: ConvertFromXPathNavigator(media.Current);
|
||||
}
|
||||
|
||||
Current.Logger.LogWarning("Could not retrieve media {MediaId} from Examine index or from legacy library.GetMedia method", id);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal CacheValues ConvertFromSearchResult(ISearchResult searchResult)
|
||||
{
|
||||
// note: fixing fields in 7.x, removed by Shan for 8.0
|
||||
|
||||
return new CacheValues
|
||||
{
|
||||
Values = searchResult.Values,
|
||||
FromExamine = true
|
||||
};
|
||||
}
|
||||
|
||||
internal CacheValues ConvertFromXPathNavigator(XPathNavigator xpath, bool forceNav = false)
|
||||
{
|
||||
if (xpath == null) throw new ArgumentNullException(nameof(xpath));
|
||||
|
||||
var values = new Dictionary<string, string> { { "nodeName", xpath.GetAttribute("nodeName", "") } };
|
||||
values["nodeTypeAlias"] = xpath.Name;
|
||||
|
||||
var result = xpath.SelectChildren(XPathNodeType.Element);
|
||||
//add the attributes e.g. id, parentId etc
|
||||
if (result.Current != null && result.Current.HasAttributes)
|
||||
{
|
||||
if (result.Current.MoveToFirstAttribute())
|
||||
{
|
||||
//checking for duplicate keys because of the 'nodeTypeAlias' might already be added above.
|
||||
if (values.ContainsKey(result.Current.Name) == false)
|
||||
{
|
||||
values[result.Current.Name] = result.Current.Value;
|
||||
}
|
||||
while (result.Current.MoveToNextAttribute())
|
||||
{
|
||||
if (values.ContainsKey(result.Current.Name) == false)
|
||||
{
|
||||
values[result.Current.Name] = result.Current.Value;
|
||||
}
|
||||
}
|
||||
result.Current.MoveToParent();
|
||||
}
|
||||
}
|
||||
// because, migration
|
||||
if (values.ContainsKey("key") == false)
|
||||
values["key"] = Guid.Empty.ToString();
|
||||
//add the user props
|
||||
while (result.MoveNext())
|
||||
{
|
||||
if (result.Current != null && result.Current.HasAttributes == false)
|
||||
{
|
||||
var value = result.Current.Value;
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
if (result.Current.HasAttributes || result.Current.SelectChildren(XPathNodeType.Element).Count > 0)
|
||||
{
|
||||
value = result.Current.OuterXml;
|
||||
}
|
||||
}
|
||||
values[result.Current.Name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return new CacheValues
|
||||
{
|
||||
Values = values,
|
||||
XPath = forceNav ? xpath : null // outside of tests we do NOT want to cache the navigator!
|
||||
};
|
||||
}
|
||||
|
||||
internal CacheValues ConvertFromIMedia(IMedia media)
|
||||
{
|
||||
var values = new Dictionary<string, string>();
|
||||
|
||||
var creator = _userService.GetProfileById(media.CreatorId);
|
||||
var creatorName = creator == null ? "" : creator.Name;
|
||||
|
||||
values["id"] = media.Id.ToString();
|
||||
values["key"] = media.Key.ToString();
|
||||
values["parentID"] = media.ParentId.ToString();
|
||||
values["level"] = media.Level.ToString();
|
||||
values["creatorID"] = media.CreatorId.ToString();
|
||||
values["creatorName"] = creatorName;
|
||||
values["writerID"] = media.CreatorId.ToString();
|
||||
values["writerName"] = creatorName;
|
||||
values["template"] = "0";
|
||||
values["urlName"] = "";
|
||||
values["sortOrder"] = media.SortOrder.ToString();
|
||||
values["createDate"] = media.CreateDate.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
values["updateDate"] = media.UpdateDate.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
values["nodeName"] = media.Name;
|
||||
values["path"] = media.Path;
|
||||
values["nodeType"] = media.ContentType.Id.ToString();
|
||||
values["nodeTypeAlias"] = media.ContentType.Alias;
|
||||
|
||||
// add the user props
|
||||
foreach (var prop in media.Properties)
|
||||
values[prop.Alias] = prop.GetValue()?.ToString();
|
||||
|
||||
return new CacheValues
|
||||
{
|
||||
Values = values
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We will need to first check if the document was loaded by Examine, if so we'll need to check if this property exists
|
||||
/// in the results, if it does not, then we'll have to revert to looking up in the db.
|
||||
/// </summary>
|
||||
/// <param name="dd"> </param>
|
||||
/// <param name="alias"></param>
|
||||
/// <returns></returns>
|
||||
private IPublishedProperty GetProperty(DictionaryPublishedContent dd, string alias)
|
||||
{
|
||||
//lets check if the alias does not exist on the document.
|
||||
//NOTE: Examine will not index empty values and we do not output empty XML Elements to the cache - either of these situations
|
||||
// would mean that the property is missing from the collection whether we are getting the value from Examine or from the library media cache.
|
||||
if (dd.Properties.All(x => x.Alias.InvariantEquals(alias) == false))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (dd.LoadedFromExamine)
|
||||
{
|
||||
//We are going to check for a special field however, that is because in some cases we store a 'Raw'
|
||||
//value in the index such as for xml/html.
|
||||
var rawValue = dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(UmbracoExamineFieldNames.RawFieldPrefix + alias));
|
||||
return rawValue
|
||||
?? dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias));
|
||||
}
|
||||
|
||||
//if its not loaded from examine, then just return the property
|
||||
return dd.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A Helper methods to return the children for media whether it is based on examine or xml
|
||||
/// </summary>
|
||||
/// <param name="parentId"></param>
|
||||
/// <param name="xpath"></param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<IPublishedContent> GetChildrenMedia(int parentId, XPathNavigator xpath = null)
|
||||
{
|
||||
// if there *is* a navigator, directly look it up
|
||||
if (xpath != null)
|
||||
{
|
||||
return ToIPublishedContent(parentId, xpath);
|
||||
}
|
||||
|
||||
// otherwise, try examine first, then re-look it up
|
||||
var searchProvider = GetSearchProviderSafe();
|
||||
|
||||
if (searchProvider != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
//first check in Examine as this is WAY faster
|
||||
var criteria = searchProvider.CreateQuery("media");
|
||||
|
||||
var filter = criteria.ParentId(parentId).Not().Field(UmbracoExamineFieldNames.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard())
|
||||
.OrderBy(new SortableField("sortOrder", SortType.Int));
|
||||
//the above filter will create a query like this, NOTE: That since the use of the wildcard, it automatically escapes it in Lucene.
|
||||
//+(+parentId:3113 -__Path:-1,-21,*) +__IndexType:media
|
||||
|
||||
// sort with the Sort field (updated for 8.0)
|
||||
var results = filter.Execute();
|
||||
|
||||
if (results.Any())
|
||||
{
|
||||
// var medias = results.Select(ConvertFromSearchResult);
|
||||
var medias = results.Select(x =>
|
||||
{
|
||||
int nid;
|
||||
if (int.TryParse(x["__NodeId"], out nid) == false && int.TryParse(x["NodeId"], out nid) == false)
|
||||
throw new Exception("Failed to extract NodeId from search result.");
|
||||
var cacheValues = GetCacheValues(nid, id => ConvertFromSearchResult(x));
|
||||
return CreateFromCacheValues(cacheValues);
|
||||
});
|
||||
|
||||
return medias;
|
||||
}
|
||||
|
||||
//if there's no result then return null. Previously we defaulted back to library.GetMedia below
|
||||
//but this will always get called for when we are getting descendants since many items won't have
|
||||
//children and then we are hitting the database again!
|
||||
//So instead we're going to rely on Examine to have the correct results like it should.
|
||||
return Enumerable.Empty<IPublishedContent>();
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
//Currently examine is throwing FileNotFound exceptions when we have a load balanced filestore and a node is published in umbraco
|
||||
//See this thread: http://examine.cdodeplex.com/discussions/264341
|
||||
//Catch the exception here for the time being, and just fallback to GetMedia
|
||||
}
|
||||
}
|
||||
|
||||
// falling back to get media
|
||||
// was library.GetMedia which had its own cache, but MediaService *also* caches
|
||||
// so, library.GetMedia is gone and now we directly work with MediaService
|
||||
// (code below copied from what library was doing)
|
||||
var media = _mediaService.GetById(parentId);
|
||||
if (media == null)
|
||||
{
|
||||
return Enumerable.Empty<IPublishedContent>();
|
||||
}
|
||||
|
||||
var serialized = _entitySerializer.Serialize(media, true);
|
||||
|
||||
var mediaIterator = serialized.CreateNavigator().Select("/");
|
||||
|
||||
return mediaIterator.Current == null
|
||||
? Enumerable.Empty<IPublishedContent>()
|
||||
: ToIPublishedContent(parentId, mediaIterator.Current);
|
||||
}
|
||||
|
||||
|
||||
internal IEnumerable<IPublishedContent> ToIPublishedContent(int parentId, XPathNavigator xpath)
|
||||
{
|
||||
var mediaList = new List<IPublishedContent>();
|
||||
|
||||
// this is so bad, really
|
||||
var item = xpath.Select("//*[@id='" + parentId + "']");
|
||||
if (item.Current == null)
|
||||
return Enumerable.Empty<IPublishedContent>();
|
||||
var items = item.Current.SelectChildren(XPathNodeType.Element);
|
||||
|
||||
// and this does not work, because... meh
|
||||
//var q = "//* [@id='" + parentId + "']/* [@id]";
|
||||
//var items = xpath.Select(q);
|
||||
|
||||
foreach (XPathNavigator itemm in items)
|
||||
{
|
||||
int id;
|
||||
if (int.TryParse(itemm.GetAttribute("id", ""), out id) == false)
|
||||
continue; // uh?
|
||||
var captured = itemm;
|
||||
var cacheValues = GetCacheValues(id, idd => ConvertFromXPathNavigator(captured));
|
||||
mediaList.Add(CreateFromCacheValues(cacheValues));
|
||||
}
|
||||
|
||||
return mediaList;
|
||||
}
|
||||
|
||||
|
||||
internal void Resync()
|
||||
{
|
||||
// clear recursive properties cached by XmlPublishedContent.GetProperty
|
||||
// assume that nothing else is going to cache IPublishedProperty items (else would need to do ByKeySearch)
|
||||
// NOTE all properties cleared when clearing the content cache (see content cache)
|
||||
//_appCache.ClearCacheObjectTypes<IPublishedProperty>();
|
||||
//_appCache.ClearCacheByKeySearch("XmlPublishedCache.PublishedMediaCache:RecursiveProperty-");
|
||||
}
|
||||
|
||||
#region Content types
|
||||
|
||||
public override IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Media, id);
|
||||
|
||||
public override IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Media, alias);
|
||||
|
||||
public override IPublishedContentType GetContentType(Guid key) => _contentTypeCache.Get(PublishedItemType.Media, key);
|
||||
|
||||
public override IEnumerable<IPublishedContent> GetByContentType(IPublishedContentType contentType)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// REFACTORING
|
||||
|
||||
// caching the basic atomic values - and the parent id
|
||||
// but NOT caching actual parent nor children and NOT even
|
||||
// the list of children ids - BUT caching the path
|
||||
|
||||
internal class CacheValues
|
||||
{
|
||||
public IReadOnlyDictionary<string, string> Values { get; set; }
|
||||
public XPathNavigator XPath { get; set; }
|
||||
public bool FromExamine { get; set; }
|
||||
}
|
||||
|
||||
public const string PublishedMediaCacheKey = "MediaCacheMeh.";
|
||||
private const int PublishedMediaCacheTimespanSeconds = 4 * 60; // 4 mins
|
||||
private static TimeSpan _publishedMediaCacheTimespan;
|
||||
private static bool _publishedMediaCacheEnabled;
|
||||
|
||||
private static void InitializeCacheConfig()
|
||||
{
|
||||
_publishedMediaCacheEnabled = true;
|
||||
_publishedMediaCacheTimespan = TimeSpan.FromSeconds(PublishedMediaCacheTimespanSeconds);
|
||||
}
|
||||
|
||||
internal IPublishedContent CreateFromCacheValues(CacheValues cacheValues)
|
||||
{
|
||||
var content = new DictionaryPublishedContent(
|
||||
cacheValues.Values,
|
||||
parentId => parentId < 0 ? null : GetUmbracoMedia(parentId),
|
||||
GetChildrenMedia,
|
||||
GetProperty,
|
||||
_appCache,
|
||||
_variationContextAccessor,
|
||||
_contentTypeCache,
|
||||
cacheValues.XPath, // though, outside of tests, that should be null
|
||||
cacheValues.FromExamine
|
||||
);
|
||||
return content.CreateModel(Current.PublishedModelFactory);
|
||||
}
|
||||
|
||||
private static CacheValues GetCacheValues(int id, Func<int, CacheValues> func)
|
||||
{
|
||||
if (_publishedMediaCacheEnabled == false)
|
||||
return func(id);
|
||||
|
||||
var cache = Current.AppCaches.RuntimeCache;
|
||||
var key = PublishedMediaCacheKey + id;
|
||||
return (CacheValues)cache.Get(key, () => func(id), _publishedMediaCacheTimespan);
|
||||
}
|
||||
|
||||
internal static void ClearCache(int id)
|
||||
{
|
||||
var cache = Current.AppCaches.RuntimeCache;
|
||||
var sid = id.ToString();
|
||||
var key = PublishedMediaCacheKey + sid;
|
||||
|
||||
// we do clear a lot of things... but the cache refresher is somewhat
|
||||
// convoluted and it's hard to tell what to clear exactly ;-(
|
||||
|
||||
// clear the parent - NOT (why?)
|
||||
//var exist = (CacheValues) cache.GetCacheItem(key);
|
||||
//if (exist != null)
|
||||
// cache.ClearCacheItem(PublishedMediaCacheKey + GetValuesValue(exist.Values, "parentID"));
|
||||
|
||||
// clear the item
|
||||
cache.Clear(key);
|
||||
|
||||
// clear all children - in case we moved and their path has changed
|
||||
var fid = "/" + sid + "/";
|
||||
cache.ClearOfType<CacheValues>((k, v) =>
|
||||
GetValuesValue(v.Values, "path", "__Path").Contains(fid));
|
||||
}
|
||||
|
||||
private static string GetValuesValue(IReadOnlyDictionary<string, string> d, params string[] keys)
|
||||
{
|
||||
string value = null;
|
||||
var ignored = keys.Any(x => d.TryGetValue(x, out value));
|
||||
return value ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Exposes a member object as IPublishedContent
|
||||
/// </summary>
|
||||
public sealed class PublishedMember : PublishedContentBase
|
||||
{
|
||||
private readonly IMember _member;
|
||||
private readonly IMembershipUser _membershipUser;
|
||||
private readonly IPublishedProperty[] _properties;
|
||||
private readonly IPublishedContentType _publishedMemberType;
|
||||
|
||||
public PublishedMember(
|
||||
IMember member,
|
||||
IPublishedContentType publishedMemberType,
|
||||
IVariationContextAccessor variationContextAccessor) : base(variationContextAccessor)
|
||||
{
|
||||
_member = member ?? throw new ArgumentNullException(nameof(member));
|
||||
_membershipUser = member;
|
||||
_publishedMemberType = publishedMemberType ?? throw new ArgumentNullException(nameof(publishedMemberType));
|
||||
|
||||
// RawValueProperty is used for two things here
|
||||
// - for the 'map properties' thing that we should really get rid of
|
||||
// - for populating properties that every member should always have, and that we force-create
|
||||
// if they are not part of the member type properties - in which case they are created as
|
||||
// simple raw properties - which are completely invariant
|
||||
|
||||
var properties = new List<IPublishedProperty>();
|
||||
foreach (var propertyType in _publishedMemberType.PropertyTypes)
|
||||
{
|
||||
var property = _member.Properties[propertyType.Alias];
|
||||
if (property == null) continue;
|
||||
|
||||
properties.Add(new RawValueProperty(propertyType, this, property.GetValue()));
|
||||
}
|
||||
EnsureMemberProperties(properties);
|
||||
_properties = properties.ToArray();
|
||||
}
|
||||
|
||||
#region Membership provider member properties
|
||||
|
||||
public string Email => _membershipUser.Email;
|
||||
|
||||
public string UserName => _membershipUser.Username;
|
||||
|
||||
public string Comments => _membershipUser.Comments;
|
||||
|
||||
public bool IsApproved => _membershipUser.IsApproved;
|
||||
|
||||
public bool IsLockedOut => _membershipUser.IsLockedOut;
|
||||
|
||||
public DateTime LastLockoutDate => _membershipUser.LastLockoutDate;
|
||||
|
||||
public DateTime CreationDate => _membershipUser.CreateDate;
|
||||
|
||||
public DateTime LastLoginDate => _membershipUser.LastLoginDate;
|
||||
|
||||
public DateTime LastPasswordChangeDate => _membershipUser.LastPasswordChangeDate;
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPublishedContent
|
||||
|
||||
public override PublishedItemType ItemType => PublishedItemType.Member;
|
||||
|
||||
public override bool IsDraft(string culture = null) => false;
|
||||
|
||||
public override bool IsPublished(string culture = null) => true;
|
||||
|
||||
public override IPublishedContent Parent => null;
|
||||
|
||||
public override IEnumerable<IPublishedContent> Children => Enumerable.Empty<IPublishedContent>();
|
||||
|
||||
public override IEnumerable<IPublishedContent> ChildrenForAllCultures => Enumerable.Empty<IPublishedContent>();
|
||||
|
||||
public override IEnumerable<IPublishedProperty> Properties => _properties;
|
||||
|
||||
public override IPublishedProperty GetProperty(string alias)
|
||||
{
|
||||
return _properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias));
|
||||
}
|
||||
|
||||
private void EnsureMemberProperties(List<IPublishedProperty> properties)
|
||||
{
|
||||
var aliases = properties.Select(x => x.Alias).ToList();
|
||||
|
||||
EnsureMemberProperty(properties, aliases, nameof(IMember.Email), Email);
|
||||
EnsureMemberProperty(properties, aliases, nameof(IMember.Username), UserName);
|
||||
EnsureMemberProperty(properties, aliases, nameof(IMember.Comments), Comments);
|
||||
EnsureMemberProperty(properties, aliases, nameof(IMember.IsApproved), IsApproved);
|
||||
EnsureMemberProperty(properties, aliases, nameof(IMember.IsLockedOut), IsLockedOut);
|
||||
EnsureMemberProperty(properties, aliases, nameof(IMember.LastLockoutDate), LastLockoutDate);
|
||||
EnsureMemberProperty(properties, aliases, nameof(IMember.CreateDate), CreateDate);
|
||||
EnsureMemberProperty(properties, aliases, nameof(IMember.LastLoginDate), LastLoginDate);
|
||||
EnsureMemberProperty(properties, aliases, nameof(IMember.LastPasswordChangeDate), LastPasswordChangeDate);
|
||||
}
|
||||
|
||||
private void EnsureMemberProperty(List<IPublishedProperty> properties, List<string> aliases, string alias, object value)
|
||||
{
|
||||
// if the property already has a value, nothing to do
|
||||
if (aliases.Contains(alias)) return;
|
||||
|
||||
// if not a property type, ignore
|
||||
var propertyType = ContentType.GetPropertyType(alias);
|
||||
if (propertyType == null) return;
|
||||
|
||||
// create a raw-value property
|
||||
// note: throws if propertyType variations is not InvariantNeutral
|
||||
var property = new RawValueProperty(propertyType, this, value);
|
||||
properties.Add(property);
|
||||
}
|
||||
|
||||
public override IPublishedContentType ContentType => _publishedMemberType;
|
||||
|
||||
public override int Id => _member.Id;
|
||||
|
||||
public override Guid Key => _member.Key;
|
||||
|
||||
public override int? TemplateId => throw new NotSupportedException();
|
||||
|
||||
public override int SortOrder => 0;
|
||||
|
||||
public override string Name => _member.Name;
|
||||
|
||||
public override IReadOnlyDictionary<string, PublishedCultureInfo> Cultures => throw new NotSupportedException();
|
||||
|
||||
public override string UrlSegment => throw new NotSupportedException();
|
||||
|
||||
public override int WriterId => _member.CreatorId;
|
||||
|
||||
public override int CreatorId => _member.CreatorId;
|
||||
|
||||
public override string Path => _member.Path;
|
||||
|
||||
public override DateTime CreateDate => _member.CreateDate;
|
||||
|
||||
public override DateTime UpdateDate => _member.UpdateDate;
|
||||
|
||||
public override int Level => _member.Level;
|
||||
|
||||
public DateTime LastPasswordChangedDate => throw new NotImplementedException();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Web.Composing;
|
||||
|
||||
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
{
|
||||
class PublishedMemberCache : IPublishedMemberCache
|
||||
{
|
||||
private readonly PublishedContentTypeCache _contentTypeCache;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
public PublishedMemberCache(PublishedContentTypeCache contentTypeCache, IVariationContextAccessor variationContextAccessor)
|
||||
{
|
||||
_contentTypeCache = contentTypeCache;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
}
|
||||
|
||||
public IPublishedContent Get(IMember member)
|
||||
{
|
||||
var type = _contentTypeCache.Get(PublishedItemType.Member, member.ContentTypeId);
|
||||
return new PublishedMember(member, type, _variationContextAccessor)
|
||||
.CreateModel(Current.PublishedModelFactory);
|
||||
}
|
||||
|
||||
#region Content types
|
||||
|
||||
public IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Member, id);
|
||||
|
||||
public IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Member, alias);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using System;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
|
||||
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a published snapshot.
|
||||
/// </summary>
|
||||
class PublishedSnapshot : IPublishedSnapshot
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PublishedSnapshot"/> class with a content cache
|
||||
/// and a media cache.
|
||||
/// </summary>
|
||||
public PublishedSnapshot(
|
||||
PublishedContentCache contentCache,
|
||||
PublishedMediaCache mediaCache,
|
||||
PublishedMemberCache memberCache,
|
||||
DomainCache domainCache)
|
||||
{
|
||||
Content = contentCache;
|
||||
Media = mediaCache;
|
||||
Members = memberCache;
|
||||
Domains = domainCache;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPublishedContentCache Content { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPublishedMediaCache Media { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPublishedMemberCache Members { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDomainCache Domains { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAppCache SnapshotCache => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAppCache ElementsCache => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable ForcedPreview(bool preview, Action<bool> callback = null)
|
||||
{
|
||||
// the XML cache does not support forcing preview, really, so, just pretend...
|
||||
return new ForcedPreviewObject();
|
||||
}
|
||||
|
||||
private class ForcedPreviewObject : DisposableObjectSlim
|
||||
{
|
||||
protected override void DisposeResources()
|
||||
{ }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
{
|
||||
// Note: RoutesCache closely follows the caching strategy dating from v4, which
|
||||
// is obviously broken in many ways (eg it's a global cache but relying to some
|
||||
// extend to the content cache, which itself is local to each request...).
|
||||
// Not going to fix it anyway.
|
||||
|
||||
class RoutesCache
|
||||
{
|
||||
private ConcurrentDictionary<int, string> _routes;
|
||||
private ConcurrentDictionary<string, int> _nodeIds;
|
||||
|
||||
// NOTE
|
||||
// RoutesCache is cleared by
|
||||
// - ContentTypeCacheRefresher, whenever anything happens to any content type
|
||||
// - DomainCacheRefresher, whenever anything happens to any domain
|
||||
// - XmlStore, whenever anything happens to the XML cache
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RoutesCache"/> class.
|
||||
/// </summary>
|
||||
public RoutesCache()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used ONLY for unit tests
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal IDictionary<int, string> GetCachedRoutes()
|
||||
{
|
||||
return _routes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used ONLY for unit tests
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal IDictionary<string, int> GetCachedIds()
|
||||
{
|
||||
return _nodeIds;
|
||||
}
|
||||
|
||||
#region Public
|
||||
|
||||
/// <summary>
|
||||
/// Stores a route for a node.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node identified.</param>
|
||||
/// <param name="route">The route.</param>
|
||||
/// <param name="trust">A value indicating whether the value can be trusted for inbound routing.</param>
|
||||
public void Store(int nodeId, string route, bool trust)
|
||||
{
|
||||
_routes.AddOrUpdate(nodeId, i => route, (i, s) => route);
|
||||
if (trust)
|
||||
_nodeIds.AddOrUpdate(route, i => nodeId, (i, s) => nodeId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a route for a node.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node identifier.</param>
|
||||
/// <returns>The route for the node, else null.</returns>
|
||||
public string GetRoute(int nodeId)
|
||||
{
|
||||
string val;
|
||||
_routes.TryGetValue(nodeId, out val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a node for a route.
|
||||
/// </summary>
|
||||
/// <param name="route">The route.</param>
|
||||
/// <returns>The node identified for the route, else zero.</returns>
|
||||
public int GetNodeId(string route)
|
||||
{
|
||||
int val;
|
||||
_nodeIds.TryGetValue(route, out val);
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the route for a node.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">The node identifier.</param>
|
||||
public void ClearNode(int nodeId)
|
||||
{
|
||||
string route;
|
||||
if (_routes.TryRemove(nodeId, out route))
|
||||
{
|
||||
int id;
|
||||
_nodeIds.TryRemove(route, out id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all routes.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_routes = new ConcurrentDictionary<int, string>();
|
||||
_nodeIds = new ConcurrentDictionary<string, int>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
using System;
|
||||
using System.Xml;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
{
|
||||
// TODO: should be a ScopeContextualBase
|
||||
internal class SafeXmlReaderWriter : IDisposable
|
||||
{
|
||||
private readonly bool _scoped;
|
||||
private readonly Action<XmlDocument> _refresh;
|
||||
private readonly Action<XmlDocument, bool> _apply;
|
||||
private IDisposable _releaser;
|
||||
private bool _applyChanges;
|
||||
private XmlDocument _xml, _origXml;
|
||||
private bool _using;
|
||||
private bool _registerXmlChange;
|
||||
|
||||
// the default enlist priority is 100
|
||||
// enlist with a lower priority to ensure that anything "default" has a clean xml
|
||||
private const int EnlistPriority = 60;
|
||||
private const string EnlistKey = "safeXmlReaderWriter";
|
||||
|
||||
private SafeXmlReaderWriter(IDisposable releaser, XmlDocument xml, Action<XmlDocument> refresh, Action<XmlDocument, bool> apply, bool isWriter, bool scoped)
|
||||
{
|
||||
_releaser = releaser;
|
||||
_refresh = refresh;
|
||||
_apply = apply;
|
||||
_scoped = scoped;
|
||||
|
||||
IsWriter = isWriter;
|
||||
|
||||
_xml = IsWriter ? Clone(xml) : xml;
|
||||
}
|
||||
|
||||
public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider)
|
||||
{
|
||||
return scopeProvider?.Context?.GetEnlisted<SafeXmlReaderWriter>(EnlistKey);
|
||||
}
|
||||
|
||||
public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider, SystemLock xmlLock, XmlDocument xml, Action<XmlDocument> refresh, Action<XmlDocument, bool> apply, bool writer)
|
||||
{
|
||||
var scopeContext = scopeProvider.Context;
|
||||
|
||||
// no real scope = just create a reader/writer instance
|
||||
if (scopeContext == null)
|
||||
{
|
||||
// obtain exclusive access to xml and create reader/writer
|
||||
var releaser = xmlLock.Lock();
|
||||
return new SafeXmlReaderWriter(releaser, xml, refresh, apply, writer, false);
|
||||
}
|
||||
|
||||
// get or create an enlisted reader/writer
|
||||
var rw = scopeContext.Enlist(EnlistKey,
|
||||
() => // creator
|
||||
{
|
||||
// obtain exclusive access to xml and create reader/writer
|
||||
var releaser = xmlLock.Lock();
|
||||
return new SafeXmlReaderWriter(releaser, xml, refresh, apply, writer, true);
|
||||
},
|
||||
(completed, item) => // action
|
||||
{
|
||||
item.DisposeForReal(completed);
|
||||
}, EnlistPriority);
|
||||
|
||||
// ensure it's not already in-use - should never happen, just being super safe
|
||||
if (rw._using)
|
||||
throw new InvalidOperationException("panic: used.");
|
||||
rw._using = true;
|
||||
|
||||
return rw;
|
||||
}
|
||||
|
||||
public bool IsWriter { get; private set; }
|
||||
|
||||
public void UpgradeToWriter(bool auto)
|
||||
{
|
||||
if (IsWriter)
|
||||
throw new InvalidOperationException("Already a writer.");
|
||||
IsWriter = true;
|
||||
|
||||
_xml = Clone(_xml);
|
||||
}
|
||||
|
||||
// for tests
|
||||
internal static Action Cloning { get; set; }
|
||||
|
||||
private XmlDocument Clone(XmlDocument xml)
|
||||
{
|
||||
Cloning?.Invoke();
|
||||
if (_origXml != null)
|
||||
throw new Exception("panic.");
|
||||
_origXml = xml;
|
||||
return (XmlDocument) xml?.CloneNode(true);
|
||||
}
|
||||
|
||||
public XmlDocument Xml
|
||||
{
|
||||
get => _xml;
|
||||
set
|
||||
{
|
||||
if (IsWriter == false)
|
||||
throw new InvalidOperationException("Not a writer.");
|
||||
_xml = value;
|
||||
}
|
||||
}
|
||||
|
||||
// registerXmlChange indicates whether to do what should be done when Xml changes,
|
||||
// that is, to request that the file be written to disk - something we don't want
|
||||
// to do if we're committing Xml precisely after we've read from disk!
|
||||
public void AcceptChanges(bool registerXmlChange = true)
|
||||
{
|
||||
if (IsWriter == false)
|
||||
throw new InvalidOperationException("Not a writer.");
|
||||
|
||||
_applyChanges = true;
|
||||
_registerXmlChange |= registerXmlChange;
|
||||
}
|
||||
|
||||
private void DisposeForReal(bool completed)
|
||||
{
|
||||
if (IsWriter)
|
||||
{
|
||||
// apply changes, or restore the original xml for the current request
|
||||
if (_applyChanges && completed)
|
||||
_apply(_xml, _registerXmlChange);
|
||||
else
|
||||
_refresh(_origXml);
|
||||
}
|
||||
|
||||
// release the lock
|
||||
_releaser.Dispose();
|
||||
_releaser = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_using = false;
|
||||
|
||||
if (_scoped == false)
|
||||
{
|
||||
// really dispose
|
||||
DisposeForReal(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// don't really dispose,
|
||||
// just apply the changes for the current request
|
||||
_refresh(_xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Tests.LegacyXmlPublishedCache
|
||||
{
|
||||
static class UmbracoContextCache
|
||||
{
|
||||
static readonly ConditionalWeakTable<IUmbracoContext, ConcurrentDictionary<string, object>> Caches
|
||||
= new ConditionalWeakTable<IUmbracoContext, ConcurrentDictionary<string, object>>();
|
||||
|
||||
public static ConcurrentDictionary<string, object> Current
|
||||
{
|
||||
get
|
||||
{
|
||||
var umbracoContext = Umbraco.Web.Composing.Current.UmbracoContext;
|
||||
|
||||
// will get or create a value
|
||||
// a ConditionalWeakTable is thread-safe
|
||||
// does not prevent the context from being disposed, and then the dictionary will be disposed too
|
||||
return umbracoContext == null ? null : Caches.GetOrCreateValue(umbracoContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user