Skip to content

Commit 339cd7a

Browse files
committed
test: add AnnotationProcessorCompileSuccessTest to validate annotation processing functionality
1 parent 8ac7f0f commit 339cd7a

1 file changed

Lines changed: 244 additions & 0 deletions

File tree

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
package com.github.thought2code.mcp.annotated.server.component;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import java.io.IOException;
7+
import java.nio.file.Files;
8+
import java.nio.file.Path;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import java.util.Locale;
12+
import java.util.stream.Collectors;
13+
import javax.tools.DiagnosticCollector;
14+
import javax.tools.JavaCompiler;
15+
import javax.tools.JavaFileObject;
16+
import javax.tools.StandardJavaFileManager;
17+
import javax.tools.ToolProvider;
18+
import org.junit.jupiter.api.Assumptions;
19+
import org.junit.jupiter.api.Test;
20+
21+
class AnnotationProcessorCompileSuccessTest {
22+
23+
@Test
24+
void processor_shouldGenerateProviderAndServiceDescriptorForValidComponents() throws IOException {
25+
CompilationResult result = compile(validComponentSource());
26+
27+
assertTrue(result.success(), () -> "Unexpected diagnostics:\n" + result.messages());
28+
assertTrue(
29+
result.generatedProviderSource().contains("implements ComponentProvider"),
30+
"Expected generated provider implementation");
31+
assertTrue(
32+
result
33+
.generatedProviderSource()
34+
.contains("private static ToolDefinition toolDefinition0()"),
35+
"Expected generated tool definition");
36+
assertTrue(
37+
result
38+
.generatedProviderSource()
39+
.contains("private static Map<String, Object> inputSchema0()"),
40+
"Expected generated input schema");
41+
assertTrue(
42+
result.generatedProviderSource().contains("definitions.put(\"Payload\", definition)"),
43+
"Expected generated nested input schema definition");
44+
assertTrue(
45+
result.generatedProviderSource().contains("fieldProperties.put(\"type\", \"integer\")"),
46+
"Expected generated output schema properties");
47+
assertTrue(
48+
result
49+
.generatedProviderSource()
50+
.contains("private static PromptDefinition promptDefinition0()"),
51+
"Expected generated prompt definition");
52+
assertTrue(
53+
result
54+
.generatedProviderSource()
55+
.contains("private static ResourceDefinition resourceDefinition0()"),
56+
"Expected generated resource definition");
57+
assertTrue(
58+
result
59+
.generatedProviderSource()
60+
.contains("private static CompletionDefinition completionDefinition0()"),
61+
"Expected generated completion definitions");
62+
assertTrue(
63+
result
64+
.generatedProviderSource()
65+
.contains("McpSchema.PromptReference.builder(\"write_summary\")"),
66+
"Expected generated prompt completion reference");
67+
assertTrue(
68+
result
69+
.generatedProviderSource()
70+
.contains("new McpSchema.ResourceReference(\"fixture://resource/{id}\")"),
71+
"Expected generated resource completion reference");
72+
assertEquals(
73+
result.generatedProviderName(),
74+
result.serviceDescriptor(),
75+
"Service descriptor should point to the generated provider");
76+
}
77+
78+
private static CompilationResult compile(String sourceCode) throws IOException {
79+
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
80+
Assumptions.assumeTrue(compiler != null, "JDK compiler is required to run this test");
81+
82+
Path tempDir = Files.createTempDirectory("annotation-processor-compile-success");
83+
Path classesDir = tempDir.resolve("classes");
84+
Path generatedSourcesDir = tempDir.resolve("generated-sources");
85+
Path sourceFile = tempDir.resolve("testfixtures/GeneratedProviderFixture.java");
86+
Files.createDirectories(sourceFile.getParent());
87+
Files.createDirectories(classesDir);
88+
Files.createDirectories(generatedSourcesDir);
89+
Files.writeString(sourceFile, sourceCode);
90+
91+
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
92+
try (StandardJavaFileManager fileManager =
93+
compiler.getStandardFileManager(diagnostics, Locale.ROOT, null)) {
94+
Iterable<? extends JavaFileObject> units =
95+
fileManager.getJavaFileObjectsFromFiles(List.of(sourceFile.toFile()));
96+
List<String> options =
97+
List.of(
98+
"-classpath",
99+
System.getProperty("java.class.path"),
100+
"-d",
101+
classesDir.toString(),
102+
"-s",
103+
generatedSourcesDir.toString(),
104+
"-source",
105+
"17",
106+
"-target",
107+
"17");
108+
109+
JavaCompiler.CompilationTask task =
110+
compiler.getTask(null, fileManager, diagnostics, options, null, units);
111+
task.setProcessors(List.of(new AnnotationProcessor()));
112+
boolean success = task.call();
113+
String messages =
114+
diagnostics.getDiagnostics().stream()
115+
.map(diagnostic -> diagnostic.getKind() + ": " + diagnostic.getMessage(Locale.ROOT))
116+
.collect(Collectors.joining("\n"));
117+
Path providerSource = generatedProviderSource(generatedSourcesDir);
118+
String provider = success ? Files.readString(providerSource) : "";
119+
String providerName =
120+
success
121+
? "com.github.thought2code.mcp.annotated.generated."
122+
+ providerSource.getFileName().toString().replace(".java", "")
123+
: "";
124+
Path serviceDescriptor =
125+
classesDir.resolve(
126+
"META-INF/services/com.github.thought2code.mcp.annotated.server.component.ComponentProvider");
127+
String serviceDescriptorContent =
128+
success && Files.exists(serviceDescriptor)
129+
? Files.readString(serviceDescriptor).trim()
130+
: "";
131+
return new CompilationResult(
132+
success, messages, providerName, provider, serviceDescriptorContent);
133+
} finally {
134+
deleteRecursively(tempDir);
135+
}
136+
}
137+
138+
private static Path generatedProviderSource(Path generatedSourcesDir) throws IOException {
139+
try (var stream = Files.walk(generatedSourcesDir)) {
140+
return stream
141+
.filter(path -> path.getFileName().toString().startsWith("GeneratedComponentProvider_"))
142+
.filter(path -> path.getFileName().toString().endsWith(".java"))
143+
.findFirst()
144+
.orElseThrow(() -> new AssertionError("Generated provider source was not created"));
145+
}
146+
}
147+
148+
private static void deleteRecursively(Path root) throws IOException {
149+
List<Path> paths = new ArrayList<>();
150+
try (var stream = Files.walk(root)) {
151+
stream.forEach(paths::add);
152+
}
153+
for (int i = paths.size() - 1; i >= 0; i--) {
154+
Files.deleteIfExists(paths.get(i));
155+
}
156+
}
157+
158+
private static String validComponentSource() {
159+
return """
160+
package testfixtures;
161+
162+
import com.github.thought2code.mcp.annotated.annotation.McpJsonSchemaDefinition;
163+
import com.github.thought2code.mcp.annotated.annotation.McpJsonSchemaProperty;
164+
import com.github.thought2code.mcp.annotated.annotation.McpPrompt;
165+
import com.github.thought2code.mcp.annotated.annotation.McpPromptCompletion;
166+
import com.github.thought2code.mcp.annotated.annotation.McpPromptParam;
167+
import com.github.thought2code.mcp.annotated.annotation.McpResource;
168+
import com.github.thought2code.mcp.annotated.annotation.McpResourceCompletion;
169+
import com.github.thought2code.mcp.annotated.annotation.McpTool;
170+
import com.github.thought2code.mcp.annotated.annotation.McpToolParam;
171+
import com.github.thought2code.mcp.annotated.server.component.completion.CompletionResult;
172+
import io.modelcontextprotocol.spec.McpSchema;
173+
import java.util.List;
174+
175+
public class GeneratedProviderFixture {
176+
@McpTool(name = "inspect_payload", title = "Inspect Payload", description = "Inspect payload")
177+
public ToolOutput inspectPayload(
178+
@McpToolParam(name = "payload", description = "Payload", required = true) Payload payload,
179+
@McpToolParam(name = "count", description = "Count", required = true) int count,
180+
String unannotated) {
181+
return new ToolOutput("ok", count);
182+
}
183+
184+
@McpPrompt(name = "write_summary", title = "Write Summary", description = "Write a summary")
185+
public String writeSummary(
186+
@McpPromptParam(name = "topic", description = "Topic", required = true) String topic,
187+
int unannotated) {
188+
return topic;
189+
}
190+
191+
@McpResource(
192+
uri = "fixture://resource/{id}",
193+
name = "fixture_resource",
194+
title = "Fixture Resource",
195+
description = "Fixture resource",
196+
mimeType = "application/json",
197+
roles = {McpSchema.Role.USER},
198+
priority = 0.75)
199+
public String resource() {
200+
return "resource";
201+
}
202+
203+
@McpPromptCompletion(name = "write_summary", title = "Summary completion")
204+
public CompletionResult completePrompt(McpSchema.CompleteRequest.CompleteArgument argument) {
205+
return CompletionResult.builder().values(List.of("Java")).total(1).hasMore(false).build();
206+
}
207+
208+
@McpResourceCompletion(uri = "fixture://resource/{id}")
209+
public CompletionResult completeResource(McpSchema.CompleteRequest.CompleteArgument argument) {
210+
return CompletionResult.empty();
211+
}
212+
213+
@McpJsonSchemaDefinition
214+
public static class Payload {
215+
@McpJsonSchemaProperty(description = "Payload name")
216+
public String name;
217+
218+
@McpJsonSchemaProperty(name = "enabled", description = "Whether enabled", required = false)
219+
public boolean enabled;
220+
}
221+
222+
public static class ToolOutput {
223+
@McpJsonSchemaProperty(description = "Status")
224+
public String status;
225+
226+
@McpJsonSchemaProperty(description = "Count")
227+
public int count;
228+
229+
public ToolOutput(String status, int count) {
230+
this.status = status;
231+
this.count = count;
232+
}
233+
}
234+
}
235+
""";
236+
}
237+
238+
private record CompilationResult(
239+
boolean success,
240+
String messages,
241+
String generatedProviderName,
242+
String generatedProviderSource,
243+
String serviceDescriptor) {}
244+
}

0 commit comments

Comments
 (0)