Added support for multiple recipe steps of the same type and added unit tests.

Enables execution of multiple child recipes in a particular sequence. This is useful for the RecipesStep for example, where you want to execute a child recipe first, then execute some commands and import some content, then execute a second child recipe.
This commit is contained in:
Sipke Schoorstra
2015-07-30 14:26:01 +01:00
parent a7d2777478
commit 73170a0d63
17 changed files with 147 additions and 69 deletions

View File

@@ -159,6 +159,7 @@
<Compile Include="Packaging\Services\FileBasedProjectSystemTests.cs" />
<Compile Include="Packaging\Services\FolderUpdaterTests.cs" />
<Compile Include="Packaging\Services\PackageInstallerTests.cs" />
<Compile Include="Recipes\RecipeHandlers\RecipeParserTest.cs" />
<Compile Include="Recipes\RecipeHandlers\RecipeExecutionStepHandlerTest.cs" />
<Compile Include="Recipes\RecipeHandlers\ModuleStepTest.cs" />
<Compile Include="Recipes\RecipeHandlers\ThemeStepTest.cs" />
@@ -336,6 +337,12 @@
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Recipes\Services\FoldersData\Sample2\Module.txt" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Recipes\Services\FoldersData\Sample2\Recipes\duplicate-steps.recipe.xml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -94,7 +94,7 @@ Features:
Enumerable.Empty<ShellParameter>());
var moduleStep = _container.Resolve<ModuleStep>();
var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep( recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep(id: "1", recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Module.SuperWiki"));
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
@@ -120,7 +120,7 @@ Features:
");
var moduleStep = _container.Resolve<ModuleStep>();
var recipeContext = new RecipeContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
var recipeContext = new RecipeContext { RecipeStep = new RecipeStep(id: "1", recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = recipeContext.RecipeStep };
recipeContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
@@ -145,7 +145,7 @@ Features:
});
var moduleStep = _container.Resolve<ModuleStep>();
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(id: "1", recipeName: "Test", name: "Module", step: new XElement("SuperWiki")) };
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Module.SuperWiki"));
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));

View File

@@ -25,7 +25,7 @@ namespace Orchard.Tests.Modules.Recipes.RecipeHandlers {
var fakeRecipeStep = _container.Resolve<StubRecipeExecutionStep>();
var context = new RecipeContext {
RecipeStep = new RecipeStep (recipeName: "FakeRecipe", name: "FakeRecipeStep", step: new XElement("FakeRecipeStep")),
RecipeStep = new RecipeStep (id: "1", recipeName: "FakeRecipe", name: "FakeRecipeStep", step: new XElement("FakeRecipeStep")),
ExecutionId = "12345"
};

View File

@@ -0,0 +1,29 @@
using System.Linq;
using Autofac;
using NUnit.Framework;
using Orchard.Recipes.Services;
namespace Orchard.Tests.Modules.Recipes.RecipeHandlers {
[TestFixture]
public class RecipeParserTest {
protected IContainer _container;
[SetUp]
public void Init() {
var builder = new ContainerBuilder();
builder.RegisterType<RecipeParser>().As<IRecipeParser>();
_container = builder.Build();
}
[Test]
public void ParsingRecipeYieldsUniqueIdsForSteps() {
var recipeText = @"<Orchard><Foo /><Bar /><Baz /></Orchard>";
var recipeParser = _container.Resolve<IRecipeParser>();
var recipe = recipeParser.ParseRecipe(recipeText);
// Assert that each step has a unique ID.
Assert.IsTrue(recipe.RecipeSteps.GroupBy(x => x.Id).All(y => y.Count() == 1));
}
}
}

View File

@@ -100,7 +100,7 @@ Features:
Enumerable.Empty<ShellParameter>());
var themeStep = _container.Resolve<ThemeStep>();
var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep (recipeName: "Test", name: "Theme", step: new XElement("SuperWiki")) };
var recipeExecutionContext = new RecipeExecutionContext {RecipeStep = new RecipeStep (id: "1", recipeName: "Test", name: "Theme", step: new XElement("SuperWiki")) };
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Theme.SuperWiki"));
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
@@ -135,7 +135,7 @@ Features:
");
var themeStep = _container.Resolve<ThemeStep>();
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Theme", step: new XElement("SuperWiki")) };
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(id: "1", recipeName: "Test", name: "Theme", step: new XElement("SuperWiki")) };
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));
Assert.Throws(typeof (InvalidOperationException), () => themeStep.Execute(recipeExecutionContext));
@@ -159,7 +159,7 @@ Features:
});
var themeStep = _container.Resolve<ThemeStep>();
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(recipeName: "Test", name: "Theme", step: new XElement("SuperWiki")) };
var recipeExecutionContext = new RecipeExecutionContext { RecipeStep = new RecipeStep(id: "1", recipeName: "Test", name: "Theme", step: new XElement("SuperWiki")) };
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("packageId", "Orchard.Theme.SuperWiki"));
recipeExecutionContext.RecipeStep.Step.Add(new XAttribute("repository", "test"));

View File

@@ -0,0 +1,8 @@
Name: Le plug-in français
Author: Bertrand Le Roy
Description:
This plug-in replaces
'the' with 'le'
Version: 1.4.1
Tags: plug-in, français, the, le
homepage: http://weblogs.asp.net/bleroy

View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<Orchard>
<Recipe>
<Name>Duplicate Steps</Name>
</Recipe>
<Recipes />
<Recipes />
<Recipes />
</Orchard>

View File

@@ -1,61 +1,52 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Xml;
using Autofac;
using Moq;
using NHibernate;
using NUnit.Framework;
using Orchard.Caching;
using Orchard.ContentManagement.Records;
using Orchard.Data;
using Orchard.Environment.Extensions;
using Orchard.Environment.Extensions.Folders;
using Orchard.Environment.Extensions.Loaders;
using Orchard.FileSystems.AppData;
using Orchard.FileSystems.WebSite;
using Orchard.Recipes.Events;
using Orchard.Recipes.Models;
using Orchard.Recipes.Services;
using Orchard.Services;
using Orchard.Tests.Environment.Extensions;
using Orchard.Tests.Stubs;
using Orchard.Recipes.Events;
using System;
using System.Linq.Expressions;
namespace Orchard.Tests.Modules.Recipes.Services {
[TestFixture]
public class RecipeManagerTests {
private IContainer _container;
public class RecipeManagerTests : DatabaseEnabledTestsBase {
private IRecipeManager _recipeManager;
private IRecipeHarvester _recipeHarvester;
private IRecipeParser _recipeParser;
private IExtensionFolders _folders;
private ISessionFactory _sessionFactory;
private ISession _session;
private const string DataPrefix = "Orchard.Tests.Modules.Recipes.Services.FoldersData.";
private string _tempFolderName;
[TestFixtureSetUp]
public void InitFixture() {
var databaseFileName = System.IO.Path.GetTempFileName();
_sessionFactory = DataUtility.CreateSessionFactory(
databaseFileName,
typeof(ContentTypeRecord),
typeof(ContentItemRecord),
typeof(ContentItemVersionRecord),
typeof(RecipeStepResultRecord));
protected override IEnumerable<Type> DatabaseTypes {
get { yield return typeof (RecipeStepResultRecord); }
}
[SetUp]
public void Init() {
public override void Register(ContainerBuilder builder) {
_tempFolderName = Path.GetTempFileName();
File.Delete(_tempFolderName);
var assembly = GetType().Assembly;
foreach (var name in assembly.GetManifestResourceNames()) {
if (name.StartsWith(DataPrefix)) {
foreach (var name in assembly.GetManifestResourceNames())
{
if (name.StartsWith(DataPrefix))
{
string text;
using (var stream = assembly.GetManifestResourceStream(name)) {
using (var stream = assembly.GetManifestResourceStream(name))
{
using (var reader = new StreamReader(stream))
text = reader.ReadToEnd();
@@ -73,15 +64,16 @@ namespace Orchard.Tests.Modules.Recipes.Services {
var targetPath = Path.Combine(_tempFolderName, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
using (var stream = new FileStream(targetPath, FileMode.Create)) {
using (var writer = new StreamWriter(stream)) {
using (var stream = new FileStream(targetPath, FileMode.Create))
{
using (var writer = new StreamWriter(stream))
{
writer.Write(text);
}
}
}
}
var builder = new ContainerBuilder();
var harvester = new ExtensionHarvester(new StubCacheManager(), new StubWebSiteFolder(), new Mock<ICriticalErrorProvider>().Object);
_folders = new ModuleFolders(new[] { _tempFolderName }, harvester);
builder.RegisterType<RecipeManager>().As<IRecipeManager>();
@@ -97,21 +89,23 @@ namespace Orchard.Tests.Modules.Recipes.Services {
builder.RegisterType<StubAsyncTokenProvider>().As<IAsyncTokenProvider>();
builder.RegisterInstance(_folders).As<IExtensionFolders>();
builder.RegisterInstance(new Mock<IRecipeExecuteEventHandler>().Object);
builder.RegisterType<Environment.Extensions.ExtensionManagerTests.StubLoaders>().As<IExtensionLoader>();
builder.RegisterType<ExtensionManagerTests.StubLoaders>().As<IExtensionLoader>();
builder.RegisterType<RecipeParser>().As<IRecipeParser>();
builder.RegisterType<StubWebSiteFolder>().As<IWebSiteFolder>();
builder.RegisterType<CustomRecipeHandler>().As<IRecipeHandler>();
builder.RegisterInstance(new StubRecipeStepResultRecordRepository()).As<IRepository<RecipeStepResultRecord>>();
}
public override void Init() {
base.Init();
_container = builder.Build();
_recipeManager = _container.Resolve<IRecipeManager>();
_recipeParser = _container.Resolve<IRecipeParser>();
_recipeHarvester = _container.Resolve<IRecipeHarvester>();
}
[TearDown]
public void Term() {
public override void Cleanup() {
Directory.Delete(_tempFolderName, true);
base.Cleanup();
}
[Test]
@@ -168,6 +162,38 @@ namespace Orchard.Tests.Modules.Recipes.Services {
Assert.That(CustomRecipeHandler.AttributeValue == "value1");
}
[Test]
public void ExecuteUpdatesStepResults()
{
var recipes = (List<Recipe>)_recipeHarvester.HarvestRecipes("Sample1");
var sampleRecipe = recipes.First();
var steps = sampleRecipe.RecipeSteps.ToArray();
_recipeManager.Execute(sampleRecipe);
var stepResultRepository = _container.Resolve<IRepository<RecipeStepResultRecord>>();
var stepResults = stepResultRepository.Table.ToArray();
Assert.That(stepResults.Count(), Is.EqualTo(steps.Count()));
Assert.IsTrue(stepResults.All(x => x.IsCompleted));
}
[Test]
public void CanExecuteSameStepMultipleTimes()
{
var recipes = (List<Recipe>)_recipeHarvester.HarvestRecipes("Sample2");
var recipe = recipes.Single(x => x.Name == "Duplicate Steps");
var steps = recipe.RecipeSteps.ToArray();
_recipeManager.Execute(recipe);
var stepResultRepository = _container.Resolve<IRepository<RecipeStepResultRecord>>();
var stepResults = stepResultRepository.Table.ToArray();
Assert.That(stepResults.Count(), Is.EqualTo(steps.Count()));
Assert.IsTrue(stepResults.All(x => x.IsCompleted));
}
}
public class StubStepQueue : IRecipeStepQueue {
@@ -182,22 +208,6 @@ namespace Orchard.Tests.Modules.Recipes.Services {
}
}
public class StubRecipeStepResultRecordRepository : IRepository<RecipeStepResultRecord> {
private List<RecipeStepResultRecord> _records = new List<RecipeStepResultRecord>();
public IQueryable<RecipeStepResultRecord> Table { get { return _records.AsQueryable(); } }
public void Copy(RecipeStepResultRecord source, RecipeStepResultRecord target) { }
public int Count(Expression<Func<RecipeStepResultRecord, bool>> predicate) { return _records.Count; }
public void Create(RecipeStepResultRecord entity) { _records.Add(entity); }
public void Delete(RecipeStepResultRecord entity) { _records.Remove(entity); }
public IEnumerable<RecipeStepResultRecord> Fetch(Expression<Func<RecipeStepResultRecord, bool>> predicate) { throw new NotImplementedException(); }
public IEnumerable<RecipeStepResultRecord> Fetch(Expression<Func<RecipeStepResultRecord, bool>> predicate, Action<Orderable<RecipeStepResultRecord>> order) { throw new NotImplementedException(); }
public IEnumerable<RecipeStepResultRecord> Fetch(Expression<Func<RecipeStepResultRecord, bool>> predicate, Action<Orderable<RecipeStepResultRecord>> order, int skip, int count) { throw new NotImplementedException(); }
public void Flush() { }
public RecipeStepResultRecord Get(Expression<Func<RecipeStepResultRecord, bool>> predicate) { throw new NotImplementedException(); }
public RecipeStepResultRecord Get(int id) { throw new NotImplementedException(); }
public void Update(RecipeStepResultRecord entity) { }
}
public class StubRecipeScheduler : IRecipeScheduler {
private readonly IRecipeStepExecutor _recipeStepExecutor;
@@ -212,7 +222,7 @@ namespace Orchard.Tests.Modules.Recipes.Services {
public class CustomRecipeHandler : IRecipeHandler {
public static string AttributeValue;
public string[] _handles = { "Module", "Theme", "Migration", "Custom1", "Custom2", "Command", "Metadata", "Feature", "Settings" };
public string[] _handles = { "Module", "Theme", "Migration", "Custom1", "Custom2", "Command", "Metadata", "Feature", "Settings", "Recipes" };
public void ExecuteRecipeStep(RecipeContext recipeContext) {
if (_handles.Contains(recipeContext.RecipeStep.Name)) {