Skip to content

Commit 3d88610

Browse files
committed
Tests
Signed-off-by: BoykoAlex <[email protected]>
1 parent c4ae83d commit 3d88610

File tree

17 files changed

+1273
-29
lines changed

17 files changed

+1273
-29
lines changed

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/JdtConfig.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import org.springframework.context.annotation.Conditional;
1919
import org.springframework.context.annotation.Configuration;
2020
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
21+
import org.springframework.ide.vscode.boot.java.BuildCommandProvider;
22+
import org.springframework.ide.vscode.boot.java.DefaultBuildCommandProvider;
2123
import org.springframework.ide.vscode.boot.java.VSCodeBuildCommandProvider;
2224
import org.springframework.ide.vscode.boot.java.codeaction.JdtAstCodeActionProvider;
2325
import org.springframework.ide.vscode.boot.java.codeaction.JdtCodeActionHandler;
@@ -230,8 +232,16 @@ public class JdtConfig {
230232
return new JdtCodeActionHandler(cuCache, providers);
231233
}
232234

233-
@Bean VSCodeBuildCommandProvider vsCodeBuildCommandProvider() {
234-
return new VSCodeBuildCommandProvider();
235+
@Bean BuildCommandProvider buildCommandProvider(SimpleLanguageServer server) {
236+
switch(LspClient.currentClient()) {
237+
case VSCODE:
238+
case THEIA:
239+
return new VSCodeBuildCommandProvider();
240+
case ECLIPSE:
241+
return new VSCodeBuildCommandProvider();
242+
default:
243+
return new DefaultBuildCommandProvider(server);
244+
}
235245
}
236246

237247
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java;
12+
13+
import java.io.IOException;
14+
import java.nio.file.Files;
15+
import java.nio.file.Path;
16+
import java.nio.file.Paths;
17+
import java.util.List;
18+
import java.util.concurrent.CompletableFuture;
19+
import java.util.concurrent.CompletionException;
20+
21+
import org.eclipse.lsp4j.Command;
22+
import org.springframework.ide.vscode.commons.java.IJavaProject;
23+
import org.springframework.ide.vscode.commons.languageserver.util.OS;
24+
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
25+
26+
import com.google.gson.JsonPrimitive;
27+
28+
public class DefaultBuildCommandProvider implements BuildCommandProvider {
29+
30+
private static final String CMD_EXEC_MAVEN_GOAL = "sts.maven.goal.custom";
31+
32+
private static final Object MAVEN_LOCK = new Object();
33+
34+
public DefaultBuildCommandProvider(SimpleLanguageServer server) {
35+
server.onCommand(CMD_EXEC_MAVEN_GOAL, params -> {
36+
String pomPath = extractString(params.getArguments().get(0));
37+
String goal = extractString(params.getArguments().get(1));
38+
return CompletableFuture.runAsync(() -> {
39+
try {
40+
mavenRegenerateMetadata(Paths.get(pomPath), goal.trim().split("\\s+")).get();
41+
} catch (Exception e) {
42+
throw new CompletionException(e);
43+
}
44+
});
45+
});
46+
}
47+
48+
@Override
49+
public Command executeMavenGoal(IJavaProject project, String goal) {
50+
Command cmd = new Command();
51+
cmd.setCommand(CMD_EXEC_MAVEN_GOAL);
52+
cmd.setTitle("Execute Maven Goal");
53+
cmd.setArguments(List.of(Paths.get(project.getProjectBuild().getBuildFile()).toFile().toString(), goal));
54+
return cmd;
55+
}
56+
57+
private static String extractString(Object o) {
58+
return o instanceof JsonPrimitive ? ((JsonPrimitive) o).getAsString() : o.toString();
59+
}
60+
61+
private CompletableFuture<Void> mavenRegenerateMetadata(Path pom, String[] goal) {
62+
synchronized(MAVEN_LOCK) {
63+
String[] cmd = new String[1 + goal.length];
64+
Path projectPath = pom.getParent();
65+
String mvnw = OS.isWindows() ? "mvnw.bat" : "./mvnw";
66+
cmd[0] = Files.isRegularFile(projectPath.resolve(mvnw)) ? mvnw : "mvn";
67+
System.arraycopy(goal, 0, cmd, 1, goal.length);
68+
try {
69+
return Runtime.getRuntime().exec(cmd, null, projectPath.toFile()).onExit().thenAccept(process -> {
70+
if (process.exitValue() != 0) {
71+
throw new CompletionException("Failed to execute Maven goal", new IllegalStateException("Errors running maven command: %s".formatted(String.join(" ", cmd))));
72+
}
73+
});
74+
} catch (IOException e) {
75+
throw new CompletionException(e);
76+
}
77+
}
78+
}
79+
80+
81+
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,10 @@
3636
import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider;
3737
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
3838
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
39-
import org.springframework.ide.vscode.commons.Version;
4039
import org.springframework.ide.vscode.commons.java.IJavaProject;
41-
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
4240
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
4341
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
42+
import org.springframework.ide.vscode.commons.protocol.java.ProjectBuild;
4443
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
4544
import org.springframework.ide.vscode.commons.rewrite.java.AddAnnotationOverMethod;
4645
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
@@ -100,32 +99,17 @@ static boolean isValidMethodBinding(IMethodBinding methodBinding) {
10099
return false;
101100
}
102101

102+
if (!methodBinding.getDeclaringClass().isInterface()) {
103+
return false;
104+
}
105+
103106
if (ASTUtils.findInTypeHierarchy(methodBinding.getDeclaringClass(), Set.of(Constants.REPOSITORY_TYPE)) == null) {
104107
return false;
105108
}
106109

107110
return true;
108111
}
109-
110-
static boolean isValidProject(IJavaProject jp) {
111-
Version version = SpringProjectUtil.getDependencyVersion(jp, "spring-data-commons");
112-
return version != null && version.compareTo(new Version(4, 0, 0, null)) >= 0;
113-
}
114-
115-
// static Optional<String> getDataQuery(DataRepositoryAotMetadataService repositoryMetadataService, IJavaProject project, IMethodBinding methodBinding) {
116-
// final String repositoryClass = methodBinding.getDeclaringClass().getBinaryName().trim();
117-
// final IMethodBinding method = methodBinding.getMethodDeclaration();
118-
//
119-
// DataRepositoryAotMetadata metadata = repositoryMetadataService.getRepositoryMetadata(project, repositoryClass);
120-
//
121-
// if (metadata != null) {
122-
// return Optional.ofNullable(repositoryMetadataService.getQueryStatement(metadata, method));
123-
// }
124-
//
125-
// return Optional.empty();
126-
//
127-
// }
128-
112+
129113
static Optional<DataRepositoryAotMetadata> getMetadata(DataRepositoryAotMetadataService dataRepositoryAotMetadataService, IJavaProject project, IMethodBinding methodBinding) {
130114
final String repositoryClass = methodBinding.getDeclaringClass().getBinaryName().trim();
131115
return dataRepositoryAotMetadataService.getRepositoryMetadata(project, repositoryClass);
@@ -135,7 +119,7 @@ protected void provideCodeLens(CancelChecker cancelToken, MethodDeclaration node
135119
cancelToken.checkCanceled();
136120

137121
IJavaProject project = projectFinder.find(document.getId()).get();
138-
if (project == null || !isValidProject(project)) {
122+
if (project == null || !QueryMethodCodeActionProvider.isValidProject(project)) {
139123
return;
140124
}
141125

@@ -187,10 +171,12 @@ private List<CodeLens> createCodeLenses(IJavaProject project, MethodDeclaration
187171
}
188172
}));
189173

190-
String refreshCmdTitle = codeLenses.isEmpty() ? "Show AOT-generated Implementation, Query, etc..." : "Refresh";
191-
Command refreshCmd = repositoryMetadataService.regenerateMetadataCommand(project);
192-
refreshCmd.setTitle(refreshCmdTitle);
193-
codeLenses.add(new CodeLens(range, refreshCmd, null));
174+
if (ProjectBuild.MAVEN_PROJECT_TYPE.equals(project.getProjectBuild().getType())) {
175+
String refreshCmdTitle = codeLenses.isEmpty() ? "Show AOT-generated Implementation, Query, etc..." : "Refresh";
176+
Command refreshCmd = repositoryMetadataService.regenerateMetadataCommand(project);
177+
refreshCmd.setTitle(refreshCmdTitle);
178+
codeLenses.add(new CodeLens(range, refreshCmd, null));
179+
}
194180

195181
}
196182
} catch (BadLocationException e) {

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/QueryMethodCodeActionProvider.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ public QueryMethodCodeActionProvider(DataRepositoryAotMetadataService repository
4444

4545
@Override
4646
public boolean isApplicable(IJavaProject project) {
47+
return isValidProject(project);
48+
}
49+
50+
static boolean isValidProject(IJavaProject project) {
4751
Version springDataJpaVersion = SpringProjectUtil.getDependencyVersion(project, "spring-data-jpa");
4852
Version springDataMongoDbVersion = SpringProjectUtil.getDependencyVersion(project, "spring-data-mongodb");
4953
return (springDataJpaVersion != null && springDataJpaVersion.getMajor() >= 4)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.data.test;
12+
13+
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertTrue;
15+
16+
import java.nio.charset.StandardCharsets;
17+
import java.nio.file.Files;
18+
import java.nio.file.Path;
19+
import java.nio.file.Paths;
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.concurrent.CompletableFuture;
23+
import java.util.concurrent.TimeUnit;
24+
25+
import org.eclipse.lsp4j.CodeLens;
26+
import org.eclipse.lsp4j.Command;
27+
import org.eclipse.lsp4j.ExecuteCommandParams;
28+
import org.eclipse.lsp4j.TextDocumentIdentifier;
29+
import org.junit.jupiter.api.BeforeEach;
30+
import org.junit.jupiter.api.Test;
31+
import org.junit.jupiter.api.extension.ExtendWith;
32+
import org.springframework.beans.factory.annotation.Autowired;
33+
import org.springframework.context.annotation.Import;
34+
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
35+
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
36+
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
37+
import org.springframework.ide.vscode.commons.java.IJavaProject;
38+
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
39+
import org.springframework.ide.vscode.commons.languageserver.util.Settings;
40+
import org.springframework.ide.vscode.commons.util.text.LanguageId;
41+
import org.springframework.ide.vscode.languageserver.testharness.Editor;
42+
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
43+
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
44+
import org.springframework.test.context.junit.jupiter.SpringExtension;
45+
46+
import com.google.gson.Gson;
47+
48+
@ExtendWith(SpringExtension.class)
49+
@BootLanguageServerTest
50+
@Import(SymbolProviderTestConf.class)
51+
public class DataRepositoryAotMetadataCodeLensProviderTest {
52+
53+
@Autowired private BootLanguageServerHarness harness;
54+
@Autowired private JavaProjectFinder projectFinder;
55+
@Autowired private SpringSymbolIndex indexer;
56+
57+
private IJavaProject testProject;
58+
59+
@BeforeEach
60+
public void setup() throws Exception {
61+
testProject = ProjectsHarness.INSTANCE.mavenProject("data-repositories-jpa-4");
62+
harness.useProject(testProject);
63+
harness.intialize(null);
64+
65+
harness.changeConfiguration(new Settings(new Gson()
66+
.toJsonTree(Map.of("boot-java", Map.of("java", Map.of("codelens-over-query-methods", true))))));
67+
68+
// trigger project creation
69+
projectFinder.find(new TextDocumentIdentifier(testProject.getLocationUri().toASCIIString())).get();
70+
71+
CompletableFuture<Void> initProject = indexer.waitOperation();
72+
initProject.get(5, TimeUnit.SECONDS);
73+
}
74+
75+
@Test
76+
void generateMetadataCodeLens() throws Exception {
77+
Path filePath = Paths.get(testProject.getLocationUri())
78+
.resolve("src/main/java/example/springdata/aot/UserRepository.java");
79+
Editor editor = harness.newEditor(LanguageId.JAVA, new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8), filePath.toUri().toASCIIString());
80+
81+
List<CodeLens> cls = editor.getCodeLenses("findUserByUsername", 1);
82+
assertEquals(1, cls.size());
83+
Command cmd = cls.get(0).getCommand();
84+
assertEquals("Show AOT-generated Implementation, Query, etc...", cmd.getTitle());
85+
86+
harness.getServer().getWorkspaceService().executeCommand(new ExecuteCommandParams(cmd.getCommand(), cmd.getArguments())).get();
87+
88+
Path metadataFile = Paths.get(testProject.getLocationUri()).resolve("target/spring-aot/main/resources/example/springdata/aot/UserRepository.json");
89+
90+
// Trigger file update and wait for the vene thandling to complete
91+
assertTrue(Files.isRegularFile(metadataFile), "AOT mtadata JSON file should be generated");
92+
harness.createFile(metadataFile.toUri().toASCIIString());
93+
harness.getServer().getAsync().waitForAll();
94+
95+
cls = editor.getCodeLenses("findUserByUsername", 1);
96+
assertTrue(cls.size() > 1);
97+
assertEquals("Turn into @Query", cls.get(0).getCommand().getTitle());
98+
assertEquals("Implementation", cls.get(1).getCommand().getTitle());
99+
assertEquals("SELECT u FROM example.springdata.aot.User u WHERE u.username = :username", cls.get(2).getCommand().getTitle());
100+
assertEquals("Refresh", cls.get(3).getCommand().getTitle());
101+
}
102+
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.data.test;
12+
13+
import static org.junit.jupiter.api.Assertions.assertTrue;
14+
15+
import java.nio.charset.StandardCharsets;
16+
import java.nio.file.Files;
17+
import java.nio.file.Path;
18+
import java.nio.file.Paths;
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.concurrent.CompletableFuture;
22+
import java.util.concurrent.TimeUnit;
23+
24+
import org.eclipse.lsp4j.CodeLens;
25+
import org.eclipse.lsp4j.TextDocumentIdentifier;
26+
import org.junit.jupiter.api.BeforeEach;
27+
import org.junit.jupiter.api.Test;
28+
import org.junit.jupiter.api.extension.ExtendWith;
29+
import org.springframework.beans.factory.annotation.Autowired;
30+
import org.springframework.context.annotation.Import;
31+
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
32+
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
33+
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
34+
import org.springframework.ide.vscode.commons.java.IJavaProject;
35+
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
36+
import org.springframework.ide.vscode.commons.languageserver.util.Settings;
37+
import org.springframework.ide.vscode.commons.util.text.LanguageId;
38+
import org.springframework.ide.vscode.languageserver.testharness.Editor;
39+
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
40+
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
41+
import org.springframework.test.context.junit.jupiter.SpringExtension;
42+
43+
import com.google.gson.Gson;
44+
45+
@ExtendWith(SpringExtension.class)
46+
@BootLanguageServerTest
47+
@Import(SymbolProviderTestConf.class)
48+
public class DataRepositoryNoAotMetadataCodeLensTest {
49+
50+
@Autowired private BootLanguageServerHarness harness;
51+
@Autowired private JavaProjectFinder projectFinder;
52+
@Autowired private SpringSymbolIndex indexer;
53+
54+
private IJavaProject testProject;
55+
56+
@BeforeEach
57+
public void setup() throws Exception {
58+
testProject = ProjectsHarness.INSTANCE.mavenProject("test-spring-data-symbols");
59+
harness.useProject(testProject);
60+
harness.intialize(null);
61+
62+
harness.changeConfiguration(new Settings(new Gson()
63+
.toJsonTree(Map.of("boot-java", Map.of("java", Map.of("codelens-over-query-methods", true))))));
64+
65+
// trigger project creation
66+
projectFinder.find(new TextDocumentIdentifier(testProject.getLocationUri().toASCIIString())).get();
67+
68+
CompletableFuture<Void> initProject = indexer.waitOperation();
69+
initProject.get(5, TimeUnit.SECONDS);
70+
}
71+
72+
@Test
73+
void codeLensOverMethod() throws Exception {
74+
Path filePath = Paths.get(testProject.getLocationUri())
75+
.resolve("src/main/java/org/test/CustomerRepository.java");
76+
Editor editor = harness.newEditor(LanguageId.JAVA, new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8), filePath.toUri().toASCIIString());
77+
78+
List<CodeLens> cls = editor.getCodeLenses("findByLastName", 1);
79+
assertTrue(cls.isEmpty());
80+
}
81+
82+
83+
}

0 commit comments

Comments
 (0)