Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/test/java/io/naftiko/cli/FileFormatEnumTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Copyright 2025-2026 Naftiko
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package io.naftiko.cli;

import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import io.naftiko.cli.enums.FileFormat;

public class FileFormatEnumTest {

@Test
public void valueOfLabelShouldReturnYamlForYamlLabel() {
FileFormat result = FileFormat.valueOfLabel("Yaml");

assertEquals(FileFormat.YAML, result);
assertEquals("yaml", result.pathName);
}

@Test
public void valueOfLabelShouldReturnJsonForJsonLabel() {
FileFormat result = FileFormat.valueOfLabel("Json");

assertEquals(FileFormat.JSON, result);
assertEquals("json", result.pathName);
}

@Test
public void valueOfLabelShouldReturnUnknownForUnrecognizedLabel() {
FileFormat result = FileFormat.valueOfLabel("Unknown");

assertEquals(FileFormat.UNKNOWN, result);
}

@Test
public void valueOfLabelShouldReturnUnknownForNull() {
FileFormat result = FileFormat.valueOfLabel(null);

assertEquals(FileFormat.UNKNOWN, result);
}

@Test
public void enumValuesShouldHaveCorrectLabels() {
assertEquals("Yaml", FileFormat.YAML.label);
assertEquals("Json", FileFormat.JSON.label);
assertEquals("Unknown", FileFormat.UNKNOWN.label);
}

@Test
public void enumValuesShouldHaveCorrectPathNames() {
assertEquals("yaml", FileFormat.YAML.pathName);
assertEquals("json", FileFormat.JSON.pathName);
assertEquals("unknown", FileFormat.UNKNOWN.pathName);
}
}
138 changes: 138 additions & 0 deletions src/test/java/io/naftiko/engine/exposes/mcp/PromptHandlerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* Copyright 2025-2026 Naftiko
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package io.naftiko.engine.exposes.mcp;

import static org.junit.jupiter.api.Assertions.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import io.naftiko.spec.exposes.McpServerPromptSpec;
import io.naftiko.spec.exposes.McpPromptMessageSpec;

public class PromptHandlerTest {

@Test
public void renderShouldThrowWhenPromptUnknown() {
McpServerPromptSpec spec = new McpServerPromptSpec();
spec.setName("known-prompt");

PromptHandler handler = new PromptHandler(List.of(spec));

IllegalArgumentException error =
assertThrows(IllegalArgumentException.class,
() -> handler.render("unknown-prompt", Map.of()));
assertTrue(error.getMessage().contains("Unknown prompt"));
}

@Test
public void renderInlineShouldSubstituteArguments() throws IOException {
McpPromptMessageSpec msg = new McpPromptMessageSpec();
msg.setRole("user");
msg.setContent("Hello {{name}}, you have {{count}} tasks");

McpServerPromptSpec spec = new McpServerPromptSpec();
spec.setName("greeting");
spec.getTemplate().add(msg);

PromptHandler handler = new PromptHandler(List.of(spec));
List<PromptHandler.RenderedMessage> result =
handler.render("greeting", Map.of("name", "Alice", "count", "5"));

assertEquals(1, result.size());
assertEquals("user", result.get(0).role);
assertEquals("Hello Alice, you have 5 tasks", result.get(0).text);
}

@Test
public void renderInlineShouldLeaveUnknownPlaceholdersUnchanged() throws IOException {
McpPromptMessageSpec msg = new McpPromptMessageSpec();
msg.setRole("user");
msg.setContent("Hello {{name}}, you have {{unknown}} items");

McpServerPromptSpec spec = new McpServerPromptSpec();
spec.setName("greeting");
spec.getTemplate().add(msg);

PromptHandler handler = new PromptHandler(List.of(spec));
List<PromptHandler.RenderedMessage> result =
handler.render("greeting", Map.of("name", "Bob"));

assertEquals("Hello Bob, you have {{unknown}} items", result.get(0).text);
}

@Test
public void renderInlineShouldNotReinterpolateInjectedValues(@TempDir Path tempDir)
throws IOException {
McpPromptMessageSpec msg = new McpPromptMessageSpec();
msg.setRole("user");
msg.setContent("You said: {{message}}");

McpServerPromptSpec spec = new McpServerPromptSpec();
spec.setName("echo");
spec.getTemplate().add(msg);

PromptHandler handler = new PromptHandler(List.of(spec));
// Argument value contains {{...}} which should NOT be re-interpolated
List<PromptHandler.RenderedMessage> result =
handler.render("echo", Map.of("message", "{{danger}}"));

assertEquals("You said: {{danger}}", result.get(0).text);
}

@Test
public void renderFileBasedShouldLoadAndSubstitute(@TempDir Path tempDir)
throws IOException {
Path promptFile = tempDir.resolve("prompt.txt");
Files.writeString(promptFile, "Analyze {{topic}} for {{audience}}");

McpServerPromptSpec spec = new McpServerPromptSpec();
spec.setName("analyze");
spec.setLocation(promptFile.toUri().toString());

PromptHandler handler = new PromptHandler(List.of(spec));
List<PromptHandler.RenderedMessage> result = handler.render("analyze",
Map.of("topic", "solar energy", "audience", "engineers"));

assertEquals(1, result.size());
assertEquals("user", result.get(0).role);
assertEquals("Analyze solar energy for engineers", result.get(0).text);
}

@Test
public void renderFileBasedShouldThrowWhenFileNotFound() {
McpServerPromptSpec spec = new McpServerPromptSpec();
spec.setName("missing");
spec.setLocation("file:///nonexistent/prompt.txt");

PromptHandler handler = new PromptHandler(List.of(spec));

IOException error = assertThrows(IOException.class,
() -> handler.render("missing", Map.of()));
assertTrue(error.getMessage().contains("not found") || error.getMessage().contains("can't find"));
}

@Test
public void substituteShouldHandleMultiplePlaceholders() {
String template = "Name: {{first}} {{last}}, Age: {{age}}";
Map<String, String> args = Map.of("first", "John", "last", "Doe", "age", "30");

String result = PromptHandler.substitute(template, args);

assertEquals("Name: John Doe, Age: 30", result);
}
}
62 changes: 62 additions & 0 deletions src/test/java/io/naftiko/engine/exposes/mcp/ToolHandlerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Copyright 2025-2026 Naftiko
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package io.naftiko.engine.exposes.mcp;

import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import io.naftiko.spec.exposes.McpServerToolSpec;

public class ToolHandlerTest {

@Test
public void handleToolCallShouldThrowForUnknownTool() {
McpServerToolSpec tool = new McpServerToolSpec();
tool.setName("known-tool");

ToolHandler handler = new ToolHandler(null, List.of(tool));

IllegalArgumentException error = assertThrows(IllegalArgumentException.class,
() -> handler.handleToolCall("unknown-tool", Map.of()));

assertTrue(error.getMessage().contains("Unknown tool"));
}

@Test
public void handleToolCallShouldHandleNullArguments() {
McpServerToolSpec tool = new McpServerToolSpec();
tool.setName("test-tool");
tool.setWith(Map.of("default_param", "default_value"));
// This will fail at execution because we have no real capability/steps setup,
// but it tests that null arguments are handled gracefully before that point

ToolHandler handler = new ToolHandler(null, List.of(tool));

assertThrows(Exception.class, () -> handler.handleToolCall("test-tool", null));
}

@Test
public void handleToolCallShouldMergeToolWithParameters() {
McpServerToolSpec tool = new McpServerToolSpec();
tool.setName("test-tool");
tool.setWith(Map.of("fromTool", "fromToolValue"));

ToolHandler handler = new ToolHandler(null, List.of(tool));

// Execution will fail beyond argument merging, but the tool is properly set up
assertThrows(Exception.class, () -> handler.handleToolCall("test-tool",
Map.of("fromArgs", "fromArgsValue")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,98 @@ public void testGetDescriptiveSkillNoTools() throws Exception {
assertNotNull(tools);
assertTrue(tools.isEmpty(), "Descriptive skill should have empty tools array");
}

// --- Skill content endpoint tests ---

@Test
@SuppressWarnings("unchecked")
public void testGetSkillContents() throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/skills/order-management/contents"))
.GET()
.build();

HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());

assertTrue(response.statusCode() == 200 || response.statusCode() == 404,
"Should return 200 when location exists or 404 when it does not");

if (response.statusCode() == 200) {
assertNotNull(response.body());

Map<String, Object> body = JSON.readValue(response.body(), Map.class);
assertNotNull(body.get("files"));
List<Map<String, Object>> files = (List<Map<String, Object>>) body.get("files");

// Each file entry should have name and path
for (Map<String, Object> file : files) {
assertNotNull(file.get("name"));
assertNotNull(file.get("path"));
}
}
}

@Test
public void testGetSkillFile() throws Exception {
HttpClient client = HttpClient.newHttpClient();
// Assuming order-management skill has directory structure with at least one file
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/skills/order-management/contents/overview"))
.GET()
.build();

HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());

// Either 200 success or 404 not found are both valid
assertTrue(response.statusCode() == 200 || response.statusCode() == 404,
"Should return 200 for existing file or 404 for missing file");

if (response.statusCode() == 200) {
assertNotNull(response.body(), "File content should not be empty");
assertNotNull(response.headers().firstValue("content-type"),
"Response should have content-type header");
}
}

@Test
public void testGetSkillDownload() throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/skills/order-management/download"))
.GET()
.build();

HttpResponse<byte[]> response =
client.send(request, HttpResponse.BodyHandlers.ofByteArray());

assertTrue(response.statusCode() == 200 || response.statusCode() == 404,
"Should return 200 when location exists or 404 when it does not");

if (response.statusCode() == 200) {
assertNotNull(response.body());
assertTrue(response.body().length > 1,
"Downloaded skill archive should not be empty");

// ZIP archives start with specific magic bytes (PK)
assertTrue(response.body()[0] == 0x50 && response.body()[1] == 0x4b,
"Downloaded file should be a ZIP archive");
}
}

@Test
public void testGetContentsUnknownSkillReturns404() throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/skills/unknown-skill/contents"))
.GET()
.build();

HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());

assertEquals(404, response.statusCode());
}
}
Loading
Loading