diff --git a/src/Microsoft.PowerApps.TestEngine.Tests/Modules/TestEngineExtensionCheckerTests.cs b/src/Microsoft.PowerApps.TestEngine.Tests/Modules/TestEngineExtensionCheckerTests.cs index ce3ad50f..310b108b 100644 --- a/src/Microsoft.PowerApps.TestEngine.Tests/Modules/TestEngineExtensionCheckerTests.cs +++ b/src/Microsoft.PowerApps.TestEngine.Tests/Modules/TestEngineExtensionCheckerTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -23,6 +26,113 @@ public class TestEngineExtensionCheckerTests Mock MockLogger; string _template; + const string TEST_WEB_PROVIDER = @" +#r ""Microsoft.PowerApps.TestEngine.dll"" + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.PowerApps.TestEngine.Config; +using Microsoft.PowerApps.TestEngine.Providers; +using Microsoft.PowerApps.TestEngine.Providers.PowerFxModel; +using Microsoft.PowerApps.TestEngine.TestInfra; +using Microsoft.PowerFx.Types; + +public class Test : ITestWebProvider +{{ + public ITestInfraFunctions? TestInfraFunctions {{ get => throw new NotImplementedException(); set => throw new NotImplementedException(); }} + public ISingleTestInstanceState? SingleTestInstanceState {{ get => throw new NotImplementedException(); set => throw new NotImplementedException(); }} + public ITestState? TestState {{ get => throw new NotImplementedException(); set => throw new NotImplementedException(); }} + public ITestProviderState? ProviderState {{ get => throw new NotImplementedException(); set => throw new NotImplementedException(); }} + + public string Name => throw new NotImplementedException(); + + public string CheckTestEngineObject => throw new NotImplementedException(); + + public string[] Namespaces => new string[] {{ ""{0}"" }}; + + public Task CheckIsIdleAsync() + {{ + throw new NotImplementedException(); + }} + + public Task CheckProviderAsync() + {{ + throw new NotImplementedException(); + }} + + public string GenerateTestUrl(string domain, string queryParams) + {{ + throw new NotImplementedException(); + }} + + public Task GetDebugInfo() + {{ + throw new NotImplementedException(); + }} + + public int GetItemCount(ItemPath itemPath) + {{ + throw new NotImplementedException(); + }} + + public T GetPropertyValueFromControl(ItemPath itemPath) + {{ + throw new NotImplementedException(); + }} + + public Task> LoadObjectModelAsync() + {{ + throw new NotImplementedException(); + }} + + public Task SelectControlAsync(ItemPath itemPath) + {{ + throw new NotImplementedException(); + }} + + public Task SetPropertyAsync(ItemPath itemPath, FormulaValue value) + {{ + throw new NotImplementedException(); + }} + + public Task TestEngineReady() + {{ + throw new NotImplementedException(); + }} +}} +"; + + const string TEST_USER_MODULE = @" +#r ""Microsoft.PowerApps.TestEngine.dll"" +#r ""Microsoft.Playwright.dll"" + +using System; +using System.Threading.Tasks; +using Microsoft.Playwright; +using Microsoft.PowerApps.TestEngine.Config; +using Microsoft.PowerApps.TestEngine.System; +using Microsoft.PowerApps.TestEngine.Users; + +public class Test : IUserManager +{{ + public string[] Namespaces => new string[] {{ ""{0}"" }}; + + public string Name => throw new NotImplementedException(); + + public int Priority => throw new NotImplementedException(); + + public bool UseStaticContext => throw new NotImplementedException(); + + public string Location {{ get => throw new NotImplementedException(); set => throw new NotImplementedException(); }} + + public Task LoginAsUserAsync(string desiredUrl, IBrowserContext context, ITestState testState, ISingleTestInstanceState singleTestInstanceState, IEnvironmentVariable environmentVariable, IUserManagerLogin userManagerLogin) + {{ + throw new NotImplementedException(); + }} +}} +"; + public TestEngineExtensionCheckerTests() { MockLogger = new Mock(); @@ -106,6 +216,75 @@ public void IsValid(string usingStatements, string script, bool useTemplate, str Assert.Equal(expected, result); } + [Theory] + [InlineData("", "", "TestEngine", true)] // Empty Allow and Deny list + [InlineData("TestEngine", "", "TestEngine", true)] // No deny list + [InlineData("", "TestEngine", "TestEngine", false)] // Invalid in deny list + [InlineData("TestEngine", "Other", "TestEngine", true)] // Valid in the allow list + [InlineData("TestEngine", "Other", "Other", false)] // Exact match + [InlineData("", "*", "Other", false)] // Any regex match + [InlineData("", "O*", "Other", false)] // Regex match wildcard + [InlineData("", "Test?", "Test1", false)] // Single character match + [InlineData("", "Test?More", "Test1More", false)] // Single character match in the middle + [InlineData("T*", "", "Test1More", true)] // Allow wildcard + [InlineData("T?2", "", "T12", true)] // Allow wildcard + [InlineData("T?2", "T?3", "T12", true)] // Allow wildcard and not match deny + public void IsValidProvider(string allow, string deny, string providerNamespace, bool expected) + { + // Arrange + var assembly = CompileScript(String.Format(TEST_WEB_PROVIDER, providerNamespace)); + + var checker = new TestEngineExtensionChecker(MockLogger.Object); + checker.GetExtentionContents = (file) => assembly; + + var settings = new TestSettingExtensions() + { + Enable = true, + AllowPowerFxNamespaces = new List() { allow }, + DenyPowerFxNamespaces = new List() { deny } + }; + + // Act + var result = checker.ValidateProvider(settings, "testengine.provider.test.dll"); + + Assert.Equal(expected, result); + } + + + [Theory] + [InlineData("", "", "TestEngine", true)] // Empty Allow and Deny list + [InlineData("TestEngine", "", "TestEngine", true)] // No deny list + [InlineData("", "TestEngine", "TestEngine", false)] // Invalid in deny list + [InlineData("TestEngine", "Other", "TestEngine", true)] // Valid in the allow list + [InlineData("TestEngine", "Other", "Other", false)] // Exact match + [InlineData("", "*", "Other", false)] // Any regex match + [InlineData("", "O*", "Other", false)] // Regex match wildcard + [InlineData("", "Test?", "Test1", false)] // Single character match + [InlineData("", "Test?More", "Test1More", false)] // Single character match in the middle + [InlineData("T*", "", "Test1More", true)] // Allow wildcard + [InlineData("T?2", "", "T12", true)] // Allow wildcard + [InlineData("T?2", "T?3", "T12", true)] // Allow wildcard and not match deny + public void IsValidUserModule(string allow, string deny, string providerNamespace, bool expected) + { + // Arrange + var assembly = CompileScript(String.Format(TEST_USER_MODULE, providerNamespace)); + + var checker = new TestEngineExtensionChecker(MockLogger.Object); + checker.GetExtentionContents = (file) => assembly; + + var settings = new TestSettingExtensions() + { + Enable = true, + AllowPowerFxNamespaces = new List() { allow }, + DenyPowerFxNamespaces = new List() { deny } + }; + + // Act + var result = checker.ValidateProvider(settings, "testengine.user.test.dll"); + + Assert.Equal(expected, result); + } + private string _functionTemplate = @" #r ""Microsoft.PowerFx.Interpreter.dll"" diff --git a/src/Microsoft.PowerApps.TestEngine.Tests/Modules/TestEngineModuleMEFLoaderTests.cs b/src/Microsoft.PowerApps.TestEngine.Tests/Modules/TestEngineModuleMEFLoaderTests.cs index 5d9c8022..2c87876f 100644 --- a/src/Microsoft.PowerApps.TestEngine.Tests/Modules/TestEngineModuleMEFLoaderTests.cs +++ b/src/Microsoft.PowerApps.TestEngine.Tests/Modules/TestEngineModuleMEFLoaderTests.cs @@ -1,5 +1,8 @@ -using System.ComponentModel.Composition.Hosting; -using System.IO; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.ComponentModel.Composition.Hosting; using System.Linq; using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; @@ -78,5 +81,60 @@ public void ModuleMatch(bool checkAssemblies, bool checkResult, string allow, st Assert.NotNull(catalog); Assert.Equal(expected, string.Join(",", catalog.Catalogs.Select(c => c.GetType().Name))); } + + [Theory] + [InlineData("provider", true, true)] + [InlineData("provider", true, false)] + [InlineData("provider", false, false)] + [InlineData("user", true, true)] + [InlineData("user", true, false)] + [InlineData("user", false, false)] + [InlineData("auth", true, true)] + [InlineData("auth", true, false)] + [InlineData("auth", false, false)] + public void ProviderMatch(string providerType, bool verify, bool valid) + { + // Arrange + var assemblyName = $"testengine.{providerType}.test.dll"; + + var setting = new TestSettingExtensions() + { + Enable = true, + CheckAssemblies = true + }; + Mock mockChecker = new Mock(); + + var loader = new TestEngineModuleMEFLoader(MockLogger.Object); + loader.DirectoryGetFiles = (location, pattern) => + { + var searchPattern = Regex.Escape(pattern).Replace(@"\*", ".*?"); + return pattern.Contains(providerType) ? new List() { assemblyName }.ToArray() : new string[] { }; + }; + + mockChecker.Setup(m => m.ValidateProvider(setting, assemblyName)).Returns(verify); + mockChecker.Setup(m => m.Verify(setting, assemblyName)).Returns(valid); + + if (valid) + { + // Use current test assembly as test + loader.LoadAssembly = (file) => new AssemblyCatalog(this.GetType().Assembly); + } + + loader.Checker = mockChecker.Object; + + // Act + var catalog = loader.LoadModules(setting); + + // Assert + if (verify && valid) + { + Assert.NotNull(catalog); + Assert.Equal("AssemblyCatalog,AssemblyCatalog", string.Join(",", catalog.Catalogs.Select(c => c.GetType().Name))); + } + else + { + Assert.Equal("AssemblyCatalog", string.Join(",", catalog.Catalogs.Select(c => c.GetType().Name))); + } + } } } diff --git a/src/Microsoft.PowerApps.TestEngine/Config/DefaultUserCertificateProvider.cs b/src/Microsoft.PowerApps.TestEngine/Config/DefaultUserCertificateProvider.cs index 77d91216..c6c000a8 100644 --- a/src/Microsoft.PowerApps.TestEngine/Config/DefaultUserCertificateProvider.cs +++ b/src/Microsoft.PowerApps.TestEngine/Config/DefaultUserCertificateProvider.cs @@ -7,6 +7,8 @@ namespace Microsoft.PowerApps.TestEngine.Config { public class DefaultUserCertificateProvider : IUserCertificateProvider { + public string[] Namespaces { get; private set; } = new string[] { "TestEngine" }; + public string Name => "default"; public X509Certificate2 RetrieveCertificateForUser(string userIdentifier) diff --git a/src/Microsoft.PowerApps.TestEngine/Config/IUserCertificateProvider.cs b/src/Microsoft.PowerApps.TestEngine/Config/IUserCertificateProvider.cs index cd23b456..b2217a80 100644 --- a/src/Microsoft.PowerApps.TestEngine/Config/IUserCertificateProvider.cs +++ b/src/Microsoft.PowerApps.TestEngine/Config/IUserCertificateProvider.cs @@ -7,6 +7,11 @@ namespace Microsoft.PowerApps.TestEngine.Config { public interface IUserCertificateProvider { + /// + /// The namespace of namespaces that this provider relates to + /// + public string[] Namespaces { get; } + public string Name { get; } public X509Certificate2 RetrieveCertificateForUser(string userIdentifier); } diff --git a/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineExtensionChecker.cs b/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineExtensionChecker.cs index 55836d03..eae69fda 100644 --- a/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineExtensionChecker.cs +++ b/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineExtensionChecker.cs @@ -7,6 +7,7 @@ using System.Reflection.PortableExecutable; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Text.RegularExpressions; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.Metadata; @@ -32,6 +33,10 @@ public class TestEngineExtensionChecker public Func GetExtentionContents = (file) => File.ReadAllBytes(file); + public const string NAMESPACE_EXPERIMENTAL = "Experimental"; + public const string NAMESPACE_TEST_ENGINE = "TestEngine"; + public const string NAMESPACE_DEPRECATED = "Deprecated"; + public TestEngineExtensionChecker() { @@ -216,6 +221,17 @@ private List GetTrustedSources(TestSettingExtensions sett return sources; } + /// + /// Validate that the provided provider file is allowed or should be denied based on the test settings + /// + /// The test settings that should be evaluated + /// The .Net Assembly file to validate + /// True if the assembly meets the test setting requirements, False if not + public virtual bool ValidateProvider(TestSettingExtensions settings, string file) + { + byte[] contents = GetExtentionContents(file); + return VerifyContainsValidNamespacePowerFxFunctions(settings, contents); + } /// /// Validate that the provided file is allowed or should be denied based on the test settings @@ -278,6 +294,23 @@ public virtual bool Validate(TestSettingExtensions settings, string file) public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions settings, byte[] assembly) { var isValid = true; + +#if DEBUG + // Add Experimenal namespaces in Debug compile if it has not been added in allow list + if (!settings.AllowPowerFxNamespaces.Contains(NAMESPACE_EXPERIMENTAL)) + { + settings.AllowPowerFxNamespaces.Add(NAMESPACE_EXPERIMENTAL); + } +#endif + +#if RELEASE + // Add Deprecated namespaces in Release compile if it has not been added in deny list + if (!settings.DenyPowerFxNamespaces.Contains(NAMESPACE_DEPRECATED)) + { + settings.DenyPowerFxNamespaces.Add(NAMESPACE_DEPRECATED); + } +#endif + using (var stream = new MemoryStream(assembly)) { stream.Position = 0; @@ -288,6 +321,47 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s foreach (TypeDefinition type in module.GetAllTypes()) { + // Provider checks are based on Namespaces string[] property + if ( + type.Interfaces.Any(i => i.InterfaceType.FullName == typeof(Providers.ITestWebProvider).FullName) + || + type.Interfaces.Any(i => i.InterfaceType.FullName == typeof(Users.IUserManager).FullName) + || + type.Interfaces.Any(i => i.InterfaceType.FullName == typeof(Config.IUserCertificateProvider).FullName) + ) + { + if (CheckPropertyArrayContainsValue(type, "Namespaces", out var values)) + { + foreach (var name in values) + { + // Check against deny list using regular expressions + if (settings.DenyPowerFxNamespaces.Any(pattern => Regex.IsMatch(name, WildcardToRegex(pattern)))) + { + Logger.LogInformation($"Deny Power FX Namespace {name} for {type.Name}"); + return false; + } + + // Check against deny wildcard and allow list using regular expressions + if (settings.DenyPowerFxNamespaces.Any(pattern => pattern == "*") && + (!settings.AllowPowerFxNamespaces.Any(pattern => Regex.IsMatch(name, WildcardToRegex(pattern))) && + name != NAMESPACE_TEST_ENGINE)) + { + Logger.LogInformation($"Deny Power FX Namespace {name} for {type.Name}"); + return false; + } + + // Check against allow list using regular expressions + if (!settings.AllowPowerFxNamespaces.Any(pattern => Regex.IsMatch(name, WildcardToRegex(pattern))) && + name != NAMESPACE_TEST_ENGINE) + { + Logger.LogInformation($"Not allow Power FX Namespace {name} for {type.Name}"); + return false; + } + } + } + } + + // Extension Module Check are based on constructor if (type.BaseType != null && type.BaseType.Name == "ReflectionFunction") { var constructors = type.GetConstructors(); @@ -312,7 +386,7 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s return false; } - var baseCall = constructor.Body.Instructions.FirstOrDefault(i => i.OpCode == OpCodes.Call && i.Operand is MethodReference && ((MethodReference)i.Operand).Name == ".ctor"); + var baseCall = constructor.Body.Instructions?.FirstOrDefault(i => i.OpCode == OpCodes.Call && i.Operand is MethodReference && ((MethodReference)i.Operand).Name == ".ctor"); if (baseCall == null) { @@ -323,7 +397,7 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s MethodReference baseConstructor = (MethodReference)baseCall.Operand; - if (baseConstructor.Parameters.Count() < 2) + if (baseConstructor.Parameters?.Count() < 2) { // Not enough parameters Logger.LogInformation($"No not enough parameters for {type.Name}"); @@ -354,17 +428,9 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s return false; } -#if DEBUG - // Add Experimenal namespaes in Debug compile it it has not been added in allow list - if (!settings.AllowPowerFxNamespaces.Contains("Experimental")) - { - settings.AllowPowerFxNamespaces.Add("Experimental"); - } -#endif - if ((settings.DenyPowerFxNamespaces.Contains("*") && ( !settings.AllowPowerFxNamespaces.Contains(name) || - (!settings.AllowPowerFxNamespaces.Contains(name) && name != "TestEngine") + (!settings.AllowPowerFxNamespaces.Contains(name) && name != NAMESPACE_TEST_ENGINE) ) )) { @@ -373,7 +439,7 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s return false; } - if (!settings.AllowPowerFxNamespaces.Contains(name) && name != "TestEngine") + if (!settings.AllowPowerFxNamespaces.Contains(name) && name != NAMESPACE_TEST_ENGINE) { Logger.LogInformation($"Not allow Power FX Namespace {name} for {type.Name}"); // Not in allow list or the Reserved TestEngine namespace @@ -385,6 +451,108 @@ public bool VerifyContainsValidNamespacePowerFxFunctions(TestSettingExtensions s return isValid; } + // Helper method to convert wildcard patterns to regular expressions + private string WildcardToRegex(string pattern) + { + return "^" + Regex.Escape(pattern).Replace("\\*", ".*").Replace("\\?", ".") + "$"; + } + + private bool CheckPropertyArrayContainsValue(TypeDefinition typeDefinition, string propertyName, out string[] values) + { + values = null; + + // Find the property by name + var property = typeDefinition.HasProperties ? typeDefinition.Properties.FirstOrDefault(p => p.Name == propertyName) : null; + if (property == null) + { + return false; + } + + // Get the property type and check if it's an array + var propertyType = property.PropertyType as ArrayType; + if (propertyType == null) + { + return false; + } + + // Assuming the property has a getter method + var getMethod = property.GetMethod; + if (getMethod == null) + { + return false; + } + + // Load the assembly and get the method body + var methodBody = getMethod.Body; + if (methodBody == null) + { + return false; + } + + // Iterate through the instructions to find the array initialization + foreach (var instruction in methodBody?.Instructions) + { + if (instruction.OpCode == OpCodes.Newarr) + { + // Call the method to get array values + var arrayValues = GetArrayValuesFromInstruction(methodBody, instruction); + values = arrayValues.OfType().ToArray(); // Ensure values are strings + return values.Length > 0; + } + } + + return false; + } + + private object[] GetArrayValuesFromInstruction(MethodBody methodBody, Instruction newarrInstruction) + { + var values = new List(); + var instructions = methodBody?.Instructions; + int index = instructions?.IndexOf(newarrInstruction) ?? 0; + + // Iterate through the instructions following the 'newarr' instruction + for (int i = index + 1; i < instructions?.Count; i++) + { + var instruction = instructions[i]; + + // Look for instructions that store values in the array + if (instruction.OpCode == OpCodes.Stelem_Ref || + instruction.OpCode == OpCodes.Stelem_I4 || + instruction.OpCode == OpCodes.Stelem_R4 || + instruction.OpCode == OpCodes.Stelem_R8) + { + // The value to be stored is usually pushed onto the stack before the Stelem instruction + var valueInstruction = instructions[i - 1]; + + // Extract the value based on the opcode + switch (valueInstruction.OpCode.Code) + { + case Code.Ldc_I4: + values.Add((int)valueInstruction.Operand); + break; + case Code.Ldc_R4: + values.Add((float)valueInstruction.Operand); + break; + case Code.Ldc_R8: + values.Add((double)valueInstruction.Operand); + break; + case Code.Ldstr: + values.Add((string)valueInstruction.Operand); + break; + // Add more cases as needed for other types + } + } + + // Stop if we reach another array initialization or method end + if (instruction.OpCode == OpCodes.Newarr || instruction.OpCode == OpCodes.Ret) + { + break; + } + } + + return values.ToArray(); + } + /// /// Get the declared Power FX Namespace assigned to a Power FX Reflection function /// diff --git a/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineModuleMEFLoader.cs b/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineModuleMEFLoader.cs index 66df489f..cd49aa19 100644 --- a/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineModuleMEFLoader.cs +++ b/src/Microsoft.PowerApps.TestEngine/Modules/TestEngineModuleMEFLoader.cs @@ -90,6 +90,12 @@ public AggregateCatalog LoadModules(TestSettingExtensions settings) var possibleUserManager = DirectoryGetFiles(location, "testengine.user.*.dll"); foreach (var possibleModule in possibleUserManager) { + if (!Checker.ValidateProvider(settings, possibleModule)) + { + _logger.LogInformation($"Skipping provider {possibleModule}"); + continue; + } + if (Checker.Verify(settings, possibleModule)) { match.Add(LoadAssembly(possibleModule)); @@ -99,6 +105,12 @@ public AggregateCatalog LoadModules(TestSettingExtensions settings) var possibleWebProviderModule = DirectoryGetFiles(location, "testengine.provider.*.dll"); foreach (var possibleModule in possibleWebProviderModule) { + if (!Checker.ValidateProvider(settings, possibleModule)) + { + _logger.LogInformation($"Skipping provider {possibleModule}"); + continue; + } + if (Checker.Verify(settings, possibleModule)) { match.Add(LoadAssembly(possibleModule)); @@ -108,6 +120,11 @@ public AggregateCatalog LoadModules(TestSettingExtensions settings) var possibleAuthTypeProviderModule = DirectoryGetFiles(location, "testengine.auth.*.dll"); foreach (var possibleModule in possibleAuthTypeProviderModule) { + if (!Checker.ValidateProvider(settings, possibleModule)) + { + _logger.LogInformation($"Skipping provider {possibleModule}"); + continue; + } if (Checker.Verify(settings, possibleModule)) { match.Add(LoadAssembly(possibleModule)); diff --git a/src/Microsoft.PowerApps.TestEngine/Providers/ITestWebProvider.cs b/src/Microsoft.PowerApps.TestEngine/Providers/ITestWebProvider.cs index 7ed786ac..1b040b5e 100644 --- a/src/Microsoft.PowerApps.TestEngine/Providers/ITestWebProvider.cs +++ b/src/Microsoft.PowerApps.TestEngine/Providers/ITestWebProvider.cs @@ -93,5 +93,10 @@ public interface ITestWebProvider /// else it throws exception /// public Task TestEngineReady(); + + /// + /// The namespace of namespaces that this provider relates to + /// + public string[] Namespaces { get; } } } diff --git a/src/Microsoft.PowerApps.TestEngine/Users/IUserManager.cs b/src/Microsoft.PowerApps.TestEngine/Users/IUserManager.cs index cc580703..f013d432 100644 --- a/src/Microsoft.PowerApps.TestEngine/Users/IUserManager.cs +++ b/src/Microsoft.PowerApps.TestEngine/Users/IUserManager.cs @@ -14,6 +14,11 @@ namespace Microsoft.PowerApps.TestEngine.Users /// public interface IUserManager { + /// + /// The namespace of namespaces that this provider relates to + /// + public string[] Namespaces { get; } + /// /// The name of the user manager as multiple Manager instances may exist /// diff --git a/src/PowerAppsTestEngine.sln b/src/PowerAppsTestEngine.sln index 9de69900..7a485232 100644 --- a/src/PowerAppsTestEngine.sln +++ b/src/PowerAppsTestEngine.sln @@ -94,9 +94,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "testengine.user.storagestat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "testengine.user.storagestate.tests", "testengine.user.storagestate.tests\testengine.user.storagestate.tests.csproj", "{BC91A675-FE44-44D7-B951-FBE8220D3399}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testengine.common.user", "testengine.common.user\testengine.common.user.csproj", "{6CA1D5BF-FF9F-4392-8CCA-03C9B97BB4CE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "testengine.common.user", "testengine.common.user\testengine.common.user.csproj", "{6CA1D5BF-FF9F-4392-8CCA-03C9B97BB4CE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "testengine.common.user.tests", "testengine.common.user.tests\testengine.common.user.tests.csproj", "{52D935F1-3567-48B7-904F-1183F824A9FB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "testengine.common.user.tests", "testengine.common.user.tests\testengine.common.user.tests.csproj", "{52D935F1-3567-48B7-904F-1183F824A9FB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/testengine.auth.certificatestore/CertificateStoreProvider.cs b/src/testengine.auth.certificatestore/CertificateStoreProvider.cs index 662dc514..0273590d 100644 --- a/src/testengine.auth.certificatestore/CertificateStoreProvider.cs +++ b/src/testengine.auth.certificatestore/CertificateStoreProvider.cs @@ -2,9 +2,6 @@ // Licensed under the MIT license. using System.ComponentModel.Composition; -using System.Runtime.CompilerServices; -using System.Runtime.ConstrainedExecution; -using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Microsoft.PowerApps.TestEngine.Config; using Microsoft.PowerApps.TestEngine.System; @@ -17,6 +14,11 @@ namespace testengine.auth [Export(typeof(IUserCertificateProvider))] public class CertificateStoreProvider : IUserCertificateProvider { + /// + /// The namespace of namespaces that this provider relates to + /// + public string[] Namespaces { get; private set; } = new string[] { "TestEngine" }; + internal static Func GetCertStore = () => new X509Store(StoreName.My, StoreLocation.CurrentUser); public string Name { get { return "certstore"; } } diff --git a/src/testengine.auth.localcertificate/LocalUserCertificateProvider.cs b/src/testengine.auth.localcertificate/LocalUserCertificateProvider.cs index d7b3ada5..fe0ce471 100644 --- a/src/testengine.auth.localcertificate/LocalUserCertificateProvider.cs +++ b/src/testengine.auth.localcertificate/LocalUserCertificateProvider.cs @@ -14,6 +14,11 @@ namespace testengine.auth [Export(typeof(IUserCertificateProvider))] public class LocalUserCertificateProvider : IUserCertificateProvider { + /// + /// The namespace of namespaces that this provider relates to + /// + public string[] Namespaces { get; private set; } = new string[] { "Deprecated" }; + public string Name { get { return "localcert"; } } private readonly IFileSystem _fileSystem; diff --git a/src/testengine.provider.canvas/PowerAppFunctions.cs b/src/testengine.provider.canvas/PowerAppFunctions.cs index e81cb0ed..8a7477c9 100644 --- a/src/testengine.provider.canvas/PowerAppFunctions.cs +++ b/src/testengine.provider.canvas/PowerAppFunctions.cs @@ -52,6 +52,8 @@ public PowerAppFunctions(ITestInfraFunctions? testInfraFunctions, ISingleTestIns public string Name { get { return "canvas"; } } + public string[] Namespaces => new string[] { "TestEngine" }; + private async Task GetPropertyValueFromControlAsync(ItemPath itemPath) { try diff --git a/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs b/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs index c5b2192e..d6ed4143 100644 --- a/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs +++ b/src/testengine.provider.mda/ModelDrivenApplicationProvider.cs @@ -55,6 +55,8 @@ public ITestState? TestState public ModelDrivenApplicationCanvasState? CanvasState { get; set; } = new ModelDrivenApplicationCanvasState(); + public string[] Namespaces => new string[] { "Experimental" }; + public static string QueryFormField = "JSON.stringify({{PropertyValue: PowerAppsTestEngine.getValue('{0}') }})"; public static string ControlPropertiesQuery = "PowerAppsTestEngine.getControlProperties('{0}')"; diff --git a/src/testengine.provider.powerapps.portal/PowerAppPortalProvider.cs b/src/testengine.provider.powerapps.portal/PowerAppPortalProvider.cs index 4361e5dc..d7ca262e 100644 --- a/src/testengine.provider.powerapps.portal/PowerAppPortalProvider.cs +++ b/src/testengine.provider.powerapps.portal/PowerAppPortalProvider.cs @@ -29,6 +29,8 @@ public class PowerAppPortalProvider : ITestWebProvider public ITestProviderState? ProviderState { get; set; } + public string[] Namespaces => new string[] { "Experimental" }; + public PowerAppPortalProvider() { diff --git a/src/testengine.user.browser/BrowserUserManagerModule.cs b/src/testengine.user.browser/BrowserUserManagerModule.cs index 2b89d652..2556466f 100644 --- a/src/testengine.user.browser/BrowserUserManagerModule.cs +++ b/src/testengine.user.browser/BrowserUserManagerModule.cs @@ -21,6 +21,11 @@ namespace testengine.user.browser [Export(typeof(IUserManager))] public class BrowserUserManagerModule : IConfigurableUserManager { + /// + /// The namespace of namespaces that this provider relates to + /// + public string[] Namespaces { get; private set; } = new string[] { "Deprecated" }; + public string Name { get { return "browser"; } } public int Priority { get { return 100; } } diff --git a/src/testengine.user.certificate/CertificateUserManagerModule.cs b/src/testengine.user.certificate/CertificateUserManagerModule.cs index 43f7183f..5b9f93f4 100644 --- a/src/testengine.user.certificate/CertificateUserManagerModule.cs +++ b/src/testengine.user.certificate/CertificateUserManagerModule.cs @@ -1,37 +1,28 @@ // Copyright(c) Microsoft Corporation. // Licensed under the MIT license. -using System; using System.ComponentModel.Composition; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Text; -using System.Text.Json; -using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; using Microsoft.Playwright; using Microsoft.PowerApps.TestEngine.Config; -using Microsoft.PowerApps.TestEngine.Modules; using Microsoft.PowerApps.TestEngine.System; -using Microsoft.PowerApps.TestEngine.TestInfra; using Microsoft.PowerApps.TestEngine.Users; -using Microsoft.PowerFx; -#if DEBUG -[assembly: InternalsVisibleTo("testengine.user.certificate.tests")] -#else -[assembly: InternalsVisibleTo("testengine.user.certificate.tests, PublicKey = 0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] -#endif namespace testengine.user.environment { [Export(typeof(IUserManager))] public class CertificateUserManagerModule : IUserManager { + /// + /// The namespace of namespaces that this provider relates to + /// + public string[] Namespaces { get; private set; } = new string[] { "TestEngine" }; + // defining these 2 for improved testability - internal static Func GetHttpClientHandler = () => new HttpClientHandler(); - internal static Func GetHttpClient = handler => new HttpClient(handler); + public static Func GetHttpClientHandler = () => new HttpClientHandler(); + public static Func GetHttpClient = handler => new HttpClient(handler); public string Name { get { return "certificate"; } } @@ -189,7 +180,7 @@ public async Task LoginAsUserAsync( await ClickStaySignedIn(desiredUrl, logger); } - internal async Task ClickStaySignedIn(string desiredUrl, ILogger logger) + public async Task ClickStaySignedIn(string desiredUrl, ILogger logger) { PageWaitForSelectorOptions selectorOptions = new PageWaitForSelectorOptions(); selectorOptions.Timeout = 8000; @@ -253,10 +244,11 @@ public async Task HandleUserEmailScreen(string selector, string value) await Page.Keyboard.PressAsync("Tab", new KeyboardPressOptions { Delay = 20 }); } - internal async Task GetCertAuthGlob(string endpoint) + public async Task GetCertAuthGlob(string endpoint) { return $"https://*certauth.{endpoint}/**"; } + public async Task InterceptRestApiCallsAsync(IPage page, string endpoint, X509Certificate2 cert, ILogger logger) { // Define the route to intercept @@ -266,7 +258,7 @@ await page.RouteAsync(endpoint, async route => }); } - internal async Task HandleRequest(IRoute route, X509Certificate2 cert, ILogger logger) + public async Task HandleRequest(IRoute route, X509Certificate2 cert, ILogger logger) { var request = route.Request; diff --git a/src/testengine.user.environment/EnvironmentUserManagerModule.cs b/src/testengine.user.environment/EnvironmentUserManagerModule.cs index ea468d49..78eaa79c 100644 --- a/src/testengine.user.environment/EnvironmentUserManagerModule.cs +++ b/src/testengine.user.environment/EnvironmentUserManagerModule.cs @@ -19,6 +19,11 @@ namespace testengine.user.environment [Export(typeof(IUserManager))] public class EnvironmentUserManagerModule : IUserManager { + /// + /// The namespace of namespaces that this provider relates to + /// + public string[] Namespaces { get; private set; } = new string[] { "Deprecated" }; + public string Name { get { return "environment"; } } public int Priority { get { return 0; } } diff --git a/src/testengine.user.storagestate/StorageStateUserManagerModule.cs b/src/testengine.user.storagestate/StorageStateUserManagerModule.cs index e1dc73b7..144210a8 100644 --- a/src/testengine.user.storagestate/StorageStateUserManagerModule.cs +++ b/src/testengine.user.storagestate/StorageStateUserManagerModule.cs @@ -23,6 +23,11 @@ namespace testengine.user.storagestate [Export(typeof(IUserManager))] public class StorageStateUserManagerModule : IConfigurableUserManager { + /// + /// The namespace of namespaces that this provider relates to + /// + public string[] Namespaces { get; private set; } = new string[] { "TestEngine" }; + public Dictionary Settings { get; private set; } public StorageStateUserManagerModule()