Skip to content

Commit

Permalink
restricting logs, output to appdata local directory
Browse files Browse the repository at this point in the history
  • Loading branch information
snamilikonda committed Nov 25, 2024
1 parent 8f5e3a1 commit 6ef43ff
Show file tree
Hide file tree
Showing 29 changed files with 637 additions and 146 deletions.
2 changes: 1 addition & 1 deletion docs/CommandInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The executable can take in inputs defined in `config.dev.json`, or as command li
| EnvironmentId | Environment that the Power Apps app you are testing is located in. For more information about environments, please view [this](https://docs.microsoft.com/en-us/power-platform/admin/environments-overview) |
| TenantId | Tenant that the Power Apps app is located in. |
| TestPlanFile | Path to the test plan that you wish to run |
| OutputDirectory | Path to folder the test results will be placed. Optional. If this is not provided, it will be placed in the `TestOutput` folder. |
| OutputDirectory | Relative path to folder the test results will be placed. Optional. If this is not provided, it will be placed in the `TestOutput` folder. All results and logs will be placed under file system's designated `Microsoft\TestEngine` location under user's temp directory. |
| LogLevel | Level for logging (Folllows [this](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-6.0)). Optional. If this is not provided, Information level logs and higher will be logged |
| QueryParams | Specify query parameters to be added to the Power Apps URL. |
| Domain | Specify what URL domain your app uses. This is optional; if not set, it will default to 'apps.powerapps.com'. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public ScreenshotFunctionTests()
public void ScreenshotFunctionThrowsOnInvalidResultDirectoryTest()
{
MockSingleTestInstanceState.Setup(x => x.GetTestResultsDirectory()).Returns("");
MockFileSystem.Setup(x => x.IsValidFilePath(It.IsAny<string>())).Returns(false);
MockFileSystem.Setup(x => x.IsNonUNCDirectoryPath(It.IsAny<string>())).Returns(false);
LoggingTestHelper.SetupMock(MockLogger);
var screenshotFunction = new ScreenshotFunction(MockTestInfraFunctions.Object, MockSingleTestInstanceState.Object, MockFileSystem.Object, MockLogger.Object);
Assert.Throws<InvalidOperationException>(() => screenshotFunction.Execute(FormulaValue.New("screenshot.png")));
Expand All @@ -50,7 +50,7 @@ public void ScreenshotFunctionThrowsOnInvalidScreenshotNameTest(string screensho
{
var testResultDirectory = "C:\\testResults";
MockSingleTestInstanceState.Setup(x => x.GetTestResultsDirectory()).Returns(testResultDirectory);
MockFileSystem.Setup(x => x.IsValidFilePath(It.IsAny<string>())).Returns(true);
MockFileSystem.Setup(x => x.IsNonUNCDirectoryPath(It.IsAny<string>())).Returns(true);
LoggingTestHelper.SetupMock(MockLogger);
var screenshotFunction = new ScreenshotFunction(MockTestInfraFunctions.Object, MockSingleTestInstanceState.Object, MockFileSystem.Object, MockLogger.Object);
Assert.Throws<ArgumentException>(() => screenshotFunction.Execute(FormulaValue.New(screenshotName)));
Expand All @@ -62,7 +62,7 @@ public void ScreenshotFunctionThrowsOnNonRelativeFilePathTest()
{
var testResultDirectory = "C:\\testResults";
MockSingleTestInstanceState.Setup(x => x.GetTestResultsDirectory()).Returns(testResultDirectory);
MockFileSystem.Setup(x => x.IsValidFilePath(It.IsAny<string>())).Returns(true);
MockFileSystem.Setup(x => x.IsNonUNCDirectoryPath(It.IsAny<string>())).Returns(true);
LoggingTestHelper.SetupMock(MockLogger);
var screenshotFunction = new ScreenshotFunction(MockTestInfraFunctions.Object, MockSingleTestInstanceState.Object, MockFileSystem.Object, MockLogger.Object);
Assert.Throws<ArgumentException>(() => screenshotFunction.Execute(FormulaValue.New(Path.Combine(Path.GetFullPath(Directory.GetCurrentDirectory()), "screeshot.jpg"))));
Expand All @@ -76,14 +76,14 @@ public void ScreenshotFunctionTest(string screenshotName)
{
var testResultDirectory = "C:\\testResults";
MockSingleTestInstanceState.Setup(x => x.GetTestResultsDirectory()).Returns(testResultDirectory);
MockFileSystem.Setup(x => x.IsValidFilePath(It.IsAny<string>())).Returns(true);
MockFileSystem.Setup(x => x.IsNonUNCDirectoryPath(It.IsAny<string>())).Returns(true);
MockTestInfraFunctions.Setup(x => x.ScreenshotAsync(It.IsAny<string>())).Returns(Task.CompletedTask);
LoggingTestHelper.SetupMock(MockLogger);
var screenshotFunction = new ScreenshotFunction(MockTestInfraFunctions.Object, MockSingleTestInstanceState.Object, MockFileSystem.Object, MockLogger.Object);
screenshotFunction.Execute(FormulaValue.New(screenshotName));

MockSingleTestInstanceState.Verify(x => x.GetTestResultsDirectory(), Times.Once());
MockFileSystem.Verify(x => x.IsValidFilePath(testResultDirectory), Times.Once());
MockFileSystem.Verify(x => x.IsNonUNCDirectoryPath(testResultDirectory), Times.Once());
MockTestInfraFunctions.Verify(x => x.ScreenshotAsync(Path.Combine(testResultDirectory, screenshotName)), Times.Once());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ public async Task ExecuteScreenshotFunctionTest()
MockTestState.Setup(x => x.OnAfterTestStepExecuted(It.IsAny<TestStepEventArgs>()));

MockSingleTestInstanceState.Setup(x => x.GetTestResultsDirectory()).Returns("C:\\testResults");
MockFileSystem.Setup(x => x.IsValidFilePath(It.IsAny<string>())).Returns(true);
MockFileSystem.Setup(x => x.IsNonUNCDirectoryPath(It.IsAny<string>())).Returns(true);
MockTestInfraFunctions.Setup(x => x.ScreenshotAsync(It.IsAny<string>())).Returns(Task.CompletedTask);
var powerFxExpression = "Screenshot(\"1.jpg\")";
var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockTestWebProvider.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public void WriteToLogsFileThrowsOnInvalidPathTest()
MockFileSystem.Setup(x => x.Exists(It.IsAny<string>())).Returns(false);
MockFileSystem.Setup(x => x.IsValidFilePath(It.IsAny<string>())).Returns(false);
MockFileSystem.Setup(x => x.CreateDirectory(It.IsAny<string>()));
MockFileSystem.Setup(x => x.GetDefaultRootTestEngine()).Returns("");
MockFileSystem.Setup(x => x.WriteTextToFile(It.IsAny<string>(), It.IsAny<string[]>())).Callback((string filePath, string[] logs) =>
{
createdLogs.Add(filePath, logs);
Expand Down
134 changes: 134 additions & 0 deletions src/Microsoft.PowerApps.TestEngine.Tests/System/FileSystemTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using System.IO;
using System;
using Microsoft.PowerApps.TestEngine.System;
using Xunit;
using System.Linq;
using Moq;
using System.Text;
using System.Collections.Generic;

namespace Microsoft.PowerApps.TestEngine.Tests.System
{
Expand All @@ -16,6 +22,14 @@ public class FileSystemTests
[InlineData("", false)]
[InlineData(null, false)]
[InlineData("C:/fold:er", false)]
[InlineData("C:/fold>er/fg", false)]
[InlineData("C:/folder/f>g", false)]
[InlineData("C:/folder/f:g", false)]
[InlineData("C:/folder/fg/", false)]
[InlineData("../folder/fg", true)]
[InlineData("../folder/f:g", false)]
[InlineData("\\\\RandomUNC", false)]
[InlineData(@"\\?\C:\folder", true)]
public void IsValidFilePathTest(string? filePath, bool expectedResult)
{
var fileSystem = new FileSystem();
Expand All @@ -37,5 +51,125 @@ public void RemoveInvalidFileNameCharsTest(string inputFileName, string expected
var result = fileSystem.RemoveInvalidFileNameChars(inputFileName);
Assert.Equal(expectedFileName, result);
}

[Fact]
public void IsWritePermittedFilePath_ValidRootedPath_ReturnsTrue()
{
var _fileSystem = new FileSystem();
var validPath = Path.Combine(_fileSystem.GetDefaultRootTestEngine(), "testfile.txt");
Assert.True(_fileSystem.IsWritePermittedFilePath(validPath));
}

[Fact]
public void IsWritePermittedFilePath_SameAsRootedPath_ReturnsFalse()
{
var _fileSystem = new FileSystem();
var validPath = Path.Combine(_fileSystem.GetDefaultRootTestEngine(), "");
Assert.False(_fileSystem.IsWritePermittedFilePath(validPath));
}

[Fact]
public void IsWritePermittedFilePath_RelativePath_ReturnsFalse()
{
var _fileSystem = new FileSystem();
var relativePath = @"..\testfile.txt";
Assert.False(_fileSystem.IsWritePermittedFilePath(relativePath));
}

[Fact]
public void IsWritePermittedFilePath_InvalidRootedPath_ReturnsFalse()
{
var _fileSystem = new FileSystem();
var invalidPath = Path.Combine(_fileSystem.GetTempPath(), "invalidfolder", "testfile.txt");
Assert.False(_fileSystem.IsWritePermittedFilePath(invalidPath));
}

[Fact]
public void IsWritePermittedFilePath_ValidRootedLongPath_ReturnsTrue()
{
var _fileSystem = new FileSystem();
var invalidPath = Path.Combine(@"\\?\", _fileSystem.GetDefaultRootTestEngine(), "testfile.txt");
Assert.True(_fileSystem.IsWritePermittedFilePath(invalidPath));
}

[Fact]
public void IsWritePermittedFilePath_NullPath_ReturnsFalse()
{
var _fileSystem = new FileSystem();
Assert.False(_fileSystem.IsWritePermittedFilePath(null));
}

[Fact]
public void IsWritePermittedFilePath_ValidPathWithParentDirectoryTraversal_ReturnsFalse()
{
var _fileSystem = new FileSystem();
var pathWithParentTraversal = _fileSystem.GetDefaultRootTestEngine() + Path.DirectorySeparatorChar + @"..\testfile.txt";
Assert.False(_fileSystem.IsWritePermittedFilePath(pathWithParentTraversal));
}

[Fact]
public void IsWritePermittedFilePath_UNCPath_ReturnsFalse()
{
var _fileSystem = new FileSystem();
var validPath = "\\\\RandomUNC";
Assert.False(_fileSystem.IsWritePermittedFilePath(validPath));
}

[Fact]
public void WriteTextToFile_UnpermittedFilePath_ThrowsInvalidOperationException()
{
var _fileSystem = new FileSystem();
var invalidFilePath = "C:\\InvalidFolder\\testfile.txt";
var exception = Assert.Throws<InvalidOperationException>(() => _fileSystem.WriteTextToFile(invalidFilePath, ""));
Assert.Contains(invalidFilePath, exception.Message);
}

[Fact]
public void WriteTextToFile_ArrayText_UnpermittedFilePath_ThrowsInvalidOperationException()
{
var _fileSystem = new FileSystem();
var invalidFilePath = "C:\\InvalidFolder\\testfile.txt";
var exception = Assert.Throws<InvalidOperationException>(() => _fileSystem.WriteTextToFile(invalidFilePath, new string[] { "This should fail." }));
Assert.Contains(invalidFilePath, exception.Message);
}

[Fact]
public void WriteFile_ArrayText_UnpermittedFilePath_ThrowsInvalidOperationException()
{
var _fileSystem = new FileSystem();
var invalidFilePath = "C:\\InvalidFolder\\testfile.txt";
var exception = Assert.Throws<InvalidOperationException>(() => _fileSystem.WriteFile(invalidFilePath, Encoding.UTF8.GetBytes("This should fail.")));
Assert.Contains(invalidFilePath, exception.Message);
}

[Theory]
[MemberData(nameof(DirectoryPathTestData))]
public void IsNonUNCDirectoryPath_Invalid_ReturnsFalse(string path, bool validity)
{
var fileSystem = new FileSystem();
Assert.Equal(fileSystem.IsNonUNCDirectoryPath(path), validity);
}

public static IEnumerable<object[]> DirectoryPathTestData()
{
return new List<object[]>
{
new object[] { @"C:\Valid\Directory", true }, // Valid absolute Windows path
new object[] { @"/valid/directory", true }, // Valid absolute Linux path
new object[] { @"relative\directory", true }, // Valid relative Windows path
new object[] { @"./relative/directory", true }, // Valid relative Linux path
new object[] { @"\\network\share\directory", false }, // UNC path (network)
new object[] { @"C:\ ", false }, // Ends with a space
new object[] { @"C:\.", false }, // Ends with a period
new object[] { @"", false }, // Empty string
new object[] { @" ", false }, // Whitespace string
new object[] { @"C:\Valid\..\Directory", true }, // Valid path with `..` (resolved)
new object[] { @"\\?\C:\Very\Long\Path", true }, // Long path prefix
new object[] { @"../relative/dir", true }, // relative Linux path
new object[] { @"C:\folder\" + new string('a', 250), true }, // Valid length
new object[] { @"C:\フォルダー", true }, // Valid Unicode path
new object[] { @"/тест/путь", true }, // Valid Unicode path
};
}
}
}
34 changes: 34 additions & 0 deletions src/Microsoft.PowerApps.TestEngine.Tests/TestEngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public async Task TestEngineWithDefaultParamsTest()
var environmentId = "defaultEnviroment";
var tenantId = new Guid("a01af035-a529-4aaf-aded-011ad676f976");
var outputDirectory = new DirectoryInfo("TestOutput");
MockFileSystem.Setup(x => x.GetDefaultRootTestEngine()).Returns(outputDirectory.FullName);
var testRunId = Guid.NewGuid().ToString();
var expectedOutputDirectory = outputDirectory.FullName;
var testRunDirectory = Path.Combine(expectedOutputDirectory, "2024-11-20T00-00-00-0000000-" + testRunId.Substring(0, 6));
Expand Down Expand Up @@ -118,6 +119,7 @@ public async Task TestEngineWithInvalidLocaleTest()
var environmentId = "defaultEnviroment";
var tenantId = new Guid("a01af035-a529-4aaf-aded-011ad676f976");
var outputDirectory = new DirectoryInfo("TestOutput");
MockFileSystem.Setup(x => x.GetDefaultRootTestEngine()).Returns(outputDirectory.FullName);
var testRunId = Guid.NewGuid().ToString();
var expectedOutputDirectory = outputDirectory.FullName;
var testRunDirectory = Path.Combine(expectedOutputDirectory, testRunId.Substring(0, 6));
Expand Down Expand Up @@ -156,6 +158,7 @@ public async Task TestEngineWithUnspecifiedLocaleShowsWarning()
var environmentId = "defaultEnviroment";
var tenantId = new Guid("a01af035-a529-4aaf-aded-011ad676f976");
var outputDirectory = new DirectoryInfo("TestOutput");
MockFileSystem.Setup(x => x.GetDefaultRootTestEngine()).Returns(outputDirectory.FullName);
var testRunId = Guid.NewGuid().ToString();
var expectedOutputDirectory = outputDirectory.FullName;
var testRunDirectory = Path.Combine(expectedOutputDirectory, "2024-11-20T00-00-00-0000000-" + testRunId.Substring(0, 6));
Expand Down Expand Up @@ -207,6 +210,7 @@ public async Task TestEngineWithMultipleBrowserConfigTest()
var expectedTestReportPath = "C:\\test.trx";

SetupMocks(expectedOutputDirectory, testSettings, testSuiteDefinition, testRunId, expectedTestReportPath);
MockFileSystem.Setup(x => x.GetDefaultRootTestEngine()).Returns(outputDirectory.FullName);

var testEngine = new TestEngine(MockState.Object, ServiceProvider, MockTestReporter.Object, MockFileSystem.Object, MockLoggerFactory.Object, MockTestEngineEventHandler.Object);
testEngine.Timestamper = () => new DateTime(2024, 11, 20);
Expand Down Expand Up @@ -247,6 +251,7 @@ public async Task TestEngineTest(DirectoryInfo outputDirectory, string domain, T
var tenantId = new Guid("a01af035-a529-4aaf-aded-011ad676f976");
var testRunId = Guid.NewGuid().ToString();

MockFileSystem.Setup(x => x.GetDefaultRootTestEngine()).Returns(outputDirectory.FullName);
var expectedOutputDirectory = outputDirectory;
if (expectedOutputDirectory == null)
{
Expand Down Expand Up @@ -328,6 +333,7 @@ private void Verify(string testConfigFile, string environmentId, string tenantId
[Theory]
[InlineData(null, "Default-EnvironmentId", "a01af035-a529-4aaf-aded-011ad676f976", "apps.powerapps.com")]
[InlineData("C:\\testPlan.fx.yaml", "", "a01af035-a529-4aaf-aded-011ad676f976", "apps.powerapps.com")]
[InlineData("C:\\testPlan.fx.yaml", "Default-EnvironmentId", "a01af035-a529-4aaf-aded-011ad676f976", "")]
public async Task TestEngineThrowsOnNullArguments(string? testConfigFilePath, string environmentId, Guid tenantId, string domain)
{
MockTestReporter.Setup(x => x.CreateTestRun(It.IsAny<string>(), It.IsAny<string>())).Returns(Guid.NewGuid().ToString());
Expand Down Expand Up @@ -361,6 +367,33 @@ public async Task TestEngineThrowsOnNullArguments(string? testConfigFilePath, st
await Assert.ThrowsAsync<ArgumentNullException>(async () => await testEngine.RunTestAsync(testConfigFile, environmentId, tenantId, outputDirectory, domain, ""));
}

[Theory]
[InlineData("C:\\testPath")]
[InlineData("testPath")]
[InlineData("..\\testPath")]
[InlineData(@"\\?\C:\testPath")]
public async Task TestEngineExceptionOnNotPermittedOutputPath(string outputDirLoc)
{
var testConfigFile = new FileInfo("C:\\testPlan.fx.yaml");
var environmentId = "defaultEnviroment";
var tenantId = new Guid("a01af035-a529-4aaf-aded-011ad676f976");
var domain = "apps.powerapps.com";

MockTestReporter.Setup(x => x.CreateTestRun(It.IsAny<string>(), It.IsAny<string>())).Returns("abcdef");
MockTestReporter.Setup(x => x.StartTestRun(It.IsAny<string>()));
MockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>())).Returns(MockLogger.Object);
LoggingTestHelper.SetupMock(MockLogger);
MockTestLoggerProvider.Setup(x => x.CreateLogger(It.IsAny<string>())).Returns(MockLogger.Object);
MockTestEngineEventHandler.Setup(x => x.EncounteredException(It.IsAny<Exception>()));

var testEngine = new TestEngine(MockState.Object, ServiceProvider, MockTestReporter.Object, MockFileSystem.Object, MockLoggerFactory.Object, MockTestEngineEventHandler.Object);
var outputDirectory = new DirectoryInfo(outputDirLoc);
MockFileSystem.Setup(x => x.GetDefaultRootTestEngine()).Returns("C:\\testPath" + Path.DirectorySeparatorChar);
var testResultsDirectory = await testEngine.RunTestAsync(testConfigFile, environmentId, tenantId, outputDirectory, domain, "");
// UserInput Exception is handled within TestEngineEventHandler, and then returns the test results directory path
MockTestEngineEventHandler.Verify(x => x.EncounteredException(It.IsAny<UserInputException>()), Times.Once());
}

[Fact]
public async Task TestEngineReturnsPathOnUserInputErrors()
{
Expand All @@ -384,6 +417,7 @@ public async Task TestEngineReturnsPathOnUserInputErrors()

var testEngine = new TestEngine(MockState.Object, ServiceProvider, MockTestReporter.Object, MockFileSystem.Object, MockLoggerFactory.Object, MockTestEngineEventHandler.Object);
var outputDirectory = new DirectoryInfo("TestOutput");
MockFileSystem.Setup(x => x.GetDefaultRootTestEngine()).Returns(outputDirectory.FullName);

var testResultsDirectory = await testEngine.RunTestAsync(testConfigFile, environmentId, tenantId, outputDirectory, domain, "");
// UserInput Exception is handled within TestEngineEventHandler, and then returns the test results directory path
Expand Down
Loading

0 comments on commit 6ef43ff

Please sign in to comment.