2020import java .util .Set ;
2121
2222import org .junit .jupiter .api .Test ;
23+ import org .junit .jupiter .api .BeforeEach ;
2324
2425import org .springframework .ai .openai .OpenAiChatOptions ;
2526import org .springframework .ai .openai .api .OpenAiApi ;
2627import org .springframework .ai .openai .api .OpenAiAudioApi ;
2728import org .springframework .ai .openai .api .OpenAiImageApi ;
2829import org .springframework .aot .hint .RuntimeHints ;
2930import org .springframework .aot .hint .TypeReference ;
31+ import org .springframework .aot .hint .MemberCategory ;
3032
3133import static org .assertj .core .api .AssertionsForClassTypes .assertThat ;
3234import static org .springframework .ai .aot .AiRuntimeHints .findJsonAnnotatedClassesInPackage ;
3335
3436class OpenAiRuntimeHintsTests {
3537
38+ private RuntimeHints runtimeHints ;
39+
40+ private OpenAiRuntimeHints openAiRuntimeHints ;
41+
42+ @ BeforeEach
43+ void setUp () {
44+ runtimeHints = new RuntimeHints ();
45+ openAiRuntimeHints = new OpenAiRuntimeHints ();
46+ }
47+
3648 @ Test
3749 void registerHints () {
38- RuntimeHints runtimeHints = new RuntimeHints ();
39- OpenAiRuntimeHints openAiRuntimeHints = new OpenAiRuntimeHints ();
4050 openAiRuntimeHints .registerHints (runtimeHints , null );
4151
4252 Set <TypeReference > jsonAnnotatedClasses = findJsonAnnotatedClassesInPackage ("org.springframework.ai.openai" );
@@ -61,4 +71,122 @@ void registerHints() {
6171 assertThat (registeredTypes .contains (TypeReference .of (OpenAiChatOptions .class ))).isTrue ();
6272 }
6373
74+ @ Test
75+ void registerHintsWithNullClassLoader () {
76+ // Test that registering hints with null ClassLoader works correctly
77+ openAiRuntimeHints .registerHints (runtimeHints , null );
78+
79+ Set <TypeReference > registeredTypes = new HashSet <>();
80+ runtimeHints .reflection ().typeHints ().forEach (typeHint -> registeredTypes .add (typeHint .getType ()));
81+
82+ assertThat (registeredTypes .size ()).isGreaterThan (0 );
83+ }
84+
85+ @ Test
86+ void registerHintsWithCustomClassLoader () {
87+ // Test that registering hints with a custom ClassLoader works correctly
88+ ClassLoader customClassLoader = Thread .currentThread ().getContextClassLoader ();
89+ openAiRuntimeHints .registerHints (runtimeHints , customClassLoader );
90+
91+ Set <TypeReference > registeredTypes = new HashSet <>();
92+ runtimeHints .reflection ().typeHints ().forEach (typeHint -> registeredTypes .add (typeHint .getType ()));
93+
94+ assertThat (registeredTypes .size ()).isGreaterThan (0 );
95+ }
96+
97+ @ Test
98+ void allMemberCategoriesAreRegistered () {
99+ openAiRuntimeHints .registerHints (runtimeHints , null );
100+
101+ Set <TypeReference > jsonAnnotatedClasses = findJsonAnnotatedClassesInPackage ("org.springframework.ai.openai" );
102+
103+ // Verify that all MemberCategory values are registered for each type
104+ runtimeHints .reflection ().typeHints ().forEach (typeHint -> {
105+ if (jsonAnnotatedClasses .contains (typeHint .getType ())) {
106+ Set <MemberCategory > expectedCategories = Set .of (MemberCategory .values ());
107+ Set <MemberCategory > actualCategories = typeHint .getMemberCategories ();
108+ assertThat (actualCategories .containsAll (expectedCategories )).isTrue ();
109+ }
110+ });
111+ }
112+
113+ @ Test
114+ void verifySpecificOpenAiApiClasses () {
115+ openAiRuntimeHints .registerHints (runtimeHints , null );
116+
117+ Set <TypeReference > registeredTypes = new HashSet <>();
118+ runtimeHints .reflection ().typeHints ().forEach (typeHint -> registeredTypes .add (typeHint .getType ()));
119+
120+ // Verify specific OpenAI API classes are registered
121+ assertThat (registeredTypes .contains (TypeReference .of (OpenAiApi .class ))).isTrue ();
122+ assertThat (registeredTypes .contains (TypeReference .of (OpenAiAudioApi .class ))).isTrue ();
123+ assertThat (registeredTypes .contains (TypeReference .of (OpenAiImageApi .class ))).isTrue ();
124+ assertThat (registeredTypes .contains (TypeReference .of (OpenAiChatOptions .class ))).isTrue ();
125+ }
126+
127+ @ Test
128+ void emptyRuntimeHintsInitiallyContainsNoTypes () {
129+ // Verify that fresh RuntimeHints instance contains no reflection hints
130+ RuntimeHints emptyHints = new RuntimeHints ();
131+ Set <TypeReference > emptyRegisteredTypes = new HashSet <>();
132+ emptyHints .reflection ().typeHints ().forEach (typeHint -> emptyRegisteredTypes .add (typeHint .getType ()));
133+
134+ assertThat (emptyRegisteredTypes .size ()).isEqualTo (0 );
135+ }
136+
137+ @ Test
138+ void multipleRegistrationCallsAreIdempotent () {
139+ // Register hints multiple times and verify no duplicates
140+ openAiRuntimeHints .registerHints (runtimeHints , null );
141+ int firstRegistrationCount = (int ) runtimeHints .reflection ().typeHints ().count ();
142+
143+ openAiRuntimeHints .registerHints (runtimeHints , null );
144+ int secondRegistrationCount = (int ) runtimeHints .reflection ().typeHints ().count ();
145+
146+ assertThat (firstRegistrationCount ).isEqualTo (secondRegistrationCount );
147+ }
148+
149+ @ Test
150+ void verifyJsonAnnotatedClassesInPackageIsNotEmpty () {
151+ Set <TypeReference > jsonAnnotatedClasses = findJsonAnnotatedClassesInPackage ("org.springframework.ai.openai" );
152+ assertThat (jsonAnnotatedClasses .size ()).isGreaterThan (0 );
153+ }
154+
155+ @ Test
156+ void verifyAllRegisteredTypesHaveReflectionHints () {
157+ openAiRuntimeHints .registerHints (runtimeHints , null );
158+
159+ // Ensure every registered type has proper reflection hints
160+ runtimeHints .reflection ().typeHints ().forEach (typeHint -> {
161+ assertThat (typeHint .getType ()).isNotNull ();
162+ assertThat (typeHint .getMemberCategories ().size ()).isGreaterThan (0 );
163+ });
164+ }
165+
166+ @ Test
167+ void verifyEnumTypesAreRegistered () {
168+ openAiRuntimeHints .registerHints (runtimeHints , null );
169+
170+ Set <TypeReference > registeredTypes = new HashSet <>();
171+ runtimeHints .reflection ().typeHints ().forEach (typeHint -> registeredTypes .add (typeHint .getType ()));
172+
173+ // Verify enum types are properly registered
174+ assertThat (registeredTypes .contains (TypeReference .of (OpenAiApi .ChatCompletionFinishReason .class ))).isTrue ();
175+ assertThat (registeredTypes .contains (TypeReference .of (OpenAiApi .OutputModality .class ))).isTrue ();
176+ assertThat (registeredTypes .contains (TypeReference .of (OpenAiAudioApi .TtsModel .class ))).isTrue ();
177+ assertThat (registeredTypes .contains (TypeReference .of (OpenAiAudioApi .WhisperModel .class ))).isTrue ();
178+ }
179+
180+ @ Test
181+ void verifyNestedClassesAreRegistered () {
182+ openAiRuntimeHints .registerHints (runtimeHints , null );
183+
184+ Set <TypeReference > registeredTypes = new HashSet <>();
185+ runtimeHints .reflection ().typeHints ().forEach (typeHint -> registeredTypes .add (typeHint .getType ()));
186+
187+ // Verify nested classes are properly registered
188+ assertThat (registeredTypes .contains (TypeReference .of (OpenAiApi .FunctionTool .class ))).isTrue ();
189+ assertThat (registeredTypes .contains (TypeReference .of (OpenAiApi .FunctionTool .Function .class ))).isTrue ();
190+ }
191+
64192}
0 commit comments