How to unit test multipart/form-data with moq in C# - c#

I have been trying to write a unit test for the method of service which gets multiple/form-data from the Request. Here is my method:
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(HttpContextAccessor.HttpContext.Request.ContentType),
DEFAULT_MULTIPART_BOUNDARY_LENGTH_LIMIT);
var reader = new MultipartReader(boundary, HttpContextAccessor.HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
var streamedFileContent = Array.Empty<byte>();
var dictionary = new Dictionary<string, string>();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
{
streamedFileContent = await FileHelpers.ProcessStreamedFile(
section, contentDisposition, Configuration.GetFileSizeLimit());
}
else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
{
var key = HeaderUtilities
.RemoveQuotes(contentDisposition.Name).Value;
var encoding = GetEncoding(section);
if (encoding == null)
throw new ErrorCodeException("error-invalid-data");
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
var value = await streamReader.ReadToEndAsync();
dictionary.Add(key, value);
}
}
}
section = await reader.ReadNextSectionAsync();
}
var file = new File
{
AuthorId = authorStaffId,
Name = dictionary.FirstOrDefault(l => l.Key == "name").Value,
Size = streamedFileContent.Length,
Extension = dictionary.FirstOrDefault(l => l.Key == "extension").Value,
MimeType = dictionary.FirstOrDefault(l => l.Key == "mimeType").Value,
IsTemporary = true,
FileId = Guid.NewGuid()
};
file = await SaveFileContentAsync(streamedFileContent, file);
file.ContentHash = CanculateContentHash(streamedFileContent);
await _fileRepository.AddAsync(file);
Here I am trying to send a request from the Postman and this way it is working fine:
And following way I am trying to test my code with xUnit and Moq:
[Fact]
public async Task UploadFileAsync_WhenFileSizeIsNotBiggerThanLimit_Successful()
{
// Arrange
ConfigureClaims();
ConfigureDatabase();
ConfigureService();
var fakeFileContent = GetFakeFileContent();
var byteContent = Convert.FromBase64String(fakeFileContent);
Stream stream = new MemoryStream(byteContent);
_moqHttpContextAccessor.Setup(x => x.HttpContext.Request.Body).Returns(stream);
_moqHttpContextAccessor.Setup(x => x.HttpContext.Request.ContentType).Returns("multipart/form-data; boundary=----231179732646258011288433");
_moqHttpContextAccessor.Setup(x => x.HttpContext.Request.ContentLength).Returns(0x000000000001d74e);
// Act
var addedFileId = _baseFileService.UploadFileAsync();
var addedFile = _fileRepository.GetById(addedFileId);
// Assert
Assert.NotNull(addedFile);
}
I got the following error message: InvalidDataException: Multipart body length limit 16384 exceeded
And I tried this way:
[Fact]
public async Task UploadFileAsync_WhenFileSizeIsNotBiggerThanLimit_Successful()
{
// Arrange
ConfigureClaims();
ConfigureDatabase();
ConfigureService();
var fakeFileContent = GetFakeFileContent();
var byteContent = Convert.FromBase64String(fakeFileContent);
var byteStringContent = byteContent.ToString();
var forDataDictionary = new Dictionary<string, StringValues>
{
{"file", byteStringContent},
{"name", "test.pdf"},
{"size", "120085"},
{"mimeType", "application/pdf" }
};
var formData = new FormCollection(forDataDictionary);
_moqHttpContextAccessor.Setup(x => x.HttpContext.Request.Form).Returns(formData);
// Act
var addedFileId = _baseFileService.UploadFileAsync();
var addedFile = _fileRepository.GetById(addedFileId);
// Assert
Assert.NotNull(addedFile);
}
I got an error because I am not getting the content from Request.Form. I am getting it from Reques.Body
Then I looked around the Web and could not find any proper answer. What can I do here?

you can take this approach to mock your IformFile.
public static IFormFile AsValidMockIFormFile(this FileInfo physicalFile)
{
var fileMock = new Mock<IFormFile>();
var ms = new MemoryStream();
var writer = new StreamWriter(ms);
writer.Write(physicalFile.OpenRead());
writer.Flush();
ms.Position = 0;
var fileName = physicalFile.Name;
//Setup mock file using info from physical file
fileMock.Setup(_ => _.FileName).Returns(fileName);
fileMock.Setup(_ => _.Length).Returns(ms.Length);
fileMock.Setup(m => m.OpenReadStream()).Returns(ms);
fileMock.Setup(m => m.ContentType).Returns("text/csv");
fileMock.Setup(m => m.ContentDisposition).Returns(string.Format("inline; filename={0}", fileName));
return fileMock.Object;
}
As this above method will give a IformFile object. but to decorate the same you need to pass some data(i.e., you need to hold a sample file in your test data)
var physicalFile = new FileInfo("filePath.txt"); // Put your sample file here
fakeFileContent = FileHelper.AsValidMockIFormFile(physicalFile);
var info=await _controller.UploadAtion(fakeFileContent) as OkObjectResult;

Related

How to write Testcases for an API that accepts an object that includes IFormFile typed fields in .NET

This is my controller code. Here it accepts UpdateMediaDto object that contains IFormFile data ( e.g. images and audios).
[HttpPut("words/{wordId}/medias/{id}")]
public async Task<ActionResult<WordMediaDto>> UpdateWordMedia(Guid wordId, Guid id, [FromForm] UpdateMediaDto mediaDto)
{
WordMedia? media = await _unitOfWork.WordMediaRepository.GetByIdAsync(id);
if (media == null) return NotFound();
if (wordId != media.WordId) return BadRequest("No Word present for this media");
var newMedia = _mediaFileMasterService.UpdateMedias(mediaDto);
//............
}
And this is my testcase
[Fact]
public async Task PUT_Media_with_UpdateMediaDto_results_WordMediaDto_success()
{
var ImagefileMock = new Mock<IFormFile>();
var content = System.Text.Encoding.UTF8.GetBytes("// byte[]....");
var ImagefileName = "sampleImage.jpg";
var ms = new MemoryStream();
var writer = new StreamWriter(ms);
writer.Write(content);
writer.Flush();
ms.Position = 0;
ImagefileMock.Setup(_ => _.OpenReadStream()).Returns(ms);
ImagefileMock.Setup(_ => _.FileName).Returns(ImagefileName);
ImagefileMock.Setup(_ => _.Length).Returns(ms.Length);
UpdateMediaDto mediaDto = new() {
Id = Guid.Parse("f1659b04-85a3-4969-7d20-08da081a9616"),
wordId=Guid.Parse("2caf24aa-4d37-4f64-aa91-4a605798c35b"),
PrimaryImage=ImagefileMock.Object,
SecondaryImage1 = ImagefileMock.Object };
HttpContent httpContent = new StringContent(JsonConvert.SerializeObject(mediaDto), Encoding.UTF8);
httpContent.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data");
//Act
var response = await _httpClient.PutAsync("api/v1/admin/words/d9823bdd-0d11-42e5-a804-4d59d393d2bc/medias/f47136e3-754a-4f2b-b5cc-08da2d91a92e", httpContent);
//Assert
response.EnsureSuccessStatusCode();
}
But Here I can't serialize the object using SerializeObject. when I execute this test case I get an error like
Newtonsoft.Json.JsonSerializationException : Self referencing loop detected for property 'Object' with type 'Castle.Proxies.IFormFileProxy'. Path 'PrimaryImage.Mock'.
How can I solve this problem... and is there any other ways to test API like this?
The proxy created by MOQ is causing an issue with the serialization in this case.
Consider using an actual FormFile instance which is derived from IFormFile.
[Fact]
public async Task PUT_Media_with_UpdateMediaDto_results_WordMediaDto_success() {
//Setup mock file using a memory stream
string content = System.Text.Encoding.UTF8.GetBytes("// byte[]....");
string ImagefileName = "sampleImage.jpg";
MemoryStream ms = new MemoryStream();
StreamWriter writer = new StreamWriter(ms);
writer.Write(content);
writer.Flush();
ms.Position = 0;
//create FormFile with desired data
IFormFile ImagefileMock = new FormFile(ms, 0, ms.Length, "name_from_form", ImagefileName);
UpdateMediaDto mediaDto = new() {
Id = Guid.Parse("f1659b04-85a3-4969-7d20-08da081a9616"),
wordId=Guid.Parse("2caf24aa-4d37-4f64-aa91-4a605798c35b"),
PrimaryImage=ImagefileMock,
SecondaryImage1 = ImagefileMock
};
HttpContent httpContent = new StringContent(JsonConvert.SerializeObject(mediaDto), Encoding.UTF8);
httpContent.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data");
//Act
var response = await _httpClient.PutAsync("api/v1/admin/words/d9823bdd-0d11-42e5-a804-4d59d393d2bc/medias/f47136e3-754a-4f2b-b5cc-08da2d91a92e", httpContent);
//Assert
response.EnsureSuccessStatusCode();
}
It seems you need to use MultipartFormDataContent to build your form and add parts. To Stream objects use StreamContent.
var multiPartformData = new MultipartFormDataContent();
var streamContent = new StreamContent(ImagefileMock);
multiPartformData .Add(streamContent);

Mock Azure Function HttpTrigger and Setup Query String

I am working on xUnit for.NET CORE Azure Function that have HttpTrigger. I have managed to mock HttpTrigger that expect to receive data in the body but struggling with Query String. I have made the generic function outside the test class so that it can be used by other Azure Functions tests.
I need help to create mock for HttpRequest that accept query string. I believe need mock that Setup type of IQueryCollection
Azure function
[FunctionName("MyFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "DELETE")] HttpRequest req,
[ServiceBus("MyServiceBus", Connection = "MyServiceBusConn")] IAsyncCollector<Message> servicebusMessage)
{
string sessionId = string.Empty;
var DateTimeNow = DateTime.Now;
sessionId = req.Query["sessions"]; //Mock to return this??
}
Method To Create Mock for HttpRequest Body
public Mock<HttpRequest> CreateMockRequest(object body)
{
var memoryStream = new MemoryStream();
var writer = new StreamWriter(memoryStream);
var json = JsonConvert.SerializeObject(body);
writer.Write(json);
writer.Flush();
memoryStream.Position = 0;
var mockRequest = new Mock<HttpRequest>();
mockRequest.Setup(x => x.Body).Returns(memoryStream);
mockRequest.Setup(x => x.ContentType).Returns("application/json");
return mockRequest;
}
Need help in following method
Method To Create Mock for HttpRequest Query String
public Mock<HttpRequest> CreateQueryMockRequest(object body)
{
var memoryStream = new MemoryStream();
var writer = new StreamWriter(memoryStream);
var json = JsonConvert.SerializeObject(body);
var mockRequest = new Mock<HttpRequest>();
mockRequest.Setup(x => x.Query).Returns(json); // This doesn't work??
mockRequest.Setup(x => x.ContentType).Returns("application/json");
return mockRequest;
}
Test Class
[Fact]
public void Function_ShouldReturn_XYZ()
{
//Arrange
var providerSessionId = RingGoExemptionTestData.GetProviderSession(); //GetProviderSession() implementation below
Mock<HttpRequest> mockHttpRequest = httpResquestFactory.CreateQueryMockRequest(providerSessionId); // this is where I am trying to use method define above
}
Dto Object for Query String
public static RingGoSession GetProviderSession()
{
var ringGoSession = new RingGoSession
{
RingGoRef = "232d3f"
};
return ringGoSession;
}
got the answer;
'Generic Method to deal with Query String`
public Mock<HttpRequest> CreateMockHttpRequest(Dictionary<string, StringValues> query)
{
var context = new DefaultHttpContext();
var request = context.Request;
request.Query = new QueryCollection(query);
var mockRequest = new Mock<HttpRequest>();
mockRequest.Setup(x => x.Query).Returns(request.Query);
return mockRequest;
}
This is how to create Mock by passing Query
var query = new Dictionary<string, StringValues>();
query.TryAdd("myKey", MyKeyValue);
Mock<HttpRequest> mockHttpRequest = httpResquestFactory.CreateMockHttpRequest(query);
You can create a new instance of QueryCollection (read this) and setup the mock.
var mockDict = new Dictionary<string, StringValues>
{
{ "key1", "some value" },
{ "sessions", "random session string"}
};
mockRequest.Setup(x => x.Query).Returns(new QueryCollection(mockDict));

Download files Angular, .Net 5

I have a problem downloading files.
When I download the file, it turns out to be empty, I think that the problem is in the wrong headers, but I could not figure out what exactly.
I have a response body
from controller
But I have empty response arraybuffer
from component
And empty zip downloaded
What am I doing wrong?
request.component.ts
downloadZip() {
this.requestService.downloadZip(this.request.id)
.subscribe((res) => {
const blob = new Blob([res], { type: 'application/zip' });
saveAs(blob, 'Обращение №' + this.request.id + '.zip');
})
}
request.service.ts
downloadZip(requestId: number): Observable<any> {
return this.http.get(this.apiUrl + '/request/downloadZip?id=' + requestId, { responseType: 'arraybuffer'});
}
RequestController.cs
[HttpGet]
[Route("api/request/downloadZip")]
public async Task<IActionResult> DownloadZip(int id)
{
var stream = await _fileStoreService.GetRequestFilesZip(id);
Response.Body = stream;
Response.ContentType = "application/zip";
return Ok();
}
FileStoreService.cs
public async Task<MemoryStream> GetRequestFilesZip(int id)
{
var query = await _db.RequestMedia
.Where(_ => _.RequestId == id)
.ToListAsync();
var fileSharePath = Path.Combine(_configuration["FileTableRootPath"], "RequestFileStore");
var files = new List<string>();
foreach (var media in query)
files.Add(Path.Combine(fileSharePath, media.Hash + Path.GetExtension(WebUtility.HtmlDecode(media.Name))));
var memory = new MemoryStream();
ZipStrings.UseUnicode = true;
using(var zipStream = new ZipOutputStream(memory))
{
zipStream.SetLevel(3);
foreach(var file in files)
{
var entry = new ZipEntry(Path.GetFileName(file))
{
DateTime = DateTime.Now
};
zipStream.PutNextEntry(entry);
var inStream = new MemoryStream();
using var f = new FileStream(file, FileMode.Open, FileAccess.Read);
await f.CopyToAsync(inStream);
inStream.Close();
var content = inStream.ToArray();
await zipStream.WriteAsync(content.AsMemory(0, content.Length));
zipStream.CloseEntry();
}
zipStream.IsStreamOwner = false;
}
memory.Position = 0;
return memory;
}
I changed this
request.service.ts
downloadZip(requestId: number) {
return this.http.get(this.apiUrl + '/request/downloadZip?id=' + requestId, { responseType: 'blob'});
}
and this
RequestController.cs
[HttpGet]
[Route("api/request/downloadZip")]
public async Task<IActionResult> DownloadZip(int id)
{
var stream = await _fileStoreService.GetRequestFilesZip(id);
return new FileStreamResult(stream, "application/zip");
}
Everything works fine

Issue in multi fileupload object with extra json parameter in REST API in ASP.NET MVC using C#

I'm creating a REST API in which accepts a JSON string with 3 different fileUpload in form data.
Here is my API controller post method
public async Task<IHttpActionResult> Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
return StatusCode(HttpStatusCode.UnsupportedMediaType);
}
var filesReadToProvider = await Request.Content.ReadAsMultipartAsync();
var obj = new SubmitReportModel();
var enggCommentImg = new List<FileModel>();
var concernAreaImg = new List<FileModel>();
var remarkImge = new List<FileModel>();
string UploadPath = ("~/Uploads/ReportsData");
Functions.CreateIfMissing(System.Web.Hosting.HostingEnvironment.MapPath(UploadPath));
foreach (var item in filesReadToProvider.Contents)
{
if (item.Headers.ContentDisposition.Name.Contains("jsonKey"))
{
var jsson = await item.ReadAsStringAsync();
obj = Newtonsoft.Json.JsonConvert.DeserializeObject<SubmitReportModel>(jsson);
}
else if (item.Headers.ContentDisposition.Name.Contains("enggCommentImg"))
{
_ = new FileModel();
var fileDataByteArray = await item.ReadAsByteArrayAsync();
FileModel objfile = await SaveFileAsync(fileDataByteArray);
enggCommentImg.Add(objfile);
}
else if (item.Headers.ContentDisposition.Name.Contains("concernAreaImg"))
{
_ = new FileModel();
var fileDataByteArray = await item.ReadAsByteArrayAsync();
FileModel objfile = await SaveFileAsync(fileDataByteArray);
concernAreaImg.Add(objfile);
}
else if (item.Headers.ContentDisposition.Name.Contains("remarkImge"))
{
_ = new FileModel();
var fileDataByteArray = await item.ReadAsByteArrayAsync();
FileModel objfile = await SaveFileAsync(fileDataByteArray);
remarkImge.Add(objfile);
}
}
return Ok();
}
private async Task<FileModel> SaveFileAsync(byte[] fileDataByteArray)
{
string uploadPath = ("~/Uploads/ReportsData");
var objfile = new FileModel();
var filename = uploadPath + "/" + ApiHelper.GetTimestamp() + ".jpg";
using (FileStream fs = new FileStream(System.Web.Hosting.HostingEnvironment.MapPath(filename), FileMode.OpenOrCreate, FileAccess.Write))
{
await fs.WriteAsync(fileDataByteArray, 0, fileDataByteArray.Length).ConfigureAwait(true);
}
objfile.Name = filename;
objfile.Type = "jpg";
return objfile;
}
This API works fine I got JSON plus multiple files in each file object as well.
It works very well when I debug using breakpoints and parse each step using f10. But when I try it without breakpoints, it only saves the first file.
Here is how I'm calling it using Postman:
I tried the same with making it synchronous but still, I get the same result - saving only one file.
Here is my synchronous POST API code
public object Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
return StatusCode(HttpStatusCode.UnsupportedMediaType);
}
//var filesReadToProvider =Request.Content.ReadAsMultipartAsync().GetAwaiter().GetResult();
var filesReadToProvider = Task.Run(async () => await Request.Content.ReadAsMultipartAsync()).Result;
var obj = new SubmitReportModel();
var enggCommentImg = new List<FileModel>();
var concernAreaImg = new List<FileModel>();
var remarkImge = new List<FileModel>();
string UploadPath = ("~/Uploads/ReportsData");
Functions.CreateIfMissing(System.Web.Hosting.HostingEnvironment.MapPath(UploadPath));
foreach (var item in filesReadToProvider.Contents)
{
if (item.Headers.ContentDisposition.Name.Contains("jsonKey"))
{
var jsson = Task.Run(async () => await item.ReadAsStringAsync()).Result;
obj = Newtonsoft.Json.JsonConvert.DeserializeObject<SubmitReportModel>(jsson);
}
else if (item.Headers.ContentDisposition.Name.Contains("enggCommentImg"))
{
_ = new FileModel();
var fileDataByteArray = Task.Run(async () => await item.ReadAsByteArrayAsync()).Result;
if (fileDataByteArray.Length > 0)
{
FileModel objfile = SaveFiles(fileDataByteArray);
enggCommentImg.Add(objfile);
}
}
else if (item.Headers.ContentDisposition.Name.Contains("concernAreaImg"))
{
_ = new FileModel();
var corsrnArr = Task.Run(async () => await item.ReadAsByteArrayAsync()).Result;
if (corsrnArr.Length > 0)
{
FileModel objfile = SaveFiles(corsrnArr);
concernAreaImg.Add(objfile);
}
}
else if (item.Headers.ContentDisposition.Name.Contains("remarkImge"))
{
_ = new FileModel();
var remarkarr = Task.Run(async () => await item.ReadAsByteArrayAsync()).Result;
if (remarkarr.Length > 0)
{
FileModel objfile = SaveFiles(remarkarr);
remarkImge.Add(objfile);
}
}
}
return 1;
}
private FileModel SaveFiles(byte[] fileDataByteArray)
{
string uploadPath = ("~/Uploads/ReportsData");
var objfile = new FileModel();
var filename = uploadPath + "/" + ApiHelper.GetTimestamp() + ".jpg";
using (FileStream fs = new FileStream(System.Web.Hosting.HostingEnvironment.MapPath(filename), FileMode.OpenOrCreate, FileAccess.Write))
{
fs.Write(fileDataByteArray, 0, fileDataByteArray.Length);
}
objfile.Name = filename;
objfile.Type = "jpg";
return objfile;
}
I'm unable to figure what exactly wrong step I made in it as there is no error in it.
UPDATE:
As theGeneral advised, I do recheck all steps and created a log so
found that all images were getting upload within a second so all of
them receive the same timestamp as name, and as a result, only one
file was getting overwrite again and again.
Keeping this thread as it is as this API work for files + other data
(JSON string in my case) and may get helpful for others

How do asp.net core universal proxy

I`m do proxy service in asp.net core, extensions where the request will be routed to another service.
My extension:
public static IApplicationBuilder UseProxy(this IApplicationBuilder builder, List<ProxySetting> options)
{
builder.Use(async (context, next) =>
{
var resultPathHandler = options.ProxySettingHandler(context.Request.Path, context.Request.Method);
if (!resultPathHandler.Equal)
//continues through the rest of the pipeline
await next();
else
{
using (var httpClient = new HttpClient())
{
var pathSetting = resultPathHandler.Setting;
var requestMessage = new HttpRequestMessage();
//добавляем хедеры
foreach (var header in context.Request.Headers)
{
if (!httpClient.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value.ToArray()))
{
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(header.Key,
header.Value.ToArray());
}
}
if (context.Request.HasFormContentType && context.Request.Form.Count > 0)
{
var temp = new Dictionary<string, object>();
foreach (var form in context.Request.Form)
{
requestMessage.Properties.Add(form.Key, form.Value);
temp.Add(form.Key, form.Value);
}
var jsonString = JsonConvert.SerializeObject(temp).Replace(":[", ":").Replace("],",",");
var mediatype = string.Empty;
if (context.Request.ContentType.Contains(';'))
mediatype = context.Request.ContentType.Split(';')[0];
else
mediatype = context.Request.ContentType;
requestMessage.Content = new StringContent(jsonString, Encoding.UTF8, mediatype);
}
requestMessage.Headers.Host = pathSetting.Host;
var host = pathSetting.GetUri(context.Request.Path);
var uriString = $"{host}{context.Request.QueryString}";
requestMessage.RequestUri = new Uri(uriString);
requestMessage.Method = new HttpMethod(pathSetting.Method);
var response = await httpClient.SendAsync(requestMessage);
var result = await response.Content.ReadAsStringAsync();
var resultObj = JsonConvert.DeserializeObject<object>(result);
context.Response.StatusCode = (int)response.StatusCode;
await context.Response.WriteAsync(resultObj.ToString());
}
}
});
return builder;
}
where this code:
if (context.Request.HasFormContentType && context.Request.Form.Count > 0)
{
var temp = new Dictionary<string, object>();
foreach (var form in context.Request.Form)
{
requestMessage.Properties.Add(form.Key, form.Value);
temp.Add(form.Key, form.Value);
}
var jsonString = JsonConvert.SerializeObject(temp).Replace(":[", ":").Replace("],",",");
var mediatype = string.Empty;
if (context.Request.ContentType.Contains(';'))
mediatype = context.Request.ContentType.Split(';')[0];
else
mediatype = context.Request.ContentType;
requestMessage.Content = new StringContent(jsonString, Encoding.UTF8, mediatype);
}
Add to content post and put parameters, with application/x-www-form-urlencoded or application/form-data, and send to other service, but i dont get this parameters in other service.
Help me, need release this feature. Thx and sorry for my bad English
You don't need to to reinvent the wheel. ASP.NET Core already has and provides own ProxyMiddleware.

Categories

Resources