问题描述
我正在研究 MoveIt API 以及如何使用 C# 代码使用它。 试图找到一些基于的示例代码 https://docs.ipswitch.com/MOVEit/Transfer2020/API/rest/#operation/POSTapi%2Fv1%2Ffolders%2F%7BId%7D%2Fsubfolders-1.0 要么 https://docs.ipswitch.com/MOVEit/Transfer2019_1/API/Rest/ MoveIt 还有一个“REST API Swagger 用户界面”
另外,我想使用 .NET 核心。我环顾四周找不到太多,终于开始构建一个,现在我已经分享了。
解决方法
我做了大量的研究和试验基础,最后我能够编写一个我认为可以分享的可测试代码。这是一些示例代码,我将模型类放在这里,这些类基于 https://docs.ipswitch.com/MOVEit/Transfer2019_1/API/Rest/ 和 MoveIt 'REST API Swagger User Interface' 它使用 HttpClient 和 HttpClient 工厂。 还添加了两部分的自动化单元测试代码。
//You need Following package
Microsoft.Extension.http
System.Net.Http
System.Net.Http.Json
///////////////////FTPException class
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
namespace FtpUtilityLib.Exceptions
{
[ExcludeFromCodeCoverage]
[Serializable]
public class FtpException : Exception
{
public FtpException()
{
}
public FtpException(string message) : base(message)
{
}
public FtpException(string message,Exception inner) : base(message,inner)
{
}
protected FtpException(
SerializationInfo info,StreamingContext context) : base(info,context)
{
}
}
}
//////////////// Models
using System.Text.Json.Serialization;
namespace FtpUtilityLib.Models
{
public class AuthError
{
[JsonPropertyName("error")]
public string Error { get; set; }
[JsonPropertyName("error_description")]
public string ErrorDescription { get; set; }
}
public class Error
{
[JsonPropertyName("detail")]
public string Detail { get; set; }
[JsonPropertyName("errorCode")]
public int ErrorCode { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; }
}
public class FileItem
{
[JsonPropertyName("uploadUsername")]
public string UploadUsername { get; set; }
[JsonPropertyName("uploadAgentVersion")]
public string UploadAgentVersion { get; set; }
[JsonPropertyName("currentFileType")]
public string CurrentFileType { get; set; }
[JsonPropertyName("hash")]
public string Hash { get; set; }
[JsonPropertyName("dlpMetaData")]
public int DlpMetaData { get; set; }
[JsonPropertyName("dlpChecked")]
public bool DlpChecked { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("dlpBlocked")]
public bool DlpBlocked { get; set; }
[JsonPropertyName("originalFileType")]
public string OriginalFileType { get; set; }
[JsonPropertyName("folderID")]
public string FolderID { get; set; }
[JsonPropertyName("uploadComment")]
public string UploadComment { get; set; }
[JsonPropertyName("uploadStamp")]
public string UploadStamp { get; set; }
[JsonPropertyName("uploadIntegrity")]
public int UploadIntegrity { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("size")]
public int Size { get; set; }
[JsonPropertyName("isNew")]
public bool IsNew { get; set; }
[JsonPropertyName("uploadIP")]
public string UploadIP { get; set; }
[JsonPropertyName("path")]
public string Path { get; set; }
[JsonPropertyName("downloadCount")]
public int DownloadCount { get; set; }
[JsonPropertyName("originalFilename")]
public string OriginalFilename { get; set; }
[JsonPropertyName("orgID")]
public string OrgID { get; set; }
[JsonPropertyName("dlpViolation")]
public string DlpViolation { get; set; }
[JsonPropertyName("uploadAgentBrand")]
public string UploadAgentBrand { get; set; }
[JsonPropertyName("uploadUserFullName")]
public string UploadUserFullName { get; set; }
}
public class FileList
{
[JsonPropertyName("items")]
public List<FileItem> Items { get; set; }
[JsonPropertyName("sorting")]
public List<Sorting> Sorting { get; set; }
[JsonPropertyName("paging")]
public Paging Paging { get; set; }
}
public class FolderItem
{
[JsonPropertyName("subfolderCount")]
public int SubfolderCount { get; set; }
[JsonPropertyName("sharedWithGroupsCount")]
public int SharedWithGroupsCount { get; set; }
[JsonPropertyName("sharedWithUsersCount")]
public int SharedWithUsersCount { get; set; }
[JsonPropertyName("isShared")]
public bool IsShared { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("parentId")]
public string ParentId { get; set; }
[JsonPropertyName("path")]
public string Path { get; set; }
[JsonPropertyName("lastContentChangeTime")]
public string LastContentChangeTime { get; set; }
[JsonPropertyName("permission")]
public Permission Permission { get; set; }
[JsonPropertyName("folderType")]
public string FolderType { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("totalFileCount")]
public int TotalFileCount { get; set; }
}
public class Permission
{
[JsonPropertyName("canListSubfolders")]
public bool CanListSubfolders { get; set; }
[JsonPropertyName("canListFiles")]
public bool CanListFiles { get; set; }
[JsonPropertyName("canChangeSettings")]
public bool CanChangeSettings { get; set; }
[JsonPropertyName("canWriteFiles")]
public bool CanWriteFiles { get; set; }
[JsonPropertyName("canAddSubfolders")]
public bool CanAddSubfolders { get; set; }
[JsonPropertyName("canDelete")]
public bool CanDelete { get; set; }
[JsonPropertyName("canReadFiles")]
public bool CanReadFiles { get; set; }
[JsonPropertyName("canDeleteFiles")]
public bool CanDeleteFiles { get; set; }
[JsonPropertyName("canShare")]
public bool CanShare { get; set; }
}
public class FolderList
{
[JsonPropertyName("paging")]
public Paging Paging { get; set; }
[JsonPropertyName("sorting")]
public List<Sorting> Sorting { get; set; }
[JsonPropertyName("items")]
public List<FolderItem> Items { get; set; }
}
public class Paging
{
[JsonPropertyName("totalPages")]
public int TotalPages { get; set; }
[JsonPropertyName("page")]
public int Page { get; set; }
[JsonPropertyName("totalItems")]
public int TotalItems { get; set; }
[JsonPropertyName("perPage")]
public int PerPage { get; set; }
}
public class Sorting
{
[JsonPropertyName("sortField")]
public string SortField { get; set; }
[JsonPropertyName("sortDirection")]
public string SortDirection { get; set; }
}
public class Token
{
[JsonPropertyName("token_type")]
public string TokenType { get; set; }
[JsonPropertyName("access_token")]
public string AccessToken { get; set; }
[JsonPropertyName("refresh_token")]
public string RefreshToken { get; set; }
[JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
}
public class UnprocessableEntityError
{
[JsonPropertyName("detail")]
public string Detail { get; set; }
[JsonPropertyName("errors")]
public List<SemanticError> SemanticErrors { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("errorCode")]
public int ErrorCode { get; set; }
}
public class SemanticError
{
[JsonPropertyName("field")]
public string Field { get; set; }
[JsonPropertyName("message")]
public string Message { get; set; }
[JsonPropertyName("rejected")]
public string Rejected { get; set; }
}
}
/////////////////////// FtpServiceGateway class
using FtpUtilityLib.Exceptions;
using FtpUtilityLib.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
[assembly: InternalsVisibleTo("MoveItRestApiUtilityLib.Tests")]
namespace FtpUtilityLib
{
internal sealed class FtpApiServiceGateway : IDisposable
{
private const string FtpApiVersionPath = @"api/v1/";
private readonly IHttpClientFactory httpClientFactory;
private bool disposed = false;
public FtpApiServiceGateway(FtpConfiguration ftpConfiguration,IHttpClientFactory httpClientFactory)
{
FtpConfiguration = ftpConfiguration;
this.httpClientFactory = httpClientFactory;
}
public FtpConfiguration FtpConfiguration { get; }
public string AccessToken { get; private set; }
public async Task SignOnToHostAsync()
{
try
{
var httpClient = CreateHttpClient();
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string,string>("grant_type","password"),new KeyValuePair<string,string>("username",FtpConfiguration.Username),string>("password",FtpConfiguration.Password),});
var httpResponse = await httpClient.PostAsync("token",formContent);
await EnsureSignOnSuccessAsync(httpResponse);
var token = await httpResponse.Content.ReadFromJsonAsync<Token>();
AccessToken = token.AccessToken;
}
catch (Exception e)
{
throw new FtpException($"Error in logging to FTP server {FtpConfiguration.Host}. " + e.Message,e);
}
}
public async Task<string> DownloadFileAsync(
string nameOfFileToBeDownloaded,string localFolderPath,string localFileName)
{
var downloadedFileId = string.Empty;
try
{
using var httpClient = CreateHttpClient();
var files = await httpClient.GetFromJsonAsync<FileList>("files?perpage=100");
FileItem file = null;
for (int i = 1; i <= files.Paging.TotalPages; i++)
{
files = await httpClient.GetFromJsonAsync<FileList>($"files?perpage=100&page={i}");
file = files.Items.FirstOrDefault(x =>
x.Name.ToLower().Equals(nameOfFileToBeDownloaded.ToLower(),StringComparison.CurrentCulture));
if (file != null)
{
break;
}
}
downloadedFileId = await SendFileDownloadRequestAsync(nameOfFileToBeDownloaded,localFolderPath,localFileName,file,httpClient,string.Empty);
}
catch (Exception e)
{
throw new FtpException($"Error in downloading the file {nameOfFileToBeDownloaded} from FTP server. " + e.Message,e);
}
return downloadedFileId;
}
public async Task<string> DownloadFileAsync(
string nameOfFileToBeDownloaded,string downloadFilePath,string localFileName)
{
var downloadedFileId = string.Empty;
try
{
using var httpClient = CreateHttpClient();
var folders = await httpClient.GetFromJsonAsync<FolderList>(@$"folders?path={downloadFilePath}");
var folder = folders.Items.SingleOrDefault(x => x.Path.ToLower().Equals(downloadFilePath.ToLower()));
if (folder != null)
{
var files = await httpClient.GetFromJsonAsync<FileList>($"folders/{folder.Id}/files?perPage=100");
var file = files.Items.SingleOrDefault(x =>
x.Name.ToLower().Equals(nameOfFileToBeDownloaded.ToLower(),StringComparison.CurrentCulture));
downloadedFileId = await SendFileDownloadRequestAsync(nameOfFileToBeDownloaded,downloadFilePath);
}
else
{
throw new FtpException($"Folder {downloadFilePath} not found on FTP server");
}
}
catch (Exception e)
{
throw new FtpException($"Error in downloading the file {nameOfFileToBeDownloaded} from FTP server. " + e.Message,e);
}
return downloadedFileId;
}
public async Task<string> UploadFile(string sourceFilePathName,string destinationPath)
{
return await UploadFileAsync(sourceFilePathName,destinationPath,"Comments are not provided");
}
public async Task<string> UploadFileAsync(string sourceFilePathName,string destinationPath,string comments)
{
string uploadedFileId;
try
{
using var multipartFormContent = new MultipartFormDataContent();
using (var sha256 = SHA256.Create())
{
await using var fileStream = File.Open(sourceFilePathName,FileMode.Open);
// Be sure it's positioned to the beginning of the stream.
fileStream.Position = 0;
// Compute the hash of the fileStream.
byte[] hashValue = sha256.ComputeHash(fileStream);
var hash = System.Text.Encoding.UTF8.GetString(hashValue,hashValue.Length);
multipartFormContent.Add(new StringContent("hash"),hash);
multipartFormContent.Add(new StringContent("hashtype"),"sha-256");
multipartFormContent.Add(new StringContent("comments"),comments);
// Close the file.
fileStream.Close();
}
await using (var fileStream = File.OpenRead(sourceFilePathName))
{
using var httpClient = CreateHttpClient();
var folders = await httpClient.GetFromJsonAsync<FolderList>(@$"folders?path={destinationPath}");
var folder = folders.Items.SingleOrDefault(x => x.Path.ToLower().Equals(destinationPath.ToLower()));
if (folder != null)
{
multipartFormContent.Add(new StreamContent(fileStream),"file",Path.GetFileName(sourceFilePathName));
var response = await httpClient.PostAsync($"folders/{folder.Id}/files",multipartFormContent);
await EnsureSuccessAsync(response);
var file = await response.Content.ReadFromJsonAsync<FileItem>();
uploadedFileId = file.Id;
}
else
{
throw new FtpException($"Folder {destinationPath} not found on FTP server");
}
fileStream.Close();
}
}
catch (Exception e)
{
throw new FtpException($"Error in Uploading the file {sourceFilePathName} to the folder {destinationPath} on FTP server. " + e.Message,e);
}
return uploadedFileId;
}
public async Task<string> UploadFileFromBufferAsync(byte[] buffer,string fileName,string comments)
{
string uploadedFileId;
try
{
using var multipartFormContent = new MultipartFormDataContent();
using var sha256 = SHA256.Create();
// Compute the hash of the fileStream.
byte[] hashValue = sha256.ComputeHash(buffer);
var hash = Encoding.UTF8.GetString(hashValue,hashValue.Length);
multipartFormContent.Add(new StringContent("hash"),hash);
multipartFormContent.Add(new StringContent("hashtype"),"sha-256");
multipartFormContent.Add(new StringContent("comments"),comments);
using var httpClient = CreateHttpClient();
var folders = await httpClient.GetFromJsonAsync<FolderList>(@$"folders?path={destinationPath}");
var folder = folders.Items.SingleOrDefault(x => x.Path.ToLower().Equals(destinationPath.ToLower()));
if (folder != null)
{
await using (var memoryStreamToUpload = new MemoryStream(buffer))
{
multipartFormContent.Add(new StreamContent(memoryStreamToUpload),fileName);
var response = await httpClient.PostAsync($"folders/{folder.Id}/files",multipartFormContent);
await EnsureSuccessAsync(response);
var file = await response.Content.ReadFromJsonAsync<FileItem>();
uploadedFileId = file.Id;
memoryStreamToUpload.Close();
}
}
else
{
throw new FtpException($"Folder {destinationPath} not found on FTP server");
}
}
catch (Exception e)
{
throw new FtpException($"Error in Uploading to the folder {destinationPath} on FTP server. " + e.Message,e);
}
return uploadedFileId;
}
private static async Task EnsureSuccessAsync(HttpResponseMessage httpResponseMessage)
{
if (httpResponseMessage.IsSuccessStatusCode)
{
return;
}
var reasonPhrase = httpResponseMessage.ReasonPhrase;
switch (httpResponseMessage.StatusCode)
{
case HttpStatusCode.UnprocessableEntity:
var unprocessableEntityError = await httpResponseMessage.Content.ReadFromJsonAsync<UnprocessableEntityError>();
var semanticErrors = new StringBuilder();
foreach (var detail in unprocessableEntityError.SemanticErrors.Select(
semanticError => $"Field: {semanticError.Field},Rejected: {semanticError.Rejected},Message: {semanticError.Message}"))
{
semanticErrors.AppendLine(detail);
}
throw new HttpRequestException(
"Calling the FTP Service resulting in an error with HttpStatusCode:" +
$" {httpResponseMessage.StatusCode},ErrorCode:{unprocessableEntityError.ErrorCode} | ErrorDetail: {unprocessableEntityError.Detail} | {semanticErrors}");
default:
var error = await httpResponseMessage.Content.ReadFromJsonAsync<Error>();
throw new HttpRequestException(
$"Calling the FTP Service resulting in an error with unexpected HttpStatusCode: " +
$"{httpResponseMessage.StatusCode},ErrorCode:{error.ErrorCode} | Detail: {error.Detail} | HttpReason: {reasonPhrase}");
}
}
private static async Task EnsureSignOnSuccessAsync(HttpResponseMessage httpResponseMessage)
{
if (httpResponseMessage.IsSuccessStatusCode)
{
return;
}
var reasonPhrase = httpResponseMessage.ReasonPhrase;
var authErrorContent = await httpResponseMessage.Content.ReadAsStringAsync();
var authError = JsonSerializer.Deserialize<AuthError>(authErrorContent);
throw new HttpRequestException(
$"Calling the FTP Service resulting in an error with HttpStatusCode:" +
$" {httpResponseMessage.StatusCode},ErrorCode:{authError.Error} | Description: {authError.ErrorDescription} | HttpReason: {reasonPhrase}");
}
private HttpClient CreateHttpClient()
{
HttpClient httpClient = httpClientFactory.CreateClient("FtpAPIClient");
httpClient.BaseAddress = new Uri($@"{FtpConfiguration.Host}/{FtpApiVersionPath}");
httpClient.DefaultRequestHeaders.Add("accept","application/json");
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer",AccessToken);
return httpClient;
}
private async Task<string> SendFileDownloadRequestAsync(
string nameOfFileToBeDownloaded,string localFileName,FileItem file,HttpClient httpClient,string downloadFilePath)
{
string downloadedFileId;
if (file != null)
{
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Add("accept","application/octet-stream");
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer",AccessToken);
var downloadResponse = await httpClient.GetAsync($"files/{file.Id}/download");
await EnsureSuccessAsync(downloadResponse);
await using var streamToReadFrom = await downloadResponse.Content.ReadAsStreamAsync();
await using var fs = new FileStream(@$"{localFolderPath}\{localFileName}",FileMode.OpenOrCreate);
await streamToReadFrom.CopyToAsync(fs);
fs.Close();
downloadedFileId = file.Id;
}
else
{
throw new FtpException(
$"File {nameOfFileToBeDownloaded} under folder {downloadFilePath} not found on FTP server");
}
return downloadedFileId;
}
private void Dispose(bool disposing)
{
if (disposing && !disposed)
{
try
{
using var httpClient = CreateHttpClient();
var httpResponse = httpClient.PostAsync($"{AccessToken}/revoke",null);
}
catch (Exception e)
{
// see where can we log error
}
disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
/////////////////////////////FtpConfiguration
using System;
namespace FtpUtilityLib
{
public sealed class FtpConfiguration
{
private int maxNumberOfSignOnAttempts;
private int waitTimeBeforeNextAttemptInMilliseconds;
public FtpConfiguration(string host,string username,string password)
{
WaitTimeBeforeNextAttemptInMilliseconds = 30000;
MaxNumberOfSignOnAttempts = 3;
Host = host;
Username = username;
Password = password;
}
public FtpConfiguration(
string host,string password,int waitTimeBeforeNextAttemptInMilliseconds,int maxNumberOfSignOnAttempts)
{
WaitTimeBeforeNextAttemptInMilliseconds = waitTimeBeforeNextAttemptInMilliseconds;
MaxNumberOfSignOnAttempts = maxNumberOfSignOnAttempts;
Host = host;
Username = username;
Password = password;
}
public string Username { get; }
public string Password { get; }
public string Host { get; }
public int MaxNumberOfSignOnAttempts
{
get => maxNumberOfSignOnAttempts;
private set
{
if (value < 1 || value > 6)
throw new ArgumentOutOfRangeException(
$"MaxNumberOfSignOnAttempts- Allowed range of value is between 1 and 6. You have provided {value}");
maxNumberOfSignOnAttempts = value;
}
}
public int WaitTimeBeforeNextAttemptInMilliseconds
{
get => waitTimeBeforeNextAttemptInMilliseconds;
private set
{
if (value < 1000 || value > 60000)
throw new ArgumentOutOfRangeException(
$"WaitTimeBeforeNextAttemptInMilliseconds- Allowed range of value is between 1000 and 60000. You have provided {value}");
waitTimeBeforeNextAttemptInMilliseconds = value;
}
}
}
}
/////////////////HttpClientFactory
using System.Net.Http;
namespace FtpUtilityLib
{
internal sealed class HttpClientFactory : IHttpClientFactory
{
public HttpClient CreateClient(string name)
{
return new HttpClient();
}
}
}
,
下面是用于自动化单元测试的测试类。由于我可以发布的文本限制,添加了 2 部分。,这是第 1 部分。您可以使用编译错误来调整引用。 TestTResponses 类在第 -2 部分上传
///Create a TestFiles folder under MSTestProject and then create SampleFile.json file in that folder. Go to properties of file select copyAlways for CopyToOutputDirectory
using FtpUtilityLib.Models;
using System.Collections.Generic;
namespace MoveItRestApiUtilityLib.Tests
{
//////////////////////FakeHttpMessageHandler
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
public class FakeHttpMessageHandler : DelegatingHandler
{
public Dictionary<(string,string),HttpResponseMessage> DesiredHttpResponseMessagesByUriAbsolutePath { get; }
public FakeHttpMessageHandler()
{
DesiredHttpResponseMessagesByUriAbsolutePath = new Dictionary<(string,HttpResponseMessage>();
//PopulateDesiredResponseList();
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,CancellationToken cancellationToken)
{
if (request.RequestUri.Query.Contains("perpage=100&page="))
{
return new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,Content = new StringContent(JsonSerializer.Serialize(TestResponse.FilesResponse),Encoding.UTF8,"application/json")
};
}
var requestUriAbsolutePath = request.RequestUri.AbsolutePath;
var responseMessage = DesiredHttpResponseMessagesByUriAbsolutePath[(requestUriAbsolutePath,request.Method.Method.ToLower())];
return await Task.FromResult(responseMessage);
}
}
/////////////////////////////
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
public static class AssertEx
{
public static void AssertExceptionMessageContains(IEnumerable<string> expected,string actual)
{
var messages = new List<string>();
foreach (var item in expected)
{
if (actual.IndexOf(item,StringComparison.OrdinalIgnoreCase) < 0)
{
messages.Add("Expected to find the substring: \"" + item + "\",in the Exception Message: " + actual);
}
}
if (messages.Any())
{
throw new AssertFailedException(String.Join(",",messages));
}
}
}
//////////////////////////////Partial Test Class in a Test file
[ExcludeFromCodeCoverage]
[TestClass]
public partial class FtpApiServiceGatewayTests
{
public static string HostName => "https://ATestHost.com";
//private Mock httpClientFactoryMock = new Mock<IHttpClientFactory>();
FtpConfiguration ftpConfiguration =
new FtpConfiguration(HostName,"SomeUser","SomePassword");
[TestMethod]
public async Task SignOnToHost_WhenValidUserIdAndPassword_ShouldSuccess()
{
//ARRANGE
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler();
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/token","post"),new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,Content = new StringContent(JsonSerializer.Serialize(TestResponse.TokenResponse),"application/json")
});
var fakeHttpClient = new HttpClient(fakeHttpMessageHandler);
httpClientFactoryMock.Setup(h => h.CreateClient(It.IsAny<string>())).Returns(fakeHttpClient);
var ftpApiServiceGateway = new FtpApiServiceGateway(ftpConfiguration,httpClientFactoryMock.Object);
//ACT
await ftpApiServiceGateway.SignOnToHostAsync();
//ASSERT
Assert.AreEqual(TestResponse.TokenResponse.AccessToken,ftpApiServiceGateway.AccessToken);
}
[TestMethod]
public async Task SignOnToHost_WhenInvalidUserIdOrPassword_ShouldThrowException()
{
//ARRANGE
var r = JsonSerializer.Serialize(TestResponse.TokenErrorResponse);
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler();
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/token",new HttpResponseMessage()
{
StatusCode = HttpStatusCode.BadRequest,Content = new StringContent(
JsonSerializer.Serialize(
TestResponse.TokenErrorResponse),httpClientFactoryMock.Object);
//ACT
try
{
await ftpApiServiceGateway.SignOnToHostAsync();
Assert.Fail("Expected Exception of type: FtpException during this test,but it was not thrown");
}
catch (FtpException e)
{
//ASSERT
AssertEx.AssertExceptionMessageContains(new string[]
{
"Error in logging to FTP server",HostName,TestResponse.TokenErrorResponse.ErrorDescription,TestResponse.TokenErrorResponse.Error,"Calling the FTP Service resulting in an error with HttpStatusCode:"
},e.Message);
}
}
[TestMethod]
public async Task DownloadFileAsync_ByFilesAPI_WhenValidFile_ShouldSuccess()
{
//ARRANGE
var nameOfFileToBeDownloaded = "SampleFile.json";
var localFolderPath = @".\TestFiles";
var localFileName = "ATest2.json";
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler();
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/files","get"),"application/json")
});
var filePathName = @".\TestFiles\SampleFile.json";
using var fileStream = File.OpenRead(filePathName);
var ms = new MemoryStream();
fileStream.CopyTo(ms);
var content = new ByteArrayContent(ms.GetBuffer());
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "SampleFile.json"
};
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/files/769379085/download",ReasonPhrase = "OK",Content = content
});
var fakeHttpClient = new HttpClient(fakeHttpMessageHandler);
httpClientFactoryMock.Setup(h => h.CreateClient(It.IsAny<string>())).Returns(fakeHttpClient);
var ftpApiServiceGateway = new FtpApiServiceGateway(ftpConfiguration,httpClientFactoryMock.Object);
//ACT
var fileId = await ftpApiServiceGateway.DownloadFileAsync(
nameOfFileToBeDownloaded,localFileName);
//ASSERT
Assert.IsFalse(string.IsNullOrEmpty(fileId));
}
[TestMethod]
public async Task DownloadFileAsync_WhenValidFile_ShouldSuccess()
{
//ARRANGE
var nameOfFileToBeDownloaded = "SampleFile.json";
var downloadFilePath = TestResponse.FtpServerTestFolderPath;
var localFolderPath = @".\TestFiles";
var localFileName = "ATest2.json";
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler();
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders",new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,Content = new StringContent(JsonSerializer.Serialize(TestResponse.FoldersResponse),"application/json")
});
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders/715431145/files",downloadFilePath,localFileName);
//ASSERT
Assert.IsFalse(string.IsNullOrEmpty(fileId));
}
[TestMethod]
public async Task DownloadFileAsync_WhenInvalidSourceFolder_ThrowsException()
{
//ARRANGE
var nameOfFileToBeDownloaded = "SampleFile.json";
var downloadFilePath = @"/Data/InvalidFolder";
var localFolderPath = @".\TestFiles";
var localFileName = "ATest2.json";
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler();
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders",Content = new StringContent(JsonSerializer.Serialize(TestResponse.FolderNotFoundResponse),"application/json")
});
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders/715431145/files","application/json")
});
var filePathName = @".\TestFiles\SampleFile.json";
using var fileStream = File.OpenRead(filePathName);
var ms = new MemoryStream();
fileStream.CopyTo(ms);
var content = new ByteArrayContent(ms.GetBuffer());
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = nameOfFileToBeDownloaded
};
var fakeHttpClient = new HttpClient(fakeHttpMessageHandler);
httpClientFactoryMock.Setup(h => h.CreateClient(It.IsAny<string>())).Returns(fakeHttpClient);
var ftpApiServiceGateway = new FtpApiServiceGateway(ftpConfiguration,httpClientFactoryMock.Object);
try
{
//ACT
var fileId = await ftpApiServiceGateway.DownloadFileAsync(
nameOfFileToBeDownloaded,localFileName);
//ASSERT
Assert.Fail("Expected Exception of type: FtpException during this test,but it was not thrown");
}
catch (FtpException e)
{
//Assert
AssertEx.AssertExceptionMessageContains(new string[]
{
$"Folder {downloadFilePath} not found on FTP server"
},e.Message);
}
}
[TestMethod]
public async Task DownloadFileAsync_WhenInvalidSourceFilename_ThrowsException()
{
//ARRANGE
var nameOfFileToBeDownloaded = "InvalidFile.json";
var downloadFilePath = TestResponse.FtpServerTestFolderPath;
var localFolderPath = @"C:\TestFolder\Test";
var localFileName = "ATest2.json";
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler();
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders",but it was not thrown");
}
catch (FtpException e)
{
//Assert
AssertEx.AssertExceptionMessageContains(new string[]
{
$"File {nameOfFileToBeDownloaded} under folder {downloadFilePath} not found on FTP server"
},e.Message);
}
}
[TestMethod]
public async Task DownloadFileAsync_WhenAccessDenied_ThrowsException()
{
//ARRANGE
var nameOfFileToBeDownloaded = "InvalidFile.json";
var downloadFilePath = TestResponse.FtpServerTestFolderPath;
var localFolderPath = @"C:\TestFolder\Test";
var localFileName = "ATest2.json";
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler();
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders",new HttpResponseMessage()
{
StatusCode = HttpStatusCode.Forbidden,Content = new StringContent(JsonSerializer.Serialize(TestResponse.ErrorResponse),but it was not thrown");
}
catch (FtpException e)
{
//Assert
AssertEx.AssertExceptionMessageContains(new string[]
{
"Error in downloading the file",nameOfFileToBeDownloaded,"from FTP server"
},e.Message);
}
}
[TestMethod]
public async Task DownloadFileAsync_WhenInternalServerError_ThrowsException()
{
//ARRANGE
var nameOfFileToBeDownloaded = "SampleFile.json";
var downloadFilePath = TestResponse.FtpServerTestFolderPath;
var localFolderPath = @"C:TestFTP\Download";
var localFileName = "ATest2.json";
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler();
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders",new HttpResponseMessage()
{
StatusCode = HttpStatusCode.InternalServerError,"application/json")
});
// var dir = Directory.GetCurrentDirectory();
var filePathName = @".\TestFiles\SampleFile.json";
using var fileStream = File.OpenRead(filePathName);
var ms = new MemoryStream();
fileStream.CopyTo(ms);
var content = new ByteArrayContent(ms.GetBuffer());
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = nameOfFileToBeDownloaded
};
var fakeHttpClient = new HttpClient(fakeHttpMessageHandler);
httpClientFactoryMock.Setup(h => h.CreateClient(It.IsAny<string>())).Returns(fakeHttpClient);
var ftpApiServiceGateway = new FtpApiServiceGateway(ftpConfiguration,e.Message);
}
}
}
,
这是自动化测试的第 -2 部分。
///////////////TestResponses class
public static class TestResponse
{
public static Token TokenResponse = new Token()
{
AccessToken = "TestTokenMwzVguxPqzF6WpIXSDfIinPr2ZgX3zBcCYo-qguVYMHSQxmwNEsdVEblVQdWAxUHriBcPaVIwZMxeNy6b5fPD_cZdtgNMiDjdKClRCM9Ps8yg",ExpiresIn = 1199,RefreshToken = "TestRefreshToken57Bne4Cs8lSXx4OYbwjsAYaJBS4uNpAN_mIrawiyC1KmGBc6YCIxe7nEQXhcCPHN3mi8nby3JkIPX190g1Ducjm5hxRHBGOmjMbDeZ35mEjvbUSFbXHrl7xzWPidkcvAg2bomkKDpwojoKxW1RkfR2UG_Qb_o7hrYS4JdQOUm3_uQcvvW9q5uR3snOEiM3Ge7U39HUBOSqOhQ",TokenType = "Bearer"
};
public static string FtpServerTestFolderPath = @"/Data/CSS_IntTest";
public static FolderList FoldersResponse =>
new FolderList()
{
Items = new List<FolderItem>()
{
new FolderItem()
{
FolderType = "Normal",Id = "715431145",IsShared = true,LastContentChangeTime = "2021-05-27T09:58:16",Name = "CSS_IntTest",ParentId = "510768038",Path = FtpServerTestFolderPath
},new FolderItem()
{
FolderType = "Normal",Id = "71556789",LastContentChangeTime = "2020-05-29T09:58:16",Name = "CSS_IntTest2",Path = @"/Data/CSS_IntTest2"
}
},Paging = new Paging()
{
Page = 1,PerPage = 25,TotalItems = 1,TotalPages = 1
},Sorting = new List<Sorting>()
{
new Sorting()
{
SortDirection = "asc",SortField = "Path",}
}
};
public static FolderList FolderNotFoundResponse =>
new FolderList()
{
Items = new List<FolderItem>(),TotalItems = 0,}
}
};
public static FileList FilesResponse =>
new FileList()
{
Items = new List<FileItem>()
{
new FileItem()
{
CurrentFileType = null,DownloadCount = 0,FolderID = null,Hash = null,Id = "769379085",Name = "samplefile.json",//OrgId = null,OriginalFileType = null,OriginalFilename =null,Path = @$"{FtpServerTestFolderPath}\samplefile.json",Size = 35935783,UploadAgentBrand = null,UploadAgentVersion = null,UploadComment = null,UploadIP = null,UploadIntegrity= 0,UploadStamp ="2021-05-21T16:45:13",UploadUserFullName = null,//UploadUserRealname = null,UploadUsername = null
},new FileItem()
{
CurrentFileType = null,Id = "769379088",Name = "TestAssociate.json",Path = @$"{FtpServerTestFolderPath}/TestAssociate.json",Size = 541,UploadStamp ="2021-05-27T09:58:16 ",UploadUsername = null
}
},PerPage = 100,TotalItems = 2,SortField = "Name",}
}
};
public static FileItem FileResponse =>
new FileItem()
{
CurrentFileType = null,OriginalFilename = null,Path = @$"{FtpServerTestFolderPath }/samplefile.json",UploadComment = null,UploadIP = null,UploadIntegrity = 0,UploadStamp = "2021-05-27T09:58:16 ",// UploadUserRealname = null,UploadUsername = null
};
public static FolderItem FolderResponse =>
new FolderItem()
{
FolderType = "Normal",Path = FtpServerTestFolderPath
};
public static AuthError TokenErrorResponse =>
new AuthError()
{
Error = "This is a Test error",ErrorDescription = "This is a Test error Description"
};
public static Error ErrorResponse =>
new Error()
{
Detail = "This is a test error detail",ErrorCode = 4567,Title = "This a test error title"
};
public static UnprocessableEntityError UnprocessableEntityResponse =>
new UnprocessableEntityError()
{
SemanticErrors = new List<SemanticError>()
{
new SemanticError()
{
Field = "fileName",Message = "Invalid character in fileName",Rejected = "Yes"
},new SemanticError()
{
Field = "Header",Message = "Invalid Value",Rejected = "Yes"
}
},Detail = "This is a test UnprocessableEntityError detail",ErrorCode = 1234,Title = "This a test error title"
};
}
/////////////////////////// Another test file
public partial class FtpApiServiceGatewayTests
{
[TestMethod]
public async Task UploadFileFromBufferAsync_WhenValidFile_ShouldSuccess()
{
//ARRANGE
var fileName = "samplefile.json";
var destinationFolder = TestResponse.FtpServerTestFolderPath;
var sourceFolder = @".\TestFiles";
await using var fileStream = File.Open($"{sourceFolder}/{fileName}",FileMode.Open);
// Be sure it's positioned to the beginning of the stream.
fileStream.Position = 0;
var memoryStream = new MemoryStream();
fileStream.CopyTo(memoryStream);
var content = memoryStream.GetBuffer();
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler();
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders",new HttpResponseMessage()
{
StatusCode = HttpStatusCode.Created,Content = new StringContent(JsonSerializer.Serialize(TestResponse.FileResponse),httpClientFactoryMock.Object);
//ACT
var fileId = await ftpApiServiceGateway.UploadFileFromBufferAsync(
content,destinationFolder,"ATest2.json","This is a test comment");
//ASSERT
Assert.IsFalse(string.IsNullOrEmpty(fileId));
}
[TestMethod]
public async Task UploadFileFromBufferAsync_WhenInvalidDestination_ShouldSuccess()
{
//ARRANGE
var fileName = "samplefile.json";
var destinationFolder = TestResponse.FtpServerTestFolderPath + "/InValidFolder";
var sourceFolder = @".\TestFiles";
await using var fileStream = File.Open($"{sourceFolder}/{fileName}",FileMode.Open);
// Be sure it's positioned to the beginning of the stream.
fileStream.Position = 0;
var memoryStream = new MemoryStream();
fileStream.CopyTo(memoryStream);
var content = memoryStream.GetBuffer();
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler(); fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders",new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,"application/json")
}); fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders/715431145/files","application/json")
});
var fakeHttpClient = new HttpClient(fakeHttpMessageHandler);
httpClientFactoryMock.Setup(h => h.CreateClient(It.IsAny<string>())).Returns(fakeHttpClient);
var ftpApiServiceGateway = new FtpApiServiceGateway(ftpConfiguration,httpClientFactoryMock.Object);
try
{
var fileId = await ftpApiServiceGateway.UploadFileFromBufferAsync(
content,"This is a test comment");
//ASSERT
Assert.Fail("Expected Exception of type: FtpException during this test,but it was not thrown");
}
catch (FtpException e)
{
//Assert
AssertEx.AssertExceptionMessageContains(new string[]
{
$"Folder {destinationFolder} not found on FTP server","Error in Uploading to the folder","To the folder","on FTP server"
},e.Message);
}
}
[TestMethod]
public async Task UploadFileFromBufferAsync_WhenInternalServerError_ShouldSuccess()
{
//ARRANGE
var fileName = "samplefile.json";
var destinationFolder = TestResponse.FtpServerTestFolderPath;
var sourceFolder = @".\TestFiles";
await using var fileStream = File.Open($"{sourceFolder}/{fileName}","application/json")
});
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders/715431145/files",httpClientFactoryMock.Object);
//ACT
try
{
var fileId = await ftpApiServiceGateway.UploadFileFromBufferAsync(
content,"This is a test comment");
Assert.Fail("Expected Exception of type: FtpException during this test,but it was not thrown");
}
catch (FtpException e)
{
AssertEx.AssertExceptionMessageContains(new string[]
{
$"Calling the FTP Service resulting in an error with unexpected HttpStatusCode:",HttpStatusCode.InternalServerError.ToString(),TestResponse.ErrorResponse.Detail,TestResponse.ErrorResponse.ErrorCode.ToString(),e.Message);
}
}
[TestMethod]
public async Task UploadFileAsync_WhenValidFile_ShouldSuccess()
{
//ARRANGE
var destinationPath = TestResponse.FtpServerTestFolderPath;
var sourceFilePathName = @".\TestFiles\samplefile.json";
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler();
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders",httpClientFactoryMock.Object);
//ACT
var fileId = await ftpApiServiceGateway.UploadFile(
sourceFilePathName,destinationPath);
Assert.IsFalse(string.IsNullOrEmpty(fileId));
}
[TestMethod]
public async Task UploadFileAsync_WhenInValidDestinationFolder_ThrowsException()
{
//ARRANGE
var destinationPath = TestResponse.FtpServerTestFolderPath + "/InValidFolder";
var sourceFilePathName = @".\TestFiles\samplefile.json";
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler();
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders",httpClientFactoryMock.Object);
//ACT
try
{
var fileId = await ftpApiServiceGateway.UploadFile(
sourceFilePathName,destinationPath);
Assert.Fail("Expected Exception of type: FtpException during this test,but it was not thrown");
}
catch (FtpException e)
{
//Assert
AssertEx.AssertExceptionMessageContains(new string[]
{
$"Folder {destinationPath} not found on FTP server","Error in Uploading the file",sourceFilePathName,e.Message);
}
}
[TestMethod]
public async Task UploadFileAsync_WhenFailedWithHttp422_ThrowsException()
{
//ARRANGE
var destinationPath = TestResponse.FtpServerTestFolderPath;
var sourceFilePathName = @".\TestFiles\samplefile.json";
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var fakeHttpMessageHandler = new FakeHttpMessageHandler();
fakeHttpMessageHandler.DesiredHttpResponseMessagesByUriAbsolutePath.Add(
("/api/v1/folders",new HttpResponseMessage()
{
StatusCode = HttpStatusCode.UnprocessableEntity,Content = new StringContent(JsonSerializer.Serialize(TestResponse.UnprocessableEntityResponse),but it was not thrown");
}
catch (FtpException e)
{
//Assert
AssertEx.AssertExceptionMessageContains(new string[]
{
$"Calling the FTP Service resulting in an error with HttpStatusCode",HttpStatusCode.UnprocessableEntity.ToString(),TestResponse.UnprocessableEntityResponse.ErrorCode.ToString(),TestResponse.UnprocessableEntityResponse.Detail,TestResponse.UnprocessableEntityResponse.SemanticErrors[0].Message,TestResponse.UnprocessableEntityResponse.SemanticErrors[1].Message,$"Error in Uploading the file",e.Message);
}
}
}
}