Skip to content

Commit 82a3c38

Browse files
authored
Merge pull request #890 from amvanbaren/bugfix/issue-889
bug: case-sensitive urls
2 parents f56afd8 + cc925fc commit 82a3c38

File tree

6 files changed

+212
-16
lines changed

6 files changed

+212
-16
lines changed

server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -771,8 +771,8 @@ public ExtensionVersion find(String namespaceName, String extensionName, String
771771
query.addJoin(EXTENSION, EXTENSION.ID.eq(EXTENSION_VERSION.EXTENSION_ID));
772772
query.addJoin(NAMESPACE, NAMESPACE.ID.eq(EXTENSION.NAMESPACE_ID));
773773
query.addConditions(
774-
EXTENSION.NAME.eq(extensionName),
775-
NAMESPACE.NAME.eq(namespaceName)
774+
EXTENSION.NAME.equalIgnoreCase(extensionName),
775+
NAMESPACE.NAME.equalIgnoreCase(namespaceName)
776776
);
777777
if(!VersionAlias.LATEST.equals(version) && !VersionAlias.PRE_RELEASE.equals(version)) {
778778
query.addConditions(EXTENSION_VERSION.VERSION.eq(version));

server/src/test/java/org/eclipse/openvsx/IntegrationTest.java

Lines changed: 128 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
import static org.assertj.core.api.Assertions.assertThat;
1313

1414
import java.io.IOException;
15+
import java.net.URI;
16+
import java.net.URISyntaxException;
17+
import java.util.List;
1518

16-
import org.eclipse.openvsx.json.ExtensionJson;
17-
import org.eclipse.openvsx.json.NamespaceJson;
18-
import org.eclipse.openvsx.json.ResultJson;
19-
import org.eclipse.openvsx.json.SearchResultJson;
19+
import com.fasterxml.jackson.databind.JsonNode;
20+
import com.fasterxml.jackson.databind.node.NullNode;
21+
import org.eclipse.openvsx.json.*;
2022
import org.junit.jupiter.api.Test;
2123
import org.springframework.beans.factory.annotation.Autowired;
2224
import org.springframework.boot.test.context.SpringBootTest;
@@ -47,50 +49,162 @@ private String apiCall(String path) {
4749
public void testPublishExtension() throws Exception {
4850
testService.createUser();
4951
createNamespace();
52+
getNamespaceMetadata("/api/editorconfig");
53+
getNamespaceMetadata("/api/editorconfig/details");
54+
duplicateNamespaceLowercase();
55+
verifyToken();
5056
publishExtension();
5157

5258
// Wait a bit until the publish extension background job has finished
5359
Thread.sleep(15000);
54-
getExtensionMetadata();
60+
61+
getExtensionMetadata("/api/editorconfig/editorconfig");
62+
getExtensionMetadata("/api/editorconfig/editorconfig/0.16.6");
63+
getExtensionMetadata("/api/editorconfig/editorconfig/latest");
64+
getExtensionMetadata("/api/editorconfig/editorconfig/universal");
65+
getExtensionMetadata("/api/editorconfig/editorconfig/universal/0.16.6");
66+
getExtensionMetadata("/api/editorconfig/editorconfig/universal/latest");
67+
68+
getVersionsMetadata("editorconfig", "editorconfig", null);
69+
getVersionsMetadata("editorconfig", "editorconfig", "universal");
70+
71+
getVersionReferencesMetadata("/api/editorconfig/editorconfig/version-references");
72+
getVersionReferencesMetadata("/api/editorconfig/editorconfig/universal/version-references");
73+
74+
getReviews();
75+
76+
getFile("/api/editorconfig/editorconfig/latest/file/download");
77+
getFile("/api/editorconfig/editorconfig/universal/latest/file/download");
78+
getFile("/api/editorconfig/editorconfig/0.16.6/file/download");
79+
getFile("/api/editorconfig/editorconfig/universal/0.16.6/file/download");
80+
getFile("/api/editorconfig/editorconfig/latest/file/editorconfig.editorconfig-0.16.6.vsix");
81+
getFile("/api/editorconfig/editorconfig/universal/latest/file/editorconfig.editorconfig-0.16.6.vsix");
82+
getFile("/api/editorconfig/editorconfig/0.16.6/file/editorconfig.editorconfig-0.16.6.vsix");
83+
getFile("/api/editorconfig/editorconfig/universal/0.16.6/file/editorconfig.editorconfig-0.16.6.vsix");
84+
getFile("/vscode/asset/editorconfig/editorconfig/0.16.6/Microsoft.VisualStudio.Services.VSIXPackage");
85+
getFile("/vscode/asset/editorconfig/editorconfig/0.16.6/Microsoft.VisualStudio.Services.VSIXPackage?targetPlatform=universal");
86+
getFile("/vscode/unpkg/editorconfig/editorconfig/0.16.6");
87+
getFile("/vscode/unpkg/editorconfig/editorconfig/0.16.6/extension.vsixmanifest");
88+
89+
getVscodeDownloadLink();
5590

5691
// Wait a bit until the new entry has landed in the search index
5792
Thread.sleep(2000);
5893
searchExtension();
94+
publishDuplicateExtensionLowercase();
5995
}
6096

6197
private void createNamespace() {
6298
var requestBody = new NamespaceJson();
63-
requestBody.name = "Equinusocio";
99+
requestBody.name = "EditorConfig";
64100
var response = restTemplate.postForEntity(apiCall("/api/-/namespace/create?token={token}"), requestBody,
65101
ResultJson.class, "test_token");
66102
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
67103
assertThat(response.getBody().error).isNull();
68104
assertThat(response.getBody().success).isEqualTo("Created namespace " + requestBody.name);
69105
}
70106

107+
private void duplicateNamespaceLowercase() {
108+
var requestBody = new NamespaceJson();
109+
requestBody.name = "editorconfig";
110+
var response = restTemplate.postForEntity(apiCall("/api/-/namespace/create?token={token}"), requestBody,
111+
ResultJson.class, "test_token");
112+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
113+
assertThat(response.getBody().success).isNull();
114+
assertThat(response.getBody().error).isEqualTo("Namespace already exists: EditorConfig");
115+
}
116+
117+
private void getNamespaceMetadata(String path) {
118+
var response = restTemplate.getForEntity(apiCall(path), JsonNode.class);
119+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
120+
var json = response.getBody();
121+
assertThat(json.get("error")).isNull();
122+
assertThat(json.get("name").asText()).isEqualTo("EditorConfig");
123+
}
124+
125+
private void verifyToken() {
126+
var response = restTemplate.getForEntity(apiCall("/api/editorconfig/verify-pat?token=test_token"), ResultJson.class);
127+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
128+
var json = response.getBody();
129+
assertThat(json.error).isNull();
130+
assertThat(json.success).isEqualTo("Valid token");
131+
}
132+
71133
private void publishExtension() throws IOException {
72-
try (var stream = getClass().getResourceAsStream("vsc-material-theme.vsix")) {
134+
try (var stream = getClass().getResourceAsStream("EditorConfig.EditorConfig-0.16.6.vsix")) {
73135
var bytes = stream.readAllBytes();
74136
var response = restTemplate.postForEntity(apiCall("/api/-/publish?token={token}"),
75137
bytes, ExtensionJson.class, "test_token");
76138
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
77139
assertThat(response.getBody().error).isNull();
78-
assertThat(response.getBody().name).isEqualTo("vsc-material-theme");
140+
assertThat(response.getBody().name).isEqualTo("EditorConfig");
79141
}
80142
}
81143

82-
private void getExtensionMetadata() {
83-
var response = restTemplate.getForEntity(apiCall("/api/Equinusocio/vsc-material-theme"), ExtensionJson.class);
144+
private void publishDuplicateExtensionLowercase() throws IOException {
145+
try (var stream = getClass().getResourceAsStream("editorconfig.editorconfig-0.16.6-2.vsix")) {
146+
var bytes = stream.readAllBytes();
147+
var response = restTemplate.postForEntity(apiCall("/api/-/publish?token={token}"),
148+
bytes, ExtensionJson.class, "test_token");
149+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
150+
assertThat(response.getBody().error).isEqualTo("Extension editorconfig.editorconfig 0.16.6 is already published.");
151+
}
152+
}
153+
154+
private void getExtensionMetadata(String url) {
155+
var response = restTemplate.getForEntity(apiCall(url), ExtensionJson.class);
156+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
157+
assertThat(response.getBody().description).isEqualTo("EditorConfig Support for Visual Studio Code");
158+
}
159+
160+
private void getVersionsMetadata(String namespace, String extension, String target) {
161+
var path = "/api/" + namespace + "/" + extension;
162+
if(target != null) {
163+
path += "/" + target;
164+
}
165+
166+
var response = restTemplate.getForEntity(apiCall(path + "/versions"), VersionsJson.class);
167+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
168+
assertThat(response.getBody().versions.size()).isEqualTo(1);
169+
170+
var version = "0.16.6";
171+
var versionPath = path + "/" + version;
172+
assertThat(response.getBody().versions.get(version)).isEqualTo(apiCall(versionPath));
173+
}
174+
175+
private void getVersionReferencesMetadata(String path) {
176+
var response = restTemplate.getForEntity(apiCall(path), VersionReferencesJson.class);
177+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
178+
assertThat(response.getBody().versions.size()).isEqualTo(1);
179+
assertThat(response.getBody().versions.get(0).version).isEqualTo("0.16.6");
180+
}
181+
182+
private void getFile(String path) {
183+
var response = restTemplate.getForEntity(apiCall(path), byte[].class);
184+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
185+
assertThat(response.getBody()).isNotEmpty();
186+
}
187+
188+
private void getReviews() {
189+
var response = restTemplate.getForEntity(apiCall("/api/editorconfig/editorconfig/reviews"), ReviewListJson.class);
84190
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
85-
assertThat(response.getBody().description).isEqualTo("The most epic theme now for Visual Studio Code");
191+
assertThat(response.getBody().error).isNull();
192+
assertThat(response.getBody().reviews.size()).isEqualTo(0);
86193
}
87194

88195
private void searchExtension() {
89-
var response = restTemplate.getForEntity(apiCall("/api/-/search?query=material"), SearchResultJson.class);
196+
var response = restTemplate.getForEntity(apiCall("/api/-/search?query=editorconfig"), SearchResultJson.class);
90197
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
91198
assertThat(response.getBody().extensions.size()).isEqualTo(1);
92199
assertThat(response.getBody().extensions.get(0).description)
93-
.isEqualTo("The most epic theme now for Visual Studio Code");
200+
.isEqualTo("EditorConfig Support for Visual Studio Code");
201+
}
202+
203+
public void getVscodeDownloadLink() throws URISyntaxException {
204+
var path = "/vscode/gallery/publishers/editorconfig/vsextensions/editorconfig/0.16.6/vspackage";
205+
var response = restTemplate.getForEntity(apiCall(path), String.class);
206+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND);
207+
var expectedPath = "/vscode/asset/editorconfig/editorconfig/0.16.6/Microsoft.VisualStudio.Services.VSIXPackage";
208+
assertThat(response.getHeaders().getLocation()).isEqualTo(new URI(apiCall(expectedPath)));
94209
}
95-
96210
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/** ******************************************************************************
2+
* Copyright (c) 2024 Precies. Software Ltd and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
* ****************************************************************************** */
10+
package org.eclipse.openvsx.web;
11+
12+
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
13+
import jakarta.persistence.EntityManager;
14+
import org.eclipse.openvsx.UserService;
15+
import org.eclipse.openvsx.eclipse.EclipseService;
16+
import org.eclipse.openvsx.repositories.RepositoryService;
17+
import org.eclipse.openvsx.security.OAuth2UserServices;
18+
import org.eclipse.openvsx.security.SecurityConfig;
19+
import org.eclipse.openvsx.security.TokenService;
20+
import org.junit.jupiter.api.Test;
21+
import org.mockito.Mockito;
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient;
24+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
25+
import org.springframework.boot.test.context.TestConfiguration;
26+
import org.springframework.boot.test.mock.mockito.MockBean;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Import;
29+
import org.springframework.test.web.servlet.MockMvc;
30+
31+
import java.util.List;
32+
33+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
34+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
35+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
36+
37+
@WebMvcTest(SitemapController.class)
38+
@AutoConfigureWebClient
39+
@MockBean({
40+
EclipseService.class, SimpleMeterRegistry.class, UserService.class, TokenService.class, EntityManager.class
41+
})
42+
public class SitemapControllerTest {
43+
44+
@MockBean
45+
RepositoryService repositories;
46+
47+
@Autowired
48+
MockMvc mockMvc;
49+
50+
@Test
51+
public void testSitemap() throws Exception {
52+
var expected = """
53+
<?xml version="1.0" encoding="UTF-8"?>
54+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
55+
<url>
56+
<loc>http://localhost/extension/EditorConfig/EditorConfig</loc>
57+
<lastmod>2024-04-10</lastmod>
58+
</url>
59+
</urlset>
60+
""";
61+
62+
var rows = List.of(new SitemapRow("EditorConfig", "EditorConfig", "2024-04-10"));
63+
Mockito.when(repositories.fetchSitemapRows()).thenReturn(rows);
64+
mockMvc.perform(get("/sitemap.xml"))
65+
.andExpect(status().isOk())
66+
.andExpect(content().xml(expected));
67+
}
68+
69+
@TestConfiguration
70+
@Import(SecurityConfig.class)
71+
static class TestConfig {
72+
@Bean
73+
OAuth2UserServices oauth2UserServices() {
74+
return new OAuth2UserServices();
75+
}
76+
77+
@Bean
78+
SitemapService sitemapService() {
79+
return new SitemapService();
80+
}
81+
}
82+
}
Binary file not shown.

0 commit comments

Comments
 (0)