diff --git a/MORYX-Framework.sln b/MORYX-Framework.sln
index f3ba24f65..a62cf04a1 100644
--- a/MORYX-Framework.sln
+++ b/MORYX-Framework.sln
@@ -116,7 +116,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moryx.Resources.Management.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moryx.Runtime.Endpoints.Tests", "src\Tests\Moryx.Runtime.Endpoints.Tests\Moryx.Runtime.Endpoints.Tests.csproj", "{7792C4E0-6D07-42C9-AC29-BAB76836FC11}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moryx.Runtime.Endpoints.IntegrationTests", "src\Tests\Moryx.Runtime.Endpoints.IntegrationTests\Moryx.Runtime.Endpoints.IntegrationTests.csproj", "{4FFB98A7-9A4C-476F-8BCC-C19B7F757BF8}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moryx.Runtime.Endpoints.IntegrationTests", "src\Tests\Moryx.Runtime.Endpoints.IntegrationTests\Moryx.Runtime.Endpoints.IntegrationTests.csproj", "{4FFB98A7-9A4C-476F-8BCC-C19B7F757BF8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moryx.TestTools.NUnit", "src\Moryx.TestTools.NUnit\Moryx.TestTools.NUnit.csproj", "{6FF878E0-AF61-4C3A-9B9C-71C35A949E51}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moryx.TestTools.IntegrationTest", "src\Moryx.TestTools.IntegrationTest\Moryx.TestTools.IntegrationTest.csproj", "{C949164C-0345-4893-9E4C-A79BC1F93F85}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -296,6 +300,14 @@ Global
{4FFB98A7-9A4C-476F-8BCC-C19B7F757BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FFB98A7-9A4C-476F-8BCC-C19B7F757BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FFB98A7-9A4C-476F-8BCC-C19B7F757BF8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6FF878E0-AF61-4C3A-9B9C-71C35A949E51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6FF878E0-AF61-4C3A-9B9C-71C35A949E51}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6FF878E0-AF61-4C3A-9B9C-71C35A949E51}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6FF878E0-AF61-4C3A-9B9C-71C35A949E51}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C949164C-0345-4893-9E4C-A79BC1F93F85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C949164C-0345-4893-9E4C-A79BC1F93F85}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C949164C-0345-4893-9E4C-A79BC1F93F85}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C949164C-0345-4893-9E4C-A79BC1F93F85}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -340,10 +352,12 @@ Global
{FEB3BA44-2CD9-445A-ABF2-C92378C443F7} = {0A466330-6ED6-4861-9C94-31B1949CDDB9}
{7792C4E0-6D07-42C9-AC29-BAB76836FC11} = {0A466330-6ED6-4861-9C94-31B1949CDDB9}
{4FFB98A7-9A4C-476F-8BCC-C19B7F757BF8} = {8517D209-5BC1-47BD-A7C7-9CF9ADD9F5B6}
+ {6FF878E0-AF61-4C3A-9B9C-71C35A949E51} = {953AAE25-26C8-4A28-AB08-61BAFE41B22F}
+ {C949164C-0345-4893-9E4C-A79BC1F93F85} = {953AAE25-26C8-4A28-AB08-61BAFE41B22F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {36EFC961-F4E7-49DC-A36A-99594FFB8243}
- RESX_TaskErrorCategory = Message
RESX_ShowErrorsInErrorList = True
+ RESX_TaskErrorCategory = Message
+ SolutionGuid = {36EFC961-F4E7-49DC-A36A-99594FFB8243}
EndGlobalSection
EndGlobal
diff --git a/VERSION b/VERSION
index 68d92dd66..fbb9ea12d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.0.6
+8.2.0
diff --git a/docs/articles/Core/Serialization/PossibleValues.md b/docs/articles/Core/Serialization/PossibleValues.md
index d68bb0bd8..e3d565101 100644
--- a/docs/articles/Core/Serialization/PossibleValues.md
+++ b/docs/articles/Core/Serialization/PossibleValues.md
@@ -26,15 +26,15 @@ public abstract class PossibleValuesAttribute : Attribute
public abstract bool UpdateFromPredecessor { get; }
///
- /// All possible values for this member represented as strings. The given container might be null
+ /// All possible values for this member represented as strings. The given containers might be null
/// and can be used to resolve possible values
///
- public abstract IEnumerable GetValues(IContainer container);
+ public virtual IEnumerable GetValues(IContainer container, IServiceProvider serviceProvider);
///
/// String to value conversion. Must be override if is set to true"/>
///
- public virtual object Parse(IContainer container, string value)
+ public virtual object Parse(IContainer container, IServiceProvider serviceProvider), string value)
{
return value;
}
diff --git a/docs/tutorials/HowToTestAModule.md b/docs/tutorials/HowToTestAModule.md
new file mode 100644
index 000000000..0ee512088
--- /dev/null
+++ b/docs/tutorials/HowToTestAModule.md
@@ -0,0 +1,45 @@
+# Setup a test environment for integration tests of a module
+
+In order to test a module in its lifecycle with its respective facade we offer the `Moryx.TestTools.IntegrationTest`.
+The package brings a `MoryxTestEnvironment`.
+With this class you can first create mocks for all module facades your module dependents on using the static `CreateModuleMock` method.
+Afterwards you can create the environment using an implementation of the `ServerModuleBase` class, an instance of the `ConfigBase` and the set of dependency mocks.
+The first two parameters are usually your `ModuleController` and your `ModuleConfig`.
+The following example shows a setup for the `IShiftManagement` facade interface. The module depends on the `IResourceManagement` and `IOperatorManagement` facades.
+
+```csharp
+private ModuleConfig _config;
+private Mock _resourceManagementMock;
+private Mock _operatorManagementMock;
+private MoryxTestEnvironment _env;
+
+[SetUp]
+public void SetUp()
+{
+ ReflectionTool.TestMode = true;
+ _config = new();
+ _resourceManagementMock = MoryxTestEnvironment.CreateModuleMock();
+ _operatorManagementMock = MoryxTestEnvironment.CreateModuleMock();
+ _env = new MoryxTestEnvironment(typeof(ModuleController),
+ new Mock[] { _resourceManagementMock, _operatorManagementMock }, _config);
+}
+```
+
+Using the created environment you can start and stop the module as you please.
+You can also retrieve the facade of the module to test all the functionalities the running module should provide.
+
+```csharp
+[Test]
+public void Start_WhenModuleIsStopped_StartsModule()
+{
+ // Arrange
+ var facade = _env.GetTestModule();
+
+ // Act
+ var module = _env.StartTestModule();
+ var module = _env.StopTestModule();
+
+ // Assert
+ Assert.That(module.State, Is.EqualTo(ServerModuleState.Stopped));
+}
+```
\ No newline at end of file
diff --git a/src/Moryx.AbstractionLayer.Products.Endpoints/Moryx.AbstractionLayer.Products.Endpoints.csproj b/src/Moryx.AbstractionLayer.Products.Endpoints/Moryx.AbstractionLayer.Products.Endpoints.csproj
index b5eeaf342..6c7673222 100644
--- a/src/Moryx.AbstractionLayer.Products.Endpoints/Moryx.AbstractionLayer.Products.Endpoints.csproj
+++ b/src/Moryx.AbstractionLayer.Products.Endpoints/Moryx.AbstractionLayer.Products.Endpoints.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/Moryx.AbstractionLayer.Products.Endpoints/PartialSerialization.cs b/src/Moryx.AbstractionLayer.Products.Endpoints/PartialSerialization.cs
index 8b4dea2c7..42adb63f5 100644
--- a/src/Moryx.AbstractionLayer.Products.Endpoints/PartialSerialization.cs
+++ b/src/Moryx.AbstractionLayer.Products.Endpoints/PartialSerialization.cs
@@ -6,6 +6,7 @@
using System.Linq;
using System.Reflection;
using Moryx.Configuration;
+using Moryx.Container;
using Moryx.Serialization;
namespace Moryx.AbstractionLayer.Products.Endpoints
@@ -31,7 +32,18 @@ static PartialSerialization()
///
/// Creates a new instance
///
- public PartialSerialization() : base(null, new EmptyValueProvider())
+ [Obsolete("Use serialization with containers instead")]
+ public PartialSerialization() : this(null, null)
+ {
+ }
+
+ ///
+ /// Create serialization with access to global and local container
+ ///
+ ///
+ ///
+ public PartialSerialization(IContainer localContainer, IServiceProvider serviceProvider)
+ : base(localContainer, serviceProvider, new EmptyValueProvider())
{
}
diff --git a/src/Moryx.AbstractionLayer.Products.Endpoints/ProductConverter.cs b/src/Moryx.AbstractionLayer.Products.Endpoints/ProductConverter.cs
index 545315451..d1b5c14d5 100644
--- a/src/Moryx.AbstractionLayer.Products.Endpoints/ProductConverter.cs
+++ b/src/Moryx.AbstractionLayer.Products.Endpoints/ProductConverter.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0
using Moryx.AbstractionLayer.Recipes;
+using Moryx.Container;
using Moryx.Serialization;
using Moryx.Tools;
using Moryx.Workplans;
@@ -20,12 +21,19 @@ public class ProductConverter
// Null object pattern for identity
private static readonly ProductIdentity EmptyIdentity = new ProductIdentity(string.Empty, 0);
- private static readonly ICustomSerialization ProductSerialization = new PartialSerialization();
- private static readonly ICustomSerialization RecipeSerialization = new PartialSerialization();
+ private readonly ICustomSerialization _productSerialization;
+ private readonly ICustomSerialization _recipeSerialization;
- public ProductConverter(IProductManagement productManagement)
+ public IContainer ProductManagerContainer { get; }
+ public IServiceProvider GlobalContainer { get; }
+
+ public ProductConverter(IProductManagement productManagement, IContainer localContainer, IServiceProvider globalContainer)
{
_productManagement = productManagement;
+ ProductManagerContainer = localContainer;
+ GlobalContainer = globalContainer;
+ _productSerialization = new PartialSerialization(localContainer, globalContainer);
+ _recipeSerialization = new PartialSerialization(localContainer, globalContainer);
}
public ProductDefinitionModel ConvertProductType(Type productType)
@@ -39,7 +47,7 @@ public ProductDefinitionModel ConvertProductType(Type productType)
Name = productType.FullName,
DisplayName = productType.GetDisplayName() ?? productType.Name,
BaseDefinition = baseTypeName,
- Properties = EntryConvert.EncodeClass(productType, ProductSerialization)
+ Properties = EntryConvert.EncodeClass(productType, _productSerialization)
};
}
@@ -73,7 +81,7 @@ public ProductModel ConvertProduct(IProductType productType, bool flat)
// Properties
var typeWrapper = _productManagement.GetTypeWrapper(productType.GetType().FullName);
var properties = typeWrapper != null ? typeWrapper.Properties.ToArray() : productType.GetType().GetProperties();
- converted.Properties = EntryConvert.EncodeObject(productType, ProductSerialization);
+ converted.Properties = EntryConvert.EncodeObject(productType, _productSerialization);
// Files
converted.Files = ConvertFiles(productType, properties);
@@ -124,7 +132,7 @@ private void ConvertParts(IProductType productType, IEnumerable pr
DisplayName = displayName,
Type = FetchProductType(property.PropertyType),
Parts = partModel is null ? new PartModel[0] : new[] { partModel },
- PropertyTemplates = EntryConvert.EncodeClass(property.PropertyType, ProductSerialization)
+ PropertyTemplates = EntryConvert.EncodeClass(property.PropertyType, _productSerialization)
};
connectors.Add(connector);
}
@@ -139,7 +147,7 @@ private void ConvertParts(IProductType productType, IEnumerable pr
DisplayName = displayName,
Type = FetchProductType(linkType),
Parts = links?.Select(ConvertPart).ToArray(),
- PropertyTemplates = EntryConvert.EncodeClass(linkType, ProductSerialization)
+ PropertyTemplates = EntryConvert.EncodeClass(linkType, _productSerialization)
};
connectors.Add(connector);
}
@@ -165,7 +173,7 @@ private PartModel ConvertPart(IProductPartLink link)
{
Id = link.Id,
Product = ConvertProduct(link.Product, true),
- Properties = EntryConvert.EncodeObject(link, ProductSerialization)
+ Properties = EntryConvert.EncodeObject(link, _productSerialization)
};
return part;
}
@@ -214,7 +222,7 @@ public IProductType ConvertProductBack(ProductModel source, ProductType converte
// Copy extended properties
var typeWrapper = _productManagement.GetTypeWrapper(converted.GetType().FullName);
var properties = typeWrapper != null ? typeWrapper.Properties.ToArray() : converted.GetType().GetProperties();
- EntryConvert.UpdateInstance(converted, source.Properties, ProductSerialization);
+ EntryConvert.UpdateInstance(converted, source.Properties, _productSerialization);
// Copy Files
ConvertFilesBack(converted, source, properties);
@@ -318,7 +326,12 @@ private static void ConvertFilesBack(object converted, ProductModel product, Pro
}
}
- public static RecipeModel ConvertRecipe(IRecipe recipe)
+ [Obsolete("Use ConvertRecipe on instance")]
+ public static RecipeModel ConvertRecipe(IRecipe recipe) => ConvertRecipe(recipe, new PartialSerialization(null, null));
+
+ public RecipeModel ConvertRecipeV2(IRecipe recipe) => ConvertRecipe(recipe, _recipeSerialization);
+
+ private static RecipeModel ConvertRecipe(IRecipe recipe, ICustomSerialization serialization)
{
// Transform to DTO and transmit
var converted = new RecipeModel
@@ -328,7 +341,7 @@ public static RecipeModel ConvertRecipe(IRecipe recipe)
Type = recipe.GetType().Name,
State = recipe.State,
Revision = recipe.Revision,
- Properties = EntryConvert.EncodeObject(recipe, RecipeSerialization),
+ Properties = EntryConvert.EncodeObject(recipe, serialization),
IsClone = recipe.Classification.HasFlag(RecipeClassification.Clone)
};
@@ -377,7 +390,7 @@ public IProductRecipe ConvertRecipeBack(RecipeModel recipe, IProductRecipe produ
productRecipe.Product = productType;
}
- EntryConvert.UpdateInstance(productRecipe, recipe.Properties, RecipeSerialization);
+ EntryConvert.UpdateInstance(productRecipe, recipe.Properties, _recipeSerialization);
// Do not update a clones classification
if (productRecipe.Classification.HasFlag(RecipeClassification.Clone))
diff --git a/src/Moryx.AbstractionLayer.Products.Endpoints/ProductManagementController.cs b/src/Moryx.AbstractionLayer.Products.Endpoints/ProductManagementController.cs
index 981aad47b..516876d7e 100644
--- a/src/Moryx.AbstractionLayer.Products.Endpoints/ProductManagementController.cs
+++ b/src/Moryx.AbstractionLayer.Products.Endpoints/ProductManagementController.cs
@@ -14,6 +14,10 @@
using Moryx.Configuration;
using Moryx.Asp.Extensions;
using Moryx.AbstractionLayer.Properties;
+using Microsoft.Extensions.DependencyInjection;
+using Moryx.AbstractionLayer.Resources;
+using Moryx.Runtime.Modules;
+using System.ComponentModel;
namespace Moryx.AbstractionLayer.Products.Endpoints
{
@@ -27,10 +31,14 @@ public class ProductManagementController : ControllerBase
{
private readonly IProductManagement _productManagement;
private readonly ProductConverter _productConverter;
- public ProductManagementController(IProductManagement productManagement)
+ public ProductManagementController(IProductManagement productManagement,
+ IModuleManager moduleManager,
+ IServiceProvider serviceProvider)
{
_productManagement = productManagement;
- _productConverter = new ProductConverter(_productManagement);
+
+ var module = moduleManager.AllModules.FirstOrDefault(module => module is IFacadeContainer);
+ _productConverter = new ProductConverter(_productManagement, module.Container, serviceProvider);
}
#region importers
@@ -39,6 +47,8 @@ public ProductManagementController(IProductManagement productManagement)
[Authorize(Policy = ProductPermissions.CanViewTypes)]
public ActionResult GetProductCustomization()
{
+ var parameterSerialization = new PossibleValuesSerialization(_productConverter.ProductManagerContainer, _productConverter.GlobalContainer,
+ new ValueProviderExecutor(new ValueProviderExecutorSettings().AddDefaultValueProvider()));
return new ProductCustomization
{
ProductTypes = GetProductTypes(),
@@ -46,7 +56,7 @@ public ActionResult GetProductCustomization()
Importers = _productManagement.Importers.Select(i => new ProductImporter
{
Name = i.Key,
- Parameters = EntryConvert.EncodeObject(i.Value, new PossibleValuesSerialization(null, new ValueProviderExecutor(new ValueProviderExecutorSettings().AddDefaultValueProvider())))
+ Parameters = EntryConvert.EncodeObject(i.Value, parameterSerialization)
}).ToArray()
};
}
@@ -247,7 +257,7 @@ public ActionResult GetRecipes(long id, int classification)
var recipes = _productManagement.GetRecipes(productType, (RecipeClassification)classification);
var recipeModels = new List();
foreach (var recipe in recipes)
- recipeModels.Add(ProductConverter.ConvertRecipe(recipe));
+ recipeModels.Add(_productConverter.ConvertRecipeV2(recipe));
return recipeModels.ToArray();
}
#endregion
@@ -330,7 +340,7 @@ public ActionResult GetRecipe(long id)
var recipe = _productManagement.LoadRecipe(id);
if (recipe == null)
return NotFound(new MoryxExceptionResponse {Title= string.Format(Strings.RecipeNotFoundException_Message, id) });
- return ProductConverter.ConvertRecipe(recipe);
+ return _productConverter.ConvertRecipeV2(recipe);
}
[HttpPost]
@@ -380,7 +390,7 @@ public ActionResult CreateRecipe(string recipeType)
var recipe = _productManagement.CreateRecipe(recipeType);
if (recipe == null)
recipe = (IProductRecipe)TypeTool.CreateInstance(recipeType);
- return ProductConverter.ConvertRecipe(recipe);
+ return _productConverter.ConvertRecipeV2(recipe);
}
#endregion
}
diff --git a/src/Moryx.AbstractionLayer.Resources.Endpoints/DataMemberAttributeValueProviderFilter.cs b/src/Moryx.AbstractionLayer.Resources.Endpoints/DataMemberAttributeValueProviderFilter.cs
new file mode 100644
index 000000000..3b15660fd
--- /dev/null
+++ b/src/Moryx.AbstractionLayer.Resources.Endpoints/DataMemberAttributeValueProviderFilter.cs
@@ -0,0 +1,26 @@
+// Copyright (c) 2023, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using System.Reflection;
+using System.Runtime.Serialization;
+using Moryx.Configuration;
+
+namespace Moryx.AbstractionLayer.Resources.Endpoints
+{
+ internal class DataMemberAttributeValueProviderFilter : IValueProviderFilter
+ {
+ private readonly bool _filterDataMembers;
+
+ public DataMemberAttributeValueProviderFilter(bool filterDataMembers)
+ {
+ _filterDataMembers = filterDataMembers;
+ }
+
+ public bool CheckProperty(PropertyInfo propertyInfo)
+ {
+ if (_filterDataMembers)
+ return propertyInfo.GetCustomAttribute() == null;
+ return propertyInfo.GetCustomAttribute() != null;
+ }
+ }
+}
diff --git a/src/Moryx.AbstractionLayer.Resources.Endpoints/Moryx.AbstractionLayer.Resources.Endpoints.csproj b/src/Moryx.AbstractionLayer.Resources.Endpoints/Moryx.AbstractionLayer.Resources.Endpoints.csproj
index 689fe70c9..21907fa30 100644
--- a/src/Moryx.AbstractionLayer.Resources.Endpoints/Moryx.AbstractionLayer.Resources.Endpoints.csproj
+++ b/src/Moryx.AbstractionLayer.Resources.Endpoints/Moryx.AbstractionLayer.Resources.Endpoints.csproj
@@ -14,7 +14,6 @@
-
diff --git a/src/Moryx.AbstractionLayer.Resources.Endpoints/ResourceManagementController.cs b/src/Moryx.AbstractionLayer.Resources.Endpoints/ResourceManagementController.cs
index 15e8d1458..15aca8484 100644
--- a/src/Moryx.AbstractionLayer.Resources.Endpoints/ResourceManagementController.cs
+++ b/src/Moryx.AbstractionLayer.Resources.Endpoints/ResourceManagementController.cs
@@ -16,7 +16,6 @@
using Moryx.AbstractionLayer.Properties;
using Moryx.Runtime.Modules;
using Moryx.Configuration;
-using Moryx.Resources.Management;
namespace Moryx.AbstractionLayer.Resources.Endpoints
{
@@ -32,12 +31,15 @@ public class ResourceModificationController : ControllerBase
private readonly IResourceTypeTree _resourceTypeTree;
private readonly ResourceSerialization _serialization;
- public ResourceModificationController(IResourceManagement resourceManagement, IResourceTypeTree resourceTypeTree, IModuleManager moduleManager)
+ public ResourceModificationController(IResourceManagement resourceManagement,
+ IResourceTypeTree resourceTypeTree,
+ IModuleManager moduleManager,
+ IServiceProvider serviceProvider)
{
_resourceManagement = resourceManagement ?? throw new ArgumentNullException(nameof(resourceManagement));
_resourceTypeTree = resourceTypeTree ?? throw new ArgumentNullException(nameof(resourceTypeTree));
var module = moduleManager.AllModules.FirstOrDefault(module => module is IFacadeContainer);
- _serialization = new ResourceSerialization(module.Container);
+ _serialization = new ResourceSerialization(module.Container, serviceProvider);
}
[HttpGet]
diff --git a/src/Moryx.AbstractionLayer.Resources.Endpoints/ResourceSerialization.cs b/src/Moryx.AbstractionLayer.Resources.Endpoints/ResourceSerialization.cs
index 2af89c41a..cf4eb70d5 100644
--- a/src/Moryx.AbstractionLayer.Resources.Endpoints/ResourceSerialization.cs
+++ b/src/Moryx.AbstractionLayer.Resources.Endpoints/ResourceSerialization.cs
@@ -18,7 +18,7 @@ internal class ResourceSerialization : PossibleValuesSerialization
///
private EntrySerializeSerialization _memberFilter = new();
- public ResourceSerialization(IContainer container) : base(container, new ValueProviderExecutor(new ValueProviderExecutorSettings()))
+ public ResourceSerialization(IContainer container, IServiceProvider serviceProvider) : base(container, serviceProvider, new ValueProviderExecutor(new ValueProviderExecutorSettings()))
{
}
diff --git a/src/Moryx.CommandCenter.Web/package.json b/src/Moryx.CommandCenter.Web/package.json
index e5441ec98..19dba0c84 100644
--- a/src/Moryx.CommandCenter.Web/package.json
+++ b/src/Moryx.CommandCenter.Web/package.json
@@ -16,7 +16,7 @@
"@emotion/styled": "^11.11.5",
"@mdi/js": "^7.4.47",
"@mdi/react": "^1.6.1",
- "@mui/material": "^5.15.15",
+ "@mui/material": "^6.1.1",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@types/react-redux": "^7.1.33",
@@ -24,8 +24,8 @@
"moment": "^2.30.1",
"path-scurry": "^1.10.2",
"query-string": "^9.0.0",
- "react": "18.2.0",
- "react-dom": "18.2.0",
+ "react": "18.3.1",
+ "react-dom": "18.3.1",
"react-redux": "^9.1.0",
"react-router": "^6.22.0",
"react-router-dom": "^6.22.0",
@@ -41,11 +41,11 @@
"@types/react-router-redux": "^5.0.27",
"css-loader": "^6.10.0",
"html-webpack-plugin": "^5.6.0",
- "rimraf": "5.0.5",
+ "rimraf": "5.0.7",
"sass": "^1.72.0",
"sass-loader": "^14.1.0",
"source-map-loader": "^5.0.0",
- "style-loader": "^3.3.4",
+ "style-loader": "^4.0.0",
"tslint": "^6.1.3",
"tslint-loader": "^3.5.4",
"tslint-react": "^5.0.0",
diff --git a/src/Moryx.Products.Samples/Moryx.Products.Samples.csproj b/src/Moryx.Products.Samples/Moryx.Products.Samples.csproj
index ccd43227d..2a9435d73 100644
--- a/src/Moryx.Products.Samples/Moryx.Products.Samples.csproj
+++ b/src/Moryx.Products.Samples/Moryx.Products.Samples.csproj
@@ -9,6 +9,7 @@
+
\ No newline at end of file
diff --git a/src/Moryx.Products.Samples/Recipe/FacacadeRecipeValueAttribute.cs b/src/Moryx.Products.Samples/Recipe/FacacadeRecipeValueAttribute.cs
new file mode 100644
index 000000000..6e3412027
--- /dev/null
+++ b/src/Moryx.Products.Samples/Recipe/FacacadeRecipeValueAttribute.cs
@@ -0,0 +1,25 @@
+using Microsoft.Extensions.DependencyInjection;
+using Moryx.Container;
+using Moryx.Serialization;
+using Moryx.TestModule;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Moryx.Products.Samples.Recipe
+{
+ public class FacacadeRecipeValueAttribute : PossibleValuesAttribute
+ {
+ public override bool OverridesConversion => false;
+
+ public override bool UpdateFromPredecessor => false;
+
+ public override IEnumerable GetValues(IContainer localContainer, IServiceProvider serviceProvider)
+ {
+ var module = serviceProvider.GetRequiredService();
+ return new[] { module.Bla.ToString("D") };
+ }
+ }
+}
diff --git a/src/Moryx.Products.Samples/Recipe/WatchProductRecipe.cs b/src/Moryx.Products.Samples/Recipe/WatchProductRecipe.cs
index d1f33565c..4fbcd4e52 100644
--- a/src/Moryx.Products.Samples/Recipe/WatchProductRecipe.cs
+++ b/src/Moryx.Products.Samples/Recipe/WatchProductRecipe.cs
@@ -21,7 +21,7 @@ public WatchProductRecipe(WatchProductRecipe source) : base(source)
Case = source.Case;
}
- [EntrySerialize]
+ [EntrySerialize, FacacadeRecipeValue]
[DisplayName("Cores Installed")]
public int CoresInstalled { get; set; }
diff --git a/src/Moryx.Resources.Management/Resources/DataMemberAttributeValueProviderFilter.cs b/src/Moryx.Resources.Management/Resources/DataMemberAttributeValueProviderFilter.cs
index 20278dccd..777c35110 100644
--- a/src/Moryx.Resources.Management/Resources/DataMemberAttributeValueProviderFilter.cs
+++ b/src/Moryx.Resources.Management/Resources/DataMemberAttributeValueProviderFilter.cs
@@ -1,12 +1,14 @@
// Copyright (c) 2023, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
+using System;
using System.Reflection;
using System.Runtime.Serialization;
using Moryx.Configuration;
namespace Moryx.Resources.Management
{
+ [Obsolete("No longer needed within the ResourceManagement")]
public class DataMemberAttributeValueProviderFilter : IValueProviderFilter
{
private readonly bool _filterDataMembers;
diff --git a/src/Moryx.Resources.Management/Resources/ResourceEntityAccessor.cs b/src/Moryx.Resources.Management/Resources/ResourceEntityAccessor.cs
index 7a3637f7f..95071c66d 100644
--- a/src/Moryx.Resources.Management/Resources/ResourceEntityAccessor.cs
+++ b/src/Moryx.Resources.Management/Resources/ResourceEntityAccessor.cs
@@ -71,9 +71,8 @@ public Resource Instantiate(IResourceTypeController typeController, IResourceGra
Instance.Name = Name;
Instance.Description = Description;
- // Copy extended data from json
- if (ExtensionData != null)
- JsonConvert.PopulateObject(ExtensionData, Instance, JsonSettings.Minimal);
+ // Copy extended data from json, or simply use JSON to provide defaults
+ JsonConvert.PopulateObject(ExtensionData ?? "{}", Instance, JsonSettings.Minimal);
return Instance;
}
@@ -110,14 +109,14 @@ public static ICollection FetchResourceTemplates(IUnitOf
{
var resourceRepo = uow.GetRepository();
- var resources =
+ var resources =
(from res in resourceRepo.Linq
where res.Deleted == null
select res)
.ToList();
var resourcEntityAccessors = resources
- .Select(res =>
+ .Select(res =>
new ResourceEntityAccessor
{
Id = res.Id,
@@ -126,13 +125,13 @@ public static ICollection FetchResourceTemplates(IUnitOf
Description = res.Description,
ExtensionData = res.ExtensionData,
Relations = (from target in res.Targets
- where target.Target.Deleted == null
- // Attention: This is Copy&Paste because of LinQ limitations
- select new ResourceRelationAccessor
- {
- Entity = target,
- Role = ResourceReferenceRole.Target,
- }).Concat(
+ where target.Target.Deleted == null
+ // Attention: This is Copy&Paste because of LinQ limitations
+ select new ResourceRelationAccessor
+ {
+ Entity = target,
+ Role = ResourceReferenceRole.Target,
+ }).Concat(
from source in res.Sources
where source.Source.Deleted == null
// Attention: This is Copy&Paste because of LinQ limitations
@@ -180,7 +179,7 @@ internal class ResourceRelationAccessor
/// Type of the reference relation
///
public ResourceRelationType RelationType => (ResourceRelationType)Entity.RelationType;
-
+
///
/// Id of the referenced resource
///
diff --git a/src/Moryx.Runtime.Endpoints/Modules/Endpoint/ModulesController.cs b/src/Moryx.Runtime.Endpoints/Modules/Endpoint/ModulesController.cs
index 9d8b03114..73d68e1c3 100644
--- a/src/Moryx.Runtime.Endpoints/Modules/Endpoint/ModulesController.cs
+++ b/src/Moryx.Runtime.Endpoints/Modules/Endpoint/ModulesController.cs
@@ -26,12 +26,14 @@ public class ModulesController : ControllerBase
private readonly IModuleManager _moduleManager;
private readonly IConfigManager _configManager;
private readonly IParallelOperations _parallelOperations;
+ private readonly IServiceProvider _serviceProvider;
- public ModulesController(IModuleManager moduleManager, IConfigManager configManager, IParallelOperations parallelOperations)
+ public ModulesController(IModuleManager moduleManager, IConfigManager configManager, IParallelOperations parallelOperations, IServiceProvider serviceProvider)
{
_moduleManager = moduleManager;
_configManager = configManager;
_parallelOperations = parallelOperations;
+ _serviceProvider = serviceProvider;
}
[HttpGet("dependencies")]
@@ -264,7 +266,7 @@ public ActionResult InvokeMethod([FromRoute] string moduleName, [FromBody
///
private ICustomSerialization CreateSerialization(IServerModule module)
{
- return new PossibleValuesSerialization(module.Container, (IEmptyPropertyProvider)_configManager)
+ return new PossibleValuesSerialization(module.Container, _serviceProvider, (IEmptyPropertyProvider)_configManager)
{
FormatProvider = Thread.CurrentThread.CurrentUICulture
};
diff --git a/src/Moryx.TestTools.IntegrationTest/Moryx.TestTools.IntegrationTest.csproj b/src/Moryx.TestTools.IntegrationTest/Moryx.TestTools.IntegrationTest.csproj
new file mode 100644
index 000000000..bf257e324
--- /dev/null
+++ b/src/Moryx.TestTools.IntegrationTest/Moryx.TestTools.IntegrationTest.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net8.0
+ Library with helper classes for integration tests.
+ true
+ MORYX;Tests;IntegrationTest
+ true
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Moryx.TestTools.IntegrationTest/MoryxTestEnvironment.cs b/src/Moryx.TestTools.IntegrationTest/MoryxTestEnvironment.cs
new file mode 100644
index 000000000..3b83c7c08
--- /dev/null
+++ b/src/Moryx.TestTools.IntegrationTest/MoryxTestEnvironment.cs
@@ -0,0 +1,132 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Moryx.Configuration;
+using Moryx.Model.InMemory;
+using Moryx.Model;
+using Moryx.Runtime.Kernel;
+using Moryx.Runtime.Modules;
+using Moryx.TestTools.UnitTest;
+using Moryx.Threading;
+using System;
+using System.Linq;
+using Moryx.Tools;
+using System.Collections.Generic;
+
+namespace Moryx.TestTools.IntegrationTest
+{
+ ///
+ /// A test environment for MORYX modules to test the module lifecycle as well as its
+ /// facade and component orchestration. The environment must be filled with mocked
+ /// dependencies.
+ ///
+ /// Type of the facade to be tested.
+ public class MoryxTestEnvironment
+ {
+ private readonly Type _moduleType;
+
+ public IServiceProvider Services { get; private set; }
+
+ ///
+ /// Creates an for integration tests of moryx. We prepare the
+ /// service collection to hold all kernel components (a mocked IConfigManager providing only the ,
+ /// , an , a and the
+ /// ). Additionally all provided mocks are registered as moryx modules.
+ ///
+ /// Type of the ModuleController of the module to be tested
+ /// An enumeration of mocks for all dependencies of the module to be tested.
+ /// We recommend using the method to properly create the mocks.
+ /// The config for the module to be tested.
+ /// Throw if is not a server module
+ public MoryxTestEnvironment(Type serverModuleType, IEnumerable dependencyMocks, ConfigBase config)
+ {
+ _moduleType = serverModuleType;
+
+ if (!serverModuleType.IsAssignableTo(typeof(IServerModule)))
+ throw new ArgumentException("Provided parameter is no server module", nameof(serverModuleType));
+
+ var dependencyTypes = serverModuleType.GetProperties()
+ .Where(p => p.GetCustomAttribute() is not null)
+ .Select(p => p.PropertyType);
+
+ var services = new ServiceCollection();
+ foreach (var type in dependencyTypes)
+ {
+ var mock = dependencyMocks.SingleOrDefault(m => type.IsAssignableFrom(m.Object.GetType())) ??
+ throw new ArgumentException($"Missing {nameof(Mock)} for dependency of type {type} of facade type {serverModuleType}", nameof(dependencyMocks));
+ services.AddSingleton(type, mock.Object);
+ services.AddSingleton(typeof(IServerModule), mock.Object);
+ }
+
+ services.AddMoryxKernel();
+ var configManagerMock = new Mock();
+ configManagerMock.Setup(c => c.GetConfiguration(config.GetType(), It.IsAny(), false)).Returns(config);
+ services.AddSingleton(configManagerMock.Object);
+
+ var parallelOpsDescriptor = services.Single(d => d.ServiceType == typeof(IParallelOperations));
+ services.Remove(parallelOpsDescriptor);
+ services.AddTransient();
+ services.AddSingleton(new InMemoryDbContextManager(Guid.NewGuid().ToString()));
+ services.AddSingleton(new NullLoggerFactory());
+ services.AddSingleton(new Mock>().Object);
+ services.AddMoryxModules();
+
+ Services = services.BuildServiceProvider();
+ _ = Services.GetRequiredService();
+ }
+
+ ///
+ /// Creates a mock of a server module with a facade interface of type .
+ /// The mock can be used in setting up a service collection for test purposes.
+ ///
+ /// Type of the facade interface
+ /// The mock of the
+ public static Mock CreateModuleMock() where FacadeType : class
+ {
+ var mock = new Mock();
+ var moduleMock = mock.As();
+ moduleMock.SetupGet(m => m.State).Returns(ServerModuleState.Running);
+ var containerMock = moduleMock.As>();
+ containerMock.SetupGet(x => x.Facade).Returns(mock.Object);
+ return mock;
+ }
+
+ ///
+ /// Initializes and starts the module with the facade interface of type
+ /// .
+ ///
+ /// The started module.
+ public IServerModule StartTestModule()
+ {
+ var module = (IServerModule)Services.GetService(_moduleType);
+
+ module.Initialize();
+ module.Container.Register(typeof(NotSoParallelOps), [typeof(IParallelOperations)], nameof(NotSoParallelOps), Container.LifeCycle.Singleton);
+
+ var strategies = module.GetType().GetProperty(nameof(ServerModuleBase.Strategies)).GetValue(module) as Dictionary;
+ if (strategies is not null && !strategies.Any(s => s.Value == nameof(NotSoParallelOps)))
+ strategies.Add(typeof(IParallelOperations), nameof(NotSoParallelOps));
+
+ module.Start();
+ return module;
+ }
+
+ ///
+ /// Stops the module with the facade interface of type .
+ ///
+ /// The stopped module.
+ public IServerModule StopTestModule()
+ {
+ var module = (IServerModule)Services.GetService(_moduleType);
+ module.Stop();
+
+ return module;
+ }
+
+ ///
+ /// Returns the service for the facade of type to be tested.
+ ///
+ public TModule GetTestModule() => Services.GetRequiredService();
+ }
+}
\ No newline at end of file
diff --git a/src/Moryx.TestTools.NUnit/MAssert.cs b/src/Moryx.TestTools.NUnit/MAssert.cs
new file mode 100644
index 000000000..8c75378d0
--- /dev/null
+++ b/src/Moryx.TestTools.NUnit/MAssert.cs
@@ -0,0 +1,54 @@
+using NUnit.Framework;
+using NUnit.Framework.Constraints;
+using NUnit.Framework.Internal;
+using System;
+using System.Linq.Expressions;
+using System.Runtime.CompilerServices;
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
+
+namespace Moryx.TestTools.NUnit
+{
+ public abstract class MAssert
+ {
+ public static void That(bool condition, string? message = null, [CallerArgumentExpression(nameof(condition))] string? predicateExpression = null)
+ {
+ That(condition, Is.True, message, predicateExpression);
+ }
+ public static void That(Func predicate, string? message = null, [CallerArgumentExpression(nameof(predicate))]string? predicateExpression = null)
+ {
+ That(predicate, Is.True, message, predicateExpression);
+ }
+
+ public static void That(T actual, IResolveConstraint constraint, string? message = null, [CallerArgumentExpression(nameof(actual))] string? predicateExpression = null)
+ {
+ if (message != null)
+ {
+ message = $"{message}\nExpression: {predicateExpression}";
+ }
+ else
+ {
+ message = predicateExpression;
+ }
+ Assert.That(actual, constraint, message);
+ }
+
+ public static void That(Func actualExpression, Constraint constraint, string? message = null, [CallerArgumentExpression(nameof(actualExpression))] string? predicateExpression = null)
+ {
+ if (message != null)
+ {
+ message = $"{message}\nExpression: {predicateExpression}";
+ }
+ else
+ {
+ message = predicateExpression;
+ }
+ int fails = TestExecutionContext.CurrentContext.CurrentResult.PendingFailures;
+ T value = default(T)!;
+ Assert.That(() => value = actualExpression(), new ThrowsNothingConstraint(), $"{message}\nExpected {constraint.Description} and");
+ if (TestExecutionContext.CurrentContext.CurrentResult.PendingFailures > fails) return; // TODO: Check if we there could be multithreading issues and whether or not we care
+ Assert.That(value, constraint, message);
+ }
+ }
+
+}
diff --git a/src/Moryx.TestTools.NUnit/Moryx.TestTools.NUnit.csproj b/src/Moryx.TestTools.NUnit/Moryx.TestTools.NUnit.csproj
new file mode 100644
index 000000000..7fea020cc
--- /dev/null
+++ b/src/Moryx.TestTools.NUnit/Moryx.TestTools.NUnit.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/src/Moryx/Configuration/PossibleValuesSerialization.cs b/src/Moryx/Configuration/PossibleValuesSerialization.cs
index 0c1b07112..fc9987f3a 100644
--- a/src/Moryx/Configuration/PossibleValuesSerialization.cs
+++ b/src/Moryx/Configuration/PossibleValuesSerialization.cs
@@ -21,6 +21,11 @@ public class PossibleValuesSerialization : DefaultSerialization
///
protected IContainer Container { get; }
+ ///
+ /// Access to level 1 service registration
+ ///
+ public IServiceProvider ServiceProvider { get; }
+
///
/// Empty property provider to pre-fill newley created objects
///
@@ -29,9 +34,17 @@ public class PossibleValuesSerialization : DefaultSerialization
///
/// Initialize base class
///
+ [Obsolete("Construct possible values with ServiceProvider for attributes that rely on it")]
public PossibleValuesSerialization(IContainer container, IEmptyPropertyProvider emptyPropertyProvider)
+ :this(container, null, emptyPropertyProvider) { }
+
+ ///
+ /// Initialize base class
+ ///
+ public PossibleValuesSerialization(IContainer container, IServiceProvider serviceProvider, IEmptyPropertyProvider emptyPropertyProvider)
{
Container = container;
+ ServiceProvider = serviceProvider;
EmptyPropertyProvider = emptyPropertyProvider;
}
@@ -45,9 +58,9 @@ public override EntryPrototype[] Prototypes(Type memberType, ICustomAttributePro
// Create prototypes from possible values
var list = new List();
- foreach (var value in possibleValuesAtt.GetValues(Container))
+ foreach (var value in possibleValuesAtt.GetValues(Container, ServiceProvider))
{
- var prototype = possibleValuesAtt.Parse(Container, value);
+ var prototype = possibleValuesAtt.Parse(Container, ServiceProvider, value);
EmptyPropertyProvider.FillEmpty(prototype);
list.Add(new EntryPrototype(value, prototype));
}
@@ -63,7 +76,7 @@ public override string[] PossibleValues(Type memberType, ICustomAttributeProvide
return base.PossibleValues(memberType, attributeProvider);
// Use attribute
- var values = valuesAttribute.GetValues(Container);
+ var values = valuesAttribute.GetValues(Container, ServiceProvider);
return values?.Distinct().ToArray();
}
@@ -90,7 +103,7 @@ public override string[] PossibleElementValues(Type memberType, ICustomAttribute
}
// Use attribute
- var values = valuesAttribute.GetValues(Container);
+ var values = valuesAttribute.GetValues(Container, ServiceProvider);
return values?.Distinct().ToArray();
}
@@ -99,7 +112,7 @@ public override object CreateInstance(Type memberType, ICustomAttributeProvider
{
var possibleValuesAtt = attributeProvider.GetCustomAttribute();
var instance = possibleValuesAtt != null
- ? possibleValuesAtt.Parse(Container, encoded.Value.Current)
+ ? possibleValuesAtt.Parse(Container, ServiceProvider, encoded.Value.Current)
: base.CreateInstance(memberType, attributeProvider, encoded);
EmptyPropertyProvider.FillEmpty(instance);
@@ -120,7 +133,7 @@ public override object ConvertValue(Type memberType, ICustomAttributeProvider at
if (value.Type == EntryValueType.Class && currentValue != null && currentValue.GetType().Name == value.Current)
return currentValue;
- var instance = att.Parse(Container, mappedEntry.Value.Current);
+ var instance = att.Parse(Container, ServiceProvider, mappedEntry.Value.Current);
if (mappedEntry.Value.Type == EntryValueType.Class)
{
EmptyPropertyProvider.FillEmpty(instance);
diff --git a/src/Moryx/Serialization/PossibleValues/PossibleValuesAttribute.cs b/src/Moryx/Serialization/PossibleValues/PossibleValuesAttribute.cs
index 328682bbe..144c28c8a 100644
--- a/src/Moryx/Serialization/PossibleValues/PossibleValuesAttribute.cs
+++ b/src/Moryx/Serialization/PossibleValues/PossibleValuesAttribute.cs
@@ -27,14 +27,39 @@ public abstract class PossibleValuesAttribute : Attribute
/// All possible values for this member represented as strings. The given container might be null
/// and can be used to resolve possible values
///
- public abstract IEnumerable GetValues(IContainer container);
+ [Obsolete("Replaced by PossibleValues with access to global service registration")]
+ public virtual IEnumerable GetValues(IContainer container)
+ {
+ return Array.Empty();
+ }
+
+ ///
+ /// Extract possible values from local or global DI registration
+ ///
+ // TODO: Make abstract in MORYX 10
+ public virtual IEnumerable GetValues(IContainer localContainer, IServiceProvider serviceProvider)
+ {
+ return GetValues(localContainer);
+ }
///
/// String to value conversion
///
+ [Obsolete("Replaced by Parse with ServiceProvider reference")]
public virtual object Parse(IContainer container, string value)
{
return value;
}
+
+ ///
+ /// Parse value from string using local or global DI container
+ ///
+ /// Module local DI container
+ /// Global service registration
+ /// Value to parse
+ public virtual object Parse(IContainer container, IServiceProvider serviceProvider, string value)
+ {
+ return Parse(container, value);
+ }
}
}
diff --git a/src/Tests/Moryx.Products.Management.Tests/ProductConverterTests.cs b/src/Tests/Moryx.Products.Management.Tests/ProductConverterTests.cs
index 403e99218..2d6338722 100644
--- a/src/Tests/Moryx.Products.Management.Tests/ProductConverterTests.cs
+++ b/src/Tests/Moryx.Products.Management.Tests/ProductConverterTests.cs
@@ -27,7 +27,7 @@ public void Setup()
{
_productManagerMock = new Mock();
- _productConverter = new ProductConverter(_productManagerMock.Object);
+ _productConverter = new ProductConverter(_productManagerMock.Object, null, null);
}
#region Products
@@ -227,7 +227,7 @@ public void ForwardBackwardRecipeConversionWithoutInformationLoss(DummyProductRe
// Act
- var convertedModel = ProductConverter.ConvertRecipe(originalRecipe);
+ var convertedModel = _productConverter.ConvertRecipeV2(originalRecipe);
var recoveredOriginal = _productConverter.ConvertRecipeBack(convertedModel, targetDummyRecipe, backupProductType);
diff --git a/src/Tests/Moryx.Resources.Management.Tests/ResourceEntityAccessorTests.cs b/src/Tests/Moryx.Resources.Management.Tests/ResourceEntityAccessorTests.cs
index 32b9dc8b1..f8701a9d0 100644
--- a/src/Tests/Moryx.Resources.Management.Tests/ResourceEntityAccessorTests.cs
+++ b/src/Tests/Moryx.Resources.Management.Tests/ResourceEntityAccessorTests.cs
@@ -9,6 +9,7 @@
using Moryx.Model.Repositories;
using Newtonsoft.Json;
using NUnit.Framework;
+using System.ComponentModel;
namespace Moryx.Resources.Management.Tests
{
@@ -22,7 +23,8 @@ public class ResourceEntityAccessorTests
public void Setup()
{
var typeControllerMock = new Mock();
- typeControllerMock.Setup(tc => tc.Create(It.IsAny())).Returns(new TestResource());
+ typeControllerMock.Setup(tc => tc.Create(It.Is(type => type == typeof(TestResource).ResourceType()))).Returns(new TestResource());
+ typeControllerMock.Setup(tc => tc.Create(It.Is(type => type == typeof(DefaultTestResource).ResourceType()))).Returns(new DefaultTestResource());
_typeControllerMock = typeControllerMock.Object;
@@ -30,6 +32,26 @@ public void Setup()
_resourceGraph = resourceCreator.Object;
}
+ [Test(Description = "Calling Instantiate without an entity sets default value")]
+ public void InstantiateWithoutEntitySetsDefaults()
+ {
+ // Arrange
+ var accessor = new ResourceEntityAccessor
+ {
+ Type = typeof(DefaultTestResource).ResourceType()
+ };
+
+ // Act
+ var resource = accessor.Instantiate(_typeControllerMock, _resourceGraph) as DefaultTestResource;
+
+ // Assert
+ Assert.NotNull(resource);
+ Assert.AreEqual(accessor.Type, resource.GetType().ResourceType());
+
+ Assert.IsTrue(resource.Enabled);
+ Assert.AreEqual(42, resource.Number);
+ }
+
[Test(Description = "Instantiates a resource")]
public void InstantiateCreatesAValidResourceObject()
{
@@ -118,6 +140,15 @@ private class ExtensionDataInherited : ExtensionDataTestBase
public long Value3 => 42;
}
+ private class DefaultTestResource : Resource
+ {
+ [DataMember, DefaultValue(42)]
+ public int Number { get; set; }
+
+ [DataMember, DefaultValue(true)]
+ public bool Enabled { get; set; }
+ }
+
private class TestResource : Resource
{
[DataMember]
diff --git a/src/Tests/Moryx.Tests/Moryx.Tests.csproj b/src/Tests/Moryx.Tests/Moryx.Tests.csproj
index 5587d8866..39faf789d 100644
--- a/src/Tests/Moryx.Tests/Moryx.Tests.csproj
+++ b/src/Tests/Moryx.Tests/Moryx.Tests.csproj
@@ -14,6 +14,8 @@
+
+
diff --git a/src/Tests/Moryx.Tests/Workplans/TransitionTests.cs b/src/Tests/Moryx.Tests/Workplans/TransitionTests.cs
index a001cf612..c5852a3ac 100644
--- a/src/Tests/Moryx.Tests/Workplans/TransitionTests.cs
+++ b/src/Tests/Moryx.Tests/Workplans/TransitionTests.cs
@@ -1,8 +1,10 @@
// Copyright (c) 2023, Phoenix Contact GmbH & Co. KG
// Licensed under the Apache License, Version 2.0
+using System;
using System.Collections.Generic;
using System.Linq;
+using Moryx.TestTools.NUnit;
using Moryx.Workplans;
using Moryx.Workplans.Transitions;
using NUnit.Framework;
@@ -56,14 +58,15 @@ public void SplitTransition()
// Act
trans.Initialize();
_inputs[0].Add(_token);
-
+
// Assert
- Assert.AreEqual(0, _inputs[0].Tokens.Count());
- Assert.IsTrue(_outputs.All(o => o.Tokens.Count() == 1));
- Assert.IsInstanceOf(_outputs[0].Tokens.First());
- Assert.IsInstanceOf(_outputs[1].Tokens.First());
- Assert.AreEqual(_token, ((SplitToken)_outputs[0].Tokens.First()).Original);
- Assert.AreEqual(_token, ((SplitToken)_outputs[1].Tokens.First()).Original);
+ Assert.Multiple(() =>
+ {
+ MAssert.That(_inputs[0].Tokens, Is.Empty);
+ MAssert.That(_outputs.Select(o => o.Tokens), Has.All.Count.EqualTo(1));
+ MAssert.That(() => ((SplitToken)_outputs[0].Tokens.First()).Original, Is.EqualTo(_token));
+ MAssert.That(() => ((SplitToken)_outputs[1].Tokens.First()).Original, Is.EqualTo(_token));
+ });
}
[Test]
@@ -83,11 +86,13 @@ public void JoinTransition()
trans.Initialize();
_inputs[0].Add(split1);
_inputs[1].Add(split2);
-
// Assert
- Assert.IsTrue(_inputs.All(i => !i.Tokens.Any()));
- Assert.AreEqual(1, _outputs[0].Tokens.Count());
- Assert.AreEqual(_token, _outputs[0].Tokens.First());
+ Assert.Multiple(() =>
+ {
+ MAssert.That(_inputs.All(i => !i.Tokens.Any()));
+ MAssert.That(_outputs[0].Tokens.Count(), Is.EqualTo(1), "The split token should be joined into one");
+ MAssert.That(_outputs[0].Tokens.First(), Is.EqualTo(_token));
+ });
}
[TestCase(0, Description = "Place only one split token on the first input")]
@@ -108,9 +113,12 @@ public void IncompleteJoinTransition(int index)
_inputs[index].Add(split);
// Assert
- Assert.AreEqual(1, _inputs[index].Tokens.Count());
- Assert.AreEqual(0, _inputs[(index + 1) % 2].Tokens.Count());
- Assert.AreEqual(0, _outputs[0].Tokens.Count());
+ Assert.Multiple(() =>
+ {
+ MAssert.That(_inputs[index].Tokens, Has.Count.EqualTo(1));
+ MAssert.That(_inputs[(index + 1) % 2].Tokens, Is.Empty);
+ MAssert.That(_outputs[0].Tokens, Is.Empty);
+ });
}
[Test]
@@ -138,10 +146,12 @@ public void SubWorkplanTransition()
_inputs[0].Add(_token);
// Assert
- Assert.AreEqual(0, _inputs[0].Tokens.Count());
- Assert.AreEqual(_token, _outputs[0].Tokens.First());
- Assert.AreEqual(2, triggered.Count);
- Assert.IsTrue(triggered.All(t => t is DummyTransition));
+ Assert.Multiple(() => {
+ MAssert.That(_inputs[0].Tokens, Is.Empty);
+ MAssert.That(() => _outputs[0].Tokens.First(), Is.EqualTo(_token));
+ MAssert.That(triggered.Count, Is.EqualTo(2));
+ MAssert.That(triggered, Has.All.InstanceOf());
+ });
}
[Test]
@@ -171,14 +181,17 @@ public void SubworkplanPause()
trans.Resume();
// Assert
- Assert.AreEqual(0, _inputs[0].Tokens.Count());
- Assert.AreEqual(_token, _outputs[0].Tokens.First());
- Assert.AreEqual(1, triggered.Count);
- Assert.IsInstanceOf(state);
- var snapshot = (WorkplanSnapshot)state;
- Assert.AreEqual(1, snapshot.Holders.Length);
- var stepId = workplan.Steps.First(s => s is PausableStep).Id;
- Assert.AreEqual(stepId, snapshot.Holders[0].HolderId);
+ Assert.Multiple(() =>
+ {
+ MAssert.That(_inputs[0].Tokens, Is.Empty);
+ MAssert.That(_outputs[0].Tokens.First(), Is.EqualTo(_token));
+ MAssert.That(triggered, Has.Count.EqualTo(1));
+ MAssert.That(state, Is.InstanceOf());
+ var snapshot = (WorkplanSnapshot)state;
+ MAssert.That(snapshot.Holders, Has.Length.EqualTo(1));
+ var stepId = workplan.Steps.First(s => s is PausableStep).Id;
+ MAssert.That(snapshot.Holders[0].HolderId, Is.EqualTo(stepId));
+ });
}
}
}