Skip to content

Commit

Permalink
fix: allow AdditionalFormDataFiles for minidump/xml posts (#72)
Browse files Browse the repository at this point in the history
* fix: allow AdditionalFormDataFiles for minidump posts

* refactor: better variable names for formdata predicate
  • Loading branch information
bobbyg603 authored Sep 30, 2024
1 parent 58b83b1 commit d6f88b2
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 65 deletions.
51 changes: 51 additions & 0 deletions BugSplatDotNetStandard.Test/Api/CrashPostClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using BugSplatDotNetStandard;
using BugSplatDotNetStandard.Api;
using BugSplatDotNetStandard.Http;
Expand All @@ -13,6 +14,7 @@
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using static Tests.StackTraceFactory;
using System.IO.Compression;

namespace Tests
{
Expand Down Expand Up @@ -202,6 +204,55 @@ public async Task CrashPostClient_PostCrashFile_PostCrashAndMetadata()
Assert.AreEqual(bugsplat.User, crashDetailsContent["user"].Value<string>());
}

[Test]
[Explicit]
public async Task CrashPostClient_PostCrashFile_AllowAdditionalFormDataAttachments()
{
var bugsplat = new BugSplat(database, application, version);
var minidump = new FileInfo("Files/minidump.dmp");
var oauth2ApiClient = OAuth2ApiClient.Create(clientId, clientSecret)
.Authenticate()
.Result;
var crashDetailsClient = CrashDetailsClient.Create(oauth2ApiClient);
var sut = new CrashPostClient(HttpClientFactory.Default, S3ClientFactory.Default);
var fileName = "hello.txt";
var expectedContent = "hello world!";
var overrideOptions = new MinidumpPostOptions()
{
FormDataParams = new List<IFormDataParam>()
{
new FormDataParam()
{
Content = new StringContent(expectedContent),
FileName = fileName,
Name = "file0"
}
}
};

var postResult = await sut.PostMinidump(
database,
application,
version,
minidump,
MinidumpPostOptions.Create(bugsplat),
overrideOptions
);

var postResponseContent = JObject.Parse(postResult.Content.ReadAsStringAsync().Result);
var id = postResponseContent["crashId"].Value<int>();
var crashDetails = await crashDetailsClient.GetCrashDetails(database, id);
var crashDetailsContent = JObject.Parse(crashDetails.Content.ReadAsStringAsync().Result);
var crashZipUrl = crashDetailsContent["dumpfile"];
using var client = new HttpClient();
var crashZipResponse = await client.GetStreamAsync(crashZipUrl.ToString());
var zipArchive = new ZipArchive(crashZipResponse);
var attachment = zipArchive.GetEntry(fileName);
using var reader = new StreamReader(attachment.Open());
var attachmentContent = await reader.ReadToEndAsync();
Assert.AreEqual(expectedContent, attachmentContent);
}

private Mock<HttpMessageHandler> CreateMockHttpClientForExceptionPost(string crashUploadUrl)
{
var getCrashUploadUrlResponse = new HttpResponseMessage();
Expand Down
116 changes: 51 additions & 65 deletions BugSplatDotNetStandard/Api/CrashPostClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,58 +41,15 @@ public async Task<HttpResponseMessage> PostException(
ExceptionPostOptions overridePostOptions = null
)
{
overridePostOptions = overridePostOptions ?? new ExceptionPostOptions();

var files = CombineListsWithDuplicatesRemoved(defaultPostOptions.Attachments, overridePostOptions.Attachments)
.Select(attachment => TryCreateInMemoryFileFromFileInfo(attachment))
.Where(file => file != null)
.ToList();

var additionalFormDataFiles = overridePostOptions.FormDataParams
.Where(file => !string.IsNullOrEmpty(file.FileName) && file.Content != null)
.Select(file => new InMemoryFile() { FileName = file.FileName, Content = file.Content.ReadAsByteArrayAsync().Result })
.ToList();

files.Add(new InMemoryFile() { FileName = "Callstack.txt", Content = Encoding.UTF8.GetBytes(stackTrace) });
files.AddRange(additionalFormDataFiles);

var zipBytes = ZipUtils.CreateInMemoryZipFile(files);
using (
var crashUploadResponse = await GetCrashUploadUrl(
database,
application,
version,
zipBytes.Length
)
)
{
ThrowIfHttpRequestFailed(crashUploadResponse);

var presignedUrl = await GetPresignedUrlFromResponse(crashUploadResponse);

using (var uploadFileResponse = await this.s3Client.UploadFileBytesToPresignedURL(presignedUrl, zipBytes))
{
ThrowIfHttpRequestFailed(uploadFileResponse);

var s3Key = presignedUrl.ToString();
var md5 = GetETagFromResponseHeaders(uploadFileResponse.Headers);
var crashTypeId = overridePostOptions?.CrashTypeId != (int)ExceptionTypeId.Unknown ? overridePostOptions.CrashTypeId : defaultPostOptions.CrashTypeId;
var commitS3CrashResponse = await CommitS3CrashUpload(
database,
application,
version,
md5,
s3Key,
crashTypeId,
defaultPostOptions,
overridePostOptions
);

ThrowIfHttpRequestFailed(commitS3CrashResponse);

return commitS3CrashResponse;
}
}
var inMemoryExceptionFile = new InMemoryFile() { FileName = "Callstack.txt", Content = Encoding.UTF8.GetBytes(stackTrace) };
return await PostInMemoryCrashFile(
database,
application,
version,
inMemoryExceptionFile,
defaultPostOptions,
overridePostOptions
);
}

public async Task<HttpResponseMessage> PostMinidump(
Expand All @@ -104,11 +61,12 @@ public async Task<HttpResponseMessage> PostMinidump(
MinidumpPostOptions overridePostOptions = null
)
{
return await PostCrashFile(
var inMemoryDmpFile = TryCreateInMemoryFileFromFileInfo(minidumpFileInfo);
return await PostInMemoryCrashFile(
database,
application,
version,
minidumpFileInfo,
inMemoryDmpFile,
defaultPostOptions,
overridePostOptions
);
Expand All @@ -123,11 +81,12 @@ public async Task<HttpResponseMessage> PostXmlReport(
XmlPostOptions overridePostOptions = null
)
{
return await PostCrashFile(
var inMemoryXmlFile = TryCreateInMemoryFileFromFileInfo(xmlFileInfo);
return await PostInMemoryCrashFile(
database,
application,
version,
xmlFileInfo,
inMemoryXmlFile,
defaultPostOptions,
overridePostOptions
);
Expand All @@ -141,15 +100,41 @@ public async Task<HttpResponseMessage> PostCrashFile(
BugSplatPostOptions defaultPostOptions,
BugSplatPostOptions overridePostOptions = null
)
{
var inMemoryCrashFile = TryCreateInMemoryFileFromFileInfo(crashFileInfo);
return await PostInMemoryCrashFile(
database,
application,
version,
inMemoryCrashFile,
defaultPostOptions,
overridePostOptions
);
}

private async Task<HttpResponseMessage> PostInMemoryCrashFile(
string database,
string application,
string version,
InMemoryFile crashFile,
BugSplatPostOptions defaultPostOptions,
BugSplatPostOptions overridePostOptions = null
)
{
overridePostOptions = overridePostOptions ?? new MinidumpPostOptions();

var files = CombineListsWithDuplicatesRemoved(defaultPostOptions.Attachments, overridePostOptions.Attachments)
var files = CombineListsWithDuplicatesRemoved(defaultPostOptions.Attachments, overridePostOptions.Attachments, (FileInfo file) => file.FullName)
.Select(attachment => TryCreateInMemoryFileFromFileInfo(attachment))
.Where(file => file != null)
.ToList();

files.Add(new InMemoryFile() { FileName = crashFileInfo.Name, Content = File.ReadAllBytes(crashFileInfo.FullName) });
var additionalFormDataFiles = CombineListsWithDuplicatesRemoved(defaultPostOptions.FormDataParams, overridePostOptions.FormDataParams, (IFormDataParam param) => param.Name)
.Where(file => !string.IsNullOrEmpty(file.FileName) && file.Content != null)
.Select(file => new InMemoryFile() { FileName = file.FileName, Content = file.Content.ReadAsByteArrayAsync().Result })
.ToList();

files.Add(crashFile);
files.AddRange(additionalFormDataFiles);

var zipBytes = ZipUtils.CreateInMemoryZipFile(files);
using (
Expand Down Expand Up @@ -196,14 +181,15 @@ public void Dispose()
this.s3Client.Dispose();
}

private List<FileInfo> CombineListsWithDuplicatesRemoved(
List<FileInfo> defaultList,
List<FileInfo> overrideList
private List<T> CombineListsWithDuplicatesRemoved<T>(
List<T> defaultList,
List<T> overrideList,
Func<T, string> predicate
)
{
return defaultList
.Concat(overrideList)
.GroupBy(file => file.FullName)
return overrideList
.Concat(defaultList)
.GroupBy(predicate)
.Select(group => group.First())
.ToList();
}
Expand Down Expand Up @@ -271,7 +257,7 @@ int crashPostSize
var baseUrl = this.CreateBaseUrlFromDatabase(database);
var path = $"{baseUrl}/api/getCrashUploadUrl";
var route = $"{path}?database={database}&appName={application}&appVersion={version}&crashPostSize={crashPostSize}";

return await httpClient.GetAsync(route);
}

Expand Down

0 comments on commit d6f88b2

Please sign in to comment.