Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/main/java/land/oras/utils/Const.java
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,19 @@ public static String currentTimestamp() {
* Application octet stream header value
*/
public static final String APPLICATION_OCTET_STREAM_HEADER_VALUE = "application/octet-stream";

/**
* Content Range header
*/
public static final String CONTENT_RANGE_HEADER = "Content-Range";

/**
* Range header
*/
public static final String RANGE_HEADER = "Range";

/**
* OCI Chunk Minimum Length header
*/
public static final String OCI_CHUNK_MIN_LENGTH_HEADER = "OCI-Chunk-Min-Length";
}
17 changes: 17 additions & 0 deletions src/main/java/land/oras/utils/OrasHttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,23 @@ public ResponseWrapper<String> post(URI uri, byte[] body, Map<String, String> he
HttpRequest.BodyPublishers.ofByteArray(body));
}

/**
* Perform a Patch request
* @param uri The URI
* @param body The body
* @param headers The headers
* @return The response
*/
public ResponseWrapper<String> patch(URI uri, byte[] body, Map<String, String> headers) {
return executeRequest(
"PATCH",
uri,
headers,
body,
HttpResponse.BodyHandlers.ofString(),
HttpRequest.BodyPublishers.ofByteArray(body));
}

/**
* Perform a PUT request
* @param uri The URI
Expand Down
48 changes: 48 additions & 0 deletions src/test/java/land/oras/RegistryWireMockTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@

package land.oras;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.patch;
import static com.github.tomakehurst.wiremock.client.WireMock.patchRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
Expand All @@ -30,18 +35,22 @@
import com.github.tomakehurst.wiremock.stubbing.Scenario;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import land.oras.auth.AuthStore;
import land.oras.auth.AuthStoreAuthenticationProvider;
import land.oras.auth.BearerTokenProvider;
import land.oras.auth.UsernamePasswordProvider;
import land.oras.exception.OrasException;
import land.oras.utils.Const;
import land.oras.utils.JsonUtils;
import land.oras.utils.OrasHttpClient;
import land.oras.utils.SupportedAlgorithm;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -356,4 +365,43 @@ void shouldRefreshExpiredToken(WireMockRuntimeInfo wmRuntimeInfo) {
byte[] blob = registry.getBlob(containerRef.withDigest(digest));
assertEquals("blob-data", new String(blob));
}

@Test
void shouldExecutePatchRequestWithHeaders(WireMockRuntimeInfo wMockRuntimeInfo) {
WireMock wireMock = wMockRuntimeInfo.getWireMock();
String registryUrl = wMockRuntimeInfo.getHttpBaseUrl().replace("http://", "");
OrasHttpClient client =
OrasHttpClient.Builder.builder().withSkipTlsVerify(true).build();

// Setup Mock to craete a PATCH request with Headers
wireMock.register(patch(urlEqualTo("/v2/test/blobs/uploads/session1"))
.withHeader(Const.CONTENT_TYPE_HEADER, equalTo(Const.APPLICATION_OCTET_STREAM_HEADER_VALUE))
.withHeader(Const.CONTENT_RANGE_HEADER, equalTo("0-1023"))
.willReturn(aResponse()
.withStatus(202)
.withHeader(Const.LOCATION_HEADER, "/v2/test/blobs/uploads/session2")
.withHeader(Const.RANGE_HEADER, "0-1023")
.withHeader(Const.OCI_CHUNK_MIN_LENGTH_HEADER, "4096")));

// Create sample data with headers
byte[] data = "test patch".getBytes();
Map<String, String> headers = new HashMap<>();
headers.put(Const.CONTENT_TYPE_HEADER, Const.APPLICATION_OCTET_STREAM_HEADER_VALUE);
headers.put(Const.CONTENT_RANGE_HEADER, "0-1023");

// Execute Patch
URI uri = URI.create("http://" + registryUrl + "/v2/test/blobs/uploads/session1");
OrasHttpClient.ResponseWrapper<String> response = client.patch(uri, data, headers);

// Verify response uses all our constants
assertEquals(202, response.statusCode());
assertEquals("/v2/test/blobs/uploads/session2", response.headers().get(Const.LOCATION_HEADER.toLowerCase()));
assertEquals("0-1023", response.headers().get(Const.RANGE_HEADER.toLowerCase()));
assertEquals("4096", response.headers().get(Const.OCI_CHUNK_MIN_LENGTH_HEADER.toLowerCase()));

// Verify the PATCH request was made with correct headers
wireMock.verifyThat(patchRequestedFor(urlEqualTo("/v2/test/blobs/uploads/session1"))
.withHeader(Const.CONTENT_TYPE_HEADER, equalTo(Const.APPLICATION_OCTET_STREAM_HEADER_VALUE))
.withHeader(Const.CONTENT_RANGE_HEADER, equalTo("0-1023")));
}
}