unit tests

This commit is contained in:
John Andrews
2024-08-26 18:16:07 +12:00
parent f92b8bbc5d
commit 444d46bf62
92 changed files with 1108 additions and 4700 deletions
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup Condition=" '$(Configuration)' == 'Debug'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.4.3" />
<PackageReference Include="MSTest.TestFramework" Version="3.4.3" />
<PackageReference Include="Moq" Version="4.20.70" />
<Reference Include="FileFlows.Plugin">
<HintPath>..\FileFlows.Plugin.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+495
View File
@@ -0,0 +1,495 @@
using FileFlows.Plugin;
using FileFlows.Plugin.Models;
using FileFlows.Plugin.Services;
using System.IO;
using FileHelper = FileFlows.Plugin.Helpers.FileHelper;
namespace PluginTestLibrary;
/// <summary>
/// Local file service
/// </summary>
public class LocalFileService : IFileService
{
/// <summary>
/// Gets or sets the path separator for the file system
/// </summary>
public char PathSeparator { get; init; } = Path.DirectorySeparatorChar;
public ReplaceVariablesDelegate? ReplaceVariables { get; set; }
/// <summary>
/// Gets or sets the allowed paths the file service can access
/// </summary>
public string[] AllowedPaths { get; init; } = null!;
/// <summary>
/// Gets or sets the permissions to use for files
/// </summary>
public int? Permissions { get; set; }
/// <summary>
/// Gets or sets the owner:group to use for files
/// </summary>
public string OwnerGroup { get; set; } = null!;
/// <summary>
/// Gets or sets the logger used for logging
/// </summary>
public ILogger? Logger { get; set; }
public Result<string[]> GetFiles(string path, string searchPattern = "", bool recursive = false)
{
if (IsProtectedPath(ref path))
return Result<string[]>.Fail("Cannot access protected path: " + path);
try
{
return Directory.GetFiles(path, searchPattern ?? string.Empty,
recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
}
catch (Exception)
{
return new string[] { };
}
}
public Result<string[]> GetDirectories(string path)
{
if (IsProtectedPath(ref path))
return Result<string[]>.Fail("Cannot access protected path: " + path);
try
{
return Directory.GetDirectories(path);
}
catch (Exception)
{
return new string[] { };
}
}
public Result<bool> DirectoryExists(string path)
{
if (IsProtectedPath(ref path))
return Result<bool>.Fail("Cannot access protected path: " + path);
try
{
return Directory.Exists(path);
}
catch (Exception)
{
return false;
}
}
public Result<bool> DirectoryDelete(string path, bool recursive = false)
{
if (IsProtectedPath(ref path))
return Result<bool>.Fail("Cannot access protected path: " + path);
try
{
Directory.Delete(path, recursive);
return true;
}
catch (Exception ex)
{
return Result<bool>.Fail(ex.Message);
}
}
public Result<bool> DirectoryMove(string path, string destination)
{
if (IsProtectedPath(ref path))
return Result<bool>.Fail("Cannot access protected path: " + path);
if (IsProtectedPath(ref destination))
return Result<bool>.Fail("Cannot access protected path: " + destination);
try
{
Directory.Move(path, destination);
SetPermissions(destination);
return true;
}
catch (Exception ex)
{
return Result<bool>.Fail(ex.Message);
}
}
public Result<bool> DirectoryCreate(string path)
{
if (IsProtectedPath(ref path))
return Result<bool>.Fail("Cannot access protected path: " + path);
try
{
var dirInfo = new DirectoryInfo(path);
if (dirInfo.Exists == false)
dirInfo.Create();
SetPermissions(path);
return true;
}
catch (Exception ex)
{
return Result<bool>.Fail(ex.Message);
}
}
public Result<DateTime> DirectoryCreationTimeUtc(string path)
{
throw new NotImplementedException();
}
public Result<DateTime> DirectoryLastWriteTimeUtc(string path)
{
throw new NotImplementedException();
}
public Result<bool> FileExists(string path)
{
if (IsProtectedPath(ref path))
return Result<bool>.Fail("Cannot access protected path: " + path);
try
{
return System.IO.File.Exists(path);
}
catch (Exception)
{
return false;
}
}
public Result<FileInformation> FileInfo(string path)
{
if (IsProtectedPath(ref path))
return Result<FileInformation>.Fail("Cannot access protected path: " + path);
try
{
FileInfo fileInfo = new FileInfo(path);
return new FileInformation
{
CreationTime = fileInfo.CreationTime,
CreationTimeUtc = fileInfo.CreationTimeUtc,
LastWriteTime = fileInfo.LastWriteTime,
LastWriteTimeUtc = fileInfo.LastWriteTimeUtc,
Extension = fileInfo.Extension.TrimStart('.'),
Name = fileInfo.Name,
FullName = fileInfo.FullName,
Length = fileInfo.Length,
Directory = fileInfo.DirectoryName!
};
}
catch (Exception ex)
{
return Result<FileInformation>.Fail(ex.Message);
}
}
public Result<bool> FileDelete(string path)
{
if (IsProtectedPath(ref path))
return Result<bool>.Fail("Cannot access protected path: " + path);
try
{
var fileInfo = new FileInfo(path);
if(fileInfo.Exists)
fileInfo.Delete();
return true;
}
catch (Exception)
{
return false;
}
}
public Result<long> FileSize(string path)
{
if (IsProtectedPath(ref path))
return Result<long>.Fail("Cannot access protected path: " + path);
try
{
var fileInfo = new FileInfo(path);
if (fileInfo.Exists == false)
return Result<long>.Fail("File does not exist");
return fileInfo.Length;
}
catch (Exception ex)
{
return Result<long>.Fail(ex.Message);
}
}
public Result<DateTime> FileCreationTimeUtc(string path)
{
if (IsProtectedPath(ref path))
return Result<DateTime>.Fail("Cannot access protected path: " + path);
try
{
var fileInfo = new FileInfo(path);
if (fileInfo.Exists == false)
return Result<DateTime>.Fail("File does not exist");
return fileInfo.CreationTimeUtc;
}
catch (Exception ex)
{
return Result<DateTime>.Fail(ex.Message);
}
}
public Result<DateTime> FileLastWriteTimeUtc(string path)
{
if (IsProtectedPath(ref path))
return Result<DateTime>.Fail("Cannot access protected path: " + path);
try
{
var fileInfo = new FileInfo(path);
if (fileInfo.Exists == false)
return Result<DateTime>.Fail("File does not exist");
return fileInfo.LastWriteTimeUtc;
}
catch (Exception ex)
{
return Result<DateTime>.Fail(ex.Message);
}
}
public Result<bool> FileMove(string path, string destination, bool overwrite = true)
{
if (IsProtectedPath(ref path))
return Result<bool>.Fail("Cannot access protected path: " + path);
if (IsProtectedPath(ref destination))
return Result<bool>.Fail("Cannot access protected path: " + destination);
try
{
var fileInfo = new FileInfo(path);
if (fileInfo.Exists == false)
return Result<bool>.Fail("File does not exist");
var destDir = new FileInfo(destination).Directory!;
if (destDir.Exists == false)
{
destDir.Create();
SetPermissions(destDir.FullName);
}
fileInfo.MoveTo(destination, overwrite);
SetPermissions(destination);
return true;
}
catch (Exception ex)
{
return Result<bool>.Fail(ex.Message);
}
}
public Result<bool> FileCopy(string path, string destination, bool overwrite = true)
{
if (IsProtectedPath(ref path))
return Result<bool>.Fail("Cannot access protected path: " + path);
if (IsProtectedPath(ref destination))
return Result<bool>.Fail("Cannot access protected path: " + destination);
try
{
var fileInfo = new FileInfo(path);
if (fileInfo.Exists == false)
return Result<bool>.Fail("File does not exist");
var destDir = new FileInfo(destination).Directory!;
if (destDir.Exists == false)
{
destDir.Create();
SetPermissions(destDir.FullName);
}
fileInfo.CopyTo(destination, overwrite);
SetPermissions(destination);
return true;
}
catch (Exception ex)
{
return Result<bool>.Fail(ex.Message);
}
}
public Result<bool> FileAppendAllText(string path, string text)
{
if (IsProtectedPath(ref path))
return Result<bool>.Fail("Cannot access protected path: " + path);
try
{
System.IO.File.AppendAllText(path, text);
SetPermissions(path);
return true;
}
catch (Exception ex)
{
return Result<bool>.Fail(ex.Message);
}
}
public bool FileIsLocal(string path) => true;
/// <summary>
/// Gets the local path
/// </summary>
/// <param name="path">the path</param>
/// <returns>the local path to the file</returns>
public Result<string> GetLocalPath(string path)
=> Result<string>.Success(path);
public Result<bool> Touch(string path)
{
if (IsProtectedPath(ref path))
return Result<bool>.Fail("Cannot access protected path: " + path);
if (DirectoryExists(path).Is(true))
{
try
{
Directory.SetLastWriteTimeUtc(path, DateTime.UtcNow);
return true;
}
catch (Exception ex)
{
return Result<bool>.Fail("Failed to touch directory: " + ex.Message);
}
}
try
{
if (System.IO.File.Exists(path))
System.IO.File.SetLastWriteTimeUtc(path, DateTime.UtcNow);
else
{
System.IO.File.Create(path);
SetPermissions(path);
}
return true;
}
catch (Exception ex)
{
return Result<bool>.Fail($"Failed to touch file: '{path}' => {ex.Message}");
}
}
public Result<long> DirectorySize(string path)
{
if (string.IsNullOrWhiteSpace(path))
return 0;
if (System.IO.File.Exists(path))
path = new FileInfo(path).Directory?.FullName ?? string.Empty;
if (string.IsNullOrWhiteSpace(path))
return 0;
if (Directory.Exists(path) == false)
return 0;
try
{
DirectoryInfo dir = new DirectoryInfo(path);
return dir.EnumerateFiles("*.*", SearchOption.AllDirectories).Sum(x => x.Length);
}
catch (Exception)
{
return 0;
}
}
public Result<bool> SetCreationTimeUtc(string path, DateTime date)
{
if (IsProtectedPath(ref path))
return Result<bool>.Fail("Cannot access protected path: " + path);
try
{
if (!System.IO.File.Exists(path))
return Result<bool>.Fail("File not found.");
System.IO.File.SetCreationTimeUtc(path, date);
return Result<bool>.Success(true);
}
catch (Exception ex)
{
return Result<bool>.Fail($"Error setting creation time: {ex.Message}");
}
}
public Result<bool> SetLastWriteTimeUtc(string path, DateTime date)
{
if (IsProtectedPath(ref path))
return Result<bool>.Fail("Cannot access protected path: " + path);
try
{
if (!System.IO.File.Exists(path))
return Result<bool>.Fail("File not found.");
System.IO.File.SetLastWriteTimeUtc(path, date);
return Result<bool>.Success(true);
}
catch (Exception ex)
{
return Result<bool>.Fail($"Error setting last write time: {ex.Message}");
}
}
/// <summary>
/// Checks if a path is accessible by the file server
/// </summary>
/// <param name="path">the path to check</param>
/// <returns>true if accessible, otherwise false</returns>
private bool IsProtectedPath(ref string path)
{
if (OperatingSystem.IsWindows())
path = path.Replace("/", "\\");
else
path = path.Replace("\\", "/");
if(ReplaceVariables != null)
path = ReplaceVariables(path, true);
if (FileHelper.IsSystemDirectory(path))
return true; // a system directory, no access
if (AllowedPaths?.Any() != true)
return false; // no allowed paths configured, allow all
if (OperatingSystem.IsWindows())
path = path.ToLowerInvariant();
for(int i=0;i<AllowedPaths.Length;i++)
{
string p = OperatingSystem.IsWindows() ? AllowedPaths[i].ToLowerInvariant().TrimEnd('\\') : AllowedPaths[i].TrimEnd('/');
if (path.StartsWith(p))
return false;
}
return true;
}
public void SetPermissions(string path, int? permissions = null, Action<string>? logMethod = null)
{
logMethod ??= (string message) => Logger?.ILog(message);
permissions = permissions != null && permissions > 0 ? permissions : Permissions;
if (permissions == null || permissions < 1)
permissions = 777;
if ((System.IO.File.Exists(path) == false && Directory.Exists(path) == false))
{
logMethod("SetPermissions: File doesnt existing, skipping");
return;
}
//StringLogger stringLogger = new StringLogger();
var logger = new TestLogger();
bool isFile = new FileInfo(path).Exists;
FileHelper.SetPermissions(logger, path, file: isFile, permissions: permissions);
FileHelper.ChangeOwner(logger, path, file: isFile, ownerGroup: OwnerGroup);
logMethod(logger.ToString());
}
}
+114
View File
@@ -0,0 +1,114 @@
#if(DEBUG)
using System.Runtime.InteropServices;
using FileFlows.Plugin;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using FileFlows.Plugin.Helpers;
using FileFlows.Plugin.Services;
using Moq;
namespace PluginTestLibrary;
/// <summary>
/// Base class for the tests
/// </summary>
[TestClass]
public abstract class TestBase
{
/// <summary>
/// The test context instance
/// </summary>
private TestContext testContextInstance = null!;
protected TestLogger Logger = new();
protected Mock<IFileService> MockFileService = new();
/// <summary>
/// Gets or sets the test context
/// </summary>
public TestContext TestContext
{
get => testContextInstance;
set => testContextInstance = value;
}
/// <summary>
/// A File created for the test
/// </summary>
public string TempFile { get; private set; } = null!;
/// <summary>
/// A path in the temp directory created for the test
/// </summary>
public string TempPath { get; private set; } = null!;
public readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
[TestInitialize]
public void TestInitialize()
{
FileHelper.DontChangeOwner = true;
FileHelper.DontSetPermissions = true;
Logger.Writer = (msg) => TestContext.WriteLine(msg);
TempPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString());
System.IO.Directory.CreateDirectory(TempPath);
TempFile = System.IO.Path.Combine(TempPath, Guid.NewGuid() + ".txt");
System.IO.File.WriteAllText(TempFile, Guid.NewGuid().ToString());
if (Directory.Exists(this.TempPath) == false)
Directory.CreateDirectory(this.TempPath);
TestStarting();
}
[TestCleanup]
public void CleanUp()
{
TestCleanUp();
TestContext.WriteLine(Logger.ToString());
}
protected virtual void TestStarting()
{
}
protected virtual void TestCleanUp()
{
}
public string GetPluginSettingsJson(string plugin)
{
if (Environment.GetEnvironmentVariable("Docker") == "1")
return File.ReadAllText("/plugin-settings/" + plugin + ".json");
return File.ReadAllText("../../../settings.json");
}
/// <summary>
/// Gets the node parameters for a file
/// </summary>
/// <param name="filename">the name of the file</param>
/// <param name="isDirectory">if the file is a directory</param>
/// <returns>the node parameters</returns>
protected NodeParameters GetNodeParameters(string filename, bool isDirectory =false)
{
string tempPath = TempPath;
string libPath = Path.Combine(tempPath, "media");
if (Directory.Exists(libPath) == false)
Directory.CreateDirectory(libPath);
var args = new NodeParameters(filename, Logger, isDirectory, libPath, new LocalFileService())
{
LibraryFileName = filename,
TempPath = tempPath
};
return args;
}
}
#endif
+90
View File
@@ -0,0 +1,90 @@
using FileFlows.Plugin;
#if (DEBUG)
namespace PluginTestLibrary;
/// <summary>
/// A logger for tests that stores the logs in memory
/// </summary>
public class TestLogger : ILogger
{
private readonly List<string> Messages = new();
/// <summary>
/// Writes an information log message
/// </summary>
/// <param name="args">the log parameters</param>
public void ILog(params object[] args)
=> Log(LogType.Info, args);
/// <summary>
/// Writes an debug log message
/// </summary>
/// <param name="args">the log parameters</param>
public void DLog(params object[] args)
=> Log(LogType.Debug, args);
/// <summary>
/// Writes an warning log message
/// </summary>
/// <param name="args">the log parameters</param>
public void WLog(params object[] args)
=> Log(LogType.Warning, args);
/// <summary>
/// Writes an error log message
/// </summary>
/// <param name="args">the log parameters</param>
public void ELog(params object[] args)
=> Log(LogType.Error, args);
/// <summary>
/// Gets the tail of the log
/// </summary>
/// <param name="length">the number of messages to get</param>
public string GetTail(int length = 50)
{
if (Messages.Count <= length)
return string.Join(Environment.NewLine, Messages);
return string.Join(Environment.NewLine, Messages.TakeLast(50));
}
/// <summary>
/// Logs a message
/// </summary>
/// <param name="type">the type of log to record</param>
/// <param name="args">the arguments of the message</param>
private void Log(LogType type, params object[] args)
{
string message = type + " -> " + string.Join(", ", args.Select(x =>
x == null ? "null" :
x.GetType().IsPrimitive ? x.ToString() :
x is string ? x.ToString() :
System.Text.Json.JsonSerializer.Serialize(x)));
Writer?.Invoke(message);
Messages.Add(message);
}
/// <summary>
/// Gets or sets an optional writer
/// </summary>
public Action<string> Writer { get; set; } = null!;
/// <summary>
/// Returns the entire log as a string
/// </summary>
/// <returns>the entire log</returns>
public override string ToString()
=> string.Join(Environment.NewLine, Messages);
/// <summary>
/// Checks if the log contains the text
/// </summary>
/// <param name="text">the text to check for</param>
/// <returns>true if it contains it, otherwise false</returns>
public bool Contains(string text)
=> Messages.Any(x => x.Contains(text));
}
#endif