Skip to content

Commit 3c9f1ad

Browse files
authored
Merge pull request #1408 from netomi/fix/duplicate-key
Fix: handle duplicate extensions included in search results
2 parents 6fee307 + 330aba2 commit 3c9f1ad

File tree

2 files changed

+146
-3
lines changed

2 files changed

+146
-3
lines changed

server/src/main/java/org/eclipse/openvsx/adapter/LocalVSCodeService.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.nio.file.Files;
3838
import java.nio.file.Path;
3939
import java.util.*;
40+
import java.util.function.Function;
4041
import java.util.stream.Collectors;
4142

4243
import static org.eclipse.openvsx.adapter.ExtensionQueryParam.Criterion.*;
@@ -145,7 +146,7 @@ public ExtensionQueryResult extensionQuery(ExtensionQueryParam param, int defaul
145146
.collect(Collectors.toList());
146147

147148
var extensionsMap = repositories.findActiveExtensionsById(ids).stream()
148-
.collect(Collectors.toMap(e -> e.getId(), e -> e));
149+
.collect(Collectors.toMap(Extension::getId, e -> e));
149150

150151
// keep the same order as search results
151152
extensionsList = ids.stream()
@@ -161,7 +162,9 @@ public ExtensionQueryResult extensionQuery(ExtensionQueryParam param, int defaul
161162
}
162163

163164
var flags = param.flags();
164-
var extensionsMap = extensionsList.stream().collect(Collectors.toMap(e -> e.getId(), e -> e));
165+
// when mapping the list of extensions to a map, we need to handle duplicate entries which can happen,
166+
// see https://github.com/eclipse/openvsx/issues/1394
167+
var extensionsMap = extensionsList.stream().collect(Collectors.toMap(Extension::getId, Function.identity(), (a, b) -> a));
165168
List<ExtensionVersion> allActiveExtensionVersions = repositories.findActiveExtensionVersions(extensionsMap.keySet(), targetPlatform);
166169

167170
List<ExtensionVersion> extensionVersions;
@@ -198,7 +201,7 @@ public ExtensionQueryResult extensionQuery(ExtensionQueryParam param, int defaul
198201

199202
var idsMap = extensionVersionsMap.values().stream()
200203
.flatMap(Collection::stream)
201-
.collect(Collectors.toMap(ev -> ev.getId(), ev -> ev));
204+
.collect(Collectors.toMap(ExtensionVersion::getId, ev -> ev));
202205

203206
fileResources = repositories.findFileResourcesByExtensionVersionIdAndType(idsMap.keySet(), types).stream()
204207
.map(r -> {
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/********************************************************************************
2+
* Copyright (c) 2025 Eclipse Foundation 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.adapter;
11+
12+
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
13+
import org.eclipse.openvsx.cache.CacheService;
14+
import org.eclipse.openvsx.entities.Extension;
15+
import org.eclipse.openvsx.entities.ExtensionVersion;
16+
import org.eclipse.openvsx.entities.Namespace;
17+
import org.eclipse.openvsx.entities.SignatureKeyPair;
18+
import org.eclipse.openvsx.publish.ExtensionVersionIntegrityService;
19+
import org.eclipse.openvsx.repositories.RepositoryService;
20+
import org.eclipse.openvsx.search.SearchUtilService;
21+
import org.eclipse.openvsx.storage.*;
22+
import org.eclipse.openvsx.util.VersionService;
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.api.extension.ExtendWith;
25+
import org.mockito.Mockito;
26+
import org.springframework.beans.factory.annotation.Autowired;
27+
import org.springframework.boot.test.context.TestConfiguration;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.test.context.bean.override.mockito.MockitoBean;
30+
import org.springframework.test.context.junit.jupiter.SpringExtension;
31+
32+
import java.time.LocalDateTime;
33+
import java.util.Collections;
34+
import java.util.List;
35+
36+
import static org.assertj.core.api.Assertions.assertThat;
37+
import static org.eclipse.openvsx.adapter.ExtensionQueryParam.*;
38+
import static org.mockito.ArgumentMatchers.*;
39+
40+
@ExtendWith(SpringExtension.class)
41+
@MockitoBean( types = {
42+
VSCodeAPI.class, SimpleMeterRegistry.class, SearchUtilService.class,
43+
VersionService.class, StorageUtilService.class, ExtensionVersionIntegrityService.class,
44+
WebResourceService.class, CacheService.class
45+
})
46+
public class LocalVSCodeServiceTest {
47+
48+
@MockitoBean
49+
RepositoryService repositories;
50+
51+
@MockitoBean
52+
VersionService versions;
53+
54+
@Autowired
55+
LocalVSCodeService vsCodeService;
56+
57+
@Test
58+
void testDuplicateExtensionsInSearch() {
59+
var extension = mockExtension();
60+
var extensionVersion = mockExtensionVersion(extension, 1, "0.1.0", "linux");
61+
62+
var criterion = new ExtensionQueryParam.Criterion(Criterion.FILTER_EXTENSION_ID, "test-1");
63+
var filter = new ExtensionQueryParam.Filter(List.of(criterion, criterion), 0, 0, 0, 0);
64+
var param = new ExtensionQueryParam(List.of(filter), 0);
65+
66+
Mockito.when(repositories.findActiveExtensionsByPublicId(any(), any())).thenReturn(List.of(extension, extension));
67+
Mockito.when(repositories.findActiveExtensionVersions(any(), any())).thenReturn(List.of(extensionVersion));
68+
Mockito.when(versions.getLatest(anyList(), anyBoolean())).thenReturn(extensionVersion);
69+
70+
var result = vsCodeService.extensionQuery(param, 10);
71+
assertThat(result.results()).hasSize(1);
72+
}
73+
74+
// ---------- UTILITY ----------//
75+
76+
private Extension mockExtension() {
77+
var namespace = new Namespace();
78+
namespace.setId(2);
79+
namespace.setPublicId("test-2");
80+
namespace.setName("redhat");
81+
82+
var extension = new Extension();
83+
extension.setId(1);
84+
extension.setPublicId("test-1");
85+
extension.setName("vscode-yaml");
86+
extension.setAverageRating(3.0);
87+
extension.setReviewCount(10L);
88+
extension.setDownloadCount(100);
89+
extension.setPublishedDate(LocalDateTime.parse("1999-12-01T09:00"));
90+
extension.setLastUpdatedDate(LocalDateTime.parse("2000-01-01T10:00"));
91+
extension.setNamespace(namespace);
92+
93+
return extension;
94+
}
95+
96+
private ExtensionVersion mockExtensionVersion(Extension extension, long id, String version, String targetPlatform) {
97+
var extVersion = new ExtensionVersion();
98+
extVersion.setId(id);
99+
extVersion.setVersion(version);
100+
extVersion.setTargetPlatform(targetPlatform);
101+
extVersion.setPreview(true);
102+
extVersion.setTimestamp(LocalDateTime.parse("2000-01-01T10:00"));
103+
extVersion.setDisplayName("YAML");
104+
extVersion.setDescription("YAML Language Support");
105+
extVersion.setEngines(List.of("vscode@^1.31.0"));
106+
extVersion.setRepository("https://github.com/redhat-developer/vscode-yaml");
107+
extVersion.setDependencies(Collections.emptyList());
108+
extVersion.setBundledExtensions(Collections.emptyList());
109+
extVersion.setLocalizedLanguages(Collections.emptyList());
110+
extVersion.setExtension(extension);
111+
112+
var keyPair = new SignatureKeyPair();
113+
keyPair.setPublicId("123-456-789");
114+
extVersion.setSignatureKeyPair(keyPair);
115+
116+
return extVersion;
117+
}
118+
119+
@TestConfiguration
120+
static class TestConfig {
121+
@Bean
122+
VersionService versionService() {
123+
return new VersionService();
124+
}
125+
126+
@Bean
127+
LocalVSCodeService vsCodeService(
128+
RepositoryService repositories,
129+
VersionService versions,
130+
SearchUtilService search,
131+
StorageUtilService storageUtil,
132+
ExtensionVersionIntegrityService integrityService,
133+
WebResourceService webResources,
134+
CacheService cache
135+
) {
136+
return new LocalVSCodeService(repositories, versions, search, storageUtil, integrityService, webResources, cache);
137+
}
138+
}
139+
140+
}

0 commit comments

Comments
 (0)