Skip to content

Commit 795eadd

Browse files
committed
Increased test coverage
added focused tests for lookup execution, external ref resolution, converter behaviors, HTTP auth handling, step orchestration, and ResourceRestlet mapping/mock flows
1 parent 4f973f4 commit 795eadd

7 files changed

Lines changed: 852 additions & 21 deletions

File tree

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* Copyright 2025-2026 Naftiko
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package io.naftiko.engine;
15+
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
import static org.junit.jupiter.api.Assertions.assertNotNull;
18+
import static org.junit.jupiter.api.Assertions.assertThrows;
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.nio.charset.StandardCharsets;
22+
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.api.io.TempDir;
26+
import org.restlet.data.MediaType;
27+
import org.restlet.representation.StringRepresentation;
28+
import com.fasterxml.jackson.databind.JsonNode;
29+
import io.naftiko.spec.OutputParameterSpec;
30+
31+
public class ConverterTest {
32+
33+
@TempDir
34+
Path tempDir;
35+
36+
@Test
37+
public void convertToJsonShouldRejectUnsupportedFormat() {
38+
StringRepresentation entity =
39+
new StringRepresentation("{}", MediaType.APPLICATION_JSON);
40+
41+
IOException error = assertThrows(IOException.class,
42+
() -> Converter.convertToJson("INI", null, entity));
43+
44+
assertEquals("Unsupported \"INI\" format specified", error.getMessage());
45+
}
46+
47+
@Test
48+
public void convertToJsonShouldRequireSchemaForProtobuf() {
49+
StringRepresentation entity =
50+
new StringRepresentation("{}", MediaType.APPLICATION_OCTET_STREAM);
51+
52+
IOException error = assertThrows(IOException.class,
53+
() -> Converter.convertToJson("Protobuf", null, entity));
54+
55+
assertEquals(
56+
"Protobuf format requires outputSchema to be specified in operation specification",
57+
error.getMessage());
58+
}
59+
60+
@Test
61+
public void loadSchemaFileShouldPreferLocalFileAndSupportClasspathFallback() throws Exception {
62+
Path localSchema = tempDir.resolve("local-schema.avsc");
63+
Files.writeString(localSchema, "{\"type\":\"record\",\"name\":\"Local\",\"fields\":[]}");
64+
65+
try (InputStream local = Converter.loadSchemaFile(localSchema.toString())) {
66+
assertNotNull(local);
67+
assertEquals('{', new String(local.readAllBytes(), StandardCharsets.UTF_8).charAt(0));
68+
}
69+
70+
try (InputStream classpath = Converter.loadSchemaFile("schemas/test-records.avsc")) {
71+
assertNotNull(classpath);
72+
String content = new String(classpath.readAllBytes(), StandardCharsets.UTF_8);
73+
assertEquals(true, content.contains("record"));
74+
}
75+
}
76+
77+
@Test
78+
public void applyMaxLengthIfNeededShouldTruncateAndIgnoreInvalidLengths() throws Exception {
79+
OutputParameterSpec truncatedSpec = new OutputParameterSpec();
80+
truncatedSpec.setMaxLength("5");
81+
82+
JsonNode truncated = Converter.applyMaxLengthIfNeeded(truncatedSpec,
83+
new com.fasterxml.jackson.databind.ObjectMapper().readTree("\"abcdefgh\""));
84+
assertEquals("abcde", truncated.asText());
85+
86+
OutputParameterSpec invalidSpec = new OutputParameterSpec();
87+
invalidSpec.setMaxLength("abc");
88+
89+
JsonNode untouched = Converter.applyMaxLengthIfNeeded(invalidSpec,
90+
new com.fasterxml.jackson.databind.ObjectMapper().readTree("\"abcdefgh\""));
91+
assertEquals("abcdefgh", untouched.asText());
92+
}
93+
94+
@Test
95+
public void jsonPathExtractShouldSupportMissingPathsAndPropertiesWithSpaces() throws Exception {
96+
JsonNode root = new com.fasterxml.jackson.databind.ObjectMapper().readTree("""
97+
{
98+
"user details": {
99+
"contact email": "alice@example.com"
100+
}
101+
}
102+
""");
103+
104+
JsonNode value = Converter.jsonPathExtract(root,
105+
"$['user details']['contact email']");
106+
JsonNode missing = Converter.jsonPathExtract(root, "$.missing.field");
107+
108+
assertEquals("alice@example.com", value.asText());
109+
assertEquals(true, missing.isNull());
110+
assertEquals("$.['user details'].email",
111+
Converter.fixJsonPathWithSpaces("$.user details.email"));
112+
}
113+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* Copyright 2025-2026 Naftiko
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package io.naftiko.engine;
15+
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
import static org.junit.jupiter.api.Assertions.assertThrows;
18+
import java.io.IOException;
19+
import java.nio.file.Files;
20+
import java.nio.file.Path;
21+
import java.util.List;
22+
import java.util.Map;
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.api.io.TempDir;
25+
import io.naftiko.spec.ExecutionContext;
26+
import io.naftiko.spec.ExternalRefKeysSpec;
27+
import io.naftiko.spec.FileResolvedExternalRefSpec;
28+
import io.naftiko.spec.RuntimeResolvedExternalRefSpec;
29+
30+
public class ExternalRefResolverTest {
31+
32+
@TempDir
33+
Path tempDir;
34+
35+
@Test
36+
public void resolveShouldLoadJsonAndYamlFileReferences() throws Exception {
37+
Path jsonFile = tempDir.resolve("secrets.json");
38+
Files.writeString(jsonFile, """
39+
{"NOTION_TOKEN":"json-token"}
40+
""");
41+
42+
Path yamlFile = tempDir.resolve("secrets.yaml");
43+
Files.writeString(yamlFile, """
44+
API_KEY: yaml-key
45+
""");
46+
47+
ExternalRefResolver resolver = new ExternalRefResolver();
48+
Map<String, String> resolved = resolver.resolve(List.of(
49+
fileRef(jsonFile, Map.of("notion_token", "NOTION_TOKEN")),
50+
fileRef(yamlFile, Map.of("api_key", "API_KEY"))),
51+
key -> null);
52+
53+
assertEquals("json-token", resolved.get("notion_token"));
54+
assertEquals("yaml-key", resolved.get("api_key"));
55+
}
56+
57+
@Test
58+
public void resolveFileReferenceShouldFailWhenMappedKeyIsMissing() throws Exception {
59+
Path jsonFile = tempDir.resolve("missing.json");
60+
Files.writeString(jsonFile, "{}\n");
61+
62+
ExternalRefResolver resolver = new ExternalRefResolver();
63+
64+
IOException error = assertThrows(IOException.class,
65+
() -> resolver.resolveFileReference(
66+
fileRef(jsonFile, Map.of("notion_token", "NOTION_TOKEN"))));
67+
68+
assertEquals("Invalid ExternalRef: key 'NOTION_TOKEN' not found in file",
69+
error.getMessage());
70+
}
71+
72+
@Test
73+
public void resolveRuntimeReferenceShouldUseExecutionContextVariables() throws Exception {
74+
ExternalRefResolver resolver = new ExternalRefResolver();
75+
76+
Map<String, String> resolved = resolver.resolve(
77+
List.of(runtimeRef(Map.of("api_key", "API_KEY"))),
78+
new ExecutionContext() {
79+
@Override
80+
public String getVariable(String key) {
81+
return "API_KEY".equals(key) ? "runtime-value" : null;
82+
}
83+
});
84+
85+
assertEquals("runtime-value", resolved.get("api_key"));
86+
}
87+
88+
@Test
89+
public void resolveRuntimeReferenceShouldFailWhenVariableIsMissing() {
90+
ExternalRefResolver resolver = new ExternalRefResolver();
91+
92+
IOException error = assertThrows(IOException.class,
93+
() -> resolver.resolveRuntimeReference(runtimeRef(Map.of("api_key", "API_KEY")),
94+
key -> null));
95+
96+
assertEquals("Invalid ExternalRef: context variable 'API_KEY' not found",
97+
error.getMessage());
98+
}
99+
100+
private static FileResolvedExternalRefSpec fileRef(Path path, Map<String, String> keys) {
101+
FileResolvedExternalRefSpec ref = new FileResolvedExternalRefSpec();
102+
ref.setUri(toResolverFriendlyFileUri(path));
103+
ref.setKeys(keysSpec(keys));
104+
return ref;
105+
}
106+
107+
private static RuntimeResolvedExternalRefSpec runtimeRef(Map<String, String> keys) {
108+
RuntimeResolvedExternalRefSpec ref = new RuntimeResolvedExternalRefSpec();
109+
ref.setKeys(keysSpec(keys));
110+
return ref;
111+
}
112+
113+
private static ExternalRefKeysSpec keysSpec(Map<String, String> keys) {
114+
ExternalRefKeysSpec spec = new ExternalRefKeysSpec();
115+
spec.setKeys(keys);
116+
return spec;
117+
}
118+
119+
private static String toResolverFriendlyFileUri(Path path) {
120+
String normalized = path.toAbsolutePath().toString().replace('\\', '/');
121+
if (normalized.length() > 2 && normalized.charAt(1) == ':') {
122+
normalized = normalized.substring(2);
123+
}
124+
return "file://" + normalized;
125+
}
126+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* Copyright 2025-2026 Naftiko
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package io.naftiko.engine;
15+
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
18+
import static org.junit.jupiter.api.Assertions.assertNotNull;
19+
import static org.junit.jupiter.api.Assertions.assertNull;
20+
import java.util.LinkedHashMap;
21+
import java.util.List;
22+
import java.util.Map;
23+
import org.junit.jupiter.api.Test;
24+
import com.fasterxml.jackson.databind.JsonNode;
25+
import com.fasterxml.jackson.databind.ObjectMapper;
26+
import com.fasterxml.jackson.databind.node.ObjectNode;
27+
28+
public class LookupExecutorTest {
29+
30+
private static final ObjectMapper MAPPER = new ObjectMapper();
31+
32+
@Test
33+
public void executeLookupShouldProjectMatchingArrayEntry() throws Exception {
34+
JsonNode indexData = MAPPER.readTree("""
35+
[
36+
{"email":"alice@example.com","id":"u-1","active":true},
37+
{"email":"bob@example.com","id":"u-2","active":false}
38+
]
39+
""");
40+
41+
JsonNode result = LookupExecutor.executeLookup(indexData, "email", "bob@example.com",
42+
List.of("id", "active", "missing"));
43+
44+
assertNotNull(result);
45+
assertEquals("u-2", result.path("id").asText());
46+
assertEquals(false, result.path("active").asBoolean());
47+
assertEquals(true, result.path("missing").isNull());
48+
}
49+
50+
@Test
51+
public void executeLookupShouldProjectMatchingObject() throws Exception {
52+
JsonNode indexData = MAPPER.readTree("""
53+
{"email":"alice@example.com","name":"Alice","team":"core"}
54+
""");
55+
56+
JsonNode result = LookupExecutor.executeLookup(indexData, "email", "alice@example.com",
57+
List.of("name", "team"));
58+
59+
assertNotNull(result);
60+
assertEquals("Alice", result.path("name").asText());
61+
assertEquals("core", result.path("team").asText());
62+
}
63+
64+
@Test
65+
public void executeLookupShouldReturnNullWhenNoMatchExists() throws Exception {
66+
JsonNode indexData = MAPPER.readTree("""
67+
[{"email":"alice@example.com","id":"u-1"}]
68+
""");
69+
70+
JsonNode result = LookupExecutor.executeLookup(indexData, "email", "nobody@example.com",
71+
List.of("id"));
72+
73+
assertNull(result);
74+
}
75+
76+
@Test
77+
public void mergeLookupResultShouldConvertScalarTypesAndPreserveComplexNodes()
78+
throws Exception {
79+
JsonNode result = MAPPER.readTree("""
80+
{
81+
"name": "Alice",
82+
"age": 42,
83+
"enabled": true,
84+
"nickname": null,
85+
"meta": {"region":"eu"}
86+
}
87+
""");
88+
Map<String, Object> target = new LinkedHashMap<>();
89+
90+
LookupExecutor.mergeLookupResult(result, target);
91+
92+
assertEquals("Alice", target.get("name"));
93+
assertEquals(42, ((Number) target.get("age")).intValue());
94+
assertEquals(true, target.get("enabled"));
95+
assertNull(target.get("nickname"));
96+
assertInstanceOf(ObjectNode.class, target.get("meta"));
97+
assertEquals("eu", ((JsonNode) target.get("meta")).path("region").asText());
98+
}
99+
}

0 commit comments

Comments
 (0)