mirror of
https://github.com/kardolus/chatgpt-cli.git
synced 2024-09-08 23:15:00 +03:00
POC
This commit is contained in:
69
client/client.go
Normal file
69
client/client.go
Normal 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
121
client/client_test.go
Normal 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
49
client/mocks_test.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user