-
Notifications
You must be signed in to change notification settings - Fork 119
Consume URI in PUT response for GitHub-owned storage for GEI #1371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3ce9bbc
df49d6c
ff927dd
5b3d7f8
3dca950
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -4,7 +4,6 @@ | |||||||||||||||
using System.Linq; | ||||||||||||||||
using System.Net.Http; | ||||||||||||||||
using System.Threading.Tasks; | ||||||||||||||||
using System.Web; | ||||||||||||||||
using Newtonsoft.Json.Linq; | ||||||||||||||||
using OctoshiftCLI.Extensions; | ||||||||||||||||
|
||||||||||||||||
|
@@ -64,11 +63,8 @@ private async Task<string> UploadMultipart(Stream archiveContent, string archive | |||||||||||||||
{ | ||||||||||||||||
// 1. Start the upload | ||||||||||||||||
var startHeaders = await StartUpload(uploadUrl, archiveName, archiveContent.Length); | ||||||||||||||||
|
||||||||||||||||
var nextUrl = GetNextUrl(startHeaders); | ||||||||||||||||
|
||||||||||||||||
var guid = HttpUtility.ParseQueryString(nextUrl.Query)["guid"]; | ||||||||||||||||
|
||||||||||||||||
// 2. Upload parts | ||||||||||||||||
int bytesRead; | ||||||||||||||||
var partsRead = 0; | ||||||||||||||||
|
@@ -80,9 +76,9 @@ private async Task<string> UploadMultipart(Stream archiveContent, string archive | |||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
// 3. Complete the upload | ||||||||||||||||
await CompleteUpload(nextUrl.ToString()); | ||||||||||||||||
var geiUri = await CompleteUpload(nextUrl.ToString()); | ||||||||||||||||
|
||||||||||||||||
return $"gei://archive/{guid}"; | ||||||||||||||||
return geiUri.ToString(); | ||||||||||||||||
} | ||||||||||||||||
catch (Exception ex) | ||||||||||||||||
{ | ||||||||||||||||
|
@@ -132,12 +128,16 @@ private async Task<Uri> UploadPart(byte[] body, int bytesRead, string nextUrl, i | |||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
private async Task CompleteUpload(string lastUrl) | ||||||||||||||||
private async Task<Uri> CompleteUpload(string lastUrl) | ||||||||||||||||
{ | ||||||||||||||||
try | ||||||||||||||||
{ | ||||||||||||||||
await _retryPolicy.Retry(async () => await _client.PutAsync(lastUrl, "")); | ||||||||||||||||
var response = await _retryPolicy.Retry(async () => await _client.PutAsync(lastUrl, "")); | ||||||||||||||||
var responseData = JObject.Parse(response); | ||||||||||||||||
|
||||||||||||||||
_log.LogInformation("Finished uploading archive"); | ||||||||||||||||
|
||||||||||||||||
return new Uri((string)responseData["uri"]); | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The cast to
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With our API, it's not possible for a 201 to be returned from uploads and not have a URI in the response. |
||||||||||||||||
} | ||||||||||||||||
catch (Exception ex) | ||||||||||||||||
{ | ||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5,6 +5,7 @@ | |||||||||||||||||||||
using System.Threading.Tasks; | ||||||||||||||||||||||
using FluentAssertions; | ||||||||||||||||||||||
using Moq; | ||||||||||||||||||||||
using Newtonsoft.Json.Linq; | ||||||||||||||||||||||
using OctoshiftCLI.Extensions; | ||||||||||||||||||||||
using OctoshiftCLI.Services; | ||||||||||||||||||||||
using Xunit; | ||||||||||||||||||||||
|
@@ -52,9 +53,20 @@ public async Task Upload_Should_Upload_All_Chunks_When_Stream_Exceeds_Limit() | |||||||||||||||||||||
const string archiveName = "test-archive"; | ||||||||||||||||||||||
const string baseUrl = "https://uploads.github.com"; | ||||||||||||||||||||||
const string guid = "c9dbd27b-f190-4fe4-979f-d0b7c9b0fcb3"; | ||||||||||||||||||||||
const string geiUri = $"gei://archive/{guid}"; | ||||||||||||||||||||||
|
||||||||||||||||||||||
var startUploadBody = new { content_type = "application/octet-stream", name = archiveName, size = contentSize }; | ||||||||||||||||||||||
|
||||||||||||||||||||||
var completeUploadResponse = new JObject | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
["guid"] = guid, | ||||||||||||||||||||||
["node_id"] = "global-relay-id", | ||||||||||||||||||||||
["name"] = archiveName, | ||||||||||||||||||||||
["size"] = largeContent.Length, | ||||||||||||||||||||||
["uri"] = geiUri, | ||||||||||||||||||||||
["created_at"] = "2025-06-23T17:13:02.818-07:00" | ||||||||||||||||||||||
}; | ||||||||||||||||||||||
Comment on lines
+60
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||||||||||
|
||||||||||||||||||||||
const string initialUploadUrl = $"/organizations/{orgDatabaseId}/gei/archive/blobs/uploads"; | ||||||||||||||||||||||
const string firstUploadUrl = $"/organizations/{orgDatabaseId}/gei/archive/blobs/uploads?part_number=1&guid={guid}"; | ||||||||||||||||||||||
const string secondUploadUrl = $"/organizations/{orgDatabaseId}/gei/archive/blobs/uploads?part_number=2&guid={guid}"; | ||||||||||||||||||||||
|
@@ -83,13 +95,13 @@ public async Task Upload_Should_Upload_All_Chunks_When_Stream_Exceeds_Limit() | |||||||||||||||||||||
// Mocking the final PUT request to complete the multipart upload | ||||||||||||||||||||||
_githubClientMock | ||||||||||||||||||||||
.Setup(m => m.PutAsync($"{baseUrl}{lastUrl}", "", null)) | ||||||||||||||||||||||
.ReturnsAsync(string.Empty); | ||||||||||||||||||||||
.ReturnsAsync(completeUploadResponse.ToString()); | ||||||||||||||||||||||
|
||||||||||||||||||||||
// act | ||||||||||||||||||||||
var result = await _archiveUploader.Upload(archiveContent, archiveName, orgDatabaseId); | ||||||||||||||||||||||
|
||||||||||||||||||||||
// assert | ||||||||||||||||||||||
result.Should().Be($"gei://archive/{guid}"); | ||||||||||||||||||||||
result.Should().Be(geiUri); | ||||||||||||||||||||||
|
||||||||||||||||||||||
_githubClientMock.Verify(m => m.PostWithFullResponseAsync(It.IsAny<string>(), It.IsAny<object>(), null), Times.Once); | ||||||||||||||||||||||
_githubClientMock.Verify(m => m.PatchWithFullResponseAsync(It.IsAny<string>(), It.IsAny<object>(), null), Times.Exactly(3)); | ||||||||||||||||||||||
|
@@ -109,10 +121,20 @@ public async Task Upload_Should_Retry_Failed_Upload_Part_Patch_Requests() | |||||||||||||||||||||
const string archiveName = "test-archive"; | ||||||||||||||||||||||
const string baseUrl = "https://uploads.github.com"; | ||||||||||||||||||||||
const string guid = "c9dbd27b-f190-4fe4-979f-d0b7c9b0fcb3"; | ||||||||||||||||||||||
const string expectedResult = $"gei://archive/{guid}"; | ||||||||||||||||||||||
const string geiUri = $"gei://archive/{guid}"; | ||||||||||||||||||||||
|
||||||||||||||||||||||
var startUploadBody = new { content_type = "application/octet-stream", name = archiveName, size = largeContent.Length }; | ||||||||||||||||||||||
|
||||||||||||||||||||||
var completeUploadResponse = new JObject | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
["guid"] = guid, | ||||||||||||||||||||||
["node_id"] = "global-relay-id", | ||||||||||||||||||||||
["name"] = archiveName, | ||||||||||||||||||||||
["size"] = largeContent.Length, | ||||||||||||||||||||||
["uri"] = geiUri, | ||||||||||||||||||||||
["created_at"] = "2025-06-23T17:13:02.818-07:00" | ||||||||||||||||||||||
}; | ||||||||||||||||||||||
|
||||||||||||||||||||||
const string initialUploadUrl = $"/organizations/{orgDatabaseId}/gei/archive/blobs/uploads"; | ||||||||||||||||||||||
const string firstUploadUrl = $"/organizations/{orgDatabaseId}/gei/archive/blobs/uploads?part_number=1&guid={guid}"; | ||||||||||||||||||||||
const string secondUploadUrl = $"/organizations/{orgDatabaseId}/gei/archive/blobs/uploads?part_number=2&guid={guid}"; | ||||||||||||||||||||||
|
@@ -139,13 +161,13 @@ public async Task Upload_Should_Retry_Failed_Upload_Part_Patch_Requests() | |||||||||||||||||||||
// Mocking the final PUT request to complete the multipart upload | ||||||||||||||||||||||
_githubClientMock | ||||||||||||||||||||||
.Setup(m => m.PutAsync($"{baseUrl}{lastUrl}", "", null)) | ||||||||||||||||||||||
.ReturnsAsync(string.Empty); | ||||||||||||||||||||||
.ReturnsAsync(completeUploadResponse.ToString()); | ||||||||||||||||||||||
|
||||||||||||||||||||||
// act | ||||||||||||||||||||||
var result = await _archiveUploader.Upload(archiveContent, archiveName, orgDatabaseId); | ||||||||||||||||||||||
|
||||||||||||||||||||||
// assert | ||||||||||||||||||||||
Assert.Equal(expectedResult, result); | ||||||||||||||||||||||
Assert.Equal(geiUri, result); | ||||||||||||||||||||||
|
||||||||||||||||||||||
_githubClientMock.Verify(m => m.PostWithFullResponseAsync(It.IsAny<string>(), It.IsAny<object>(), null), Times.Once); | ||||||||||||||||||||||
_githubClientMock.Verify(m => m.PatchWithFullResponseAsync(It.IsAny<string>(), It.IsAny<object>(), null), Times.Exactly(4)); // 2 retries + 2 success | ||||||||||||||||||||||
|
@@ -165,10 +187,20 @@ public async Task Upload_Should_Retry_Failed_Start_Upload_Post_Request() | |||||||||||||||||||||
const string archiveName = "test-archive"; | ||||||||||||||||||||||
const string baseUrl = "https://uploads.github.com"; | ||||||||||||||||||||||
const string guid = "c9dbd27b-f190-4fe4-979f-d0b7c9b0fcb3"; | ||||||||||||||||||||||
const string expectedResult = $"gei://archive/{guid}"; | ||||||||||||||||||||||
const string geiUri = $"gei://archive/{guid}"; | ||||||||||||||||||||||
|
||||||||||||||||||||||
var startUploadBody = new { content_type = "application/octet-stream", name = archiveName, size = largeContent.Length }; | ||||||||||||||||||||||
|
||||||||||||||||||||||
var completeUploadResponse = new JObject | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
["guid"] = guid, | ||||||||||||||||||||||
["node_id"] = "global-relay-id", | ||||||||||||||||||||||
["name"] = archiveName, | ||||||||||||||||||||||
["size"] = largeContent.Length, | ||||||||||||||||||||||
["uri"] = geiUri, | ||||||||||||||||||||||
["created_at"] = "2025-06-23T17:13:02.818-07:00" | ||||||||||||||||||||||
}; | ||||||||||||||||||||||
|
||||||||||||||||||||||
const string initialUploadUrl = $"/organizations/{orgDatabaseId}/gei/archive/blobs/uploads"; | ||||||||||||||||||||||
const string firstUploadUrl = $"/organizations/{orgDatabaseId}/gei/archive/blobs/uploads?part_number=1&guid={guid}"; | ||||||||||||||||||||||
const string secondUploadUrl = $"/organizations/{orgDatabaseId}/gei/archive/blobs/uploads?part_number=2&guid={guid}"; | ||||||||||||||||||||||
|
@@ -195,13 +227,13 @@ public async Task Upload_Should_Retry_Failed_Start_Upload_Post_Request() | |||||||||||||||||||||
// Mocking the final PUT request to complete the multipart upload | ||||||||||||||||||||||
_githubClientMock | ||||||||||||||||||||||
.Setup(m => m.PutAsync($"{baseUrl}{lastUrl}", "", null)) | ||||||||||||||||||||||
.ReturnsAsync(string.Empty); | ||||||||||||||||||||||
.ReturnsAsync(completeUploadResponse.ToString()); | ||||||||||||||||||||||
|
||||||||||||||||||||||
// act | ||||||||||||||||||||||
var result = await _archiveUploader.Upload(archiveContent, archiveName, orgDatabaseId); | ||||||||||||||||||||||
|
||||||||||||||||||||||
// assert | ||||||||||||||||||||||
Assert.Equal(expectedResult, result); | ||||||||||||||||||||||
Assert.Equal(geiUri, result); | ||||||||||||||||||||||
|
||||||||||||||||||||||
_githubClientMock.Verify(m => m.PostWithFullResponseAsync(It.IsAny<string>(), It.IsAny<object>(), null), Times.Exactly(3)); // 2 retries + 1 success | ||||||||||||||||||||||
_githubClientMock.Verify(m => m.PatchWithFullResponseAsync(It.IsAny<string>(), It.IsAny<object>(), null), Times.Exactly(2)); | ||||||||||||||||||||||
|
@@ -221,10 +253,20 @@ public async Task Upload_Should_Retry_Failed_Complete_Upload_Put_Request() | |||||||||||||||||||||
const string archiveName = "test-archive"; | ||||||||||||||||||||||
const string baseUrl = "https://uploads.github.com"; | ||||||||||||||||||||||
const string guid = "c9dbd27b-f190-4fe4-979f-d0b7c9b0fcb3"; | ||||||||||||||||||||||
const string expectedResult = $"gei://archive/{guid}"; | ||||||||||||||||||||||
const string geiUri = $"gei://archive/{guid}"; | ||||||||||||||||||||||
|
||||||||||||||||||||||
var startUploadBody = new { content_type = "application/octet-stream", name = archiveName, size = largeContent.Length }; | ||||||||||||||||||||||
|
||||||||||||||||||||||
var completeUploadResponse = new JObject | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
["guid"] = guid, | ||||||||||||||||||||||
["node_id"] = "global-relay-id", | ||||||||||||||||||||||
["name"] = archiveName, | ||||||||||||||||||||||
["size"] = largeContent.Length, | ||||||||||||||||||||||
["uri"] = geiUri, | ||||||||||||||||||||||
["created_at"] = "2025-06-23T17:13:02.818-07:00" | ||||||||||||||||||||||
}; | ||||||||||||||||||||||
|
||||||||||||||||||||||
const string initialUploadUrl = $"/organizations/{orgDatabaseId}/gei/archive/blobs/uploads"; | ||||||||||||||||||||||
const string firstUploadUrl = $"/organizations/{orgDatabaseId}/gei/archive/blobs/uploads?part_number=1&guid={guid}"; | ||||||||||||||||||||||
const string secondUploadUrl = $"/organizations/{orgDatabaseId}/gei/archive/blobs/uploads?part_number=2&guid={guid}"; | ||||||||||||||||||||||
|
@@ -251,13 +293,13 @@ public async Task Upload_Should_Retry_Failed_Complete_Upload_Put_Request() | |||||||||||||||||||||
.SetupSequence(m => m.PutAsync($"{baseUrl}{lastUrl}", "", null)) | ||||||||||||||||||||||
.ThrowsAsync(new TimeoutException("The operation was canceled.")) | ||||||||||||||||||||||
.ThrowsAsync(new TimeoutException("The operation was canceled.")) | ||||||||||||||||||||||
.ReturnsAsync(string.Empty); | ||||||||||||||||||||||
.ReturnsAsync(completeUploadResponse.ToString()); | ||||||||||||||||||||||
|
||||||||||||||||||||||
// act | ||||||||||||||||||||||
var result = await _archiveUploader.Upload(archiveContent, archiveName, orgDatabaseId); | ||||||||||||||||||||||
|
||||||||||||||||||||||
// assert | ||||||||||||||||||||||
Assert.Equal(expectedResult, result); | ||||||||||||||||||||||
Assert.Equal(geiUri, result); | ||||||||||||||||||||||
|
||||||||||||||||||||||
_githubClientMock.Verify(m => m.PostWithFullResponseAsync(It.IsAny<string>(), It.IsAny<object>(), null), Times.Once); | ||||||||||||||||||||||
_githubClientMock.Verify(m => m.PatchWithFullResponseAsync(It.IsAny<string>(), It.IsAny<object>(), null), Times.Exactly(2)); | ||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable
geiUri
is aUri
object, but callingToString()
on it is redundant since the method return type isstring
. This should returngeiUri.ToString()
or change the return type toUri
and returngeiUri
directly.Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This suggestion produces this error: