Skip to content

Commit 48aadc4

Browse files
Copilotbrunoborges
andcommitted
Add ToolInvocationTest to achieve 100% coverage
Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>
1 parent b533336 commit 48aadc4

1 file changed

Lines changed: 182 additions & 0 deletions

File tree

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
package com.github.copilot.sdk;
6+
7+
import static org.junit.jupiter.api.Assertions.*;
8+
9+
import org.junit.jupiter.api.Test;
10+
11+
import com.fasterxml.jackson.databind.ObjectMapper;
12+
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
13+
import com.fasterxml.jackson.databind.node.ObjectNode;
14+
import com.github.copilot.sdk.json.ToolInvocation;
15+
16+
/**
17+
* Unit tests for {@link ToolInvocation}.
18+
* <p>
19+
* Tests getter methods, type-safe deserialization, and null handling
20+
* to improve coverage beyond what E2E tests exercise.
21+
*/
22+
public class ToolInvocationTest {
23+
24+
private static final ObjectMapper MAPPER = new ObjectMapper();
25+
26+
/**
27+
* Test all basic getters return values set via setters.
28+
*/
29+
@Test
30+
void testGettersReturnSetValues() {
31+
ToolInvocation invocation = new ToolInvocation()
32+
.setSessionId("test-session-123")
33+
.setToolCallId("call_abc123")
34+
.setToolName("test_tool");
35+
36+
assertEquals("test-session-123", invocation.getSessionId());
37+
assertEquals("call_abc123", invocation.getToolCallId());
38+
assertEquals("test_tool", invocation.getToolName());
39+
}
40+
41+
/**
42+
* Test getArguments returns null when no arguments are set.
43+
*/
44+
@Test
45+
void testGetArgumentsWhenNull() {
46+
ToolInvocation invocation = new ToolInvocation();
47+
assertNull(invocation.getArguments(), "getArguments should return null when argumentsNode is null");
48+
}
49+
50+
/**
51+
* Test getArguments returns a Map when arguments are set.
52+
*/
53+
@Test
54+
void testGetArgumentsReturnsMap() {
55+
ToolInvocation invocation = new ToolInvocation();
56+
57+
// Create a JsonNode with some arguments
58+
ObjectNode argsNode = JsonNodeFactory.instance.objectNode();
59+
argsNode.put("location", "San Francisco");
60+
argsNode.put("units", "celsius");
61+
62+
invocation.setArguments(argsNode);
63+
64+
var args = invocation.getArguments();
65+
assertNotNull(args);
66+
assertEquals("San Francisco", args.get("location"));
67+
assertEquals("celsius", args.get("units"));
68+
}
69+
70+
/**
71+
* Test getArgumentsAs deserializes to a record type.
72+
*/
73+
@Test
74+
void testGetArgumentsAsWithRecord() {
75+
ToolInvocation invocation = new ToolInvocation();
76+
77+
// Create a JsonNode with weather arguments
78+
ObjectNode argsNode = JsonNodeFactory.instance.objectNode();
79+
argsNode.put("city", "Paris");
80+
argsNode.put("units", "metric");
81+
82+
invocation.setArguments(argsNode);
83+
84+
// Deserialize to record
85+
WeatherArgs args = invocation.getArgumentsAs(WeatherArgs.class);
86+
assertNotNull(args);
87+
assertEquals("Paris", args.city());
88+
assertEquals("metric", args.units());
89+
}
90+
91+
/**
92+
* Test getArgumentsAs deserializes to a POJO.
93+
*/
94+
@Test
95+
void testGetArgumentsAsWithPojo() {
96+
ToolInvocation invocation = new ToolInvocation();
97+
98+
// Create a JsonNode with user data
99+
ObjectNode argsNode = JsonNodeFactory.instance.objectNode();
100+
argsNode.put("username", "alice");
101+
argsNode.put("age", 30);
102+
103+
invocation.setArguments(argsNode);
104+
105+
// Deserialize to POJO
106+
UserData userData = invocation.getArgumentsAs(UserData.class);
107+
assertNotNull(userData);
108+
assertEquals("alice", userData.getUsername());
109+
assertEquals(30, userData.getAge());
110+
}
111+
112+
/**
113+
* Test getArgumentsAs throws IllegalArgumentException on deserialization failure.
114+
*/
115+
@Test
116+
void testGetArgumentsAsThrowsOnInvalidType() {
117+
ToolInvocation invocation = new ToolInvocation();
118+
119+
// Create invalid JSON for the target type (missing required field)
120+
ObjectNode argsNode = JsonNodeFactory.instance.objectNode();
121+
argsNode.put("invalid_field", "value");
122+
123+
invocation.setArguments(argsNode);
124+
125+
// Try to deserialize to a type that doesn't match
126+
IllegalArgumentException exception = assertThrows(
127+
IllegalArgumentException.class,
128+
() -> invocation.getArgumentsAs(StrictType.class),
129+
"Should throw IllegalArgumentException for invalid deserialization"
130+
);
131+
132+
assertTrue(exception.getMessage().contains("Failed to deserialize arguments"));
133+
assertTrue(exception.getMessage().contains("StrictType"));
134+
}
135+
136+
/**
137+
* Record for testing type-safe argument deserialization.
138+
*/
139+
record WeatherArgs(String city, String units) {}
140+
141+
/**
142+
* POJO for testing type-safe argument deserialization.
143+
*/
144+
public static class UserData {
145+
private String username;
146+
private int age;
147+
148+
public String getUsername() {
149+
return username;
150+
}
151+
152+
public void setUsername(String username) {
153+
this.username = username;
154+
}
155+
156+
public int getAge() {
157+
return age;
158+
}
159+
160+
public void setAge(int age) {
161+
this.age = age;
162+
}
163+
}
164+
165+
/**
166+
* Strict type with constructor that throws, for testing error handling.
167+
*/
168+
public static class StrictType {
169+
private final String requiredField;
170+
171+
public StrictType(String requiredField) {
172+
if (requiredField == null) {
173+
throw new IllegalArgumentException("requiredField cannot be null");
174+
}
175+
this.requiredField = requiredField;
176+
}
177+
178+
public String getRequiredField() {
179+
return requiredField;
180+
}
181+
}
182+
}

0 commit comments

Comments
 (0)