Skip to content

Commit 6ff6f25

Browse files
authored
Merge pull request #122 from copilot-community-sdk/copilot/improve-jacoco-coverage
Add unit tests for ToolInvocation to achieve 100% coverage
2 parents c73eab7 + e42ce67 commit 6ff6f25

File tree

1 file changed

+177
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)