using System;
using System.IO;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Renci.SshNet.Tests.Common
{
///
/// Base class for tests that replay a scripted server transcript (see )
/// into a download, with an isolated destination directory.
///
///
/// The destination is <root>/a/b/dest below a unique temporary root, so that anything
/// written above it, into a/b or a, is detected by .
///
public abstract class ScpClientTranscriptTestBase
{
///
/// Gets the temporary root directory, which is deleted after each test.
///
protected string Root { get; private set; }
///
/// Gets the destination directory, <root>/a/b/dest.
///
protected string Destination { get; private set; }
///
/// Gets the parent of the destination directory, <root>/a/b.
///
protected string DestinationParent
{
get
{
return Path.GetDirectoryName(Destination);
}
}
[TestInitialize]
public void CreateDestination()
{
Root = Path.Combine(Path.GetTempPath(), "sshnet-scp-" + Guid.NewGuid().ToString("N"));
Destination = Directory.CreateDirectory(Path.Combine(Root, "a", "b", "dest")).FullName;
}
[TestCleanup]
public void DeleteRoot()
{
if (Root != null && Directory.Exists(Root))
{
Directory.Delete(LiteralPath(Root), recursive: true);
}
}
///
/// Returns the same path with the \\?\ prefix on Windows, which turns off Win32 path
/// normalization, so that entries with names such as file. or CON (which some
/// tests create on purpose) can be read and deleted.
///
protected static string LiteralPath(string path)
{
return Path.DirectorySeparatorChar == '\\' ? @"\\?\" + path : path;
}
///
/// Lists every file system entry below , as paths relative to it using
/// '/' as separator, in no particular order.
///
protected static string[] ListEntries(string directory)
{
return Directory.GetFileSystemEntries(directory, "*", SearchOption.AllDirectories)
.Select(p => p.Substring(directory.Length + 1).Replace(Path.DirectorySeparatorChar, '/'))
.ToArray();
}
///
/// Runs a recursive download of into the destination directory, or
/// into if specified (for example, the destination spelled
/// differently).
///
protected ScpTranscriptResult DownloadDirectory(ScpServerTranscript transcript, string destinationPath = null)
{
return transcript.Run(
client => client.Download("/remote", new DirectoryInfo(destinationPath ?? Destination)),
closeChannel: true);
}
///
/// Asserts that nothing exists below the root other than the directories leading to the
/// destination, the contents of the destination, and (a path
/// relative to the root, using '/' as separator), if specified.
///
/// An entry outside of the destination that the test created itself.
/// A description of the scenario, included in the failure message.
protected void AssertNothingOutsideDestination(string expectedOutside = null, string context = null)
{
var outside = ListEntries(Root).Where(p => p is not "a" and not "a/b" and not "a/b/dest" &&
!p.StartsWith("a/b/dest/", StringComparison.Ordinal) &&
!string.Equals(p, expectedOutside, StringComparison.Ordinal))
.ToArray();
Assert.IsEmpty(outside, context + " Written outside of the destination: " + string.Join(", ", outside));
}
///
/// Deletes everything in the destination directory.
///
protected void ClearDestination()
{
foreach (var entry in Directory.GetFileSystemEntries(LiteralPath(Destination)))
{
if (Directory.Exists(entry))
{
Directory.Delete(entry, recursive: true);
}
else
{
File.Delete(entry);
}
}
}
}
}