V10: fix build warnings in test projects (#12509)

* Run code cleanup

* Dotnet format benchmarks project

* Fix up Test.Common

* Run dotnet format + manual cleanup

* Run code cleanup for unit tests

* Run dotnet format

* Fix up errors

* Manual cleanup of Unit test project

* Update tests/Umbraco.Tests.Benchmarks/HexStringBenchmarks.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update tests/Umbraco.Tests.Integration/Testing/TestDbMeta.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update tests/Umbraco.Tests.Benchmarks/TypeFinderBenchmarks.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Fix according to review

* Fix after merge

* Fix errors

Co-authored-by: Nikolaj Geisle <niko737@edu.ucl.dk>
Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
Co-authored-by: Zeegaan <nge@umbraco.dk>
This commit is contained in:
Nikolaj Geisle
2022-06-21 08:09:38 +02:00
committed by GitHub
parent 29961d40a3
commit 7aeb400fce
599 changed files with 87303 additions and 86123 deletions

View File

@@ -1,16 +1,15 @@
namespace Umbraco.TestData.Configuration
{
public class TestDataSettings
{
/// <summary>
/// Gets or sets a value indicating whether the test data generation is enabled.
/// </summary>
public bool Enabled { get; set; } = false;
namespace Umbraco.TestData.Configuration;
/// <summary>
/// Gets or sets a value indicating whether persisted local database cache files for content and media are disabled.
/// </summary>
/// <value>The URL path.</value>
public bool IgnoreLocalDb { get; set; } = false;
}
public class TestDataSettings
{
/// <summary>
/// Gets or sets a value indicating whether the test data generation is enabled.
/// </summary>
public bool Enabled { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether persisted local database cache files for content and media are disabled.
/// </summary>
/// <value>The URL path.</value>
public bool IgnoreLocalDb { get; set; } = false;
}

View File

@@ -7,48 +7,44 @@ using Umbraco.Cms.Infrastructure.PublishedCache;
using Umbraco.Cms.Web.Common.ApplicationBuilder;
using Umbraco.TestData.Configuration;
namespace Umbraco.TestData.Extensions
namespace Umbraco.TestData.Extensions;
public static class UmbracoBuilderExtensions
{
public static class UmbracoBuilderExtensions
public static IUmbracoBuilder AddUmbracoTestData(this IUmbracoBuilder builder)
{
public static IUmbracoBuilder AddUmbracoTestData(this IUmbracoBuilder builder)
if (builder.Services.Any(x => x.ServiceType == typeof(LoadTestController)))
{
if (builder.Services.Any(x => x.ServiceType == typeof(LoadTestController)))
{
// We assume the test data project is composed if any implementations of LoadTestController exist.
return builder;
}
IConfigurationSection testDataSection = builder.Config.GetSection("Umbraco:CMS:TestData");
TestDataSettings config = testDataSection.Get<TestDataSettings>();
if (config == null || config.Enabled == false)
{
return builder;
}
builder.Services.Configure<TestDataSettings>(testDataSection);
if (config.IgnoreLocalDb)
{
builder.Services.AddSingleton(factory => new PublishedSnapshotServiceOptions
{
IgnoreLocalDb = true
});
}
builder.Services.Configure<UmbracoPipelineOptions>(options =>
options.AddFilter(new UmbracoPipelineFilter(nameof(LoadTestController))
{
Endpoints = app => app.UseEndpoints(endpoints =>
endpoints.MapControllerRoute(
"LoadTest",
"/LoadTest/{action}",
new { controller = "LoadTest", Action = "Index" }))
}));
builder.Services.AddScoped(typeof(LoadTestController));
// We assume the test data project is composed if any implementations of LoadTestController exist.
return builder;
}
var testDataSection = builder.Config.GetSection("Umbraco:CMS:TestData");
var config = testDataSection.Get<TestDataSettings>();
if (config == null || config.Enabled == false)
{
return builder;
}
builder.Services.Configure<TestDataSettings>(testDataSection);
if (config.IgnoreLocalDb)
{
builder.Services.AddSingleton(factory => new PublishedSnapshotServiceOptions { IgnoreLocalDb = true });
}
builder.Services.Configure<UmbracoPipelineOptions>(options =>
options.AddFilter(new UmbracoPipelineFilter(nameof(LoadTestController))
{
Endpoints = app => app.UseEndpoints(endpoints =>
endpoints.MapControllerRoute(
"LoadTest",
"/LoadTest/{action}",
new { controller = "LoadTest", Action = "Index" }))
}));
builder.Services.AddScoped(typeof(LoadTestController));
return builder;
}
}

View File

@@ -4,10 +4,9 @@ using Umbraco.TestData.Extensions;
// see https://github.com/Shazwazza/UmbracoScripts/tree/master/src/LoadTesting
namespace Umbraco.TestData
namespace Umbraco.TestData;
public class LoadTestComposer : IComposer
{
public class LoadTestComposer : IComposer
{
public void Compose(IUmbracoBuilder builder) => builder.AddUmbracoTestData();
}
public void Compose(IUmbracoBuilder builder) => builder.AddUmbracoTestData();
}

View File

@@ -10,24 +10,28 @@ using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
// see https://github.com/Shazwazza/UmbracoScripts/tree/master/src/LoadTesting
namespace Umbraco.TestData
namespace Umbraco.TestData;
public class LoadTestController : Controller
{
public class LoadTestController : Controller
{
private static readonly Random s_random = new Random();
private static readonly object s_locko = new object();
private const string ContainerAlias = "LoadTestContainer";
private const string ContentAlias = "LoadTestContent";
private const int TextboxDefinitionId = -88;
private const int MaxCreate = 1000;
private static volatile int s_containerId = -1;
private const string FootHtml = @"</body>
</html>";
private const string ContainerAlias = "LoadTestContainer";
private const string ContentAlias = "LoadTestContent";
private const int TextboxDefinitionId = -88;
private const int MaxCreate = 1000;
private static readonly Random s_random = new();
private static readonly object s_locko = new();
private static readonly string s_headHtml = @"<html>
private static volatile int s_containerId = -1;
private static readonly string s_headHtml = @"<html>
<head>
<title>LoadTest</title>
<style>
@@ -48,10 +52,7 @@ namespace Umbraco.TestData
</div>
";
private const string FootHtml = @"</body>
</html>";
private static readonly string s_containerTemplateText = @"
private static readonly string s_containerTemplateText = @"
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@inject Umbraco.Cms.Core.Configuration.IUmbracoVersion _umbracoVersion
@{
@@ -92,42 +93,43 @@ namespace Umbraco.TestData
</div>
" + FootHtml;
private readonly IContentTypeService _contentTypeService;
private readonly IContentService _contentService;
private readonly IDataTypeService _dataTypeService;
private readonly IFileService _fileService;
private readonly IShortStringHelper _shortStringHelper;
private readonly Cms.Core.Hosting.IHostingEnvironment _hostingEnvironment;
private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly IContentService _contentService;
public LoadTestController(
IContentTypeService contentTypeService,
IContentService contentService,
IDataTypeService dataTypeService,
IFileService fileService,
IShortStringHelper shortStringHelper,
Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
IHostApplicationLifetime hostApplicationLifetime)
private readonly IContentTypeService _contentTypeService;
private readonly IDataTypeService _dataTypeService;
private readonly IFileService _fileService;
private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IShortStringHelper _shortStringHelper;
public LoadTestController(
IContentTypeService contentTypeService,
IContentService contentService,
IDataTypeService dataTypeService,
IFileService fileService,
IShortStringHelper shortStringHelper,
IHostingEnvironment hostingEnvironment,
IHostApplicationLifetime hostApplicationLifetime)
{
_contentTypeService = contentTypeService;
_contentService = contentService;
_dataTypeService = dataTypeService;
_fileService = fileService;
_shortStringHelper = shortStringHelper;
_hostingEnvironment = hostingEnvironment;
_hostApplicationLifetime = hostApplicationLifetime;
}
public IActionResult Index()
{
var res = EnsureInitialize();
if (res != null)
{
_contentTypeService = contentTypeService;
_contentService = contentService;
_dataTypeService = dataTypeService;
_fileService = fileService;
_shortStringHelper = shortStringHelper;
_hostingEnvironment = hostingEnvironment;
_hostApplicationLifetime = hostApplicationLifetime;
return res;
}
public IActionResult Index()
{
IActionResult res = EnsureInitialize();
if (res != null)
{
return res;
}
var html = @"Welcome. You can:
var html = @"Welcome. You can:
<ul>
<li><a href=""/LoadTestContainer"">List existing contents</a> (u:url)</li>
<li><a href=""/LoadTest/Create?o=browser"">Create a content</a> (o:origin, r:restart, n:number)</li>
@@ -139,249 +141,250 @@ namespace Umbraco.TestData
</ul>
";
return ContentHtml(html);
return ContentHtml(html);
}
private IActionResult EnsureInitialize()
{
if (s_containerId > 0)
{
return null;
}
private IActionResult EnsureInitialize()
lock (s_locko)
{
if (s_containerId > 0)
{
return null;
}
lock (s_locko)
var contentType = _contentTypeService.Get(ContentAlias);
if (contentType == null)
{
if (s_containerId > 0)
{
return null;
}
IContentType contentType = _contentTypeService.Get(ContentAlias);
if (contentType == null)
{
return ContentHtml("Not installed, first you must <a href=\"/LoadTest/Install\">install</a>.");
}
IContentType containerType = _contentTypeService.Get(ContainerAlias);
if (containerType == null)
{
return ContentHtml("Panic! Container type is missing.");
}
IContent container = _contentService.GetPagedOfType(containerType.Id, 0, 100, out _, null).FirstOrDefault();
if (container == null)
{
return ContentHtml("Panic! Container is missing.");
}
s_containerId = container.Id;
return null;
}
}
private IActionResult ContentHtml(string s) => Content(s_headHtml + s + FootHtml, "text/html");
public IActionResult Install()
{
var contentType = new ContentType(_shortStringHelper, -1)
{
Alias = ContentAlias,
Name = "LoadTest Content",
Description = "Content for LoadTest",
Icon = "icon-document"
};
IDataType def = _dataTypeService.GetDataType(TextboxDefinitionId);
contentType.AddPropertyType(new PropertyType(_shortStringHelper, def)
{
Name = "Origin",
Alias = "origin",
Description = "The origin of the content.",
});
_contentTypeService.Save(contentType);
Template containerTemplate = ImportTemplate(
"LoadTestContainer",
"LoadTestContainer",
s_containerTemplateText);
var containerType = new ContentType(_shortStringHelper, -1)
{
Alias = ContainerAlias,
Name = "LoadTest Container",
Description = "Container for LoadTest content",
Icon = "icon-document",
AllowedAsRoot = true,
IsContainer = true
};
containerType.AllowedContentTypes = containerType.AllowedContentTypes.Union(new[]
{
new ContentTypeSort(new Lazy<int>(() => contentType.Id), 0, contentType.Alias),
});
containerType.AllowedTemplates = containerType.AllowedTemplates.Union(new[] { containerTemplate });
containerType.SetDefaultTemplate(containerTemplate);
_contentTypeService.Save(containerType);
IContent content = _contentService.Create("LoadTestContainer", -1, ContainerAlias);
_contentService.SaveAndPublish(content);
return ContentHtml("Installed.");
}
private Template ImportTemplate(string name, string alias, string text, ITemplate master = null)
{
var t = new Template(_shortStringHelper, name, alias) { Content = text };
if (master != null)
{
t.SetMasterTemplate(master);
return ContentHtml("Not installed, first you must <a href=\"/LoadTest/Install\">install</a>.");
}
_fileService.SaveTemplate(t);
return t;
}
public IActionResult Create(int n = 1, int r = 0, string o = null)
{
IActionResult res = EnsureInitialize();
if (res != null)
var containerType = _contentTypeService.Get(ContainerAlias);
if (containerType == null)
{
return res;
return ContentHtml("Panic! Container type is missing.");
}
if (r < 0)
var container = _contentService.GetPagedOfType(containerType.Id, 0, 100, out _, null).FirstOrDefault();
if (container == null)
{
r = 0;
return ContentHtml("Panic! Container is missing.");
}
if (r > 100)
{
r = 100;
}
var restart = GetRandom(0, 100) > (100 - r);
if (n < 1)
{
n = 1;
}
if (n > MaxCreate)
{
n = MaxCreate;
}
for (int i = 0; i < n; i++)
{
var name = Guid.NewGuid().ToString("N").ToUpper() + "-" + (restart ? "R" : "X") + "-" + o;
IContent content = _contentService.Create(name, s_containerId, ContentAlias);
content.SetValue("origin", o);
_contentService.SaveAndPublish(content);
}
if (restart)
{
DoRestart();
}
return ContentHtml("Created " + n + " content"
+ (restart ? ", and restarted" : "")
+ ".");
}
private static int GetRandom(int minValue, int maxValue)
{
lock (s_locko)
{
return s_random.Next(minValue, maxValue);
}
}
public IActionResult Clear()
{
IActionResult res = EnsureInitialize();
if (res != null)
{
return res;
}
IContentType contentType = _contentTypeService.Get(ContentAlias);
_contentService.DeleteOfType(contentType.Id);
return ContentHtml("Cleared.");
}
private void DoRestart()
{
HttpContext.User = null;
Thread.CurrentPrincipal = null;
_hostApplicationLifetime.StopApplication();
}
public IActionResult ColdBootRestart()
{
Directory.Delete(_hostingEnvironment.MapPathContentRoot(Path.Combine(Constants.SystemDirectories.TempData,"DistCache")), true);
DoRestart();
return Content("Cold Boot Restarted.");
}
public IActionResult Restart()
{
DoRestart();
return ContentHtml("Restarted.");
}
public IActionResult Die()
{
var timer = new Timer(_ => throw new Exception("die!"));
_ = timer.Change(100, 0);
return ContentHtml("Dying.");
}
public IActionResult Domains()
{
AppDomain currentDomain = AppDomain.CurrentDomain;
var currentName = currentDomain.FriendlyName;
var pos = currentName.IndexOf('-');
if (pos > 0)
{
currentName = currentName.Substring(0, pos);
}
var text = new StringBuilder();
text.Append("<div class=\"block\">Process ID: " + Process.GetCurrentProcess().Id + "</div>");
text.Append("<div class=\"block\">");
// TODO (V9): Commented out as I assume not available?
////text.Append("<div>IIS Site: " + HostingEnvironment.ApplicationHost.GetSiteName() + "</div>");
text.Append("<div>App ID: " + currentName + "</div>");
//text.Append("<div>AppPool: " + Zbu.WebManagement.AppPoolHelper.GetCurrentApplicationPoolName() + "</div>");
text.Append("</div>");
text.Append("<div class=\"block\">Domains:<ul>");
text.Append("<li>Not implemented.</li>");
/*
foreach (var domain in Zbu.WebManagement.AppDomainHelper.GetAppDomains().OrderBy(x => x.Id))
{
var name = domain.FriendlyName;
pos = name.IndexOf('-');
if (pos > 0) name = name.Substring(0, pos);
text.Append("<li style=\""
+ (name != currentName ? "color: #cccccc;" : "")
//+ (domain.Id == currentDomain.Id ? "" : "")
+ "\">"
+"[" + domain.Id + "] " + name
+ (domain.IsDefaultAppDomain() ? " (default)" : "")
+ (domain.Id == currentDomain.Id ? " (current)" : "")
+ "</li>");
}
*/
text.Append("</ul></div>");
return ContentHtml(text.ToString());
s_containerId = container.Id;
return null;
}
}
private IActionResult ContentHtml(string s) => Content(s_headHtml + s + FootHtml, "text/html");
public IActionResult Install()
{
var contentType = new ContentType(_shortStringHelper, -1)
{
Alias = ContentAlias,
Name = "LoadTest Content",
Description = "Content for LoadTest",
Icon = "icon-document"
};
var def = _dataTypeService.GetDataType(TextboxDefinitionId);
contentType.AddPropertyType(new PropertyType(_shortStringHelper, def)
{
Name = "Origin",
Alias = "origin",
Description = "The origin of the content."
});
_contentTypeService.Save(contentType);
var containerTemplate = ImportTemplate(
"LoadTestContainer",
"LoadTestContainer",
s_containerTemplateText);
var containerType = new ContentType(_shortStringHelper, -1)
{
Alias = ContainerAlias,
Name = "LoadTest Container",
Description = "Container for LoadTest content",
Icon = "icon-document",
AllowedAsRoot = true,
IsContainer = true
};
containerType.AllowedContentTypes = containerType.AllowedContentTypes.Union(new[]
{
new ContentTypeSort(new Lazy<int>(() => contentType.Id), 0, contentType.Alias)
});
containerType.AllowedTemplates = containerType.AllowedTemplates.Union(new[] { containerTemplate });
containerType.SetDefaultTemplate(containerTemplate);
_contentTypeService.Save(containerType);
var content = _contentService.Create("LoadTestContainer", -1, ContainerAlias);
_contentService.SaveAndPublish(content);
return ContentHtml("Installed.");
}
private Template ImportTemplate(string name, string alias, string text, ITemplate master = null)
{
var t = new Template(_shortStringHelper, name, alias) { Content = text };
if (master != null)
{
t.SetMasterTemplate(master);
}
_fileService.SaveTemplate(t);
return t;
}
public IActionResult Create(int n = 1, int r = 0, string o = null)
{
var res = EnsureInitialize();
if (res != null)
{
return res;
}
if (r < 0)
{
r = 0;
}
if (r > 100)
{
r = 100;
}
var restart = GetRandom(0, 100) > 100 - r;
if (n < 1)
{
n = 1;
}
if (n > MaxCreate)
{
n = MaxCreate;
}
for (var i = 0; i < n; i++)
{
var name = Guid.NewGuid().ToString("N").ToUpper() + "-" + (restart ? "R" : "X") + "-" + o;
var content = _contentService.Create(name, s_containerId, ContentAlias);
content.SetValue("origin", o);
_contentService.SaveAndPublish(content);
}
if (restart)
{
DoRestart();
}
return ContentHtml("Created " + n + " content"
+ (restart ? ", and restarted" : string.Empty)
+ ".");
}
private static int GetRandom(int minValue, int maxValue)
{
lock (s_locko)
{
return s_random.Next(minValue, maxValue);
}
}
public IActionResult Clear()
{
var res = EnsureInitialize();
if (res != null)
{
return res;
}
var contentType = _contentTypeService.Get(ContentAlias);
_contentService.DeleteOfType(contentType.Id);
return ContentHtml("Cleared.");
}
private void DoRestart()
{
HttpContext.User = null;
Thread.CurrentPrincipal = null;
_hostApplicationLifetime.StopApplication();
}
public IActionResult ColdBootRestart()
{
Directory.Delete(
_hostingEnvironment.MapPathContentRoot(Path.Combine(Constants.SystemDirectories.TempData, "DistCache")),
true);
DoRestart();
return Content("Cold Boot Restarted.");
}
public IActionResult Restart()
{
DoRestart();
return ContentHtml("Restarted.");
}
public IActionResult Die()
{
var timer = new Timer(_ => throw new Exception("die!"));
_ = timer.Change(100, 0);
return ContentHtml("Dying.");
}
public IActionResult Domains()
{
var currentDomain = AppDomain.CurrentDomain;
var currentName = currentDomain.FriendlyName;
var pos = currentName.IndexOf('-');
if (pos > 0)
{
currentName = currentName.Substring(0, pos);
}
var text = new StringBuilder();
text.Append("<div class=\"block\">Process ID: " + Process.GetCurrentProcess().Id + "</div>");
text.Append("<div class=\"block\">");
// TODO (V9): Commented out as I assume not available?
////text.Append("<div>IIS Site: " + HostingEnvironment.ApplicationHost.GetSiteName() + "</div>");
text.Append("<div>App ID: " + currentName + "</div>");
//text.Append("<div>AppPool: " + Zbu.WebManagement.AppPoolHelper.GetCurrentApplicationPoolName() + "</div>");
text.Append("</div>");
text.Append("<div class=\"block\">Domains:<ul>");
text.Append("<li>Not implemented.</li>");
/*
foreach (var domain in Zbu.WebManagement.AppDomainHelper.GetAppDomains().OrderBy(x => x.Id))
{
var name = domain.FriendlyName;
pos = name.IndexOf('-');
if (pos > 0) name = name.Substring(0, pos);
text.Append("<li style=\""
+ (name != currentName ? "color: #cccccc;" : "")
//+ (domain.Id == currentDomain.Id ? "" : "")
+ "\">"
+"[" + domain.Id + "] " + name
+ (domain.IsDefaultAppDomain() ? " (default)" : "")
+ (domain.Id == currentDomain.Id ? " (current)" : "")
+ "</li>");
}
*/
text.Append("</ul></div>");
return ContentHtml(text.ToString());
}
}

View File

@@ -13,114 +13,112 @@ using Umbraco.Cms.Web.Website.Controllers;
using Umbraco.Extensions;
using Umbraco.TestData.Configuration;
namespace Umbraco.TestData
namespace Umbraco.TestData;
public class SegmentTestController : SurfaceController
{
public class SegmentTestController : SurfaceController
private readonly IOptions<TestDataSettings> _testDataSettings;
public SegmentTestController(
IUmbracoContextAccessor umbracoContextAccessor,
IUmbracoDatabaseFactory databaseFactory,
ServiceContext services,
AppCaches appCaches,
IProfilingLogger profilingLogger,
IPublishedUrlProvider publishedUrlProvider,
IOptions<TestDataSettings> testDataSettings)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) =>
_testDataSettings = testDataSettings;
public IActionResult EnableDocTypeSegments(string alias, string propertyTypeAlias)
{
private IOptions<TestDataSettings> _testDataSettings;
public SegmentTestController(
IUmbracoContextAccessor umbracoContextAccessor,
IUmbracoDatabaseFactory databaseFactory,
ServiceContext services,
AppCaches appCaches,
IProfilingLogger profilingLogger,
IPublishedUrlProvider publishedUrlProvider,
IOptions<TestDataSettings> testDataSettings)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
if (_testDataSettings.Value.Enabled != true)
{
_testDataSettings = testDataSettings;
return HttpNotFound();
}
public IActionResult EnableDocTypeSegments(string alias, string propertyTypeAlias)
var ct = Services.ContentTypeService.Get(alias);
if (ct == null)
{
if(_testDataSettings.Value.Enabled != true)
{
return HttpNotFound();
}
IContentType ct = Services.ContentTypeService.Get(alias);
if (ct == null)
{
return Content($"No document type found by alias {alias}");
}
IPropertyType propType = ct.PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias);
if (propType == null)
{
return Content($"The document type {alias} does not have a property type {propertyTypeAlias ?? "null"}");
}
if (ct.Variations.VariesBySegment())
{
return Content($"The document type {alias} already allows segments, nothing has been changed");
}
ct.SetVariesBy(ContentVariation.Segment);
propType.SetVariesBy(ContentVariation.Segment);
Services.ContentTypeService.Save(ct);
return Content($"The document type {alias} and property type {propertyTypeAlias} now allows segments");
return Content($"No document type found by alias {alias}");
}
private IActionResult HttpNotFound() => throw new NotImplementedException();
public IActionResult DisableDocTypeSegments(string alias)
var propType = ct.PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias);
if (propType == null)
{
if (_testDataSettings.Value.Enabled != true)
{
return HttpNotFound();
}
IContentType ct = Services.ContentTypeService.Get(alias);
if (ct == null)
{
return Content($"No document type found by alias {alias}");
}
if (!ct.VariesBySegment())
{
return Content($"The document type {alias} does not allow segments, nothing has been changed");
}
ct.SetVariesBy(ContentVariation.Segment, false);
Services.ContentTypeService.Save(ct);
return Content($"The document type {alias} no longer allows segments");
return Content($"The document type {alias} does not have a property type {propertyTypeAlias ?? "null"}");
}
public ActionResult AddSegmentData(int contentId, string propertyAlias, string value, string segment, string? culture = null)
if (ct.Variations.VariesBySegment())
{
IContent content = Services.ContentService.GetById(contentId);
if (content == null)
{
return Content($"No content found by id {contentId}");
}
if (propertyAlias.IsNullOrWhiteSpace() || !content.HasProperty(propertyAlias))
{
return Content($"The content by id {contentId} does not contain a property with alias {propertyAlias ?? "null"}");
}
if (content.ContentType.VariesByCulture() && culture.IsNullOrWhiteSpace())
{
return Content($"The content by id {contentId} varies by culture but no culture was specified");
}
if (value.IsNullOrWhiteSpace())
{
return Content("'value' cannot be null");
}
if (segment.IsNullOrWhiteSpace())
{
return Content("'segment' cannot be null");
}
content.SetValue(propertyAlias, value, culture, segment);
Services.ContentService.Save(content);
return Content($"Segment value has been set on content {contentId} for property {propertyAlias}");
return Content($"The document type {alias} already allows segments, nothing has been changed");
}
ct.SetVariesBy(ContentVariation.Segment);
propType.SetVariesBy(ContentVariation.Segment);
Services.ContentTypeService.Save(ct);
return Content($"The document type {alias} and property type {propertyTypeAlias} now allows segments");
}
private IActionResult HttpNotFound() => throw new NotImplementedException();
public IActionResult DisableDocTypeSegments(string alias)
{
if (_testDataSettings.Value.Enabled != true)
{
return HttpNotFound();
}
var ct = Services.ContentTypeService.Get(alias);
if (ct == null)
{
return Content($"No document type found by alias {alias}");
}
if (!ct.VariesBySegment())
{
return Content($"The document type {alias} does not allow segments, nothing has been changed");
}
ct.SetVariesBy(ContentVariation.Segment, false);
Services.ContentTypeService.Save(ct);
return Content($"The document type {alias} no longer allows segments");
}
public ActionResult AddSegmentData(int contentId, string propertyAlias, string value, string segment, string? culture = null)
{
var content = Services.ContentService.GetById(contentId);
if (content == null)
{
return Content($"No content found by id {contentId}");
}
if (propertyAlias.IsNullOrWhiteSpace() || !content.HasProperty(propertyAlias))
{
return Content(
$"The content by id {contentId} does not contain a property with alias {propertyAlias ?? "null"}");
}
if (content.ContentType.VariesByCulture() && culture.IsNullOrWhiteSpace())
{
return Content($"The content by id {contentId} varies by culture but no culture was specified");
}
if (value.IsNullOrWhiteSpace())
{
return Content("'value' cannot be null");
}
if (segment.IsNullOrWhiteSpace())
{
return Content("'segment' cannot be null");
}
content.SetValue(propertyAlias, value, culture, segment);
Services.ContentService.Save(content);
return Content($"Segment value has been set on content {contentId} for property {propertyAlias}");
}
}

View File

@@ -19,306 +19,301 @@ using Umbraco.Cms.Infrastructure.Serialization;
using Umbraco.Cms.Web.Website.Controllers;
using Umbraco.Extensions;
using Umbraco.TestData.Configuration;
using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.TestData
namespace Umbraco.TestData;
/// <summary>
/// Creates test data
/// </summary>
public class UmbracoTestDataController : SurfaceController
{
/// <summary>
/// Creates test data
/// </summary>
public class UmbracoTestDataController : SurfaceController
private const string RichTextDataTypeName = "UmbracoTestDataContent.RTE";
private const string MediaPickerDataTypeName = "UmbracoTestDataContent.MediaPicker";
private const string TextDataTypeName = "UmbracoTestDataContent.Text";
private const string TestDataContentTypeAlias = "umbTestDataContent";
private readonly PropertyEditorCollection _propertyEditors;
private readonly ICoreScopeProvider _scopeProvider;
private readonly IShortStringHelper _shortStringHelper;
private readonly TestDataSettings _testDataSettings;
public UmbracoTestDataController(
IUmbracoContextAccessor umbracoContextAccessor,
IUmbracoDatabaseFactory databaseFactory,
ServiceContext services,
AppCaches appCaches,
IProfilingLogger profilingLogger,
IPublishedUrlProvider publishedUrlProvider,
ICoreScopeProvider scopeProvider,
PropertyEditorCollection propertyEditors,
IShortStringHelper shortStringHelper,
IOptions<TestDataSettings> testDataSettings)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
private const string RichTextDataTypeName = "UmbracoTestDataContent.RTE";
private const string MediaPickerDataTypeName = "UmbracoTestDataContent.MediaPicker";
private const string TextDataTypeName = "UmbracoTestDataContent.Text";
private const string TestDataContentTypeAlias = "umbTestDataContent";
private readonly ICoreScopeProvider _scopeProvider;
private readonly PropertyEditorCollection _propertyEditors;
private readonly IShortStringHelper _shortStringHelper;
private readonly TestDataSettings _testDataSettings;
_scopeProvider = scopeProvider;
_propertyEditors = propertyEditors;
_shortStringHelper = shortStringHelper;
_testDataSettings = testDataSettings.Value;
}
public UmbracoTestDataController(
IUmbracoContextAccessor umbracoContextAccessor,
IUmbracoDatabaseFactory databaseFactory,
ServiceContext services,
AppCaches appCaches,
IProfilingLogger profilingLogger,
IPublishedUrlProvider publishedUrlProvider,
ICoreScopeProvider scopeProvider,
PropertyEditorCollection propertyEditors,
IShortStringHelper shortStringHelper,
IOptions<TestDataSettings> testDataSettings)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
/// <summary>
/// Creates a content and associated media tree (hierarchy)
/// </summary>
/// <param name="count"></param>
/// <param name="depth"></param>
/// <param name="locale"></param>
/// <returns></returns>
/// <remarks>
/// Each content item created is associated to a media item via a media picker and therefore a relation is created
/// between the two
/// </remarks>
public IActionResult CreateTree(int count, int depth, string locale = "en")
{
if (_testDataSettings.Enabled == false)
{
_scopeProvider = scopeProvider;
_propertyEditors = propertyEditors;
_shortStringHelper = shortStringHelper;
_testDataSettings = testDataSettings.Value;
return NotFound();
}
/// <summary>
/// Creates a content and associated media tree (hierarchy)
/// </summary>
/// <param name="count"></param>
/// <param name="depth"></param>
/// <param name="locale"></param>
/// <returns></returns>
/// <remarks>
/// Each content item created is associated to a media item via a media picker and therefore a relation is created between the two
/// </remarks>
public IActionResult CreateTree(int count, int depth, string locale = "en")
if (!Validate(count, depth, out var message, out var perLevel))
{
if (_testDataSettings.Enabled == false)
{
return NotFound();
}
if (!Validate(count, depth, out var message, out var perLevel))
{
throw new InvalidOperationException(message);
}
var faker = new Faker(locale);
var company = faker.Company.CompanyName();
using (ICoreScope scope = _scopeProvider.CreateCoreScope())
{
var imageIds = CreateMediaTree(company, faker, count, depth).ToList();
var contentIds = CreateContentTree(company, faker, count, depth, imageIds, out var root).ToList();
Services.ContentService.SaveAndPublishBranch(root, true);
scope.Complete();
}
return Content("Done");
throw new InvalidOperationException(message);
}
private static bool Validate(int count, int depth, out string message, out int perLevel)
var faker = new Faker(locale);
var company = faker.Company.CompanyName();
using (var scope = _scopeProvider.CreateCoreScope())
{
perLevel = 0;
message = null;
var imageIds = CreateMediaTree(company, faker, count, depth).ToList();
var contentIds = CreateContentTree(company, faker, count, depth, imageIds, out var root).ToList();
if (count <= 0)
{
message = "Count must be more than 0";
return false;
}
Services.ContentService.SaveAndPublishBranch(root, true);
perLevel = count / depth;
if (perLevel < 1)
{
message = "Count not high enough for specified for number of levels required";
return false;
}
return true;
scope.Complete();
}
/// <summary>
/// Utility to create a tree hierarchy
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parent"></param>
/// <param name="count"></param>
/// <param name="depth"></param>
/// <param name="create">
/// A callback that returns a tuple of Content and another callback to produce a Container.
/// For media, a container will be another folder, for content the container will be the Content itself.
/// </param>
/// <returns></returns>
private IEnumerable<Udi> CreateHierarchy<T>(
T parent, int count, int depth,
Func<T, (T content, Func<T> container)> create)
where T: class, IContentBase
return Content("Done");
}
private static bool Validate(int count, int depth, out string message, out int perLevel)
{
perLevel = 0;
message = null;
if (count <= 0)
{
yield return parent.GetUdi();
message = "Count must be more than 0";
return false;
}
// This will not calculate a balanced tree but it will ensure that there will be enough nodes deep enough to not fill up the tree.
var totalDescendants = count - 1;
var perLevel = Math.Ceiling(totalDescendants / (double)depth);
var perBranch = Math.Ceiling(perLevel / depth);
perLevel = count / depth;
if (perLevel < 1)
{
message = "Count not high enough for specified for number of levels required";
return false;
}
var tracked = new Stack<(T parent, int childCount)>();
return true;
}
var currChildCount = 0;
/// <summary>
/// Utility to create a tree hierarchy
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parent"></param>
/// <param name="count"></param>
/// <param name="depth"></param>
/// <param name="create">
/// A callback that returns a tuple of Content and another callback to produce a Container.
/// For media, a container will be another folder, for content the container will be the Content itself.
/// </param>
/// <returns></returns>
private IEnumerable<Udi> CreateHierarchy<T>(T parent, int count, int depth, Func<T, (T content, Func<T> container)> create)
where T : class, IContentBase
{
yield return parent.GetUdi();
for (int i = 0; i < count; i++)
// This will not calculate a balanced tree but it will ensure that there will be enough nodes deep enough to not fill up the tree.
var totalDescendants = count - 1;
var perLevel = Math.Ceiling(totalDescendants / (double)depth);
var perBranch = Math.Ceiling(perLevel / depth);
var tracked = new Stack<(T parent, int childCount)>();
var currChildCount = 0;
for (var i = 0; i < count; i++)
{
var (content, container) = create(parent);
var contentItem = content;
yield return contentItem.GetUdi();
currChildCount++;
if (currChildCount == perBranch)
{
(T content, Func<T> container) created = create(parent);
T contentItem = created.content;
// move back up...
yield return contentItem.GetUdi();
var prev = tracked.Pop();
currChildCount++;
// restore child count
currChildCount = prev.childCount;
// restore the parent
parent = prev.parent;
}
else if (contentItem.Level < depth)
{
// track the current parent and it's current child count
tracked.Push((parent, currChildCount));
if (currChildCount == perBranch)
{
// move back up...
(T parent, int childCount) prev = tracked.Pop();
// restore child count
currChildCount = prev.childCount;
// restore the parent
parent = prev.parent;
}
else if (contentItem.Level < depth)
{
// track the current parent and it's current child count
tracked.Push((parent, currChildCount));
// not at max depth, create below
parent = created.container();
currChildCount = 0;
}
// not at max depth, create below
parent = container();
currChildCount = 0;
}
}
}
/// <summary>
/// Creates the media tree hiearachy
/// </summary>
/// <param name="company"></param>
/// <param name="faker"></param>
/// <param name="count"></param>
/// <param name="depth"></param>
/// <returns></returns>
private IEnumerable<Udi> CreateMediaTree(string company, Faker faker, int count, int depth)
/// <summary>
/// Creates the media tree hiearachy
/// </summary>
/// <param name="company"></param>
/// <param name="faker"></param>
/// <param name="count"></param>
/// <param name="depth"></param>
/// <returns></returns>
private IEnumerable<Udi> CreateMediaTree(string company, Faker faker, int count, int depth)
{
var parent =
Services.MediaService.CreateMediaWithIdentity(company, -1, Constants.Conventions.MediaTypes.Folder);
return CreateHierarchy(parent, count, depth, currParent =>
{
IMedia parent = Services.MediaService.CreateMediaWithIdentity(company, -1, Constants.Conventions.MediaTypes.Folder);
var imageUrl = faker.Image.PicsumUrl();
return CreateHierarchy(parent, count, depth, currParent =>
// we are appending a &ext=.jpg to the end of this for a reason. The result of this URL will be something like:
// https://picsum.photos/640/480/?image=106
// and due to the way that we detect images there must be an extension so we'll change it to
// https://picsum.photos/640/480/?image=106&ext=.jpg
// which will trick our app into parsing this and thinking it's an image ... which it is so that's good.
// if we don't do this we don't get thumbnails in the back office.
imageUrl += "&ext=.jpg";
var media = Services.MediaService.CreateMedia(faker.Commerce.ProductName(), currParent, Constants.Conventions.MediaTypes.Image);
media.SetValue(Constants.Conventions.Media.File, imageUrl);
Services.MediaService.Save(media);
return (media, () =>
{
var imageUrl = faker.Image.PicsumUrl();
// create a folder to contain child media
var container = Services.MediaService.CreateMediaWithIdentity(faker.Commerce.Department(), currParent, Constants.Conventions.MediaTypes.Folder);
return container;
}
);
});
}
// we are appending a &ext=.jpg to the end of this for a reason. The result of this URL will be something like:
// https://picsum.photos/640/480/?image=106
// and due to the way that we detect images there must be an extension so we'll change it to
// https://picsum.photos/640/480/?image=106&ext=.jpg
// which will trick our app into parsing this and thinking it's an image ... which it is so that's good.
// if we don't do this we don't get thumbnails in the back office.
imageUrl += "&ext=.jpg";
/// <summary>
/// Creates the content tree hiearachy
/// </summary>
/// <param name="company"></param>
/// <param name="faker"></param>
/// <param name="count"></param>
/// <param name="depth"></param>
/// <param name="imageIds"></param>
/// <param name="root"></param>
/// <returns></returns>
private IEnumerable<Udi> CreateContentTree(string company, Faker faker, int count, int depth, List<Udi> imageIds, out IContent root)
{
var random = new Random(company.GetHashCode());
IMedia media = Services.MediaService.CreateMedia(faker.Commerce.ProductName(), currParent, Constants.Conventions.MediaTypes.Image);
media.SetValue(Constants.Conventions.Media.File, imageUrl);
Services.MediaService.Save(media);
return (media, () =>
{
// create a folder to contain child media
IMedia container = Services.MediaService.CreateMediaWithIdentity(faker.Commerce.Department(), currParent, Constants.Conventions.MediaTypes.Folder);
return container;
});
});
}
var docType = GetOrCreateContentType();
/// <summary>
/// Creates the content tree hiearachy
/// </summary>
/// <param name="company"></param>
/// <param name="faker"></param>
/// <param name="count"></param>
/// <param name="depth"></param>
/// <param name="imageIds"></param>
/// <returns></returns>
private IEnumerable<Udi> CreateContentTree(string company, Faker faker, int count, int depth, List<Udi> imageIds, out IContent root)
var parent = Services.ContentService.Create(company, -1, docType.Alias);
// give it some reasonable data (100 reviews)
parent.SetValue("review", string.Join(" ", Enumerable.Range(0, 100).Select(x => faker.Rant.Review())));
parent.SetValue("desc", company);
parent.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]);
Services.ContentService.Save(parent);
root = parent;
return CreateHierarchy(parent, count, depth, currParent =>
{
var random = new Random(company.GetHashCode());
IContentType docType = GetOrCreateContentType();
IContent parent = Services.ContentService.Create(company, -1, docType.Alias);
var content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias);
// give it some reasonable data (100 reviews)
parent.SetValue("review", string.Join(" ", Enumerable.Range(0, 100).Select(x => faker.Rant.Review())));
parent.SetValue("desc", company);
parent.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]);
Services.ContentService.Save(parent);
content.SetValue("review", string.Join(" ", Enumerable.Range(0, 100).Select(x => faker.Rant.Review())));
content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective())));
content.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]);
root = parent;
Services.ContentService.Save(content);
return (content, () => content);
});
}
return CreateHierarchy(parent, count, depth, currParent =>
{
IContent content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias);
// give it some reasonable data (100 reviews)
content.SetValue("review", string.Join(" ", Enumerable.Range(0, 100).Select(x => faker.Rant.Review())));
content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective())));
content.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]);
Services.ContentService.Save(content);
return (content, () => content);
});
}
private IContentType GetOrCreateContentType()
private IContentType GetOrCreateContentType()
{
var docType = Services.ContentTypeService.Get(TestDataContentTypeAlias);
if (docType != null)
{
IContentType docType = Services.ContentTypeService.Get(TestDataContentTypeAlias);
if (docType != null)
{
return docType;
}
docType = new ContentType(_shortStringHelper, -1)
{
Alias = TestDataContentTypeAlias,
Name = "Umbraco Test Data Content",
Icon = "icon-science color-green"
};
docType.AddPropertyGroup("content", "Content");
docType.AddPropertyType(new PropertyType(_shortStringHelper, GetOrCreateRichText(), "review")
{
Name = "Review"
});
docType.AddPropertyType(new PropertyType(_shortStringHelper, GetOrCreateMediaPicker(), "media")
{
Name = "Media"
});
docType.AddPropertyType(new PropertyType(_shortStringHelper, GetOrCreateText(), "desc")
{
Name = "Description"
});
Services.ContentTypeService.Save(docType);
docType.AllowedContentTypes = new[] { new ContentTypeSort(docType.Id, 0) };
Services.ContentTypeService.Save(docType);
return docType;
}
private IDataType GetOrCreateRichText() => GetOrCreateDataType(RichTextDataTypeName, Constants.PropertyEditors.Aliases.TinyMce);
private IDataType GetOrCreateMediaPicker() => GetOrCreateDataType(MediaPickerDataTypeName, Constants.PropertyEditors.Aliases.MediaPicker);
private IDataType GetOrCreateText() => GetOrCreateDataType(TextDataTypeName, Constants.PropertyEditors.Aliases.TextBox);
private IDataType GetOrCreateDataType(string name, string editorAlias)
docType = new ContentType(_shortStringHelper, -1)
{
IDataType dt = Services.DataTypeService.GetDataType(name);
if (dt != null)
{
return dt;
}
Alias = TestDataContentTypeAlias,
Name = "Umbraco Test Data Content",
Icon = "icon-science color-green"
};
docType.AddPropertyGroup("content", "Content");
docType.AddPropertyType(new PropertyType(_shortStringHelper, GetOrCreateRichText(), "review")
{
Name = "Review"
});
docType.AddPropertyType(
new PropertyType(_shortStringHelper, GetOrCreateMediaPicker(), "media") { Name = "Media" });
docType.AddPropertyType(new PropertyType(_shortStringHelper, GetOrCreateText(), "desc") { Name = "Description" });
Services.ContentTypeService.Save(docType);
docType.AllowedContentTypes = new[] { new ContentTypeSort(docType.Id, 0) };
Services.ContentTypeService.Save(docType);
return docType;
}
IDataEditor editor = _propertyEditors.FirstOrDefault(x => x.Alias == editorAlias);
if (editor == null)
{
throw new InvalidOperationException($"No {editorAlias} editor found");
}
private IDataType GetOrCreateRichText() =>
GetOrCreateDataType(RichTextDataTypeName, Constants.PropertyEditors.Aliases.TinyMce);
var serializer = new ConfigurationEditorJsonSerializer();
private IDataType GetOrCreateMediaPicker() =>
GetOrCreateDataType(MediaPickerDataTypeName, Constants.PropertyEditors.Aliases.MediaPicker);
dt = new DataType(editor, serializer)
{
Name = name,
Configuration = editor.GetConfigurationEditor().DefaultConfigurationObject,
DatabaseType = ValueStorageType.Ntext
};
private IDataType GetOrCreateText() =>
GetOrCreateDataType(TextDataTypeName, Constants.PropertyEditors.Aliases.TextBox);
Services.DataTypeService.Save(dt);
private IDataType GetOrCreateDataType(string name, string editorAlias)
{
var dt = Services.DataTypeService.GetDataType(name);
if (dt != null)
{
return dt;
}
var editor = _propertyEditors.FirstOrDefault(x => x.Alias == editorAlias);
if (editor == null)
{
throw new InvalidOperationException($"No {editorAlias} editor found");
}
var serializer = new ConfigurationEditorJsonSerializer();
dt = new DataType(editor, serializer)
{
Name = name,
Configuration = editor.GetConfigurationEditor().DefaultConfigurationObject,
DatabaseType = ValueStorageType.Ntext
};
Services.DataTypeService.Save(dt);
return dt;
}
}