This commit is contained in:
kardolus
2023-05-01 11:42:21 -04:00
commit dfdbcffc11
219 changed files with 202123 additions and 0 deletions

69
client/client.go Normal file
View File

@@ -0,0 +1,69 @@
package client
import (
"encoding/json"
"errors"
"fmt"
"github.com/kardolus/chatgpt-poc/http"
"github.com/kardolus/chatgpt-poc/types"
)
const (
model = "gpt-3.5-turbo"
role = "user"
URL = "https://api.openai.com/v1/chat/completions"
)
type Client struct {
caller http.Caller
}
func New(caller http.Caller) *Client {
return &Client{caller: caller}
}
func (c *Client) Query(input string) (string, error) {
body, err := CreateBody(input)
if err != nil {
return "", err
}
raw, err := c.caller.Post(URL, body)
if err != nil {
return "", err
}
if raw == nil {
return "", errors.New("empty response")
}
var response types.Response
if err := json.Unmarshal(raw, &response); err != nil {
return "", fmt.Errorf("failed to decode response: %w", err)
}
if len(response.Choices) == 0 {
return "", errors.New("no responses returned")
}
return response.Choices[0].Message.Content, nil
}
func CreateBody(query string) ([]byte, error) {
message := types.Message{
Role: role,
Content: query,
}
body := types.Request{
Model: model,
Messages: []types.Message{message},
}
result, err := json.Marshal(body)
if err != nil {
return nil, err
}
return result, nil
}

121
client/client_test.go Normal file
View File

@@ -0,0 +1,121 @@
package client_test
import (
"encoding/json"
"errors"
"github.com/golang/mock/gomock"
_ "github.com/golang/mock/mockgen/model"
"github.com/kardolus/chatgpt-poc/client"
"github.com/kardolus/chatgpt-poc/types"
"testing"
. "github.com/onsi/gomega"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
)
//go:generate mockgen -destination=mocks_test.go -package=client_test github.com/kardolus/chatgpt-poc/http Caller
var (
mockCtrl *gomock.Controller
mockCaller *MockCaller
subject *client.Client
)
func TestUnitClient(t *testing.T) {
spec.Run(t, "Testing the client package", testClient, spec.Report(report.Terminal{}))
}
func testClient(t *testing.T, when spec.G, it spec.S) {
it.Before(func() {
RegisterTestingT(t)
mockCtrl = gomock.NewController(t)
mockCaller = NewMockCaller(mockCtrl)
subject = client.New(mockCaller)
})
it.After(func() {
mockCtrl.Finish()
})
when("Query()", func() {
const query = "test query"
var (
err error
body []byte
)
it.Before(func() {
body, err = client.CreateBody(query)
Expect(err).NotTo(HaveOccurred())
})
it("throws an error when the http callout fails", func() {
errorMsg := "error message"
mockCaller.EXPECT().Post(client.URL, body).Return(nil, errors.New(errorMsg))
_, err = subject.Query(query)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal(errorMsg))
})
it("throws an error when the response is empty", func() {
mockCaller.EXPECT().Post(client.URL, body).Return(nil, nil)
_, err = subject.Query(query)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("empty response"))
})
it("throws an error when the response is a malformed json", func() {
malformed := "{no"
mockCaller.EXPECT().Post(client.URL, body).Return([]byte(malformed), nil)
_, err = subject.Query(query)
Expect(err).To(HaveOccurred())
Expect(err.Error()).Should(HavePrefix("failed to decode response:"))
})
it("throws an error when the response is missing Choices", func() {
response := &types.Response{
ID: "id",
Object: "object",
Created: 0,
Model: "model",
Choices: []types.Choice{},
}
respBytes, err := json.Marshal(response)
Expect(err).NotTo(HaveOccurred())
mockCaller.EXPECT().Post(client.URL, body).Return(respBytes, nil)
_, err = subject.Query(query)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("no responses returned"))
})
it("parses a valid http response as expected", func() {
choice := types.Choice{
Message: types.Message{
Role: "role",
Content: "content",
},
FinishReason: "",
Index: 0,
}
response := &types.Response{
ID: "id",
Object: "object",
Created: 0,
Model: "model",
Choices: []types.Choice{choice},
}
respBytes, err := json.Marshal(response)
Expect(err).NotTo(HaveOccurred())
mockCaller.EXPECT().Post(client.URL, body).Return(respBytes, nil)
result, err := subject.Query(query)
Expect(err).NotTo(HaveOccurred())
Expect(result).To(Equal("content"))
})
})
}

49
client/mocks_test.go Normal file
View File

@@ -0,0 +1,49 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/kardolus/chatgpt-poc/http (interfaces: Caller)
// Package client_test is a generated GoMock package.
package client_test
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockCaller is a mock of Caller interface.
type MockCaller struct {
ctrl *gomock.Controller
recorder *MockCallerMockRecorder
}
// MockCallerMockRecorder is the mock recorder for MockCaller.
type MockCallerMockRecorder struct {
mock *MockCaller
}
// NewMockCaller creates a new mock instance.
func NewMockCaller(ctrl *gomock.Controller) *MockCaller {
mock := &MockCaller{ctrl: ctrl}
mock.recorder = &MockCallerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCaller) EXPECT() *MockCallerMockRecorder {
return m.recorder
}
// Post mocks base method.
func (m *MockCaller) Post(arg0 string, arg1 []byte) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Post", arg0, arg1)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Post indicates an expected call of Post.
func (mr *MockCallerMockRecorder) Post(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Post", reflect.TypeOf((*MockCaller)(nil).Post), arg0, arg1)
}