Rename ExtensionManifest to PluginConfiguration

This commit is contained in:
kjac
2023-02-14 16:59:08 +01:00
parent 92e53c236e
commit 4087f7805e
23 changed files with 327 additions and 303 deletions

View File

@@ -1,35 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels.Package;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.Mapping;
namespace Umbraco.Cms.Api.Management.Controllers.Package;
public class AllPackagesController : PackageControllerBase
{
private readonly IExtensionManifestService _extensionManifestService;
private readonly IUmbracoMapper _umbracoMapper;
public AllPackagesController(IExtensionManifestService extensionManifestService, IUmbracoMapper umbracoMapper)
{
_extensionManifestService = extensionManifestService;
_umbracoMapper = umbracoMapper;
}
[HttpGet("all")]
[MapToApiVersion("1.0")]
// TODO: proper view model + mapper
[ProducesResponseType(typeof(PagedViewModel<ExtensionManifestViewModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedViewModel<ExtensionManifestViewModel>>> AllMigrationStatuses(int skip = 0, int take = 100)
{
ExtensionManifest[] extensionManifests = (await _extensionManifestService.GetManifestsAsync()).ToArray();
return Ok(
new PagedViewModel<ExtensionManifestViewModel>
{
Items = _umbracoMapper.MapEnumerable<ExtensionManifest, ExtensionManifestViewModel>(extensionManifests.Skip(skip).Take(take)),
Total = extensionManifests.Length
});
}
}

View File

@@ -0,0 +1,34 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels.Package;
using Umbraco.Cms.Core.Plugin;
using Umbraco.Cms.Core.Mapping;
namespace Umbraco.Cms.Api.Management.Controllers.Package;
public class AllPluginsController : PackageControllerBase
{
private readonly IPluginConfigurationService _pluginConfigurationService;
private readonly IUmbracoMapper _umbracoMapper;
public AllPluginsController(IPluginConfigurationService pluginConfigurationService, IUmbracoMapper umbracoMapper)
{
_pluginConfigurationService = pluginConfigurationService;
_umbracoMapper = umbracoMapper;
}
[HttpGet("plugins")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<PluginConfigurationViewModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedViewModel<PluginConfigurationViewModel>>> AllPlugins(int skip = 0, int take = 100)
{
PluginConfiguration[] pluginConfigurations = (await _pluginConfigurationService.GetPluginConfigurationsAsync()).ToArray();
return Ok(
new PagedViewModel<PluginConfigurationViewModel>
{
Items = _umbracoMapper.MapEnumerable<PluginConfiguration, PluginConfigurationViewModel>(pluginConfigurations.Skip(skip).Take(take)),
Total = pluginConfigurations.Length
});
}
}

View File

@@ -8,7 +8,7 @@ internal static class PackageBuilderExtensions
{
internal static IUmbracoBuilder AddPackages(this IUmbracoBuilder builder)
{
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>().Add<ExtensionManifestViewModelMapDefinition>();
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>().Add<PluginConfigurationViewModelMapDefinition>();
return builder;
}

View File

@@ -1,16 +1,17 @@
using Umbraco.Cms.Api.Management.ViewModels.Package;
using Umbraco.Cms.Core.Plugin;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.Mapping;
namespace Umbraco.Cms.Api.Management.Mapping.Package;
public class ExtensionManifestViewModelMapDefinition : IMapDefinition
public class PluginConfigurationViewModelMapDefinition : IMapDefinition
{
public void DefineMaps(IUmbracoMapper mapper)
=> mapper.Define<ExtensionManifest, ExtensionManifestViewModel>((_, _) => new ExtensionManifestViewModel(), Map);
=> mapper.Define<PluginConfiguration, PluginConfigurationViewModel>((_, _) => new PluginConfigurationViewModel(), Map);
// Umbraco.Code.MapAll
private static void Map(ExtensionManifest source, ExtensionManifestViewModel target, MapperContext context)
private static void Map(PluginConfiguration source, PluginConfigurationViewModel target, MapperContext context)
{
target.Name = source.Name;
target.Version = source.Version;

View File

@@ -1,6 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.Package;
public class ExtensionManifestViewModel
public class PluginConfigurationViewModel
{
public string Name { get; set; } = string.Empty;

View File

@@ -0,0 +1,10 @@
using Microsoft.Extensions.FileProviders;
namespace Umbraco.Cms.Core.IO;
/// <summary>
/// Factory for creating <see cref="IFileProvider" /> instances for providing the umbraco-package.json file.
/// </summary>
public interface IPluginConfigurationFileProviderFactory : IFileProviderFactory
{
}

View File

@@ -1,6 +0,0 @@
namespace Umbraco.Cms.Core.Manifest;
public interface IExtensionManifestService
{
Task<IEnumerable<ExtensionManifest>> GetManifestsAsync();
}

View File

@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Core.Plugin;
public interface IPluginConfigurationService
{
Task<IEnumerable<PluginConfiguration>> GetPluginConfigurationsAsync();
}

View File

@@ -1,6 +1,6 @@
namespace Umbraco.Cms.Core.Manifest;
namespace Umbraco.Cms.Core.Plugin;
public class ExtensionManifest
public class PluginConfiguration
{
public required string Name { get; set; }

View File

@@ -4,6 +4,7 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Plugin;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
@@ -19,7 +20,7 @@ internal class TelemetryService : ITelemetryService
private readonly ISiteIdentifierService _siteIdentifierService;
private readonly IUmbracoVersion _umbracoVersion;
private readonly IUsageInformationService _usageInformationService;
private readonly IExtensionManifestService _extensionManifestService;
private readonly IPluginConfigurationService _pluginConfigurationService;
[Obsolete("Please use the constructor that does not take an IManifestParser. Will be removed in V15.")]
public TelemetryService(
@@ -34,7 +35,7 @@ internal class TelemetryService : ITelemetryService
siteIdentifierService,
usageInformationService,
metricsConsentService,
StaticServiceProvider.Instance.GetRequiredService<IExtensionManifestService>())
StaticServiceProvider.Instance.GetRequiredService<IPluginConfigurationService>())
{
}
@@ -45,13 +46,13 @@ internal class TelemetryService : ITelemetryService
ISiteIdentifierService siteIdentifierService,
IUsageInformationService usageInformationService,
IMetricsConsentService metricsConsentService,
IExtensionManifestService extensionManifestService)
IPluginConfigurationService pluginConfigurationService)
: this(
umbracoVersion,
siteIdentifierService,
usageInformationService,
metricsConsentService,
extensionManifestService)
pluginConfigurationService)
{
}
@@ -63,13 +64,13 @@ internal class TelemetryService : ITelemetryService
ISiteIdentifierService siteIdentifierService,
IUsageInformationService usageInformationService,
IMetricsConsentService metricsConsentService,
IExtensionManifestService extensionManifestService)
IPluginConfigurationService pluginConfigurationService)
{
_umbracoVersion = umbracoVersion;
_siteIdentifierService = siteIdentifierService;
_usageInformationService = usageInformationService;
_metricsConsentService = metricsConsentService;
_extensionManifestService = extensionManifestService;
_pluginConfigurationService = pluginConfigurationService;
}
[Obsolete("Please use GetTelemetryReportDataAsync. Will be removed in V15.")]
@@ -107,7 +108,7 @@ internal class TelemetryService : ITelemetryService
return null;
}
IEnumerable<ExtensionManifest> manifests = await _extensionManifestService.GetManifestsAsync();
IEnumerable<PluginConfiguration> manifests = await _pluginConfigurationService.GetPluginConfigurationsAsync();
return manifests
.Where(manifest => manifest.AllowTelemetry)

View File

@@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.DistributedLocking;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Plugin;
using Umbraco.Cms.Core.Handlers;
using Umbraco.Cms.Core.HealthChecks.NotificationMethods;
using Umbraco.Cms.Core.Hosting;
@@ -39,6 +40,7 @@ using Umbraco.Cms.Core.Trees;
using Umbraco.Cms.Core.Web;
using Umbraco.Cms.Infrastructure.DistributedLocking;
using Umbraco.Cms.Infrastructure.Examine;
using Umbraco.Cms.Infrastructure.Plugin;
using Umbraco.Cms.Infrastructure.HealthChecks;
using Umbraco.Cms.Infrastructure.HostedServices;
using Umbraco.Cms.Infrastructure.Install;
@@ -127,8 +129,8 @@ public static partial class UmbracoBuilderExtensions
// register manifest parser, will be injected in collection builders where needed
builder.Services.AddSingleton<IManifestParser, ManifestParser>();
builder.Services.AddSingleton<IExtensionManifestReader, ExtensionManifestReader>();
builder.Services.AddSingleton<IExtensionManifestService, ExtensionManifestService>();
builder.Services.AddSingleton<IPluginConfigurationReader, PluginConfigurationReader>();
builder.Services.AddSingleton<IPluginConfigurationService, PluginConfigurationService>();
// register the manifest filter collection builder (collection is empty by default)
builder.ManifestFilters();

View File

@@ -1,93 +0,0 @@
using System.Text;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Manifest;
internal sealed class ExtensionManifestReader : IExtensionManifestReader
{
private readonly IManifestFileProviderFactory _manifestFileProviderFactory;
private readonly IJsonSerializer _jsonSerializer;
private readonly ILogger<ExtensionManifestReader> _logger;
public ExtensionManifestReader(IManifestFileProviderFactory manifestFileProviderFactory, IJsonSerializer jsonSerializer, ILogger<ExtensionManifestReader> logger)
{
_manifestFileProviderFactory = manifestFileProviderFactory;
_jsonSerializer = jsonSerializer;
_logger = logger;
}
public async Task<IEnumerable<ExtensionManifest>> ReadManifestsAsync()
{
IFileProvider? manifestFileProvider = _manifestFileProviderFactory.Create();
if (manifestFileProvider is null)
{
throw new ArgumentNullException(nameof(manifestFileProvider));
}
IFileInfo[] manifestFiles = GetAllManifestFiles(manifestFileProvider, Constants.SystemDirectories.AppPlugins).ToArray();
return await ParseManifests(manifestFiles);
}
private static IEnumerable<IFileInfo> GetAllManifestFiles(IFileProvider fileProvider, string path)
{
const string extensionFileName = "umbraco-package.json";
foreach (IFileInfo fileInfo in fileProvider.GetDirectoryContents(path))
{
if (fileInfo.IsDirectory)
{
var virtualPath = WebPath.Combine(path, fileInfo.Name);
// find all extension manifest files recursively
foreach (IFileInfo nested in GetAllManifestFiles(fileProvider, virtualPath))
{
yield return nested;
}
}
else if (fileInfo.Name.InvariantEquals(extensionFileName))
{
yield return fileInfo;
}
}
}
private async Task<IEnumerable<ExtensionManifest>> ParseManifests(IFileInfo[] manifestFiles)
{
var manifests = new List<ExtensionManifest>();
foreach (IFileInfo fileInfo in manifestFiles)
{
string fileContent;
await using (Stream stream = fileInfo.CreateReadStream())
{
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
fileContent = await reader.ReadToEndAsync();
}
}
if (fileContent.IsNullOrWhiteSpace())
{
continue;
}
try
{
ExtensionManifest? manifest = _jsonSerializer.Deserialize<ExtensionManifest>(fileContent);
if (manifest != null)
{
manifests.Add(manifest);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Unable to load extension manifest file: {FileName}", fileInfo.Name);
}
}
return manifests;
}
}

View File

@@ -1,23 +0,0 @@
using Umbraco.Cms.Core.Cache;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Manifest;
internal sealed class ExtensionManifestService : IExtensionManifestService
{
private readonly IExtensionManifestReader _extensionManifestReader;
private readonly IAppPolicyCache _cache;
public ExtensionManifestService(IExtensionManifestReader extensionManifestReader, AppCaches appCaches)
{
_extensionManifestReader = extensionManifestReader;
_cache = appCaches.RuntimeCache;
}
public async Task<IEnumerable<ExtensionManifest>> GetManifestsAsync()
=> await _cache.GetCacheItemAsync(
$"{nameof(ExtensionManifestService)}-Manifests",
async () => await _extensionManifestReader.ReadManifestsAsync(),
TimeSpan.FromMinutes(10))
?? Array.Empty<ExtensionManifest>();
}

View File

@@ -1,6 +0,0 @@
namespace Umbraco.Cms.Core.Manifest;
public interface IExtensionManifestReader
{
Task<IEnumerable<ExtensionManifest>> ReadManifestsAsync();
}

View File

@@ -0,0 +1,8 @@
using Umbraco.Cms.Core.Plugin;
namespace Umbraco.Cms.Infrastructure.Plugin;
public interface IPluginConfigurationReader
{
Task<IEnumerable<PluginConfiguration>> ReadPluginConfigurationsAsync();
}

View File

@@ -0,0 +1,98 @@
using System.Text;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Plugin;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Plugin;
internal sealed class PluginConfigurationReader : IPluginConfigurationReader
{
private readonly IPluginConfigurationFileProviderFactory _pluginConfigurationFileProviderFactory;
private readonly IJsonSerializer _jsonSerializer;
private readonly ILogger<PluginConfigurationReader> _logger;
public PluginConfigurationReader(
IPluginConfigurationFileProviderFactory pluginConfigurationFileProviderFactory,
IJsonSerializer jsonSerializer,
ILogger<PluginConfigurationReader> logger)
{
_pluginConfigurationFileProviderFactory = pluginConfigurationFileProviderFactory;
_jsonSerializer = jsonSerializer;
_logger = logger;
}
public async Task<IEnumerable<PluginConfiguration>> ReadPluginConfigurationsAsync()
{
IFileProvider? fileProvider = _pluginConfigurationFileProviderFactory.Create();
if (fileProvider is null)
{
throw new ArgumentNullException(nameof(fileProvider));
}
IFileInfo[] files = GetAllPluginConfigurationFiles(fileProvider, Constants.SystemDirectories.AppPlugins).ToArray();
return await ParsePluginConfigurationFiles(files);
}
private static IEnumerable<IFileInfo> GetAllPluginConfigurationFiles(IFileProvider fileProvider, string path)
{
const string extensionFileName = "umbraco-package.json";
foreach (IFileInfo fileInfo in fileProvider.GetDirectoryContents(path))
{
if (fileInfo.IsDirectory)
{
var virtualPath = WebPath.Combine(path, fileInfo.Name);
// find all extension package configuration files recursively
foreach (IFileInfo nested in GetAllPluginConfigurationFiles(fileProvider, virtualPath))
{
yield return nested;
}
}
else if (fileInfo.Name.InvariantEquals(extensionFileName))
{
yield return fileInfo;
}
}
}
private async Task<IEnumerable<PluginConfiguration>> ParsePluginConfigurationFiles(IFileInfo[] files)
{
var pluginConfigurations = new List<PluginConfiguration>();
foreach (IFileInfo fileInfo in files)
{
string fileContent;
await using (Stream stream = fileInfo.CreateReadStream())
{
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
fileContent = await reader.ReadToEndAsync();
}
}
if (fileContent.IsNullOrWhiteSpace())
{
continue;
}
try
{
PluginConfiguration? pluginConfiguration = _jsonSerializer.Deserialize<PluginConfiguration>(fileContent);
if (pluginConfiguration != null)
{
pluginConfigurations.Add(pluginConfiguration);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Unable to load plugin configuration file: {FileName}", fileInfo.Name);
}
}
return pluginConfigurations;
}
}

View File

@@ -0,0 +1,24 @@
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Plugin;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Plugin;
internal sealed class PluginConfigurationService : IPluginConfigurationService
{
private readonly IPluginConfigurationReader _pluginConfigurationReader;
private readonly IAppPolicyCache _cache;
public PluginConfigurationService(IPluginConfigurationReader pluginConfigurationReader, AppCaches appCaches)
{
_pluginConfigurationReader = pluginConfigurationReader;
_cache = appCaches.RuntimeCache;
}
public async Task<IEnumerable<PluginConfiguration>> GetPluginConfigurationsAsync()
=> await _cache.GetCacheItemAsync(
$"{nameof(PluginConfigurationService)}-PluginConfigurations",
async () => await _pluginConfigurationReader.ReadPluginConfigurationsAsync(),
TimeSpan.FromMinutes(10))
?? Array.Empty<PluginConfiguration>();
}

View File

@@ -151,6 +151,7 @@ public static partial class UmbracoBuilderExtensions
// therefore no need to register it as singleton
builder.Services.AddSingleton<IManifestFileProviderFactory, ContentAndWebRootFileProviderFactory>();
builder.Services.AddSingleton<IGridEditorsConfigFileProviderFactory, WebRootFileProviderFactory>();
builder.Services.AddSingleton<IPluginConfigurationFileProviderFactory, ContentAndWebRootFileProviderFactory>();
// Must be added here because DbProviderFactories is netstandard 2.1 so cannot exist in Infra for now
builder.Services.AddSingleton<IDbProviderFactoryCreator>(factory => new DbProviderFactoryCreator(

View File

@@ -4,7 +4,7 @@ using Umbraco.Cms.Core.IO;
namespace Umbraco.Cms.Web.Common.FileProviders;
public class ContentAndWebRootFileProviderFactory : IManifestFileProviderFactory
public class ContentAndWebRootFileProviderFactory : IManifestFileProviderFactory, IPluginConfigurationFileProviderFactory
{
private readonly IWebHostEnvironment _webHostEnvironment;

View File

@@ -1,66 +0,0 @@
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Manifest;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Manifest;
[TestFixture]
public class ExtensionManifestServiceTests
{
private IExtensionManifestService _service;
private Mock<IExtensionManifestReader> _readerMock;
private IAppPolicyCache _runtimeCache;
[SetUp]
public void SetUp()
{
_readerMock = new Mock<IExtensionManifestReader>();
_readerMock.Setup(r => r.ReadManifestsAsync()).ReturnsAsync(
new[]
{
new ExtensionManifest { Name = "Test", Extensions = Array.Empty<object>() }
});
_runtimeCache = new ObjectCacheAppCache();
AppCaches appCaches = new AppCaches(
_runtimeCache,
NoAppCache.Instance,
new IsolatedCaches(type => NoAppCache.Instance));
_service = new ExtensionManifestService(_readerMock.Object, appCaches);
}
[Test]
public async Task CachesManifests()
{
var result = await _service.GetManifestsAsync();
Assert.AreEqual(1, result.Count());
var result2 = await _service.GetManifestsAsync();
Assert.AreEqual(1, result2.Count());
var result3 = await _service.GetManifestsAsync();
Assert.AreEqual(1, result3.Count());
_readerMock.Verify(r => r.ReadManifestsAsync(), Times.Exactly(1));
}
[Test]
public async Task ReloadsManifestsAfterCacheClear()
{
var result = await _service.GetManifestsAsync();
Assert.AreEqual(1, result.Count());
_runtimeCache.Clear();
var result2 = await _service.GetManifestsAsync();
Assert.AreEqual(1, result2.Count());
_runtimeCache.Clear();
var result3 = await _service.GetManifestsAsync();
Assert.AreEqual(1, result3.Count());
_runtimeCache.Clear();
_readerMock.Verify(r => r.ReadManifestsAsync(), Times.Exactly(3));
}
}

View File

@@ -6,17 +6,17 @@ using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Infrastructure.Plugin;
using Umbraco.Cms.Infrastructure.Serialization;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Manifest;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Plugin;
[TestFixture]
public class ExtensionManifestReaderTests
public class PluginConfigurationReaderTests
{
private IExtensionManifestReader _reader;
private IPluginConfigurationReader _reader;
private Mock<IDirectoryContents> _rootDirectoryContentsMock;
private Mock<ILogger<ExtensionManifestReader>> _loggerMock;
private Mock<ILogger<PluginConfigurationReader>> _loggerMock;
private Mock<IFileProvider> _fileProviderMock;
[SetUp]
@@ -27,82 +27,82 @@ public class ExtensionManifestReaderTests
_fileProviderMock
.Setup(m => m.GetDirectoryContents(Constants.SystemDirectories.AppPlugins))
.Returns(_rootDirectoryContentsMock.Object);
var fileProviderFactoryMock = new Mock<IManifestFileProviderFactory>();
var fileProviderFactoryMock = new Mock<IPluginConfigurationFileProviderFactory>();
fileProviderFactoryMock.Setup(m => m.Create()).Returns(_fileProviderMock.Object);
_loggerMock = new Mock<ILogger<ExtensionManifestReader>>();
_reader = new ExtensionManifestReader(fileProviderFactoryMock.Object, new SystemTextJsonSerializer(), _loggerMock.Object);
_loggerMock = new Mock<ILogger<PluginConfigurationReader>>();
_reader = new PluginConfigurationReader(fileProviderFactoryMock.Object, new SystemTextJsonSerializer(), _loggerMock.Object);
}
[Test]
public async Task CanReadManifestAtRoot()
public async Task Can_Read_PluginConfigurations_At_Root()
{
_rootDirectoryContentsMock
.Setup(f => f.GetEnumerator())
.Returns(new List<IFileInfo> { CreateExtensionManifestFile() }.GetEnumerator());
.Returns(new List<IFileInfo> { CreatePluginConfigurationFile() }.GetEnumerator());
var result = await _reader.ReadManifestsAsync();
var result = await _reader.ReadPluginConfigurationsAsync();
Assert.AreEqual(1, result.Count());
var first = result.First();
Assert.AreEqual("My Extension Manifest", first.Name);
Assert.AreEqual("My Plugin Configuration", first.Name);
Assert.AreEqual("1.2.3", first.Version);
Assert.AreEqual(2, first.Extensions.Count());
Assert.IsTrue(first.Extensions.All(e => e is JsonElement));
}
[Test]
public async Task CanReadManifestsInRootDirectories()
public async Task Can_Read_PluginConfiguration_In_Root_Directories()
{
var directory1 = CreateDirectoryMock("/my-extension", CreateExtensionManifestFile(DefaultManifestContent("Extension One")));
var directory2 = CreateDirectoryMock("/my-other-extension", CreateExtensionManifestFile(DefaultManifestContent("Extension Two")));
var plugin1 = CreateDirectoryMock("/my-extension", CreatePluginConfigurationFile(DefaultPluginConfigurationContent("Plugin One")));
var plugin2 = CreateDirectoryMock("/my-other-extension", CreatePluginConfigurationFile(DefaultPluginConfigurationContent("Plugin Two")));
_rootDirectoryContentsMock
.Setup(f => f.GetEnumerator())
.Returns(new List<IFileInfo> { directory1, directory2 }.GetEnumerator());
.Returns(new List<IFileInfo> { plugin1, plugin2 }.GetEnumerator());
var result = await _reader.ReadManifestsAsync();
var result = await _reader.ReadPluginConfigurationsAsync();
Assert.AreEqual(2, result.Count());
Assert.AreEqual("Extension One", result.First().Name);
Assert.AreEqual("Extension Two", result.Last().Name);
Assert.AreEqual("Plugin One", result.First().Name);
Assert.AreEqual("Plugin Two", result.Last().Name);
}
[Test]
public async Task CanReadManifestsRecursively()
public async Task Can_Read_PluginConfigurations_Recursively()
{
var childFolder = CreateDirectoryMock("/my-parent-folder/my-child-folder", CreateExtensionManifestFile(DefaultManifestContent("Nested Extension")));
var childFolder = CreateDirectoryMock("/my-parent-folder/my-child-folder", CreatePluginConfigurationFile(DefaultPluginConfigurationContent("Nested Plugin")));
var parentFolder = CreateDirectoryMock("/my-parent-folder", childFolder);
_rootDirectoryContentsMock
.Setup(f => f.GetEnumerator())
.Returns(new List<IFileInfo> { parentFolder }.GetEnumerator());
var result = await _reader.ReadManifestsAsync();
var result = await _reader.ReadPluginConfigurationsAsync();
Assert.AreEqual(1, result.Count());
Assert.AreEqual("Nested Extension", result.First().Name);
Assert.AreEqual("Nested Plugin", result.First().Name);
}
[Test]
public async Task CanSkipEmptyDirectories()
public async Task Can_Skip_Empty_Directories()
{
var extensionFolder = CreateDirectoryMock("/my-extension-folder", CreateExtensionManifestFile(DefaultManifestContent("My Extension")));
var pluginFolder = CreateDirectoryMock("/my-plugin-folder", CreatePluginConfigurationFile(DefaultPluginConfigurationContent("My Plugin")));
var emptyFolder = CreateDirectoryMock("/my-empty-folder");
_rootDirectoryContentsMock
.Setup(f => f.GetEnumerator())
.Returns(new List<IFileInfo> { emptyFolder, extensionFolder }.GetEnumerator());
.Returns(new List<IFileInfo> { emptyFolder, pluginFolder }.GetEnumerator());
var result = await _reader.ReadManifestsAsync();
var result = await _reader.ReadPluginConfigurationsAsync();
Assert.AreEqual(1, result.Count());
Assert.AreEqual("My Extension", result.First().Name);
Assert.AreEqual("My Plugin", result.First().Name);
}
[Test]
public async Task CanSkipOtherFiles()
public async Task Can_Skip_Other_Files()
{
var extensionFolder = CreateDirectoryMock(
"/my-extension-folder",
var pluginFolder = CreateDirectoryMock(
"/my-plugin-folder",
CreateOtherFile("my.js"),
CreateExtensionManifestFile(DefaultManifestContent("My Extension")));
CreatePluginConfigurationFile(DefaultPluginConfigurationContent("My Plugin")));
var otherFolder = CreateDirectoryMock(
"/my-empty-folder",
CreateOtherFile("some.js"),
@@ -110,15 +110,15 @@ public class ExtensionManifestReaderTests
_rootDirectoryContentsMock
.Setup(f => f.GetEnumerator())
.Returns(new List<IFileInfo> { otherFolder, extensionFolder }.GetEnumerator());
.Returns(new List<IFileInfo> { otherFolder, pluginFolder }.GetEnumerator());
var result = await _reader.ReadManifestsAsync();
var result = await _reader.ReadPluginConfigurationsAsync();
Assert.AreEqual(1, result.Count());
Assert.AreEqual("My Extension", result.First().Name);
Assert.AreEqual("My Plugin", result.First().Name);
}
[Test]
public async Task CanHandleAllEmptyDirectories()
public async Task Can_Handle_All_Empty_Directories()
{
var folders = Enumerable.Range(1, 10).Select(i => CreateDirectoryMock($"/my-empty-folder-{i}")).ToList();
@@ -126,12 +126,12 @@ public class ExtensionManifestReaderTests
.Setup(f => f.GetEnumerator())
.Returns(folders.GetEnumerator());
var result = await _reader.ReadManifestsAsync();
var result = await _reader.ReadPluginConfigurationsAsync();
Assert.AreEqual(0, result.Count());
}
[Test]
public async Task CannotReadManifestWithoutName()
public async Task Cannot_Read_PluginConfiguration_Without_Name()
{
var content = @"{
""version"": ""1.2.3"",
@@ -145,16 +145,16 @@ public class ExtensionManifestReaderTests
}";
_rootDirectoryContentsMock
.Setup(f => f.GetEnumerator())
.Returns(new List<IFileInfo> { CreateExtensionManifestFile(content) }.GetEnumerator());
.Returns(new List<IFileInfo> { CreatePluginConfigurationFile(content) }.GetEnumerator());
var result = await _reader.ReadManifestsAsync();
var result = await _reader.ReadPluginConfigurationsAsync();
Assert.AreEqual(0, result.Count());
EnsureLogErrorWasCalled();
}
[Test]
public async Task CannotReadManifestWithoutExtensions()
public async Task Cannot_Read_PluginConfiguration_Without_Extensions()
{
var content = @"{
""name"": ""Something"",
@@ -163,9 +163,9 @@ public class ExtensionManifestReaderTests
}";
_rootDirectoryContentsMock
.Setup(f => f.GetEnumerator())
.Returns(new List<IFileInfo> { CreateExtensionManifestFile(content) }.GetEnumerator());
.Returns(new List<IFileInfo> { CreatePluginConfigurationFile(content) }.GetEnumerator());
var result = await _reader.ReadManifestsAsync();
var result = await _reader.ReadPluginConfigurationsAsync();
Assert.AreEqual(0, result.Count());
EnsureLogErrorWasCalled();
@@ -173,13 +173,13 @@ public class ExtensionManifestReaderTests
[TestCase("This is not JSON")]
[TestCase(@"{""name"": ""invalid-json"", ""version"": ")]
public async Task CannotReadInvalidManifest(string content)
public async Task Cannot_Read_Invalid_PluginConfiguration(string content)
{
_rootDirectoryContentsMock
.Setup(f => f.GetEnumerator())
.Returns(new List<IFileInfo> { CreateExtensionManifestFile(content) }.GetEnumerator());
.Returns(new List<IFileInfo> { CreatePluginConfigurationFile(content) }.GetEnumerator());
var result = await _reader.ReadManifestsAsync();
var result = await _reader.ReadPluginConfigurationsAsync();
Assert.AreEqual(0, result.Count());
EnsureLogErrorWasCalled();
@@ -213,9 +213,9 @@ public class ExtensionManifestReaderTests
return fileInfo.Object;
}
private IFileInfo CreateExtensionManifestFile(string? content = null)
private IFileInfo CreatePluginConfigurationFile(string? content = null)
{
content ??= DefaultManifestContent();
content ??= DefaultPluginConfigurationContent();
var fileInfo = new Mock<IFileInfo>();
fileInfo.SetupGet(f => f.IsDirectory).Returns(false);
@@ -235,7 +235,7 @@ public class ExtensionManifestReaderTests
return fileInfo.Object;
}
private static string DefaultManifestContent(string name = "My Extension Manifest")
private static string DefaultPluginConfigurationContent(string name = "My Plugin Configuration")
=> @"{
""name"": ""##NAME##"",
""version"": ""1.2.3"",

View File

@@ -0,0 +1,67 @@
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Plugin;
using Umbraco.Cms.Infrastructure.Plugin;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Plugin;
[TestFixture]
public class PluginConfigurationServiceTests
{
private IPluginConfigurationService _service;
private Mock<IPluginConfigurationReader> _readerMock;
private IAppPolicyCache _runtimeCache;
[SetUp]
public void SetUp()
{
_readerMock = new Mock<IPluginConfigurationReader>();
_readerMock.Setup(r => r.ReadPluginConfigurationsAsync()).ReturnsAsync(
new[]
{
new PluginConfiguration { Name = "Test", Extensions = Array.Empty<object>() }
});
_runtimeCache = new ObjectCacheAppCache();
AppCaches appCaches = new AppCaches(
_runtimeCache,
NoAppCache.Instance,
new IsolatedCaches(type => NoAppCache.Instance));
_service = new PluginConfigurationService(_readerMock.Object, appCaches);
}
[Test]
public async Task CachesExtensionPackageConfigurations()
{
var result = await _service.GetPluginConfigurationsAsync();
Assert.AreEqual(1, result.Count());
var result2 = await _service.GetPluginConfigurationsAsync();
Assert.AreEqual(1, result2.Count());
var result3 = await _service.GetPluginConfigurationsAsync();
Assert.AreEqual(1, result3.Count());
_readerMock.Verify(r => r.ReadPluginConfigurationsAsync(), Times.Exactly(1));
}
[Test]
public async Task ReloadsExtensionPackageConfigurationsAfterCacheClear()
{
var result = await _service.GetPluginConfigurationsAsync();
Assert.AreEqual(1, result.Count());
_runtimeCache.Clear();
var result2 = await _service.GetPluginConfigurationsAsync();
Assert.AreEqual(1, result2.Count());
_runtimeCache.Clear();
var result3 = await _service.GetPluginConfigurationsAsync();
Assert.AreEqual(1, result3.Count());
_runtimeCache.Clear();
_readerMock.Verify(r => r.ReadPluginConfigurationsAsync(), Times.Exactly(3));
}
}

View File

@@ -1,6 +1,7 @@
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Plugin;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Semver;
@@ -23,7 +24,7 @@ public class TelemetryServiceTests
siteIdentifierServiceMock.Object,
usageInformationServiceMock.Object,
Mock.Of<IMetricsConsentService>(),
Mock.Of<IExtensionManifestService>());
Mock.Of<IPluginConfigurationService>());
Guid guid;
await sut.GetTelemetryReportDataAsync();
@@ -39,7 +40,7 @@ public class TelemetryServiceTests
CreateSiteIdentifierService(false),
Mock.Of<IUsageInformationService>(),
Mock.Of<IMetricsConsentService>(),
Mock.Of<IExtensionManifestService>());
Mock.Of<IPluginConfigurationService>());
var result = await sut.GetTelemetryReportDataAsync();
Assert.IsNull(result);
@@ -57,7 +58,7 @@ public class TelemetryServiceTests
CreateSiteIdentifierService(),
Mock.Of<IUsageInformationService>(),
metricsConsentService.Object,
Mock.Of<IExtensionManifestService>());
Mock.Of<IPluginConfigurationService>());
var result = await sut.GetTelemetryReportDataAsync();
@@ -72,7 +73,7 @@ public class TelemetryServiceTests
var versionPackageName = "VersionPackage";
var packageVersion = "1.0.0";
var noVersionPackageName = "NoVersionPackage";
ExtensionManifest[] manifests =
PluginConfiguration[] manifests =
{
new() { Name = versionPackageName, Version = packageVersion, Extensions = Array.Empty<object>()},
new() { Name = noVersionPackageName, Extensions = Array.Empty<object>() },
@@ -107,7 +108,7 @@ public class TelemetryServiceTests
public async Task RespectsAllowPackageTelemetry()
{
var version = CreateUmbracoVersion(9, 1, 1);
ExtensionManifest[] manifests =
PluginConfiguration[] manifests =
{
new() { Name = "DoNotTrack", AllowTelemetry = false, Extensions = Array.Empty<object>() },
new() { Name = "TrackingAllowed", AllowTelemetry = true, Extensions = Array.Empty<object>() },
@@ -132,10 +133,10 @@ public class TelemetryServiceTests
});
}
private IExtensionManifestService CreateExtensionManifestService(IEnumerable<ExtensionManifest> manifests)
private IPluginConfigurationService CreateExtensionManifestService(IEnumerable<PluginConfiguration> manifests)
{
var mock = new Mock<IExtensionManifestService>();
mock.Setup(x => x.GetManifestsAsync()).Returns(Task.FromResult(manifests));
var mock = new Mock<IPluginConfigurationService>();
mock.Setup(x => x.GetPluginConfigurationsAsync()).Returns(Task.FromResult(manifests));
return mock.Object;
}