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+ }
0 commit comments