From 76ae4858afd6fa0e9e7d4a6fab8eaa9ee985cf14 Mon Sep 17 00:00:00 2001 From: Whitea029 <1664915115@qq.com> Date: Wed, 14 May 2025 10:49:31 +0800 Subject: [PATCH 1/6] test: enhance error accumulator and form builder tests, add marshaller tests --- internal/error_accumulator_test.go | 41 +++------ internal/form_builder.go | 3 + internal/form_builder_test.go | 135 +++++++++++++++++++++++++++++ internal/marshaller_test.go | 34 ++++++++ internal/unmarshaler_test.go | 37 ++++++++ 5 files changed, 223 insertions(+), 27 deletions(-) create mode 100644 internal/marshaller_test.go create mode 100644 internal/unmarshaler_test.go diff --git a/internal/error_accumulator_test.go b/internal/error_accumulator_test.go index d48f28177..7a3a7941c 100644 --- a/internal/error_accumulator_test.go +++ b/internal/error_accumulator_test.go @@ -1,41 +1,28 @@ package openai_test import ( - "bytes" - "errors" "testing" - utils "github.com/sashabaranov/go-openai/internal" - "github.com/sashabaranov/go-openai/internal/test" + openai "github.com/sashabaranov/go-openai/internal" + "github.com/sashabaranov/go-openai/internal/test/checks" ) -func TestErrorAccumulatorBytes(t *testing.T) { - accumulator := &utils.DefaultErrorAccumulator{ - Buffer: &bytes.Buffer{}, - } - - errBytes := accumulator.Bytes() - if len(errBytes) != 0 { - t.Fatalf("Did not return nil with empty bytes: %s", string(errBytes)) - } +func TestDefaultErrorAccumulator_WriteMultiple(t *testing.T) { + ea := openai.NewErrorAccumulator().(*openai.DefaultErrorAccumulator) - err := accumulator.Write([]byte("{}")) - if err != nil { - t.Fatalf("%+v", err) - } + checks.NoError(t, ea.Write([]byte("{\"error\": \"test1\"}"))) + checks.NoError(t, ea.Write([]byte("{\"error\": \"test2\"}"))) - errBytes = accumulator.Bytes() - if len(errBytes) == 0 { - t.Fatalf("Did not return error bytes when has error: %s", string(errBytes)) + expected := "{\"error\": \"test1\"}{\"error\": \"test2\"}" + if string(ea.Bytes()) != expected { + t.Fatalf("Expected %q, got %q", expected, ea.Bytes()) } } -func TestErrorByteWriteErrors(t *testing.T) { - accumulator := &utils.DefaultErrorAccumulator{ - Buffer: &test.FailingErrorBuffer{}, - } - err := accumulator.Write([]byte("{")) - if !errors.Is(err, test.ErrTestErrorAccumulatorWriteFailed) { - t.Fatalf("Did not return error when write failed: %v", err) +func TestDefaultErrorAccumulator_EmptyBuffer(t *testing.T) { + ea := openai.NewErrorAccumulator().(*openai.DefaultErrorAccumulator) + + if len(ea.Bytes()) != 0 { + t.Fatal("Buffer should be empty initially") } } diff --git a/internal/form_builder.go b/internal/form_builder.go index 2224fad45..c2d18fcb7 100644 --- a/internal/form_builder.go +++ b/internal/form_builder.go @@ -53,6 +53,9 @@ func (fb *DefaultFormBuilder) createFormFile(fieldname string, r io.Reader, file } func (fb *DefaultFormBuilder) WriteField(fieldname, value string) error { + if fieldname == "" { + return fmt.Errorf("fieldname cannot be empty") + } return fb.writer.WriteField(fieldname, value) } diff --git a/internal/form_builder_test.go b/internal/form_builder_test.go index 8df989e3b..a47ce79fd 100644 --- a/internal/form_builder_test.go +++ b/internal/form_builder_test.go @@ -1,6 +1,8 @@ package openai //nolint:testpackage // testing private field import ( + "strings" + "github.com/sashabaranov/go-openai/internal/test/checks" "bytes" @@ -9,6 +11,47 @@ import ( "testing" ) +type mockFormBuilder struct { + mockCreateFormFile func(string, *os.File) error + mockWriteField func(string, string) error + mockClose func() error +} + +func (m *mockFormBuilder) CreateFormFile(fieldname string, file *os.File) error { + return m.mockCreateFormFile(fieldname, file) +} + +func (m *mockFormBuilder) WriteField(fieldname, value string) error { + return m.mockWriteField(fieldname, value) +} + +func (m *mockFormBuilder) Close() error { + return m.mockClose() +} + +func (m *mockFormBuilder) FormDataContentType() string { + return "" +} + +func TestCloseMethod(t *testing.T) { + t.Run("NormalClose", func(t *testing.T) { + body := &bytes.Buffer{} + builder := NewFormBuilder(body) + checks.NoError(t, builder.Close(), "正常关闭应成功") + }) + + t.Run("ErrorPropagation", func(t *testing.T) { + errorMock := errors.New("mock close error") + mockBuilder := &mockFormBuilder{ + mockClose: func() error { + return errorMock + }, + } + err := mockBuilder.Close() + checks.ErrorIs(t, err, errorMock, "应传递关闭错误") + }) +} + type failingWriter struct { } @@ -43,3 +86,95 @@ func TestFormBuilderWithClosedFile(t *testing.T) { checks.HasError(t, err, "formbuilder should return error if file is closed") checks.ErrorIs(t, err, os.ErrClosed, "formbuilder should return error if file is closed") } + +func TestMultiPartFormUploads(t *testing.T) { + body := &bytes.Buffer{} + builder := NewFormBuilder(body) + + t.Run("MultipleFiles", func(t *testing.T) { + file1, _ := os.CreateTemp(t.TempDir(), "*.png") + file2, _ := os.CreateTemp(t.TempDir(), "*.jpg") + defer file1.Close() + defer file2.Close() + + checks.NoError(t, builder.CreateFormFile("image1", file1), "PNG file upload failed") + checks.NoError(t, builder.CreateFormFile("image2", file2), "JPG file upload failed") + checks.NoError(t, builder.WriteField("description", "test images"), "Field write failed") + }) + + t.Run("LargeFileConcurrent", func(t *testing.T) { + bigFile, _ := os.CreateTemp(t.TempDir(), "*.bin") + defer bigFile.Close() + bigFile.Write(make([]byte, 1024*1024*5)) // 5MB test file + + checks.NoError(t, builder.CreateFormFile("bigfile", bigFile), "Large file upload failed") + checks.NoError(t, builder.WriteField("note", "large file test"), "Field write failed") + }) + + t.Run("MixedContentTypes", func(t *testing.T) { + csvFile, _ := os.CreateTemp(t.TempDir(), "*.csv") + textFile, _ := os.CreateTemp(t.TempDir(), "*.txt") + defer csvFile.Close() + defer textFile.Close() + + checks.NoError(t, builder.CreateFormFile("data", csvFile), "CSV file upload failed") + checks.NoError(t, builder.CreateFormFile("text", textFile), "Text file upload failed") + checks.NoError(t, builder.WriteField("format", "mixed"), "Field write failed") + }) +} + +func TestFormDataContentType(t *testing.T) { + body := &bytes.Buffer{} + builder := NewFormBuilder(body) + contentType := builder.FormDataContentType() + if !strings.HasPrefix(contentType, "multipart/form-data") { + t.Fatalf("Content-Type格式错误,期望multipart/form-data开头,实际得到:%s", contentType) + } +} + +func TestCreateFormFileReader(t *testing.T) { + body := &bytes.Buffer{} + builder := NewFormBuilder(body) + + t.Run("SpecialCharacters", func(t *testing.T) { + checks.NoError(t, builder.CreateFormFileReader("field", strings.NewReader("content"), "测 试@file.txt"), "特殊字符文件名应处理成功") + }) + + t.Run("InvalidReader", func(t *testing.T) { + err := builder.CreateFormFileReader("field", &failingReader{}, "valid.txt") + checks.HasError(t, err, "无效reader应返回错误") + }) +} + +type failingReader struct{} + +func (r *failingReader) Read(p []byte) (int, error) { + return 0, errors.New("mock read error") +} + +func TestWriteFieldEdgeCases(t *testing.T) { + mockErr := errors.New("mock write error") + t.Run("EmptyFieldName", func(t *testing.T) { + body := &bytes.Buffer{} + builder := NewFormBuilder(body) + err := builder.WriteField("", "valid-value") + checks.HasError(t, err, "should return error for empty field name") + }) + + t.Run("EmptyValue", func(t *testing.T) { + body := &bytes.Buffer{} + builder := NewFormBuilder(body) + err := builder.WriteField("valid-field", "") + checks.NoError(t, err, "should allow empty value") + }) + + t.Run("MockWriterFailure", func(t *testing.T) { + mockBuilder := &mockFormBuilder{ + mockWriteField: func(_, _ string) error { + return mockErr + }, + } + err := mockBuilder.WriteField("field", "value") + checks.ErrorIs(t, err, mockErr, "should propagate write error") + }) +} diff --git a/internal/marshaller_test.go b/internal/marshaller_test.go new file mode 100644 index 000000000..70694faed --- /dev/null +++ b/internal/marshaller_test.go @@ -0,0 +1,34 @@ +package openai_test + +import ( + "testing" + + openai "github.com/sashabaranov/go-openai/internal" + "github.com/sashabaranov/go-openai/internal/test/checks" +) + +func TestJSONMarshaller_Normal(t *testing.T) { + jm := &openai.JSONMarshaller{} + data := map[string]string{"key": "value"} + + b, err := jm.Marshal(data) + checks.NoError(t, err) + if len(b) == 0 { + t.Fatal("should return non-empty bytes") + } +} + +func TestJSONMarshaller_InvalidInput(t *testing.T) { + jm := &openai.JSONMarshaller{} + _, err := jm.Marshal(make(chan int)) + checks.HasError(t, err, "should return error for unsupported type") +} + +func TestJSONMarshaller_EmptyValue(t *testing.T) { + jm := &openai.JSONMarshaller{} + b, err := jm.Marshal(nil) + checks.NoError(t, err) + if string(b) != "null" { + t.Fatalf("unexpected marshaled value: %s", string(b)) + } +} diff --git a/internal/unmarshaler_test.go b/internal/unmarshaler_test.go new file mode 100644 index 000000000..d63eac779 --- /dev/null +++ b/internal/unmarshaler_test.go @@ -0,0 +1,37 @@ +package openai_test + +import ( + "testing" + + openai "github.com/sashabaranov/go-openai/internal" + "github.com/sashabaranov/go-openai/internal/test/checks" +) + +func TestJSONUnmarshaler_Normal(t *testing.T) { + jm := &openai.JSONUnmarshaler{} + data := []byte(`{"key":"value"}`) + var v map[string]string + + err := jm.Unmarshal(data, &v) + checks.NoError(t, err) + if v["key"] != "value" { + t.Fatal("unmarshal result mismatch") + } +} + +func TestJSONUnmarshaler_InvalidJSON(t *testing.T) { + jm := &openai.JSONUnmarshaler{} + data := []byte(`{invalid}`) + var v map[string]interface{} + + err := jm.Unmarshal(data, &v) + checks.HasError(t, err, "should return error for invalid JSON") +} + +func TestJSONUnmarshaler_EmptyInput(t *testing.T) { + jm := &openai.JSONUnmarshaler{} + var v interface{} + + err := jm.Unmarshal(nil, &v) + checks.HasError(t, err, "should return error for nil input") +} From d0cfb0aa6a8bec800830740b689f416dd392cdff Mon Sep 17 00:00:00 2001 From: Whitea029 <1664915115@qq.com> Date: Wed, 14 May 2025 11:06:00 +0800 Subject: [PATCH 2/6] test: fix some issue form golangci-lint --- internal/error_accumulator_test.go | 12 ++++++++---- internal/form_builder_test.go | 6 +++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/internal/error_accumulator_test.go b/internal/error_accumulator_test.go index 7a3a7941c..3fa9d7714 100644 --- a/internal/error_accumulator_test.go +++ b/internal/error_accumulator_test.go @@ -8,8 +8,10 @@ import ( ) func TestDefaultErrorAccumulator_WriteMultiple(t *testing.T) { - ea := openai.NewErrorAccumulator().(*openai.DefaultErrorAccumulator) - + ea, ok := openai.NewErrorAccumulator().(*openai.DefaultErrorAccumulator) + if !ok { + t.Fatal("type assertion to *DefaultErrorAccumulator failed") + } checks.NoError(t, ea.Write([]byte("{\"error\": \"test1\"}"))) checks.NoError(t, ea.Write([]byte("{\"error\": \"test2\"}"))) @@ -20,8 +22,10 @@ func TestDefaultErrorAccumulator_WriteMultiple(t *testing.T) { } func TestDefaultErrorAccumulator_EmptyBuffer(t *testing.T) { - ea := openai.NewErrorAccumulator().(*openai.DefaultErrorAccumulator) - + ea, ok := openai.NewErrorAccumulator().(*openai.DefaultErrorAccumulator) + if !ok { + t.Fatal("type assertion to *DefaultErrorAccumulator failed") + } if len(ea.Bytes()) != 0 { t.Fatal("Buffer should be empty initially") } diff --git a/internal/form_builder_test.go b/internal/form_builder_test.go index a47ce79fd..7e3529d47 100644 --- a/internal/form_builder_test.go +++ b/internal/form_builder_test.go @@ -105,8 +105,8 @@ func TestMultiPartFormUploads(t *testing.T) { t.Run("LargeFileConcurrent", func(t *testing.T) { bigFile, _ := os.CreateTemp(t.TempDir(), "*.bin") defer bigFile.Close() - bigFile.Write(make([]byte, 1024*1024*5)) // 5MB test file - + _, err := bigFile.Write(make([]byte, 1024*1024*5)) // 5MB test file + checks.NoError(t, err, "Failed to write large file data") checks.NoError(t, builder.CreateFormFile("bigfile", bigFile), "Large file upload failed") checks.NoError(t, builder.WriteField("note", "large file test"), "Field write failed") }) @@ -148,7 +148,7 @@ func TestCreateFormFileReader(t *testing.T) { type failingReader struct{} -func (r *failingReader) Read(p []byte) (int, error) { +func (r *failingReader) Read(_ []byte) (int, error) { return 0, errors.New("mock read error") } From ebfffde6dcaa020050c676a43dfe26b50a6a15c6 Mon Sep 17 00:00:00 2001 From: Whitea029 <1664915115@qq.com> Date: Thu, 15 May 2025 18:45:50 +0800 Subject: [PATCH 3/6] test: gofmt form builder test --- internal/form_builder_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/form_builder_test.go b/internal/form_builder_test.go index 7e3529d47..12f8911ba 100644 --- a/internal/form_builder_test.go +++ b/internal/form_builder_test.go @@ -1,12 +1,12 @@ package openai //nolint:testpackage // testing private field import ( + "errors" "strings" "github.com/sashabaranov/go-openai/internal/test/checks" "bytes" - "errors" "os" "testing" ) From d1dd4d2b8eddadeb12b39cf676ef40034a9fff33 Mon Sep 17 00:00:00 2001 From: Whitea029 <1664915115@qq.com> Date: Tue, 17 Jun 2025 10:52:25 +0800 Subject: [PATCH 4/6] fix --- internal/form_builder_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/internal/form_builder_test.go b/internal/form_builder_test.go index fc35d2fe8..7e231bce0 100644 --- a/internal/form_builder_test.go +++ b/internal/form_builder_test.go @@ -131,3 +131,15 @@ func TestFormBuilderWithReader(t *testing.T) { err = builder.CreateFormFileReader("file", rnc, "") checks.NoError(t, err, "formbuilder should not return error") } + +func TestFormDataContentType(t *testing.T) { + t.Run("ReturnsUnderlyingWriterContentType", func(t *testing.T) { + buf := &bytes.Buffer{} + builder := NewFormBuilder(buf) + + contentType := builder.FormDataContentType() + if contentType == "" { + t.Errorf("expected non-empty content type, got empty string") + } + }) +} From 4625965dab75f394e52d40643c24973b5322faa7 Mon Sep 17 00:00:00 2001 From: Whitea029 <1664915115@qq.com> Date: Tue, 17 Jun 2025 11:01:35 +0800 Subject: [PATCH 5/6] fix --- internal/form_builder_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/internal/form_builder_test.go b/internal/form_builder_test.go index 7e231bce0..ef97ac377 100644 --- a/internal/form_builder_test.go +++ b/internal/form_builder_test.go @@ -143,3 +143,22 @@ func TestFormDataContentType(t *testing.T) { } }) } + +func TestWriteField(t *testing.T) { + t.Run("EmptyFieldNameShouldReturnError", func(t *testing.T) { + buf := &bytes.Buffer{} + builder := NewFormBuilder(buf) + + err := builder.WriteField("", "some value") + checks.HasError(t, err, "fieldname is required") + }) + + t.Run("ValidFieldNameShouldSucceed", func(t *testing.T) { + buf := &bytes.Buffer{} + builder := NewFormBuilder(buf) + + err := builder.WriteField("key", "value") + checks.NoError(t, err, "should write field without error") + }) + +} From 9dc5736169ae14538212e1f27dfa7aa93debf905 Mon Sep 17 00:00:00 2001 From: Whitea029 <1664915115@qq.com> Date: Tue, 17 Jun 2025 11:04:45 +0800 Subject: [PATCH 6/6] fix lint --- internal/form_builder_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/form_builder_test.go b/internal/form_builder_test.go index ef97ac377..ddd6b8448 100644 --- a/internal/form_builder_test.go +++ b/internal/form_builder_test.go @@ -160,5 +160,4 @@ func TestWriteField(t *testing.T) { err := builder.WriteField("key", "value") checks.NoError(t, err, "should write field without error") }) - }