From bbc59f2dd15b9858e0fc0d0728286814aba80046 Mon Sep 17 00:00:00 2001 From: sgerhardt Date: Fri, 19 Jul 2024 08:39:27 -0500 Subject: [PATCH] Add tests, CI steps, and linting --- .github/workflows/.golangci.yml | 18 ++++++ .github/workflows/golangci-lint.yml | 37 ++++++++++++ Makefile | 6 +- internal/client/client.go | 2 +- internal/client/client_test.go | 92 +++++++++++++++++++++++++++++ internal/client/web_test.go | 67 +++++++++++++++++++++ 6 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/.golangci.yml create mode 100644 .github/workflows/golangci-lint.yml create mode 100644 internal/client/client_test.go create mode 100644 internal/client/web_test.go diff --git a/.github/workflows/.golangci.yml b/.github/workflows/.golangci.yml new file mode 100644 index 0000000..9c8b02f --- /dev/null +++ b/.github/workflows/.golangci.yml @@ -0,0 +1,18 @@ +linters: + enable: + - govet + - golint + - errcheck + - staticcheck + - unused + - structcheck + - varcheck + - paralleltest + +issues: + exclude-rules: + - path: _test\.go + linters: + - golint + - staticcheck + diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..c51691b --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,37 @@ +name: Go Lint + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + lint: + + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.22 + + - name: Install golangci-lint + run: | + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.59.1 + + - name: Install dependencies + run: go mod download + + - name: Run golangci-lint + run: | + golangci-lint run + + - name: Run tests + run: go test -v ./... \ No newline at end of file diff --git a/Makefile b/Makefile index e98e7c7..ff93811 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ MOCKERY_CMD = mockery --name=Http --dir=./internal/client --output=./internal/client/mocks --outpkg=mocks +GOLANGCI_LINT := $(GOPATH)/bin/golangci-lint -# Define the target to generate mocks .PHONY: mocks mocks: @echo "Generating mocks..." @@ -31,6 +31,10 @@ test: install-mockery mocks go test ./... @echo "Tests completed." +.PHONY: lint +lint: $(GOLANGCI_LINT) + $(GOLANGCI_LINT) run + .PHONY: build build: @echo "Building binary..." diff --git a/internal/client/client.go b/internal/client/client.go index 3133482..53e9471 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -56,7 +56,7 @@ func (c *ElevenLabs) fileWithTimestamp() string { } else if !strings.HasSuffix(c.Config.OutputDir, string(os.PathSeparator)) { prefix = c.Config.OutputDir + string(os.PathSeparator) } - return prefix + fmt.Sprintf("%s", formattedTime) + ".mp3" + return prefix + formattedTime + ".mp3" } func (c *ElevenLabs) Write(data []byte) (int, error) { diff --git a/internal/client/client_test.go b/internal/client/client_test.go new file mode 100644 index 0000000..7f18fa1 --- /dev/null +++ b/internal/client/client_test.go @@ -0,0 +1,92 @@ +package client + +import ( + "bytes" + "chatter/internal/client/mocks" + "chatter/internal/config" + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "io" + "net/http" + "testing" +) + +func TestClient_GenerateVoiceFromText(t *testing.T) { + type fields struct { + apiKey string + outputFilePath string + } + type args struct { + text string + voiceID string + } + + tests := []struct { + name string + fields fields + args args + error error + mockSetup func(client *mocks.Http) + }{ + { + name: "errors if voice id is not populated", + args: args{ + text: "test", + voiceID: "", + }, + error: errors.New("voice ID is required"), + mockSetup: func(client *mocks.Http) {}, + }, + { + name: "marshals a payload to json", + fields: fields{}, + args: args{ + text: "testing", + voiceID: "stephen_hawking", + }, + + mockSetup: func(client *mocks.Http) { + mockResp := &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte("bytes representing the mp3 file..."))), + } + + mockResp.StatusCode = http.StatusOK + mockResp.Body = io.NopCloser(bytes.NewReader([]byte("bytes representing the mp3 file..."))) + // Set up the expectation + client.On("Do", mock.AnythingOfType("*http.Request")).Return(mockResp, nil).Run(func(args mock.Arguments) { + req := args.Get(0).(*http.Request) + // Verify the body of the request is the expected json + body := new(bytes.Buffer) + _, err := body.ReadFrom(req.Body) + require.NoError(t, err) + assert.Equal(t, body.String(), `{"text":"testing","model_id":"eleven_monolingual_v1","voice_settings":{"stability":0,"similarity_boost":0}}`) + }) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient := mocks.NewHttp(t) + tt.mockSetup(mockClient) + cfg := config.AppConfig{ + CharacterRequestLimit: 100, + OutputDir: tt.fields.outputFilePath, + APIKey: tt.fields.apiKey, + VoiceID: tt.args.voiceID, + } + c := New(cfg, mockClient) + _, err := c.FromText(tt.args.text, tt.args.voiceID) + if tt.error != nil { + assert.EqualError(t, err, tt.error.Error()) + return + } + + require.NoError(t, err) + + mockClient.AssertExpectations(t) + }) + } +} diff --git a/internal/client/web_test.go b/internal/client/web_test.go new file mode 100644 index 0000000..c3ef770 --- /dev/null +++ b/internal/client/web_test.go @@ -0,0 +1,67 @@ +package client + +import ( + "chatter/internal/client/mocks" + "chatter/internal/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "io" + "net/http" + "strings" + "testing" +) + +func TestWebReader(t *testing.T) { + + tests := []struct { + name string + want []string + + error error + charLimit int + mockSetup func(client *mocks.Http) + }{ + { + name: "Given a website, read the header and body", + want: []string{"This is the h1\nThis is paragraph text\n"}, + charLimit: 100, + mockSetup: func(client *mocks.Http) { + client.On("Do", mock.Anything).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(`

This is the h1

This is paragraph text

`)), + }, nil) + }, + }, + { + name: "Given a website that requires batching requests", + want: []string{"This ", "is th", "e h1\n", "This ", "is pa", "ragra", "ph te", "xt\n"}, + charLimit: 5, + mockSetup: func(client *mocks.Http) { + client.On("Do", mock.Anything).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(`

This is the h1

This is paragraph text

`)), + }, nil) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient := mocks.NewHttp(t) + tt.mockSetup(mockClient) + appConfig := config.AppConfig{ + CharacterRequestLimit: tt.charLimit, + APIKey: "testkey", + VoiceID: "testvoice", + WebsiteURL: "https://test.com", + } + c := New(appConfig, mockClient) + texts, err := c.FromWebsite("https://test.com") + if tt.error != nil { + assert.EqualError(t, err, tt.error.Error()) + return + } + assert.Equal(t, tt.want, texts) + mockClient.AssertExpectations(t) + }) + } +}