Skip to content

Commit 2d79b4b

Browse files
committed
test(gemini-model): add unit tests to cover errors
- project and API key are mutually exclusive in the client initializer - generate with empty model name - maybe append user content when length is zero - maybe append user content when there is no user role - Name() method
1 parent cffe3b0 commit 2d79b4b

File tree

4 files changed

+134
-3
lines changed

4 files changed

+134
-3
lines changed

model/gemini/constants.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package gemini
2+
3+
const (
4+
systemInstructionText = "Handle the requests as specified in the System Instruction."
5+
continueProcessingText = "Continue processing previous requests as instructed. Exit or provide a summary if no more outputs are needed."
6+
)

model/gemini/errors.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package gemini
2+
3+
import "errors"
4+
5+
var errEmptyModelName = errors.New("model name cannot be empty")

model/gemini/gemini.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ type geminiModel struct {
4545
//
4646
// An error is returned if the [genai.Client] fails to initialize.
4747
func NewModel(ctx context.Context, modelName string, cfg *genai.ClientConfig) (model.LLM, error) {
48+
if modelName == "" {
49+
return nil, errEmptyModelName
50+
}
51+
4852
client, err := genai.NewClient(ctx, cfg)
4953
if err != nil {
5054
return nil, err
@@ -133,10 +137,19 @@ func (m *geminiModel) generateStream(ctx context.Context, req *model.LLMRequest)
133137
// maybeAppendUserContent appends a user content, so that model can continue to output.
134138
func (m *geminiModel) maybeAppendUserContent(req *model.LLMRequest) {
135139
if len(req.Contents) == 0 {
136-
req.Contents = append(req.Contents, genai.NewContentFromText("Handle the requests as specified in the System Instruction.", "user"))
140+
req.Contents = append(req.Contents, genai.NewContentFromText(systemInstructionText, "user"))
141+
}
142+
143+
// get the last non-nil content
144+
var last genai.Content
145+
for i := len(req.Contents) - 1; i >= 0; i-- {
146+
if req.Contents[i] != nil {
147+
last = *req.Contents[i]
148+
break
149+
}
137150
}
138151

139-
if last := req.Contents[len(req.Contents)-1]; last != nil && last.Role != "user" {
140-
req.Contents = append(req.Contents, genai.NewContentFromText("Continue processing previous requests as instructed. Exit or provide a summary if no more outputs are needed.", "user"))
152+
if last.Role != "user" {
153+
req.Contents = append(req.Contents, genai.NewContentFromText(continueProcessingText, "user"))
141154
}
142155
}

model/gemini/gemini_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package gemini
1616

1717
import (
18+
"errors"
1819
"fmt"
1920
"iter"
2021
"net/http"
@@ -187,6 +188,112 @@ func TestModel_TrackingHeaders(t *testing.T) {
187188
})
188189
}
189190

191+
func TestModel_Name(t *testing.T) {
192+
model := &geminiModel{name: "gemini-2.5-flash"}
193+
if got := model.Name(); got != "gemini-2.5-flash" {
194+
t.Fatalf("Model.Name() = %v, want %v", got, "gemini-2.5-flash")
195+
}
196+
}
197+
198+
func TestModel_NewModelError(t *testing.T) {
199+
_, err := NewModel(t.Context(), "gemini-2.5-flash", &genai.ClientConfig{
200+
// project and API key are mutually exclusive in the client initializer
201+
APIKey: "api-key",
202+
Project: "project-id",
203+
})
204+
if err == nil {
205+
t.Fatal("NewModel() expected error due to project and API key are mutually exclusive, got nil")
206+
}
207+
if want := "mutually exclusive"; !strings.Contains(err.Error(), want) {
208+
t.Fatalf("NewModel() error = %q, want to contain %q", err, want)
209+
}
210+
}
211+
212+
func TestModel_NewModelWithEmptyName(t *testing.T) {
213+
_, err := NewModel(t.Context(), "", &genai.ClientConfig{
214+
APIKey: "fakekey",
215+
})
216+
if err == nil {
217+
t.Fatal("NewModel() expected error for empty model name, got nil")
218+
}
219+
if !errors.Is(err, errEmptyModelName) {
220+
t.Fatalf("NewModel() error = %q, want to contain %q", err, errEmptyModelName.Error())
221+
}
222+
}
223+
224+
func TestModel_MaybeAppendUserContent(t *testing.T) {
225+
tests := []struct {
226+
name string
227+
initialContents []*genai.Content
228+
wantLen int
229+
wantLastRole string
230+
wantAppendedText string
231+
}{
232+
{
233+
name: "empty contents",
234+
initialContents: []*genai.Content{},
235+
wantLen: 1,
236+
wantLastRole: genai.RoleUser,
237+
wantAppendedText: systemInstructionText,
238+
},
239+
{
240+
name: "last role is not user",
241+
initialContents: []*genai.Content{{Role: genai.RoleModel}},
242+
wantLen: 2,
243+
wantLastRole: genai.RoleUser,
244+
wantAppendedText: continueProcessingText,
245+
},
246+
{
247+
name: "last role is user",
248+
initialContents: []*genai.Content{{Role: genai.RoleUser}},
249+
wantLen: 1,
250+
wantLastRole: genai.RoleUser,
251+
wantAppendedText: "",
252+
},
253+
{
254+
name: "last content from model, followed by nil",
255+
initialContents: []*genai.Content{{Role: genai.RoleModel}, nil},
256+
wantLen: 3,
257+
wantLastRole: genai.RoleUser,
258+
wantAppendedText: continueProcessingText,
259+
},
260+
}
261+
262+
for _, tt := range tests {
263+
t.Run(tt.name, func(t *testing.T) {
264+
newModel := &geminiModel{name: "gemini-2.5-flash"}
265+
contents := make([]*genai.Content, len(tt.initialContents))
266+
copy(contents, tt.initialContents)
267+
req := &model.LLMRequest{
268+
Contents: contents,
269+
}
270+
newModel.maybeAppendUserContent(req)
271+
272+
if gotLen := len(req.Contents); gotLen != tt.wantLen {
273+
t.Fatalf("Expected Contents length to be %d, got %d", tt.wantLen, gotLen)
274+
}
275+
276+
if len(req.Contents) > 0 {
277+
lastContent := req.Contents[len(req.Contents)-1]
278+
if lastContent.Role != tt.wantLastRole {
279+
t.Fatalf("Expected last content role to be %q, got %q", tt.wantLastRole, lastContent.Role)
280+
}
281+
282+
if tt.wantAppendedText != "" {
283+
if len(lastContent.Parts) == 0 {
284+
t.Fatal("Expected appended content to have at least one part, but it has none")
285+
}
286+
textPart := lastContent.Parts[0].Text
287+
288+
if textPart != tt.wantAppendedText {
289+
t.Errorf("Unexpected text in appended content. got %q, want %q", textPart, tt.wantAppendedText)
290+
}
291+
}
292+
}
293+
})
294+
}
295+
}
296+
190297
// newGeminiTestClientConfig returns the genai.ClientConfig configured for record and replay.
191298
func newGeminiTestClientConfig(t *testing.T, rrfile string) *genai.ClientConfig {
192299
t.Helper()

0 commit comments

Comments
 (0)