3030 * Tests for {@link CacheEligibilityResolver}.
3131 *
3232 * @author Austin Dase
33+ * @author Soby Chacko
3334 */
3435class CacheEligibilityResolverTests {
3536
@@ -85,6 +86,14 @@ void toolCacheControlRespectsStrategy() {
8586 .build ());
8687 assertThat (sys .resolveToolCacheControl ()).isNull ();
8788
89+ // TOOLS_ONLY -> tool caching enabled, system messages NOT cached
90+ CacheEligibilityResolver toolsOnly = CacheEligibilityResolver .from (AnthropicCacheOptions .builder ()
91+ .strategy (AnthropicCacheStrategy .TOOLS_ONLY )
92+ .messageTypeTtl (MessageType .SYSTEM , AnthropicCacheTtl .ONE_HOUR )
93+ .build ());
94+ assertThat (toolsOnly .resolveToolCacheControl ()).isNotNull ();
95+ assertThat (toolsOnly .resolve (MessageType .SYSTEM , "Large system prompt text" )).isNull ();
96+
8897 // SYSTEM_AND_TOOLS -> tool caching enabled (uses SYSTEM TTL)
8998 CacheEligibilityResolver sysAndTools = CacheEligibilityResolver .from (AnthropicCacheOptions .builder ()
9099 .strategy (AnthropicCacheStrategy .SYSTEM_AND_TOOLS )
@@ -100,4 +109,185 @@ void toolCacheControlRespectsStrategy() {
100109 assertThat (history .resolveToolCacheControl ()).isNotNull ();
101110 }
102111
112+ @ Test
113+ void toolsOnlyStrategyBehavior () {
114+ AnthropicCacheOptions options = AnthropicCacheOptions .builder ()
115+ .strategy (AnthropicCacheStrategy .TOOLS_ONLY )
116+ .messageTypeMinContentLength (MessageType .SYSTEM , 100 )
117+ .build ();
118+ CacheEligibilityResolver resolver = CacheEligibilityResolver .from (options );
119+
120+ // Caching is enabled
121+ assertThat (resolver .isCachingEnabled ()).isTrue ();
122+
123+ // System messages should NOT be cached
124+ assertThat (resolver .resolve (MessageType .SYSTEM , "Large system prompt with plenty of content" ))
125+ .as ("System messages should not be cached with TOOLS_ONLY strategy" )
126+ .isNull ();
127+
128+ // User messages should NOT be cached
129+ assertThat (resolver .resolve (MessageType .USER , "User message content" )).isNull ();
130+
131+ // Assistant messages should NOT be cached
132+ assertThat (resolver .resolve (MessageType .ASSISTANT , "Assistant message content" )).isNull ();
133+
134+ // Tool messages should NOT be cached
135+ assertThat (resolver .resolve (MessageType .TOOL , "Tool result content" )).isNull ();
136+
137+ // Tool definitions SHOULD be cached
138+ AnthropicApi .ChatCompletionRequest .CacheControl toolCache = resolver .resolveToolCacheControl ();
139+ assertThat (toolCache ).as ("Tool definitions should be cached with TOOLS_ONLY strategy" ).isNotNull ();
140+ assertThat (toolCache .type ()).isEqualTo ("ephemeral" );
141+ }
142+
143+ @ Test
144+ void breakpointCountForEachStrategy () {
145+ // NONE: 0 breakpoints
146+ CacheEligibilityResolver none = CacheEligibilityResolver
147+ .from (AnthropicCacheOptions .builder ().strategy (AnthropicCacheStrategy .NONE ).build ());
148+ assertThat (none .resolveToolCacheControl ()).isNull ();
149+ assertThat (none .resolve (MessageType .SYSTEM , "content" )).isNull ();
150+
151+ // SYSTEM_ONLY: 1 breakpoint (system only, tools implicit)
152+ CacheEligibilityResolver systemOnly = CacheEligibilityResolver
153+ .from (AnthropicCacheOptions .builder ().strategy (AnthropicCacheStrategy .SYSTEM_ONLY ).build ());
154+ assertThat (systemOnly .resolveToolCacheControl ()).as ("SYSTEM_ONLY should not explicitly cache tools" ).isNull ();
155+ assertThat (systemOnly .resolve (MessageType .SYSTEM , "content" )).isNotNull ();
156+
157+ // TOOLS_ONLY: 1 breakpoint (tools only)
158+ CacheEligibilityResolver toolsOnly = CacheEligibilityResolver
159+ .from (AnthropicCacheOptions .builder ().strategy (AnthropicCacheStrategy .TOOLS_ONLY ).build ());
160+ assertThat (toolsOnly .resolveToolCacheControl ()).as ("TOOLS_ONLY should cache tools" ).isNotNull ();
161+ assertThat (toolsOnly .resolve (MessageType .SYSTEM , "content" )).as ("TOOLS_ONLY should not cache system" ).isNull ();
162+
163+ // SYSTEM_AND_TOOLS: 2 breakpoints (tools + system)
164+ CacheEligibilityResolver systemAndTools = CacheEligibilityResolver
165+ .from (AnthropicCacheOptions .builder ().strategy (AnthropicCacheStrategy .SYSTEM_AND_TOOLS ).build ());
166+ assertThat (systemAndTools .resolveToolCacheControl ()).as ("SYSTEM_AND_TOOLS should cache tools" ).isNotNull ();
167+ assertThat (systemAndTools .resolve (MessageType .SYSTEM , "content" )).as ("SYSTEM_AND_TOOLS should cache system" )
168+ .isNotNull ();
169+ }
170+
171+ @ Test
172+ void messageTypeEligibilityPerStrategy () {
173+ // NONE: No message types eligible
174+ CacheEligibilityResolver none = CacheEligibilityResolver
175+ .from (AnthropicCacheOptions .builder ().strategy (AnthropicCacheStrategy .NONE ).build ());
176+ assertThat (none .resolve (MessageType .SYSTEM , "content" )).isNull ();
177+ assertThat (none .resolve (MessageType .USER , "content" )).isNull ();
178+ assertThat (none .resolve (MessageType .ASSISTANT , "content" )).isNull ();
179+ assertThat (none .resolve (MessageType .TOOL , "content" )).isNull ();
180+
181+ // SYSTEM_ONLY: Only SYSTEM eligible
182+ CacheEligibilityResolver systemOnly = CacheEligibilityResolver
183+ .from (AnthropicCacheOptions .builder ().strategy (AnthropicCacheStrategy .SYSTEM_ONLY ).build ());
184+ assertThat (systemOnly .resolve (MessageType .SYSTEM , "content" )).isNotNull ();
185+ assertThat (systemOnly .resolve (MessageType .USER , "content" )).isNull ();
186+ assertThat (systemOnly .resolve (MessageType .ASSISTANT , "content" )).isNull ();
187+ assertThat (systemOnly .resolve (MessageType .TOOL , "content" )).isNull ();
188+
189+ // TOOLS_ONLY: No message types eligible (only tool definitions)
190+ CacheEligibilityResolver toolsOnly = CacheEligibilityResolver
191+ .from (AnthropicCacheOptions .builder ().strategy (AnthropicCacheStrategy .TOOLS_ONLY ).build ());
192+ assertThat (toolsOnly .resolve (MessageType .SYSTEM , "content" )).isNull ();
193+ assertThat (toolsOnly .resolve (MessageType .USER , "content" )).isNull ();
194+ assertThat (toolsOnly .resolve (MessageType .ASSISTANT , "content" )).isNull ();
195+ assertThat (toolsOnly .resolve (MessageType .TOOL , "content" )).isNull ();
196+
197+ // SYSTEM_AND_TOOLS: Only SYSTEM eligible
198+ CacheEligibilityResolver systemAndTools = CacheEligibilityResolver
199+ .from (AnthropicCacheOptions .builder ().strategy (AnthropicCacheStrategy .SYSTEM_AND_TOOLS ).build ());
200+ assertThat (systemAndTools .resolve (MessageType .SYSTEM , "content" )).isNotNull ();
201+ assertThat (systemAndTools .resolve (MessageType .USER , "content" )).isNull ();
202+ assertThat (systemAndTools .resolve (MessageType .ASSISTANT , "content" )).isNull ();
203+ assertThat (systemAndTools .resolve (MessageType .TOOL , "content" )).isNull ();
204+
205+ // CONVERSATION_HISTORY: All message types eligible
206+ CacheEligibilityResolver history = CacheEligibilityResolver
207+ .from (AnthropicCacheOptions .builder ().strategy (AnthropicCacheStrategy .CONVERSATION_HISTORY ).build ());
208+ assertThat (history .resolve (MessageType .SYSTEM , "content" )).isNotNull ();
209+ assertThat (history .resolve (MessageType .USER , "content" )).isNotNull ();
210+ assertThat (history .resolve (MessageType .ASSISTANT , "content" )).isNotNull ();
211+ assertThat (history .resolve (MessageType .TOOL , "content" )).isNotNull ();
212+ }
213+
214+ @ Test
215+ void toolsOnlyIsolationFromSystemChanges () {
216+ // Validates that TOOLS_ONLY resolver behavior is consistent
217+ // regardless of system message content (simulating different system prompts)
218+ CacheEligibilityResolver resolver = CacheEligibilityResolver
219+ .from (AnthropicCacheOptions .builder ().strategy (AnthropicCacheStrategy .TOOLS_ONLY ).build ());
220+
221+ // Different system prompts should all be ineligible for caching
222+ assertThat (resolver .resolve (MessageType .SYSTEM , "You are a helpful assistant" ))
223+ .as ("System prompt 1 should not be cached" )
224+ .isNull ();
225+ assertThat (resolver .resolve (MessageType .SYSTEM , "You are a STRICT validator" ))
226+ .as ("System prompt 2 should not be cached" )
227+ .isNull ();
228+ assertThat (resolver .resolve (MessageType .SYSTEM , "You are a creative writer" ))
229+ .as ("System prompt 3 should not be cached" )
230+ .isNull ();
231+
232+ // Tool cache eligibility should remain consistent
233+ assertThat (resolver .resolveToolCacheControl ()).as ("Tools should always be cacheable" ).isNotNull ();
234+ }
235+
236+ @ Test
237+ void systemAndToolsIndependentBreakpoints () {
238+ // Validates that SYSTEM_AND_TOOLS creates two independent eligibility checks
239+ CacheEligibilityResolver resolver = CacheEligibilityResolver
240+ .from (AnthropicCacheOptions .builder ().strategy (AnthropicCacheStrategy .SYSTEM_AND_TOOLS ).build ());
241+
242+ // Both tools and system should be independently eligible
243+ AnthropicApi .ChatCompletionRequest .CacheControl toolCache = resolver .resolveToolCacheControl ();
244+ AnthropicApi .ChatCompletionRequest .CacheControl systemCache = resolver .resolve (MessageType .SYSTEM , "content" );
245+
246+ assertThat (toolCache ).as ("Tools should be cacheable" ).isNotNull ();
247+ assertThat (systemCache ).as ("System should be cacheable" ).isNotNull ();
248+
249+ // They should use the same TTL (both use SYSTEM message type TTL)
250+ assertThat (toolCache .ttl ()).isEqualTo (systemCache .ttl ());
251+ }
252+
253+ @ Test
254+ void breakpointLimitEnforced () {
255+ AnthropicCacheOptions options = AnthropicCacheOptions .builder ()
256+ .strategy (AnthropicCacheStrategy .CONVERSATION_HISTORY )
257+ .build ();
258+ CacheEligibilityResolver resolver = CacheEligibilityResolver .from (options );
259+
260+ // Use up breakpoints by resolving multiple times
261+ resolver .resolve (MessageType .SYSTEM , "content" ); // Uses breakpoint 1
262+ resolver .useCacheBlock ();
263+ resolver .resolve (MessageType .USER , "content" ); // Uses breakpoint 2
264+ resolver .useCacheBlock ();
265+ resolver .resolve (MessageType .ASSISTANT , "content" ); // Uses breakpoint 3
266+ resolver .useCacheBlock ();
267+ resolver .resolve (MessageType .TOOL , "content" ); // Uses breakpoint 4
268+ resolver .useCacheBlock ();
269+
270+ // 5th attempt should return null (all 4 breakpoints used)
271+ assertThat (resolver .resolve (MessageType .USER , "more content" ))
272+ .as ("Should return null when all 4 breakpoints are used" )
273+ .isNull ();
274+ }
275+
276+ @ Test
277+ void emptyAndNullContentHandling () {
278+ CacheEligibilityResolver resolver = CacheEligibilityResolver
279+ .from (AnthropicCacheOptions .builder ().strategy (AnthropicCacheStrategy .CONVERSATION_HISTORY ).build ());
280+
281+ // Empty string should not be cached
282+ assertThat (resolver .resolve (MessageType .SYSTEM , "" )).as ("Empty string should not be cached" ).isNull ();
283+
284+ // Null should not be cached
285+ assertThat (resolver .resolve (MessageType .SYSTEM , null )).as ("Null content should not be cached" ).isNull ();
286+
287+ // Whitespace-only should be cached if it meets length requirement
288+ assertThat (resolver .resolve (MessageType .SYSTEM , " " ))
289+ .as ("Whitespace-only content meeting length requirements should be cacheable" )
290+ .isNotNull ();
291+ }
292+
103293}
0 commit comments