Skip to content

Commit

Permalink
Adopt to new web platform specifier extension resource url format
Browse files Browse the repository at this point in the history
Added target to unpkg endpoint
Added unit tests
  • Loading branch information
amvanbaren committed Jun 20, 2023
1 parent 63eae3e commit 5e75785
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public interface IVSCodeService {

ExtensionQueryResult extensionQuery(ExtensionQueryParam param, int defaultPageSize);

ResponseEntity<byte[]> browse(String namespaceName, String extensionName, String version, String path);
ResponseEntity<byte[]> browse(String namespaceName, String extensionName, String version, String targetPlatform, String path);

String download(String namespace, String extension, String version, String targetPlatform);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.openvsx.entities.Extension;
import org.eclipse.openvsx.entities.ExtensionVersion;
import org.eclipse.openvsx.entities.FileResource;
Expand Down Expand Up @@ -386,15 +387,20 @@ public String download(String namespace, String extension, String version, Strin
}

@Override
public ResponseEntity<byte[]> browse(String namespaceName, String extensionName, String version, String path) {
public ResponseEntity<byte[]> browse(String namespaceName, String extensionName, String version, String targetPlatform, String path) {
if(isBuiltInExtensionNamespace(namespaceName)) {
return new ResponseEntity<>(("Built-in extension namespace '" + namespaceName + "' not allowed").getBytes(StandardCharsets.UTF_8), null, HttpStatus.BAD_REQUEST);
}

var extVersions = repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName);
var extVersion = extVersions.stream().max(Comparator.<ExtensionVersion, Boolean>comparing(ExtensionVersion::isUniversalTargetPlatform)
.thenComparing(ExtensionVersion::getTargetPlatform))
.orElse(null);
ExtensionVersion extVersion;
if(StringUtils.isEmpty(targetPlatform)) {
var extVersions = repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName);
extVersion = extVersions.stream().max(Comparator.<ExtensionVersion, Boolean>comparing(ExtensionVersion::isUniversalTargetPlatform)
.thenComparing(ExtensionVersion::getTargetPlatform))
.orElse(null);
} else {
extVersion = repositories.findActiveExtensionVersionByVersion(version, targetPlatform, extensionName, namespaceName);
}

if (extVersion == null) {
throw new NotFoundException();
Expand All @@ -412,7 +418,7 @@ public ResponseEntity<byte[]> browse(String namespaceName, String extensionName,

return exactMatch != null
? browseFile(exactMatch, namespaceName, extensionName, extVersion.getTargetPlatform(), version)
: browseDirectory(resources, namespaceName, extensionName, version, path);
: browseDirectory(resources, namespaceName, extensionName, targetPlatform, version, path);
}

private ResponseEntity<byte[]> browseFile(
Expand Down Expand Up @@ -454,12 +460,16 @@ private ResponseEntity<byte[]> browseDirectory(
List<FileResource> resources,
String namespaceName,
String extensionName,
String targetPlatform,
String version,
String path
) {
if(!path.isEmpty() && !path.endsWith("/")) {
path += "/";
}
if(StringUtils.isNotEmpty(targetPlatform)) {
version += "+" + targetPlatform;
}

var urls = new HashSet<String>();
var baseUrl = UrlUtil.createApiUrl(UrlUtil.getBaseUrl(), "vscode", "unpkg", namespaceName, extensionName, version);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.openvsx.UpstreamProxyService;
import org.eclipse.openvsx.UrlConfigService;
import org.eclipse.openvsx.util.HttpHeadersUtil;
Expand Down Expand Up @@ -76,7 +77,11 @@ public ExtensionQueryResult extensionQuery(ExtensionQueryParam param, int defaul
}

@Override
public ResponseEntity<byte[]> browse(String namespaceName, String extensionName, String version, String path) {
public ResponseEntity<byte[]> browse(String namespaceName, String extensionName, String version, String targetPlatform, String path) {
if(StringUtils.isNotEmpty(targetPlatform)) {
version += "+" + targetPlatform;
}

var urlTemplate = urlConfigService.getUpstreamUrl() + "/vscode/unpkg/{namespace}/{extension}/{version}";
var uriVariables = new HashMap<>(Map.of(
"namespace", namespaceName,
Expand Down
19 changes: 17 additions & 2 deletions server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
********************************************************************************/
package org.eclipse.openvsx.adapter;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.openvsx.util.NotFoundException;
import org.eclipse.openvsx.util.TargetPlatform;
import org.eclipse.openvsx.util.UrlUtil;
Expand Down Expand Up @@ -129,12 +130,26 @@ public ResponseEntity<byte[]> browse(
HttpServletRequest request,
@PathVariable String namespaceName,
@PathVariable String extensionName,
@PathVariable String version
@PathVariable String version,
@RequestParam(required = false) String target
) {
if(StringUtils.isEmpty(target)) {
var index = version.lastIndexOf('+');
if(index >= 0 && index + 1 < version.length()) {
target = version.substring(index + 1);
if(TargetPlatform.isValid(target)) {
version = version.substring(0, index);
}
}
}
if(StringUtils.isNotEmpty(target) && !TargetPlatform.isValid(target)) {
target = null;
}

var path = UrlUtil.extractWildcardPath(request);
for (var service : getVSCodeServices()) {
try {
return service.browse(namespaceName, extensionName, version, path);
return service.browse(namespaceName, extensionName, version, target, path);
} catch (NotFoundException exc) {
// Try the next registry
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public interface ExtensionVersionRepository extends Repository<ExtensionVersion,

ExtensionVersion findByVersionAndTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(String version, String targetPlatform, String extensionName, String namespace);

ExtensionVersion findByVersionAndTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCaseAndActiveTrue(String version, String targetPlatform, String extensionName, String namespace);

Streamable<ExtensionVersion> findByVersionAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(String version, String extensionName, String namespace);

Streamable<ExtensionVersion> findByPublishedWithUser(UserData user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ public List<ExtensionVersion> findActiveExtensionVersionsByVersion(String versio
return extensionVersionJooqRepo.findAllActiveByVersionAndExtensionNameAndNamespaceName(version, extensionName, namespaceName);
}

public ExtensionVersion findActiveExtensionVersionByVersion(String version, String targetPlatform, String extensionName, String namespaceName) {
return extensionVersionRepo.findByVersionAndTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCaseAndActiveTrue(version, targetPlatform, extensionName, namespaceName);
}

public List<ExtensionVersion> findActiveExtensionVersionsByExtensionName(String targetPlatform, String extensionName, String namespaceName) {
return extensionVersionJooqRepo.findAllActiveByExtensionNameAndNamespaceName(targetPlatform, extensionName, namespaceName);
}
Expand Down
163 changes: 163 additions & 0 deletions server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,80 @@ public void testBrowseTopDir() throws Exception {
.andExpect(content().json("[\"http://localhost/vscode/unpkg/foo/bar/1.3.4/extension.vsixmanifest\",\"http://localhost/vscode/unpkg/foo/bar/1.3.4/extension/\"]"));
}

@Test
public void testBrowseTopDirTargetPlatform() throws Exception {
var version = "1.3.4";
var targetPlatform = "linux-x64";
var extensionName = "bar";
var namespaceName = "foo";
var namespace = new Namespace();
namespace.setName(namespaceName);
var extension = new Extension();
extension.setId(0L);
extension.setName(extensionName);
extension.setNamespace(namespace);
var extVersion = new ExtensionVersion();
extVersion.setId(1L);
extVersion.setVersion(version);
extVersion.setTargetPlatform(targetPlatform);
extVersion.setExtension(extension);

Mockito.when(repositories.findActiveExtensionVersionByVersion(version, targetPlatform, extensionName, namespaceName))
.thenReturn(extVersion);

var vsixResource = mockFileResource(15, extVersion, "extension.vsixmanifest", RESOURCE, STORAGE_DB, "<xml></xml>".getBytes(StandardCharsets.UTF_8));
var manifestResource = mockFileResource(16, extVersion, "extension/package.json", RESOURCE, STORAGE_DB, "{\"package\":\"json\"}".getBytes(StandardCharsets.UTF_8));
var readmeResource = mockFileResource(17, extVersion, "extension/README.md", RESOURCE, STORAGE_DB, "README".getBytes(StandardCharsets.UTF_8));
var changelogResource = mockFileResource(18, extVersion, "extension/CHANGELOG.md", RESOURCE, STORAGE_DB, "CHANGELOG".getBytes(StandardCharsets.UTF_8));
var licenseResource = mockFileResource(19, extVersion, "extension/LICENSE.txt", RESOURCE, STORAGE_DB, "LICENSE".getBytes(StandardCharsets.UTF_8));
var iconResource = mockFileResource(20, extVersion, "extension/images/icon128.png", RESOURCE, STORAGE_DB, "ICON128".getBytes(StandardCharsets.UTF_8));

Mockito.when(repositories.findResourceFileResources(1L, ""))
.thenReturn(List.of(vsixResource, manifestResource, readmeResource, changelogResource, licenseResource, iconResource));

mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}?target={targetPlatform}", namespaceName, extensionName, version, targetPlatform))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(content().json("[\"http://localhost/vscode/unpkg/foo/bar/1.3.4+linux-x64/extension.vsixmanifest\",\"http://localhost/vscode/unpkg/foo/bar/1.3.4+linux-x64/extension/\"]"));
}

@Test
public void testBrowseTopDirVersionTargetPlatform() throws Exception {
var version = "1.3.4-rc-1+armhf";
var targetPlatform = "darwin-x64";
var extensionName = "bar";
var namespaceName = "foo";
var namespace = new Namespace();
namespace.setName(namespaceName);
var extension = new Extension();
extension.setId(0L);
extension.setName(extensionName);
extension.setNamespace(namespace);
var extVersion = new ExtensionVersion();
extVersion.setId(1L);
extVersion.setVersion(version);
extVersion.setTargetPlatform(targetPlatform);
extVersion.setExtension(extension);

Mockito.when(repositories.findActiveExtensionVersionByVersion(version, targetPlatform, extensionName, namespaceName))
.thenReturn(extVersion);

var vsixResource = mockFileResource(15, extVersion, "extension.vsixmanifest", RESOURCE, STORAGE_DB, "<xml></xml>".getBytes(StandardCharsets.UTF_8));
var manifestResource = mockFileResource(16, extVersion, "extension/package.json", RESOURCE, STORAGE_DB, "{\"package\":\"json\"}".getBytes(StandardCharsets.UTF_8));
var readmeResource = mockFileResource(17, extVersion, "extension/README.md", RESOURCE, STORAGE_DB, "README".getBytes(StandardCharsets.UTF_8));
var changelogResource = mockFileResource(18, extVersion, "extension/CHANGELOG.md", RESOURCE, STORAGE_DB, "CHANGELOG".getBytes(StandardCharsets.UTF_8));
var licenseResource = mockFileResource(19, extVersion, "extension/LICENSE.txt", RESOURCE, STORAGE_DB, "LICENSE".getBytes(StandardCharsets.UTF_8));
var iconResource = mockFileResource(20, extVersion, "extension/images/icon128.png", RESOURCE, STORAGE_DB, "ICON128".getBytes(StandardCharsets.UTF_8));

Mockito.when(repositories.findResourceFileResources(1L, ""))
.thenReturn(List.of(vsixResource, manifestResource, readmeResource, changelogResource, licenseResource, iconResource));

mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}+{targetPlatform}", namespaceName, extensionName, version, targetPlatform))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(content().json("[\"http://localhost/vscode/unpkg/foo/bar/1.3.4-rc-1+armhf+darwin-x64/extension.vsixmanifest\",\"http://localhost/vscode/unpkg/foo/bar/1.3.4-rc-1+armhf+darwin-x64/extension/\"]"));
}

@Test
public void testBrowseVsixManifest() throws Exception {
var version = "1.3.4";
Expand Down Expand Up @@ -601,6 +675,95 @@ public void testBrowseIcon() throws Exception {
.andExpect(content().bytes(content));
}

@Test
public void testBrowseIconTargetPlatform() throws Exception {
var version = "1.3.4";
var targetPlatform = "win32-x64";
var extensionName = "bar";
var namespaceName = "foo";
var namespace = new Namespace();
namespace.setName(namespaceName);
var extension = new Extension();
extension.setId(0L);
extension.setName(extensionName);
extension.setNamespace(namespace);
var extVersion = new ExtensionVersion();
extVersion.setId(1L);
extVersion.setVersion(version);
extVersion.setTargetPlatform(targetPlatform);
extVersion.setExtension(extension);
Mockito.when(repositories.findActiveExtensionVersionByVersion(version, targetPlatform, extensionName, namespaceName))
.thenReturn(extVersion);

var content = "ICON128".getBytes(StandardCharsets.UTF_8);
var iconResource = mockFileResource(20, extVersion, "extension/images/icon128.png", RESOURCE, STORAGE_DB, content);
Mockito.when(repositories.findResourceFileResources(1L, "extension/images/icon128.png"))
.thenReturn(List.of(iconResource));

mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}/{path}?target={targetPlatform}", namespaceName, extensionName, version, "extension/images/icon128.png", targetPlatform))
.andExpect(status().isOk())
.andExpect(content().bytes(content));
}

@Test
public void testBrowseIconVersionTargetPlatform() throws Exception {
var version = "1.3.4-ga+armhf";
var targetPlatform = "alpine-x64";
var extensionName = "bar";
var namespaceName = "foo";
var namespace = new Namespace();
namespace.setName(namespaceName);
var extension = new Extension();
extension.setId(0L);
extension.setName(extensionName);
extension.setNamespace(namespace);
var extVersion = new ExtensionVersion();
extVersion.setId(1L);
extVersion.setVersion(version);
extVersion.setTargetPlatform(targetPlatform);
extVersion.setExtension(extension);
Mockito.when(repositories.findActiveExtensionVersionByVersion(version, targetPlatform, extensionName, namespaceName))
.thenReturn(extVersion);

var content = "ICON128".getBytes(StandardCharsets.UTF_8);
var iconResource = mockFileResource(20, extVersion, "extension/images/icon128.png", RESOURCE, STORAGE_DB, content);
Mockito.when(repositories.findResourceFileResources(1L, "extension/images/icon128.png"))
.thenReturn(List.of(iconResource));

mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}+{targetPlatform}/{path}", namespaceName, extensionName, version, targetPlatform, "extension/images/icon128.png"))
.andExpect(status().isOk())
.andExpect(content().bytes(content));
}

@Test
public void testBrowseIconVersionInvalidTargetPlatform() throws Exception {
var version = "1.3.4-ga+armhf";
var extensionName = "bar";
var namespaceName = "foo";
var namespace = new Namespace();
namespace.setName(namespaceName);
var extension = new Extension();
extension.setId(0L);
extension.setName(extensionName);
extension.setNamespace(namespace);
var extVersion = new ExtensionVersion();
extVersion.setId(1L);
extVersion.setVersion(version);
extVersion.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL);
extVersion.setExtension(extension);
Mockito.when(repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName))
.thenReturn(List.of(extVersion));

var content = "ICON128".getBytes(StandardCharsets.UTF_8);
var iconResource = mockFileResource(20, extVersion, "extension/images/icon128.png", RESOURCE, STORAGE_DB, content);
Mockito.when(repositories.findResourceFileResources(1L, "extension/images/icon128.png"))
.thenReturn(List.of(iconResource));

mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}/{path}", namespaceName, extensionName, version, "extension/images/icon128.png"))
.andExpect(status().isOk())
.andExpect(content().bytes(content));
}

@Test
public void testDownload() throws Exception {
mockExtensionVersion();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ void testExecuteQueries() {
() -> repositories.topNamespaceExtensionVersions(NOW, 1),
() -> repositories.findFileResourcesByExtensionVersionIdAndType(LONG_LIST, STRING_LIST),
() -> repositories.findActiveExtensionVersionsByVersion("version", "extensionName", "namespaceName"),
() -> repositories.findActiveExtensionVersionByVersion("version", "targetPlatform", "extensionName", "namespaceName"),
() -> repositories.findResourceFileResources(1L, "prefix"),
() -> repositories.findActiveExtensionVersions(LONG_LIST, "targetPlatform"),
() -> repositories.findActiveExtension("name", "namespaceName"),
Expand Down

0 comments on commit 5e75785

Please sign in to comment.