Skip to content

Commit

Permalink
Merge pull request #20 from GerardSmit/feature/upload-stream
Browse files Browse the repository at this point in the history
Added Stream to UploadAsync
  • Loading branch information
josiasmontag authored Jan 2, 2023
2 parents 581056c + 73d8d91 commit 74693ff
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 42 deletions.
6 changes: 5 additions & 1 deletion CloudConvert.API/CloudConvert.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<IsPackable>true</IsPackable>
</PropertyGroup>


<ItemGroup>
<InternalsVisibleTo Include="CloudConvert.Test" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2021.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
Expand Down
19 changes: 13 additions & 6 deletions CloudConvert.API/CloudConvertAPI.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
Expand Down Expand Up @@ -33,6 +34,7 @@ public interface ICloudConvertAPI
#endregion

Task<string> UploadAsync(string url, byte[] file, string fileName, object parameters);
Task<string> UploadAsync(string url, Stream file, string fileName, object parameters);
bool ValidateWebhookSignatures(string payloadString, string signature, string signingSecret);
string CreateSignedUrl(string baseUrl, string signingSecret, JobCreateRequest job, string cacheKey = null);
}
Expand All @@ -50,13 +52,17 @@ public class CloudConvertAPI : ICloudConvertAPI
const string publicUrlSyncApi = "https://sync.api.cloudconvert.com/v2";
static readonly char[] base64Padding = { '=' };


public CloudConvertAPI(string api_key, bool isSandbox = false)
internal CloudConvertAPI(RestHelper restHelper, string api_key, bool isSandbox = false)
{
_apiUrl = isSandbox ? sandboxUrlApi : publicUrlApi;
_apiSyncUrl = isSandbox ? sandboxUrlSyncApi : publicUrlSyncApi;
_api_key += api_key;
_restHelper = new RestHelper();
_restHelper = restHelper;
}

public CloudConvertAPI(string api_key, bool isSandbox = false)
: this(new RestHelper(), api_key, isSandbox)
{
}

public CloudConvertAPI(string url, string api_key)
Expand All @@ -82,7 +88,7 @@ private HttpRequestMessage GetRequest(string endpoint, HttpMethod method, object
return request;
}

private HttpRequestMessage GetMultipartFormDataRequest(string endpoint, HttpMethod method, byte[] file, string fileName, Dictionary<string, string> parameters = null)
private HttpRequestMessage GetMultipartFormDataRequest(string endpoint, HttpMethod method, HttpContent fileContent, string fileName, Dictionary<string, string> parameters = null)
{
var content = new MultipartFormDataContent();
var request = new HttpRequestMessage { RequestUri = new Uri(endpoint), Method = method, };
Expand All @@ -95,7 +101,6 @@ private HttpRequestMessage GetMultipartFormDataRequest(string endpoint, HttpMeth
}
}

var fileContent = new ByteArrayContent(file);
fileContent.Headers.Add("Content-Disposition", $"form-data; name=\"file\"; filename=\"{ new string(Encoding.UTF8.GetBytes(fileName).Select(b => (char)b).ToArray())}\"");
content.Add(fileContent);

Expand Down Expand Up @@ -214,7 +219,9 @@ private HttpRequestMessage GetMultipartFormDataRequest(string endpoint, HttpMeth

#endregion

public Task<string> UploadAsync(string url, byte[] file, string fileName, object parameters) => _restHelper.RequestAsync(GetMultipartFormDataRequest($"{url}", HttpMethod.Post, file, fileName, GetParameters(parameters)));
public Task<string> UploadAsync(string url, byte[] file, string fileName, object parameters) => _restHelper.RequestAsync(GetMultipartFormDataRequest(url, HttpMethod.Post, new ByteArrayContent(file), fileName, GetParameters(parameters)));

public Task<string> UploadAsync(string url, Stream stream, string fileName, object parameters) => _restHelper.RequestAsync(GetMultipartFormDataRequest(url, HttpMethod.Post, new StreamContent(stream), fileName, GetParameters(parameters)));

public string CreateSignedUrl(string baseUrl, string signingSecret, JobCreateRequest job, string cacheKey = null)
{
Expand Down
5 changes: 5 additions & 0 deletions CloudConvert.API/RestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ internal RestHelper()
_httpClient.Timeout = System.TimeSpan.FromMilliseconds(System.Threading.Timeout.Infinite);
}

internal RestHelper(HttpClient httpClient)
{
_httpClient = httpClient;
}

public async Task<T> RequestAsync<T>(HttpRequestMessage request)
{
var response = await _httpClient.SendAsync(request);
Expand Down
37 changes: 37 additions & 0 deletions CloudConvert.Test/Extensions/MockExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Moq.Language.Flow;
using Moq.Protected;

namespace CloudConvert.Test.Extensions
{
public static class MockExtensions
{
public static IReturnsResult<HttpMessageHandler> MockResponse(this Mock<HttpMessageHandler> mock, string endpoint, string fileName)
{
return mock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync",
ItExpr.Is<HttpRequestMessage>(message => message.RequestUri.AbsolutePath.EndsWith(endpoint)),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Responses", fileName)))
});
}

public static void VerifyRequest(this Mock<HttpMessageHandler> mock, string endpoint, Times times)
{
mock.Protected()
.Verify("SendAsync",
times,
ItExpr.Is<HttpRequestMessage>(message => message.RequestUri.AbsolutePath.EndsWith(endpoint)),
ItExpr.IsAny<CancellationToken>());
}
}
}
46 changes: 38 additions & 8 deletions CloudConvert.Test/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ public void Setup()
_cloudConvertAPI = new CloudConvertAPI(apiKey, true);
}

[Test]
public async Task CreateJob()
[TestCase("stream")]
[TestCase("bytes")]
public async Task CreateJob(string streamingMethod)
{
var job = await _cloudConvertAPI.CreateJobAsync(new JobCreateRequest
{
Expand All @@ -48,10 +49,25 @@ public async Task CreateJob()
Assert.IsNotNull(uploadTask);

var path = AppDomain.CurrentDomain.BaseDirectory + @"TestFiles/input.pdf";
byte[] file = File.ReadAllBytes(path);
string fileName = "input.pdf";

var result = await _cloudConvertAPI.UploadAsync(uploadTask.Result.Form.Url.ToString(), file, fileName, uploadTask.Result.Form.Parameters);
string result;

switch (streamingMethod)
{
case "bytes":
{
byte[] file = File.ReadAllBytes(path);
result = await _cloudConvertAPI.UploadAsync(uploadTask.Result.Form.Url.ToString(), file, fileName, uploadTask.Result.Form.Parameters);
break;
}
case "stream":
{
using (var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
result = await _cloudConvertAPI.UploadAsync(uploadTask.Result.Form.Url.ToString(), stream, fileName, uploadTask.Result.Form.Parameters);
break;
}
}

job = await _cloudConvertAPI.WaitJobAsync(job.Data.Id);

Expand All @@ -69,8 +85,9 @@ public async Task CreateJob()
using (var client = new WebClient()) client.DownloadFile(fileExport.Url, fileExport.Filename);
}

[Test]
public async Task CreateTask()
[TestCase("stream")]
[TestCase("bytes")]
public async Task CreateTask(string streamingMethod)
{
// import

Expand All @@ -79,10 +96,23 @@ public async Task CreateTask()
var importTask = await _cloudConvertAPI.CreateTaskAsync(ImportUploadCreateRequest.Operation, reqImport);

var path = AppDomain.CurrentDomain.BaseDirectory + @"TestFiles/input.pdf";
byte[] file = File.ReadAllBytes(path);
string fileName = "input.pdf";

await _cloudConvertAPI.UploadAsync(importTask.Data.Result.Form.Url.ToString(), file, fileName, importTask.Data.Result.Form.Parameters);
switch (streamingMethod)
{
case "bytes":
{
byte[] file = File.ReadAllBytes(path);
await _cloudConvertAPI.UploadAsync(importTask.Data.Result.Form.Url.ToString(), file, fileName, importTask.Data.Result.Form.Parameters);
break;
}
case "stream":
{
using (var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
await _cloudConvertAPI.UploadAsync(importTask.Data.Result.Form.Url.ToString(), stream, fileName, importTask.Data.Result.Form.Parameters);
break;
}
}

importTask = await _cloudConvertAPI.WaitTaskAsync(importTask.Data.Id);

Expand Down
67 changes: 53 additions & 14 deletions CloudConvert.Test/TestTasks.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using CloudConvert.API;
using CloudConvert.API.Models;
using CloudConvert.API.Models.Enums;
using CloudConvert.API.Models.ImportOperations;
using CloudConvert.API.Models.TaskModels;
using CloudConvert.API.Models.TaskOperations;
using CloudConvert.Test.Extensions;
using Moq;
using Moq.Protected;
using Newtonsoft.Json;
using NUnit.Framework;

namespace CloudConvert.Test
{
public class TestTasks
{
readonly Mock<ICloudConvertAPI> _cloudConvertAPI = new Mock<ICloudConvertAPI>();

[Test]
public async Task GetAllTasks()
{
Expand All @@ -26,10 +29,12 @@ public async Task GetAllTasks()

var path = @"Responses/tasks.json";
string json = File.ReadAllText(path);
_cloudConvertAPI.Setup(cc => cc.GetAllTasksAsync(filter))

var cloudConvertApi = new Mock<ICloudConvertAPI>();
cloudConvertApi.Setup(cc => cc.GetAllTasksAsync(filter))
.ReturnsAsync(JsonConvert.DeserializeObject<ListResponse<TaskResponse>>(json));

var tasks = await _cloudConvertAPI.Object.GetAllTasksAsync(filter);
var tasks = await cloudConvertApi.Object.GetAllTasksAsync(filter);

Assert.IsNotNull(tasks);
Assert.IsTrue(tasks.Data.Count >= 0);
Expand Down Expand Up @@ -64,10 +69,12 @@ public async Task CreateTask()

var path = AppDomain.CurrentDomain.BaseDirectory + @"Responses/task_created.json";
string json = File.ReadAllText(path);
_cloudConvertAPI.Setup(cc => cc.CreateTaskAsync(ConvertCreateRequest.Operation, req))

var cloudConvertApi = new Mock<ICloudConvertAPI>();
cloudConvertApi.Setup(cc => cc.CreateTaskAsync(ConvertCreateRequest.Operation, req))
.ReturnsAsync(JsonConvert.DeserializeObject<Response<TaskResponse>>(json));

var task = await _cloudConvertAPI.Object.CreateTaskAsync(ConvertCreateRequest.Operation, req);
var task = await cloudConvertApi.Object.CreateTaskAsync(ConvertCreateRequest.Operation, req);

Assert.IsNotNull(task);
Assert.IsTrue(task.Data.Status == API.Models.Enums.TaskStatus.waiting);
Expand All @@ -80,6 +87,8 @@ public async Task GetTask()

var path = AppDomain.CurrentDomain.BaseDirectory + @"Responses/task.json";
string json = File.ReadAllText(path);

var _cloudConvertAPI = new Mock<ICloudConvertAPI>();
_cloudConvertAPI.Setup(cc => cc.GetTaskAsync(id, null))
.ReturnsAsync(JsonConvert.DeserializeObject<Response<TaskResponse>>(json));

Expand All @@ -96,10 +105,12 @@ public async Task WaitTask()

var path = AppDomain.CurrentDomain.BaseDirectory + @"Responses/task.json";
string json = File.ReadAllText(path);
_cloudConvertAPI.Setup(cc => cc.WaitTaskAsync(id))

var cloudConvertApi = new Mock<ICloudConvertAPI>();
cloudConvertApi.Setup(cc => cc.WaitTaskAsync(id))
.ReturnsAsync(JsonConvert.DeserializeObject<Response<TaskResponse>>(json));

var task = await _cloudConvertAPI.Object.WaitTaskAsync(id);
var task = await cloudConvertApi.Object.WaitTaskAsync(id);

Assert.IsNotNull(task);
Assert.IsTrue(task.Data.Operation == "convert");
Expand All @@ -111,32 +122,60 @@ public async Task DeleteTask()
{
string id = "9de1a620-952c-4482-9d44-681ae28d72a1";

_cloudConvertAPI.Setup(cc => cc.DeleteTaskAsync(id));
var cloudConvertApi = new Mock<ICloudConvertAPI>();
cloudConvertApi.Setup(cc => cc.DeleteTaskAsync(id));

await _cloudConvertAPI.Object.DeleteTaskAsync("c8a8da46-3758-45bf-b983-2510e3170acb");
await cloudConvertApi.Object.DeleteTaskAsync("c8a8da46-3758-45bf-b983-2510e3170acb");
}

[Test]
public async Task Upload()
{
var cloudConvertApi = new Mock<ICloudConvertAPI>();
var req = new ImportUploadCreateRequest();

var path = AppDomain.CurrentDomain.BaseDirectory + @"Responses/upload_task_created.json";
string json = File.ReadAllText(path);
_cloudConvertAPI.Setup(cc => cc.CreateTaskAsync(ImportUploadCreateRequest.Operation, req))
cloudConvertApi.Setup(cc => cc.CreateTaskAsync(ImportUploadCreateRequest.Operation, req))
.ReturnsAsync(JsonConvert.DeserializeObject<Response<TaskResponse>>(json));

var task = await _cloudConvertAPI.Object.CreateTaskAsync(ImportUploadCreateRequest.Operation, req);
var task = await cloudConvertApi.Object.CreateTaskAsync(ImportUploadCreateRequest.Operation, req);

Assert.IsNotNull(task);

var pathFile = AppDomain.CurrentDomain.BaseDirectory + @"TestFiles/input.pdf";
byte[] file = File.ReadAllBytes(pathFile);
string fileName = "input.pdf";

_cloudConvertAPI.Setup(cc => cc.UploadAsync(task.Data.Result.Form.Url.ToString(), file, fileName, task.Data.Result.Form.Parameters));
cloudConvertApi.Setup(cc => cc.UploadAsync(task.Data.Result.Form.Url.ToString(), file, fileName, task.Data.Result.Form.Parameters));

await cloudConvertApi.Object.UploadAsync(task.Data.Result.Form.Url.ToString(), file, fileName, task.Data.Result.Form.Parameters);
}

[Test]
public async Task UploadStream()
{
var httpMessageHandlerMock = new Mock<HttpMessageHandler>();
httpMessageHandlerMock.MockResponse("/import/upload", "upload_task_created.json");
httpMessageHandlerMock.MockResponse("/tasks", "tasks.json");

var httpClient = new HttpClient(httpMessageHandlerMock.Object);
var restHelper = new RestHelper(httpClient);
var req = new ImportUploadCreateRequest();
var cloudConvertApi = new CloudConvertAPI(restHelper, "API_KEY");

var task = await cloudConvertApi.CreateTaskAsync(ImportUploadCreateRequest.Operation, req);

Assert.IsNotNull(task);
httpMessageHandlerMock.VerifyRequest("/import/upload", Times.Once());

var streamMock = new Mock<Stream>();
var fileName = "input.pdf";

await cloudConvertApi.UploadAsync(task.Data.Result.Form.Url.ToString(), streamMock.Object, fileName, task.Data.Result.Form.Parameters);

await _cloudConvertAPI.Object.UploadAsync(task.Data.Result.Form.Url.ToString(), file, fileName, task.Data.Result.Form.Parameters);
httpMessageHandlerMock.VerifyRequest("/tasks", Times.Once());
streamMock.Protected().Verify("Dispose", Times.Never(), args: true);
}
}
}
Loading

0 comments on commit 74693ff

Please sign in to comment.