Skip to content

Commit 6157165

Browse files
spboyerCopilot
andauthored
test: coverage backfill for v0.38.0 features (#399)
* test: coverage backfill for v0.38.0 features Adds focused tests to bring six packages above the 70% bar ahead of the v0.38.0 release: - internal/copilotevents 0.0% -> 100.0% - internal/telemetry 64.3% -> 93.6% - internal/mcpmock 67.3% -> 93.3% - internal/snapshot 62.1% -> 82.8% - internal/adversarial 67.2% -> 73.1% - internal/graders/argmatcher 66.2% -> 88.4% Overall coverage: 77.5% -> 79.2%. Tests only; no production code changed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * test: avoid Windows tempdir cleanup race in file exporter test Windows holds the file handle open briefly after stdouttrace shuts down, which causes t.TempDir's RemoveAll to fail. Use a manual MkdirTemp with best-effort cleanup so the test passes consistently on windows-latest. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b264705 commit 6157165

7 files changed

Lines changed: 1079 additions & 0 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package adversarial
2+
3+
import (
4+
"path/filepath"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestPackTaskRelPaths(t *testing.T) {
11+
p := &Pack{Manifest: Manifest{Tasks: []string{"a/b.yaml", "c.yaml"}}}
12+
got := p.TaskRelPaths("/root")
13+
require.Equal(t, []string{filepath.Join("/root", "a", "b.yaml"), filepath.Join("/root", "c.yaml")}, got)
14+
15+
empty := &Pack{}
16+
require.Empty(t, empty.TaskRelPaths("/x"))
17+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package copilotevents
2+
3+
import (
4+
"testing"
5+
6+
copilot "github.com/github/copilot-sdk/go"
7+
8+
"github.com/microsoft/waza/internal/agentevent"
9+
)
10+
11+
func TestFromSDKEmpty(t *testing.T) {
12+
if got := FromSDK(nil); got != nil {
13+
t.Fatalf("FromSDK(nil) = %v", got)
14+
}
15+
}
16+
17+
func TestFromSDKWrapsAndKinds(t *testing.T) {
18+
sdkEvents := []copilot.SessionEvent{
19+
{Data: &copilot.SessionStartData{}},
20+
{Data: &copilot.SessionShutdownData{}},
21+
{Data: &copilot.SessionErrorData{Message: "err"}},
22+
{Data: &copilot.SessionInfoData{Message: "info"}},
23+
{Data: &copilot.SessionWarningData{Message: "warn"}},
24+
{Data: &copilot.UserMessageData{Content: "u"}},
25+
{Data: &copilot.AssistantMessageData{Content: "a"}},
26+
{Data: &copilot.AssistantMessageDeltaData{DeltaContent: "d"}},
27+
{Data: &copilot.AssistantReasoningData{Content: "r"}},
28+
{Data: &copilot.AssistantUsageData{}},
29+
{Data: &copilot.SystemMessageData{Content: "s"}},
30+
{Data: &copilot.ToolExecutionStartData{ToolCallID: "1"}},
31+
{Data: &copilot.ToolExecutionCompleteData{ToolCallID: "1"}},
32+
{Data: &copilot.ToolExecutionPartialResultData{ToolCallID: "1"}},
33+
{Data: &copilot.ToolExecutionProgressData{ToolCallID: "1"}},
34+
{Data: &copilot.ToolUserRequestedData{ToolCallID: "1"}},
35+
{Data: &copilot.SkillInvokedData{}},
36+
{Data: &copilot.HookStartData{}},
37+
{Data: &copilot.HookEndData{}},
38+
// Unknown SDK event type falls through to KindRaw via RawSessionEventData.
39+
{Data: &copilot.RawSessionEventData{EventType: copilot.SessionEventType("custom.unknown")}},
40+
}
41+
expectedKinds := []agentevent.Kind{
42+
agentevent.KindSessionStart,
43+
agentevent.KindSessionShutdown,
44+
agentevent.KindSessionError,
45+
agentevent.KindSessionInfo,
46+
agentevent.KindSessionWarning,
47+
agentevent.KindUserMessage,
48+
agentevent.KindAssistantMessage,
49+
agentevent.KindAssistantMessageDelta,
50+
agentevent.KindAssistantReasoning,
51+
agentevent.KindAssistantUsage,
52+
agentevent.KindSystemMessage,
53+
agentevent.KindToolExecutionStart,
54+
agentevent.KindToolExecutionComplete,
55+
agentevent.KindToolExecutionPartialResult,
56+
agentevent.KindToolExecutionProgress,
57+
agentevent.KindToolUserRequested,
58+
agentevent.KindSkillInvoked,
59+
agentevent.KindHookStart,
60+
agentevent.KindHookEnd,
61+
agentevent.KindRaw,
62+
}
63+
64+
out := FromSDK(sdkEvents)
65+
if len(out) != len(sdkEvents) {
66+
t.Fatalf("FromSDK len = %d, want %d", len(out), len(sdkEvents))
67+
}
68+
for i, e := range out {
69+
if e.Kind() != expectedKinds[i] {
70+
t.Errorf("event %d kind = %s, want %s", i, e.Kind(), expectedKinds[i])
71+
}
72+
// Raw round-trip preserves the SDK event.
73+
se, ok := AsSDKEvent(e)
74+
if !ok {
75+
t.Errorf("event %d: AsSDKEvent !ok", i)
76+
continue
77+
}
78+
if se.Type() != sdkEvents[i].Type() {
79+
t.Errorf("event %d type = %s, want %s", i, se.Type(), sdkEvents[i].Type())
80+
}
81+
}
82+
83+
// ToSDK strips wrappers and preserves order.
84+
roundTrip := ToSDK(out)
85+
if len(roundTrip) != len(sdkEvents) {
86+
t.Fatalf("ToSDK len = %d, want %d", len(roundTrip), len(sdkEvents))
87+
}
88+
}
89+
90+
func TestToSDKEmpty(t *testing.T) {
91+
if got := ToSDK(nil); got != nil {
92+
t.Fatalf("ToSDK(nil) = %v", got)
93+
}
94+
}
95+
96+
func TestToSDKSkipsNonSDK(t *testing.T) {
97+
// agentevent.New with a non-SDK raw value should be skipped by ToSDK.
98+
nonSDK := agentevent.New(agentevent.KindRaw, "not-an-sdk-event")
99+
wrapped := WrapSDKEvent(copilot.SessionEvent{Data: &copilot.UserMessageData{Content: "x"}})
100+
out := ToSDK([]agentevent.Event{nonSDK, wrapped})
101+
if len(out) != 1 {
102+
t.Fatalf("ToSDK = %d events, want 1", len(out))
103+
}
104+
}
105+
106+
func TestAsSDKEventMiss(t *testing.T) {
107+
e := agentevent.New(agentevent.KindRaw, 42)
108+
if _, ok := AsSDKEvent(e); ok {
109+
t.Fatal("AsSDKEvent should be false for non-SDK raw")
110+
}
111+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package copilotevents
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
copilot "github.com/github/copilot-sdk/go"
8+
)
9+
10+
func evt(data copilot.SessionEventData) copilot.SessionEvent {
11+
return copilot.SessionEvent{Data: data}
12+
}
13+
14+
func TestContent(t *testing.T) {
15+
cases := []struct {
16+
name string
17+
evt copilot.SessionEvent
18+
want string
19+
ok bool
20+
}{
21+
{"user", evt(&copilot.UserMessageData{Content: "hello"}), "hello", true},
22+
{"assistant", evt(&copilot.AssistantMessageData{Content: "hi"}), "hi", true},
23+
{"reasoning", evt(&copilot.AssistantReasoningData{Content: "thinking"}), "thinking", true},
24+
{"system", evt(&copilot.SystemMessageData{Content: "sys"}), "sys", true},
25+
{"other", evt(&copilot.SessionStartData{}), "", false},
26+
}
27+
for _, c := range cases {
28+
t.Run(c.name, func(t *testing.T) {
29+
got, ok := Content(c.evt)
30+
if got != c.want || ok != c.ok {
31+
t.Fatalf("Content() = (%q, %v), want (%q, %v)", got, ok, c.want, c.ok)
32+
}
33+
})
34+
}
35+
}
36+
37+
func TestDeltaContent(t *testing.T) {
38+
if got, ok := DeltaContent(evt(&copilot.AssistantMessageDeltaData{DeltaContent: "delta"})); !ok || got != "delta" {
39+
t.Fatalf("DeltaContent = (%q, %v)", got, ok)
40+
}
41+
if got, ok := DeltaContent(evt(&copilot.UserMessageData{})); ok || got != "" {
42+
t.Fatalf("DeltaContent on non-delta returned (%q, %v)", got, ok)
43+
}
44+
}
45+
46+
func TestMessage(t *testing.T) {
47+
cases := []struct {
48+
name string
49+
evt copilot.SessionEvent
50+
want string
51+
ok bool
52+
}{
53+
{"error", evt(&copilot.SessionErrorData{Message: "boom"}), "boom", true},
54+
{"info", evt(&copilot.SessionInfoData{Message: "fyi"}), "fyi", true},
55+
{"warning", evt(&copilot.SessionWarningData{Message: "warn"}), "warn", true},
56+
{"other", evt(&copilot.UserMessageData{}), "", false},
57+
}
58+
for _, c := range cases {
59+
t.Run(c.name, func(t *testing.T) {
60+
got, ok := Message(c.evt)
61+
if got != c.want || ok != c.ok {
62+
t.Fatalf("Message = (%q, %v), want (%q, %v)", got, ok, c.want, c.ok)
63+
}
64+
})
65+
}
66+
}
67+
68+
func TestReasoningText(t *testing.T) {
69+
text := "because"
70+
if got := ReasoningText(evt(&copilot.AssistantMessageData{ReasoningText: &text})); got == nil || *got != text {
71+
t.Fatalf("ReasoningText = %v", got)
72+
}
73+
if got := ReasoningText(evt(&copilot.UserMessageData{})); got != nil {
74+
t.Fatalf("ReasoningText on non-assistant got %v", got)
75+
}
76+
}
77+
78+
func TestTypedAccessors(t *testing.T) {
79+
if _, ok := SessionStart(evt(&copilot.SessionStartData{})); !ok {
80+
t.Fatal("SessionStart")
81+
}
82+
if _, ok := SessionStart(evt(&copilot.UserMessageData{})); ok {
83+
t.Fatal("SessionStart should miss")
84+
}
85+
if _, ok := ToolStart(evt(&copilot.ToolExecutionStartData{})); !ok {
86+
t.Fatal("ToolStart")
87+
}
88+
if _, ok := ToolUserRequested(evt(&copilot.ToolUserRequestedData{})); !ok {
89+
t.Fatal("ToolUserRequested")
90+
}
91+
if _, ok := ToolComplete(evt(&copilot.ToolExecutionCompleteData{})); !ok {
92+
t.Fatal("ToolComplete")
93+
}
94+
if _, ok := ToolPartial(evt(&copilot.ToolExecutionPartialResultData{})); !ok {
95+
t.Fatal("ToolPartial")
96+
}
97+
if _, ok := ToolProgress(evt(&copilot.ToolExecutionProgressData{})); !ok {
98+
t.Fatal("ToolProgress")
99+
}
100+
if _, ok := SkillInvoked(evt(&copilot.SkillInvokedData{})); !ok {
101+
t.Fatal("SkillInvoked")
102+
}
103+
if _, ok := HookStart(evt(&copilot.HookStartData{})); !ok {
104+
t.Fatal("HookStart")
105+
}
106+
if _, ok := HookEnd(evt(&copilot.HookEndData{})); !ok {
107+
t.Fatal("HookEnd")
108+
}
109+
if _, ok := Shutdown(evt(&copilot.SessionShutdownData{})); !ok {
110+
t.Fatal("Shutdown")
111+
}
112+
if _, ok := AssistantUsage(evt(&copilot.AssistantUsageData{})); !ok {
113+
t.Fatal("AssistantUsage")
114+
}
115+
}
116+
117+
func TestToolCallID(t *testing.T) {
118+
cases := []struct {
119+
name string
120+
evt copilot.SessionEvent
121+
want string
122+
ok bool
123+
}{
124+
{"start", evt(&copilot.ToolExecutionStartData{ToolCallID: "a"}), "a", true},
125+
{"complete", evt(&copilot.ToolExecutionCompleteData{ToolCallID: "b"}), "b", true},
126+
{"partial", evt(&copilot.ToolExecutionPartialResultData{ToolCallID: "c"}), "c", true},
127+
{"progress", evt(&copilot.ToolExecutionProgressData{ToolCallID: "d"}), "d", true},
128+
{"requested", evt(&copilot.ToolUserRequestedData{ToolCallID: "e"}), "e", true},
129+
{"empty", evt(&copilot.ToolExecutionStartData{}), "", false},
130+
{"other", evt(&copilot.UserMessageData{}), "", false},
131+
}
132+
for _, c := range cases {
133+
t.Run(c.name, func(t *testing.T) {
134+
got, ok := ToolCallID(c.evt)
135+
if got != c.want || ok != c.ok {
136+
t.Fatalf("ToolCallID = (%q, %v), want (%q, %v)", got, ok, c.want, c.ok)
137+
}
138+
})
139+
}
140+
}
141+
142+
func TestRawData(t *testing.T) {
143+
payload := map[string]any{"foo": "bar"}
144+
got := RawData(copilot.SessionEventTypeSessionInfo, payload)
145+
raw, ok := got.(*copilot.RawSessionEventData)
146+
if !ok {
147+
t.Fatalf("RawData returned %T", got)
148+
}
149+
if raw.EventType != copilot.SessionEventTypeSessionInfo {
150+
t.Fatalf("EventType = %v", raw.EventType)
151+
}
152+
var back map[string]any
153+
if err := json.Unmarshal(raw.Raw, &back); err != nil {
154+
t.Fatalf("invalid json: %v", err)
155+
}
156+
if back["foo"] != "bar" {
157+
t.Fatalf("Raw = %s", raw.Raw)
158+
}
159+
160+
// unmarshalable payload falls back to "{}"
161+
bad := RawData(copilot.SessionEventTypeSessionInfo, make(chan int))
162+
rd, ok := bad.(*copilot.RawSessionEventData)
163+
if !ok {
164+
t.Fatalf("expected *RawSessionEventData, got %T", bad)
165+
}
166+
if string(rd.Raw) != "{}" {
167+
t.Fatalf("expected fallback {}, got %s", rd.Raw)
168+
}
169+
}

0 commit comments

Comments
 (0)