Files
Umbraco-CMS/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestParserTests.cs
Bjarke Berg 0f1c2f7022 Ensure no management api for v12 (#14197)
* Remove management api

* Remove actual files
2023-05-04 13:32:41 +02:00

566 lines
20 KiB
C#

// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Linq;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Dashboards;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.PropertyEditors.Validators;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Infrastructure.Serialization;
using Umbraco.Cms.Tests.UnitTests.TestHelpers;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Manifest;
[TestFixture]
public class ManifestParserTests
{
[SetUp]
public void Setup()
{
var validators = new IManifestValueValidator[]
{
new RequiredValidator(Mock.Of<ILocalizedTextService>()),
new RegexValidator(Mock.Of<ILocalizedTextService>(), null),
new DelimitedValueValidator(),
};
_ioHelper = TestHelper.IOHelper;
var loggerFactory = NullLoggerFactory.Instance;
_parser = new ManifestParser(
AppCaches.Disabled,
new ManifestValueValidatorCollection(() => validators),
new ManifestFilterCollection(() => Enumerable.Empty<IManifestFilter>()),
loggerFactory.CreateLogger<ManifestParser>(),
_ioHelper,
TestHelper.GetHostingEnvironment(),
new JsonNetSerializer(),
Mock.Of<ILocalizedTextService>(),
Mock.Of<IShortStringHelper>(),
Mock.Of<IDataValueEditorFactory>(),
Mock.Of<IManifestFileProviderFactory>());
}
private ManifestParser _parser;
private IIOHelper _ioHelper;
[Test]
public void DelimitedValueValidator()
{
const string json = @"{'propertyEditors': [
{
alias: 'Test.Test2',
name: 'Test 2',
isParameterEditor: true,
defaultConfig: { key1: 'some default val' },
editor: {
view: '~/App_Plugins/MyPackage/PropertyEditors/MyEditor.html',
valueType: 'int',
validation: {
delimited: {
delimiter: ',',
pattern: '^[a-zA-Z]*$'
}
}
}
}
]}";
var manifest = _parser.ParseManifest(json);
Assert.AreEqual(1, manifest.ParameterEditors.Length);
Assert.AreEqual(1, manifest.ParameterEditors[0].GetValueEditor().Validators.Count);
Assert.IsTrue(manifest.ParameterEditors[0].GetValueEditor().Validators[0] is DelimitedValueValidator);
var validator = manifest.ParameterEditors[0].GetValueEditor().Validators[0] as DelimitedValueValidator;
Assert.IsNotNull(validator.Configuration);
Assert.AreEqual(",", validator.Configuration.Delimiter);
Assert.AreEqual("^[a-zA-Z]*$", validator.Configuration.Pattern);
}
[Test]
public void CanParseComments()
{
const string json1 = @"
// this is a single-line comment
{
""x"": 2, // this is an end-of-line comment
""y"": 3, /* this is a single line comment block
/* comment */ ""z"": /* comment */ 4,
""t"": ""this is /* comment */ a string"",
""u"": ""this is // more comment in a string""
}
";
var jobject = (JObject)JsonConvert.DeserializeObject(json1);
Assert.AreEqual("2", jobject.Property("x").Value.ToString());
Assert.AreEqual("3", jobject.Property("y").Value.ToString());
Assert.AreEqual("4", jobject.Property("z").Value.ToString());
Assert.AreEqual("this is /* comment */ a string", jobject.Property("t").Value.ToString());
Assert.AreEqual("this is // more comment in a string", jobject.Property("u").Value.ToString());
}
[Test]
public void ThrowOnJsonError()
{
// invalid json, missing the final ']' on javascript
const string json = @"{
propertyEditors: []/*we have empty property editors**/,
javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2.js' }";
// parsing fails
Assert.Throws<JsonReaderException>(() => _parser.ParseManifest(json));
}
[Test]
public void CanParseManifest_ScriptsAndStylesheets()
{
var json = "{}";
var manifest = _parser.ParseManifest(json);
Assert.AreEqual(0, manifest.Scripts.Length);
json = "{javascript: []}";
manifest = _parser.ParseManifest(json);
Assert.AreEqual(0, manifest.Scripts.Length);
json = "{javascript: ['~/test.js', '~/test2.js']}";
manifest = _parser.ParseManifest(json);
Assert.AreEqual(2, manifest.Scripts.Length);
json = "{propertyEditors: [], javascript: ['~/test.js', '~/test2.js']}";
manifest = _parser.ParseManifest(json);
Assert.AreEqual(2, manifest.Scripts.Length);
Assert.AreEqual(_ioHelper.ResolveUrl("/test.js"), manifest.Scripts[0]);
Assert.AreEqual(_ioHelper.ResolveUrl("/test2.js"), manifest.Scripts[1]);
// kludge is gone - must filter before parsing
json = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()) +
"{propertyEditors: [], javascript: ['~/test.js', '~/test2.js']}";
Assert.Throws<JsonReaderException>(() => _parser.ParseManifest(json));
json = "{}";
manifest = _parser.ParseManifest(json);
Assert.AreEqual(0, manifest.Stylesheets.Length);
json = "{css: []}";
manifest = _parser.ParseManifest(json);
Assert.AreEqual(0, manifest.Stylesheets.Length);
json = "{css: ['~/style.css', '~/folder-name/sdsdsd/stylesheet.css']}";
manifest = _parser.ParseManifest(json);
Assert.AreEqual(2, manifest.Stylesheets.Length);
json = "{propertyEditors: [], css: ['~/stylesheet.css', '~/random-long-name.css']}";
manifest = _parser.ParseManifest(json);
Assert.AreEqual(2, manifest.Stylesheets.Length);
json =
"{propertyEditors: [], javascript: ['~/test.js', '~/test2.js'], css: ['~/stylesheet.css', '~/random-long-name.css']}";
manifest = _parser.ParseManifest(json);
Assert.AreEqual(2, manifest.Scripts.Length);
Assert.AreEqual(2, manifest.Stylesheets.Length);
}
[Test]
public void CanParseManifest_PropertyEditors()
{
const string json = @"{'propertyEditors': [
{
alias: 'Test.Test1',
name: 'Test 1',
editor: {
view: '~/App_Plugins/MyPackage/PropertyEditors/MyEditor.html',
valueType: 'int',
hideLabel: true,
validation: {
'required': true,
'Regex': '\\d*'
}
},
prevalues: {
fields: [
{
label: 'Some config 1',
key: 'key1',
view: '~/App_Plugins/MyPackage/PropertyEditors/Views/pre-val1.html',
validation: {
required: true
}
},
{
label: 'Some config 2',
key: 'key2',
view: '~/App_Plugins/MyPackage/PropertyEditors/Views/pre-val2.html'
}
]
}
},
{
alias: 'Test.Test2',
name: 'Test 2',
isParameterEditor: true,
defaultConfig: { key1: 'some default val' },
editor: {
view: '~/App_Plugins/MyPackage/PropertyEditors/MyEditor.html',
valueType: 'int',
validation: {
required : true,
regex : '\\d*'
}
}
}
]}";
var manifest = _parser.ParseManifest(json);
Assert.AreEqual(2, manifest.PropertyEditors.Length);
var editor = manifest.PropertyEditors[1];
Assert.IsTrue((editor.Type & EditorType.MacroParameter) > 0);
Assert.IsNotEmpty(editor.DefaultConfiguration);
Assert.AreEqual("some default val", editor.DefaultConfiguration["key1"]);
editor = manifest.PropertyEditors[0];
Assert.AreEqual("Test.Test1", editor.Alias);
Assert.AreEqual("Test 1", editor.Name);
Assert.IsFalse((editor.Type & EditorType.MacroParameter) > 0);
var valueEditor = editor.GetValueEditor();
Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/PropertyEditors/MyEditor.html"), valueEditor.View);
Assert.AreEqual("int", valueEditor.ValueType);
Assert.IsTrue(valueEditor.HideLabel);
// these two don't make much sense here
//// valueEditor.RegexValidator;
//// valueEditor.RequiredValidator;
var validators = valueEditor.Validators;
Assert.AreEqual(2, validators.Count);
var validator = validators[0];
var v1 = validator as RequiredValidator;
Assert.IsNotNull(v1);
Assert.AreEqual("Required", v1.ValidationName);
validator = validators[1];
var v2 = validator as RegexValidator;
Assert.IsNotNull(v2);
Assert.AreEqual("Regex", v2.ValidationName);
Assert.AreEqual("\\d*", v2.Configuration);
// this is not part of the manifest
var preValues = editor.GetConfigurationEditor().DefaultConfiguration;
Assert.IsEmpty(preValues);
var preValueEditor = editor.GetConfigurationEditor();
Assert.IsNotNull(preValueEditor);
Assert.IsNotNull(preValueEditor.Fields);
Assert.AreEqual(2, preValueEditor.Fields.Count);
var f = preValueEditor.Fields[0];
Assert.AreEqual("key1", f.Key);
Assert.AreEqual("Some config 1", f.Name);
Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/PropertyEditors/Views/pre-val1.html"), f.View);
var fvalidators = f.Validators;
Assert.IsNotNull(fvalidators);
Assert.AreEqual(1, fvalidators.Count);
var fv = fvalidators[0] as RequiredValidator;
Assert.IsNotNull(fv);
Assert.AreEqual("Required", fv.ValidationName);
f = preValueEditor.Fields[1];
Assert.AreEqual("key2", f.Key);
Assert.AreEqual("Some config 2", f.Name);
Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/PropertyEditors/Views/pre-val2.html"), f.View);
fvalidators = f.Validators;
Assert.IsNotNull(fvalidators);
Assert.AreEqual(0, fvalidators.Count);
}
[Test]
public void CanParseManifest_ParameterEditors()
{
const string json = @"{'parameterEditors': [
{
alias: 'parameter1',
name: 'My Parameter',
view: '~/App_Plugins/MyPackage/PropertyEditors/MyEditor.html'
},
{
alias: 'parameter2',
name: 'Another parameter',
config: { key1: 'some config val' },
view: '~/App_Plugins/MyPackage/PropertyEditors/CsvEditor.html'
},
{
alias: 'parameter3',
name: 'Yet another parameter'
}
]}";
var manifest = _parser.ParseManifest(json);
Assert.AreEqual(3, manifest.ParameterEditors.Length);
Assert.IsTrue(manifest.ParameterEditors.All(x => (x.Type & EditorType.MacroParameter) > 0));
var editor = manifest.ParameterEditors[1];
Assert.AreEqual("parameter2", editor.Alias);
Assert.AreEqual("Another parameter", editor.Name);
var config = editor.DefaultConfiguration;
Assert.AreEqual(1, config.Count);
Assert.IsTrue(config.ContainsKey("key1"));
Assert.AreEqual("some config val", config["key1"]);
var valueEditor = editor.GetValueEditor();
Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/PropertyEditors/CsvEditor.html"), valueEditor.View);
editor = manifest.ParameterEditors[2];
Assert.Throws<InvalidOperationException>(() =>
{
var valueEditor = editor.GetValueEditor();
});
}
[Test]
public void CanParseManifest_GridEditors()
{
const string json = @"{
'javascript': [ ],
'css': [ ],
'gridEditors': [
{
'name': 'Small Hero',
'alias': 'small-hero',
'view': '~/App_Plugins/MyPlugin/small-hero/editortemplate.html',
'render': '~/Views/Partials/Grid/Editors/SmallHero.cshtml',
'icon': 'icon-presentation',
'config': {
'image': {
'size': {
'width': 1200,
'height': 185
}
},
'link': {
'maxNumberOfItems': 1,
'minNumberOfItems': 0
}
}
},
{
'name': 'Document Links By Category',
'alias': 'document-links-by-category',
'view': '~/App_Plugins/MyPlugin/document-links-by-category/editortemplate.html',
'render': '~/Views/Partials/Grid/Editors/DocumentLinksByCategory.cshtml',
'icon': 'icon-umb-members'
}
]
}";
var manifest = _parser.ParseManifest(json);
Assert.AreEqual(2, manifest.GridEditors.Length);
var editor = manifest.GridEditors[0];
Assert.AreEqual("small-hero", editor.Alias);
Assert.AreEqual("Small Hero", editor.Name);
Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPlugin/small-hero/editortemplate.html"), editor.View);
Assert.AreEqual(_ioHelper.ResolveUrl("/Views/Partials/Grid/Editors/SmallHero.cshtml"), editor.Render);
Assert.AreEqual("icon-presentation", editor.Icon);
var config = editor.Config;
Assert.AreEqual(2, config.Count);
Assert.IsTrue(config.ContainsKey("image"));
var c = config["image"];
Assert.IsInstanceOf<JObject>(c); // FIXME: is this what we want?
Assert.IsTrue(config.ContainsKey("link"));
c = config["link"];
Assert.IsInstanceOf<JObject>(c); // FIXME: is this what we want?
// FIXME: should we resolveUrl in configs?
}
[Test]
public void CanParseManifest_ContentApps()
{
const string json = @"{'contentApps': [
{
alias: 'myPackageApp1',
name: 'My App1',
icon: 'icon-foo',
view: '~/App_Plugins/MyPackage/ContentApps/MyApp1.html'
},
{
alias: 'myPackageApp2',
name: 'My App2',
config: { key1: 'some config val' },
icon: 'icon-bar',
view: '~/App_Plugins/MyPackage/ContentApps/MyApp2.html'
}
]}";
var manifest = _parser.ParseManifest(json);
Assert.AreEqual(2, manifest.ContentApps.Length);
Assert.IsInstanceOf<ManifestContentAppDefinition>(manifest.ContentApps[0]);
var app0 = manifest.ContentApps[0];
Assert.AreEqual("myPackageApp1", app0.Alias);
Assert.AreEqual("My App1", app0.Name);
Assert.AreEqual("icon-foo", app0.Icon);
Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/ContentApps/MyApp1.html"), app0.View);
Assert.IsInstanceOf<ManifestContentAppDefinition>(manifest.ContentApps[1]);
var app1 = manifest.ContentApps[1];
Assert.AreEqual("myPackageApp2", app1.Alias);
Assert.AreEqual("My App2", app1.Name);
Assert.AreEqual("icon-bar", app1.Icon);
Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/ContentApps/MyApp2.html"), app1.View);
}
[Test]
public void CanParseManifest_Dashboards()
{
const string json = @"{'dashboards': [
{
'alias': 'something',
'view': '~/App_Plugins/MyPackage/Dashboards/one.html',
'sections': [ 'content' ],
'access': [ {'grant':'user'}, {'deny':'foo'} ]
},
{
'alias': 'something.else',
'weight': -1,
'view': '~/App_Plugins/MyPackage/Dashboards/two.html',
'sections': [ 'forms' ],
}
]}";
var manifest = _parser.ParseManifest(json);
Assert.AreEqual(2, manifest.Dashboards.Length);
Assert.IsInstanceOf<ManifestDashboard>(manifest.Dashboards[0]);
var db0 = manifest.Dashboards[0];
Assert.AreEqual("something", db0.Alias);
Assert.AreEqual(100, db0.Weight);
Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/Dashboards/one.html"), db0.View);
Assert.AreEqual(1, db0.Sections.Length);
Assert.AreEqual("content", db0.Sections[0]);
Assert.AreEqual(2, db0.AccessRules.Length);
Assert.AreEqual(AccessRuleType.Grant, db0.AccessRules[0].Type);
Assert.AreEqual("user", db0.AccessRules[0].Value);
Assert.AreEqual(AccessRuleType.Deny, db0.AccessRules[1].Type);
Assert.AreEqual("foo", db0.AccessRules[1].Value);
Assert.IsInstanceOf<ManifestDashboard>(manifest.Dashboards[1]);
var db1 = manifest.Dashboards[1];
Assert.AreEqual("something.else", db1.Alias);
Assert.AreEqual(-1, db1.Weight);
Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/Dashboards/two.html"), db1.View);
Assert.AreEqual(1, db1.Sections.Length);
Assert.AreEqual("forms", db1.Sections[0]);
}
[Test]
public void CanParseManifest_Sections()
{
const string json = @"{'sections': [
{ ""alias"": ""content"", ""name"": ""Content"" },
{ ""alias"": ""hello"", ""name"": ""World"" }
]}";
var manifest = _parser.ParseManifest(json);
Assert.AreEqual(2, manifest.Sections.Length);
Assert.AreEqual("content", manifest.Sections[0].Alias);
Assert.AreEqual("hello", manifest.Sections[1].Alias);
Assert.AreEqual("Content", manifest.Sections[0].Name);
Assert.AreEqual("World", manifest.Sections[1].Name);
}
[Test]
public void CanParseManifest_Version()
{
const string json = @"{""name"": ""VersionPackage"", ""version"": ""1.0.0""}";
var manifest = _parser.ParseManifest(json);
Assert.Multiple(() =>
{
Assert.AreEqual("VersionPackage", manifest.PackageName);
Assert.AreEqual("1.0.0", manifest.Version);
});
}
[Test]
public void CanParseManifest_TrackingAllowed()
{
const string json = @"{""allowPackageTelemetry"": false }";
var manifest = _parser.ParseManifest(json);
Assert.IsFalse(manifest.AllowPackageTelemetry);
}
[Test]
public void CanParseManifest_ParameterEditors_SupportsReadOnly()
{
const string json = @"{'parameterEditors': [
{
alias: 'parameter1',
name: 'My Parameter',
view: '~/App_Plugins/MyPackage/PropertyEditors/MyEditor.html',
supportsReadOnly: true
}]}";
var manifest = _parser.ParseManifest(json);
Assert.IsTrue(manifest.ParameterEditors.FirstOrDefault().SupportsReadOnly);
}
[Test]
public void CanParseManifest_PropertyEditors_SupportsReadOnly()
{
const string json = @"{'propertyEditors': [
{
alias: 'Test.Test1',
name: 'Test 1',
supportsReadOnly: true,
editor: {
view: '~/App_Plugins/MyPackage/PropertyEditors/MyEditor.html',
valueType: 'int',
hideLabel: true,
validation: {
'required': true,
'Regex': '\\d*'
}
},
prevalues: {
fields: [
{
label: 'Some config 1',
key: 'key1',
view: '~/App_Plugins/MyPackage/PropertyEditors/Views/pre-val1.html',
validation: {
required: true
}
},
{
label: 'Some config 2',
key: 'key2',
view: '~/App_Plugins/MyPackage/PropertyEditors/Views/pre-val2.html'
}
]
}
}]}";
var manifest = _parser.ParseManifest(json);
Assert.IsTrue(manifest.PropertyEditors.FirstOrDefault().SupportsReadOnly);
}
}