diff --git a/.gitignore b/.gitignore index 47f446d..fecd324 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,8 @@ dist/ # temporary track files tracks/ +# go tooling +bin/ + .config.env* *.vim diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 0000000..56c9134 --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,9 @@ +quiet: False +with-expecter: true +dir: "cmd/transcriber/mocks/{{.PackagePath}}" +packages: + github.com/mattermost/calls-transcriber/cmd/transcriber/call: + config: + interfaces: + APIClient: + diff --git a/Makefile b/Makefile index 29cf036..8e37faa 100644 --- a/Makefile +++ b/Makefile @@ -119,6 +119,10 @@ GO_TEST_OPTS += -mod=readonly -failfast -race # Temporary folder to output compiled binaries artifacts GO_OUT_BIN_DIR := ./dist +# We need to export GOBIN to allow it to be set +# for processes spawned from the Makefile +export GOBIN ?= $(PWD)/bin + ## Github Variables # A github access token that provides access to upload artifacts under releases GITHUB_TOKEN ?= a_token @@ -414,3 +418,8 @@ clean: ## to clean-up @$(INFO) cleaning /${GO_OUT_BIN_DIR} folder... $(AT)rm -rf ${GO_OUT_BIN_DIR} || ${FAIL} @$(OK) cleaning /${GO_OUT_BIN_DIR} folder + +.PHONY: mocks +mocks: ## Create mock files + $(GO) install github.com/vektra/mockery/v2/...@v2.40.3 + $(GOBIN)/mockery diff --git a/cmd/transcriber/call/job.go b/cmd/transcriber/call/job.go index 17f0bcd..5407d95 100644 --- a/cmd/transcriber/call/job.go +++ b/cmd/transcriber/call/job.go @@ -11,7 +11,7 @@ import ( func (t *Transcriber) postJobStatus(status public.JobStatus) error { apiURL := fmt.Sprintf("%s/plugins/%s/bot/calls/%s/jobs/%s/status", - t.apiClient.URL, pluginID, t.cfg.CallID, t.cfg.TranscriptionID) + t.apiURL, pluginID, t.cfg.CallID, t.cfg.TranscriptionID) payload, err := json.Marshal(&status) if err != nil { diff --git a/cmd/transcriber/call/live_captions.go b/cmd/transcriber/call/live_captions.go index 8b28c9b..17eb03b 100644 --- a/cmd/transcriber/call/live_captions.go +++ b/cmd/transcriber/call/live_captions.go @@ -244,7 +244,6 @@ func (t *Transcriber) processLiveCaptionsForTrack(ctx trackContext, pktPayloadsC } if err := t.client.SendWS(wsEvCaption, public.CaptionMsg{ SessionID: ctx.sessionID, - UserID: ctx.user.Id, Text: text, NewAudioLenMs: float64(newAudioLenMs), }, false); err != nil { diff --git a/cmd/transcriber/call/tracks.go b/cmd/transcriber/call/tracks.go index 79ec2f9..2b8d604 100644 --- a/cmd/transcriber/call/tracks.go +++ b/cmd/transcriber/call/tracks.go @@ -73,13 +73,8 @@ func (t *Transcriber) handleTrack(ctx any) error { return nil } - user, err := t.getUserForSession(sessionID) - if err != nil { - return fmt.Errorf("failed to get user for session: %w", err) - } - t.liveTracksWg.Add(1) - go t.processLiveTrack(track, sessionID, user) + go t.processLiveTrack(track, sessionID) return nil } @@ -87,14 +82,20 @@ func (t *Transcriber) handleTrack(ctx any) error { // processLiveTrack saves the content of a voice track to a file for later processing. // This involves muxing the raw Opus packets into a OGG file with the // timings adjusted to account for any potential gaps due to mute/unmute sequences. -func (t *Transcriber) processLiveTrack(track trackRemote, sessionID string, user *model.User) { +func (t *Transcriber) processLiveTrack(track trackRemote, sessionID string) { ctx := trackContext{ trackID: track.ID(), sessionID: sessionID, - user: user, - filename: filepath.Join(getDataDir(), fmt.Sprintf("%s_%s.ogg", user.Id, track.ID())), } + user, err := t.getUserForSession(ctx.sessionID) + if err != nil { + slog.Error("failed to get user for session", slog.String("err", err.Error()), slog.String("trackID", ctx.trackID)) + return + } + ctx.user = user + ctx.filename = filepath.Join(getDataDir(), fmt.Sprintf("%s_%s.ogg", user.Id, track.ID())) + slog.Debug("processing voice track", slog.String("username", user.Username), slog.String("sessionID", sessionID), diff --git a/cmd/transcriber/call/transcriber.go b/cmd/transcriber/call/transcriber.go index 0cd3d13..e2ce897 100644 --- a/cmd/transcriber/call/transcriber.go +++ b/cmd/transcriber/call/transcriber.go @@ -3,7 +3,9 @@ package call import ( "context" "fmt" + "io" "log/slog" + "net/http" "sync" "sync/atomic" "time" @@ -21,11 +23,18 @@ const ( maxTracksContexes = 256 ) +type APIClient interface { + DoAPIRequest(ctx context.Context, method, url, data, etag string) (*http.Response, error) + DoAPIRequestBytes(ctx context.Context, method, url string, data []byte, etag string) (*http.Response, error) + DoAPIRequestReader(ctx context.Context, method, url string, data io.Reader, headers map[string]string) (*http.Response, error) +} + type Transcriber struct { cfg config.CallTranscriberConfig client *client.Client - apiClient *model.Client4 + apiClient APIClient + apiURL string errCh chan error doneCh chan struct{} @@ -50,6 +59,7 @@ func NewTranscriber(cfg config.CallTranscriberConfig) (t *Transcriber, retErr er t = &Transcriber{ cfg: cfg, apiClient: apiClient, + apiURL: apiClient.URL, } defer func() { @@ -75,16 +85,12 @@ func NewTranscriber(cfg config.CallTranscriberConfig) (t *Transcriber, retErr er return t, err } - t = &Transcriber{ - cfg: cfg, - client: rtcdClient, - apiClient: apiClient, - errCh: make(chan error, 1), - doneCh: make(chan struct{}), - trackCtxs: make(chan trackContext, maxTracksContexes), - captionsPoolQueueCh: make(chan captionPackage, transcriberQueueChBuffer), - captionsPoolDoneCh: make(chan struct{}), - } + t.client = rtcdClient + t.errCh = make(chan error, 1) + t.doneCh = make(chan struct{}) + t.trackCtxs = make(chan trackContext, maxTracksContexes) + t.captionsPoolQueueCh = make(chan captionPackage, transcriberQueueChBuffer) + t.captionsPoolDoneCh = make(chan struct{}) return } diff --git a/cmd/transcriber/call/transcriber_test.go b/cmd/transcriber/call/transcriber_test.go index f293e1e..17029cb 100644 --- a/cmd/transcriber/call/transcriber_test.go +++ b/cmd/transcriber/call/transcriber_test.go @@ -4,18 +4,24 @@ import ( "fmt" "io" "log/slog" + "net/http" "os" "path/filepath" + "strings" "testing" "time" "github.com/mattermost/calls-transcriber/cmd/transcriber/config" "github.com/mattermost/calls-transcriber/cmd/transcriber/ogg" + mocks "github.com/mattermost/calls-transcriber/cmd/transcriber/mocks/github.com/mattermost/calls-transcriber/cmd/transcriber/call" + "github.com/mattermost/mattermost/server/public/model" "github.com/pion/interceptor" "github.com/pion/rtp" + + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -104,6 +110,17 @@ func TestProcessLiveTrack(t *testing.T) { t.Run("empty payloads", func(t *testing.T) { tr := setupTranscriberForTest(t) + mockClient := &mocks.MockAPIClient{} + tr.apiClient = mockClient + + defer mockClient.AssertExpectations(t) + + mockClient.On("DoAPIRequest", mock.Anything, http.MethodGet, + "http://localhost:8065/plugins/com.mattermost.calls/bot/calls/8w8jorhr7j83uqr6y1st894hqe/sessions/sessionID/profile", "", ""). + Return(&http.Response{ + Body: io.NopCloser(strings.NewReader(`{"id": "userID", "username": "testuser"}`)), + }, nil).Once() + track := &trackRemoteMock{ id: "trackID", } @@ -158,7 +175,6 @@ func TestProcessLiveTrack(t *testing.T) { } sessionID := "sessionID" - user := &model.User{Id: "userID", Username: "testuser"} dataDir := os.Getenv("DATA_DIR") os.Setenv("DATA_DIR", os.TempDir()) @@ -166,11 +182,11 @@ func TestProcessLiveTrack(t *testing.T) { tr.liveTracksWg.Add(1) tr.startTime.Store(newTimeP(time.Now().Add(-time.Second))) - tr.processLiveTrack(track, sessionID, user) + tr.processLiveTrack(track, sessionID) close(tr.trackCtxs) require.Len(t, tr.trackCtxs, 1) - trackFile, err := os.Open(filepath.Join(getDataDir(), fmt.Sprintf("%s_%s.ogg", user.Id, track.id))) + trackFile, err := os.Open(filepath.Join(getDataDir(), fmt.Sprintf("userID_%s.ogg", track.id))) defer trackFile.Close() require.NoError(t, err) @@ -206,6 +222,17 @@ func TestProcessLiveTrack(t *testing.T) { t.Run("out of order packets", func(t *testing.T) { tr := setupTranscriberForTest(t) + mockClient := &mocks.MockAPIClient{} + tr.apiClient = mockClient + + defer mockClient.AssertExpectations(t) + + mockClient.On("DoAPIRequest", mock.Anything, http.MethodGet, + "http://localhost:8065/plugins/com.mattermost.calls/bot/calls/8w8jorhr7j83uqr6y1st894hqe/sessions/sessionID/profile", "", ""). + Return(&http.Response{ + Body: io.NopCloser(strings.NewReader(`{"id": "userID", "username": "testuser"}`)), + }, nil).Once() + track := &trackRemoteMock{ id: "trackID", } @@ -253,7 +280,6 @@ func TestProcessLiveTrack(t *testing.T) { } sessionID := "sessionID" - user := &model.User{Id: "userID", Username: "testuser"} dataDir := os.Getenv("DATA_DIR") os.Setenv("DATA_DIR", os.TempDir()) @@ -261,11 +287,11 @@ func TestProcessLiveTrack(t *testing.T) { tr.liveTracksWg.Add(1) tr.startTime.Store(newTimeP(time.Now().Add(-time.Second))) - tr.processLiveTrack(track, sessionID, user) + tr.processLiveTrack(track, sessionID) close(tr.trackCtxs) require.Len(t, tr.trackCtxs, 1) - trackFile, err := os.Open(filepath.Join(getDataDir(), fmt.Sprintf("%s_%s.ogg", user.Id, track.id))) + trackFile, err := os.Open(filepath.Join(getDataDir(), fmt.Sprintf("userID_%s.ogg", track.id))) defer trackFile.Close() require.NoError(t, err) @@ -300,6 +326,17 @@ func TestProcessLiveTrack(t *testing.T) { t.Run("timestamp wrap around", func(t *testing.T) { tr := setupTranscriberForTest(t) + mockClient := &mocks.MockAPIClient{} + tr.apiClient = mockClient + + defer mockClient.AssertExpectations(t) + + mockClient.On("DoAPIRequest", mock.Anything, http.MethodGet, + "http://localhost:8065/plugins/com.mattermost.calls/bot/calls/8w8jorhr7j83uqr6y1st894hqe/sessions/sessionID/profile", "", ""). + Return(&http.Response{ + Body: io.NopCloser(strings.NewReader(`{"id": "userID", "username": "testuser"}`)), + }, nil).Once() + track := &trackRemoteMock{ id: "trackID", } @@ -347,7 +384,6 @@ func TestProcessLiveTrack(t *testing.T) { } sessionID := "sessionID" - user := &model.User{Id: "userID", Username: "testuser"} dataDir := os.Getenv("DATA_DIR") os.Setenv("DATA_DIR", os.TempDir()) @@ -355,11 +391,11 @@ func TestProcessLiveTrack(t *testing.T) { tr.liveTracksWg.Add(1) tr.startTime.Store(newTimeP(time.Now().Add(-time.Second))) - tr.processLiveTrack(track, sessionID, user) + tr.processLiveTrack(track, sessionID) close(tr.trackCtxs) require.Len(t, tr.trackCtxs, 1) - trackFile, err := os.Open(filepath.Join(getDataDir(), fmt.Sprintf("%s_%s.ogg", user.Id, track.id))) + trackFile, err := os.Open(filepath.Join(getDataDir(), fmt.Sprintf("userID_%s.ogg", track.id))) defer trackFile.Close() require.NoError(t, err) @@ -395,4 +431,31 @@ func TestProcessLiveTrack(t *testing.T) { require.Equal(t, io.EOF, err) }) }) + + t.Run("should reattempt getUserForSession on failure", func(t *testing.T) { + tr := setupTranscriberForTest(t) + + mockClient := &mocks.MockAPIClient{} + tr.apiClient = mockClient + + defer mockClient.AssertExpectations(t) + + mockClient.On("DoAPIRequest", mock.Anything, http.MethodGet, + "http://localhost:8065/plugins/com.mattermost.calls/bot/calls/8w8jorhr7j83uqr6y1st894hqe/sessions/sessionID/profile", "", ""). + Return(nil, fmt.Errorf("failed")).Once() + + mockClient.On("DoAPIRequest", mock.Anything, http.MethodGet, + "http://localhost:8065/plugins/com.mattermost.calls/bot/calls/8w8jorhr7j83uqr6y1st894hqe/sessions/sessionID/profile", "", ""). + Return(&http.Response{ + Body: io.NopCloser(strings.NewReader(`{"id": "userID", "username": "testuser"}`)), + }, nil).Once() + + tr.liveTracksWg.Add(1) + tr.startTime.Store(newTimeP(time.Now().Add(-time.Second))) + tr.processLiveTrack(&trackRemoteMock{ + id: "trackID", + }, "sessionID") + close(tr.trackCtxs) + require.Len(t, tr.trackCtxs, 1) + }) } diff --git a/cmd/transcriber/call/utils.go b/cmd/transcriber/call/utils.go index b8c21b8..831748f 100644 --- a/cmd/transcriber/call/utils.go +++ b/cmd/transcriber/call/utils.go @@ -19,33 +19,51 @@ import ( ) const ( - httpRequestTimeout = 5 * time.Second - httpUploadTimeout = 10 * time.Second - uploadRetryAttemptWaitTime = 5 * time.Second + httpRequestTimeout = 5 * time.Second + httpUploadTimeout = 10 * time.Second + uploadRetryAttemptWaitTime = 5 * time.Second + getUserRetryAttemptWaitTime = time.Second ) var ( filenameSanitizationRE = regexp.MustCompile(`[\\:*?\"<>|\n\s/]`) - maxUploadRetryAttempts = 5 + maxAPIRetryAttempts = 5 ) func (t *Transcriber) getUserForSession(sessionID string) (*model.User, error) { - ctx, cancelFn := context.WithTimeout(context.Background(), httpRequestTimeout) - defer cancelFn() + getUser := func() (*model.User, error) { + ctx, cancelFn := context.WithTimeout(context.Background(), httpRequestTimeout) + defer cancelFn() - url := fmt.Sprintf("%s/plugins/%s/bot/calls/%s/sessions/%s/profile", t.cfg.SiteURL, pluginID, t.cfg.CallID, sessionID) - resp, err := t.apiClient.DoAPIRequest(ctx, http.MethodGet, url, "", "") - if err != nil { - return nil, fmt.Errorf("failed to fetch user profile: %w", err) + url := fmt.Sprintf("%s/plugins/%s/bot/calls/%s/sessions/%s/profile", t.cfg.SiteURL, pluginID, t.cfg.CallID, sessionID) + resp, err := t.apiClient.DoAPIRequest(ctx, http.MethodGet, url, "", "") + if err != nil { + return nil, fmt.Errorf("failed to fetch user profile: %w", err) + } + defer resp.Body.Close() + + var user *model.User + if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { + return nil, fmt.Errorf("failed to unmarshal user profile: %w", err) + } + + return user, nil } - defer resp.Body.Close() - var user *model.User - if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { - return nil, fmt.Errorf("failed to unmarshal user profile: %w", err) + for i := 0; i < maxAPIRetryAttempts; i++ { + user, err := getUser() + if err == nil { + return user, nil + } + + slog.Error("getUserForSession failed", + slog.String("err", err.Error()), + slog.Duration("reattempt_time", getUserRetryAttemptWaitTime)) + + time.Sleep(getUserRetryAttemptWaitTime) } - return user, nil + return nil, fmt.Errorf("failed to get user for call: max attempts reached") } func getDataDir() string { @@ -64,7 +82,7 @@ func getModelsDir() string { func (t *Transcriber) publishTranscription(tr transcribe.Transcription) (err error) { var fname string - for i := 0; i < maxUploadRetryAttempts; i++ { + for i := 0; i < maxAPIRetryAttempts; i++ { if i > 0 { slog.Error("getFilenameForCall failed", slog.String("err", err.Error()), @@ -129,10 +147,10 @@ func (t *Transcriber) publishTranscription(tr transcribe.Transcription) (err err return fmt.Errorf("failed to stat file: %w", err) } - apiURL := fmt.Sprintf("%s/plugins/%s/bot", t.apiClient.URL, pluginID) + apiURL := fmt.Sprintf("%s/plugins/%s/bot", t.apiURL, pluginID) var lastErr error - for i := 0; i < maxUploadRetryAttempts; i++ { + for i := 0; i < maxAPIRetryAttempts; i++ { if i > 0 { slog.Error("publishTranscription failed", slog.Duration("reattempt_time", uploadRetryAttemptWaitTime)) time.Sleep(uploadRetryAttemptWaitTime) diff --git a/cmd/transcriber/call/utils_test.go b/cmd/transcriber/call/utils_test.go index f1fc60a..9ce5e71 100644 --- a/cmd/transcriber/call/utils_test.go +++ b/cmd/transcriber/call/utils_test.go @@ -128,7 +128,7 @@ All right, we should be recording. Welcome everyone, developers meeting for Dece os.Setenv("DATA_DIR", filepath.Dir(vttFile.Name())) defer os.Setenv("DATA_DIR", dataDir) - maxUploadRetryAttempts = 2 + maxAPIRetryAttempts = 2 t.Run("upload session creation failure", func(t *testing.T) { middlewares = []middleware{ diff --git a/cmd/transcriber/mocks/github.com/mattermost/calls-transcriber/cmd/transcriber/call/mock_APIClient.go b/cmd/transcriber/mocks/github.com/mattermost/calls-transcriber/cmd/transcriber/call/mock_APIClient.go new file mode 100644 index 0000000..bbff6ef --- /dev/null +++ b/cmd/transcriber/mocks/github.com/mattermost/calls-transcriber/cmd/transcriber/call/mock_APIClient.go @@ -0,0 +1,225 @@ +// Code generated by mockery v2.40.3. DO NOT EDIT. + +package call + +import ( + context "context" + http "net/http" + + io "io" + + mock "github.com/stretchr/testify/mock" +) + +// MockAPIClient is an autogenerated mock type for the APIClient type +type MockAPIClient struct { + mock.Mock +} + +type MockAPIClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockAPIClient) EXPECT() *MockAPIClient_Expecter { + return &MockAPIClient_Expecter{mock: &_m.Mock} +} + +// DoAPIRequest provides a mock function with given fields: ctx, method, url, data, etag +func (_m *MockAPIClient) DoAPIRequest(ctx context.Context, method string, url string, data string, etag string) (*http.Response, error) { + ret := _m.Called(ctx, method, url, data, etag) + + if len(ret) == 0 { + panic("no return value specified for DoAPIRequest") + } + + var r0 *http.Response + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) (*http.Response, error)); ok { + return rf(ctx, method, url, data, etag) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string) *http.Response); ok { + r0 = rf(ctx, method, url, data, etag) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*http.Response) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, string, string) error); ok { + r1 = rf(ctx, method, url, data, etag) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAPIClient_DoAPIRequest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DoAPIRequest' +type MockAPIClient_DoAPIRequest_Call struct { + *mock.Call +} + +// DoAPIRequest is a helper method to define mock.On call +// - ctx context.Context +// - method string +// - url string +// - data string +// - etag string +func (_e *MockAPIClient_Expecter) DoAPIRequest(ctx interface{}, method interface{}, url interface{}, data interface{}, etag interface{}) *MockAPIClient_DoAPIRequest_Call { + return &MockAPIClient_DoAPIRequest_Call{Call: _e.mock.On("DoAPIRequest", ctx, method, url, data, etag)} +} + +func (_c *MockAPIClient_DoAPIRequest_Call) Run(run func(ctx context.Context, method string, url string, data string, etag string)) *MockAPIClient_DoAPIRequest_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string)) + }) + return _c +} + +func (_c *MockAPIClient_DoAPIRequest_Call) Return(_a0 *http.Response, _a1 error) *MockAPIClient_DoAPIRequest_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAPIClient_DoAPIRequest_Call) RunAndReturn(run func(context.Context, string, string, string, string) (*http.Response, error)) *MockAPIClient_DoAPIRequest_Call { + _c.Call.Return(run) + return _c +} + +// DoAPIRequestBytes provides a mock function with given fields: ctx, method, url, data, etag +func (_m *MockAPIClient) DoAPIRequestBytes(ctx context.Context, method string, url string, data []byte, etag string) (*http.Response, error) { + ret := _m.Called(ctx, method, url, data, etag) + + if len(ret) == 0 { + panic("no return value specified for DoAPIRequestBytes") + } + + var r0 *http.Response + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) (*http.Response, error)); ok { + return rf(ctx, method, url, data, etag) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, []byte, string) *http.Response); ok { + r0 = rf(ctx, method, url, data, etag) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*http.Response) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, []byte, string) error); ok { + r1 = rf(ctx, method, url, data, etag) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAPIClient_DoAPIRequestBytes_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DoAPIRequestBytes' +type MockAPIClient_DoAPIRequestBytes_Call struct { + *mock.Call +} + +// DoAPIRequestBytes is a helper method to define mock.On call +// - ctx context.Context +// - method string +// - url string +// - data []byte +// - etag string +func (_e *MockAPIClient_Expecter) DoAPIRequestBytes(ctx interface{}, method interface{}, url interface{}, data interface{}, etag interface{}) *MockAPIClient_DoAPIRequestBytes_Call { + return &MockAPIClient_DoAPIRequestBytes_Call{Call: _e.mock.On("DoAPIRequestBytes", ctx, method, url, data, etag)} +} + +func (_c *MockAPIClient_DoAPIRequestBytes_Call) Run(run func(ctx context.Context, method string, url string, data []byte, etag string)) *MockAPIClient_DoAPIRequestBytes_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].([]byte), args[4].(string)) + }) + return _c +} + +func (_c *MockAPIClient_DoAPIRequestBytes_Call) Return(_a0 *http.Response, _a1 error) *MockAPIClient_DoAPIRequestBytes_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAPIClient_DoAPIRequestBytes_Call) RunAndReturn(run func(context.Context, string, string, []byte, string) (*http.Response, error)) *MockAPIClient_DoAPIRequestBytes_Call { + _c.Call.Return(run) + return _c +} + +// DoAPIRequestReader provides a mock function with given fields: ctx, method, url, data, headers +func (_m *MockAPIClient) DoAPIRequestReader(ctx context.Context, method string, url string, data io.Reader, headers map[string]string) (*http.Response, error) { + ret := _m.Called(ctx, method, url, data, headers) + + if len(ret) == 0 { + panic("no return value specified for DoAPIRequestReader") + } + + var r0 *http.Response + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, io.Reader, map[string]string) (*http.Response, error)); ok { + return rf(ctx, method, url, data, headers) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, io.Reader, map[string]string) *http.Response); ok { + r0 = rf(ctx, method, url, data, headers) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*http.Response) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, io.Reader, map[string]string) error); ok { + r1 = rf(ctx, method, url, data, headers) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAPIClient_DoAPIRequestReader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DoAPIRequestReader' +type MockAPIClient_DoAPIRequestReader_Call struct { + *mock.Call +} + +// DoAPIRequestReader is a helper method to define mock.On call +// - ctx context.Context +// - method string +// - url string +// - data io.Reader +// - headers map[string]string +func (_e *MockAPIClient_Expecter) DoAPIRequestReader(ctx interface{}, method interface{}, url interface{}, data interface{}, headers interface{}) *MockAPIClient_DoAPIRequestReader_Call { + return &MockAPIClient_DoAPIRequestReader_Call{Call: _e.mock.On("DoAPIRequestReader", ctx, method, url, data, headers)} +} + +func (_c *MockAPIClient_DoAPIRequestReader_Call) Run(run func(ctx context.Context, method string, url string, data io.Reader, headers map[string]string)) *MockAPIClient_DoAPIRequestReader_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(io.Reader), args[4].(map[string]string)) + }) + return _c +} + +func (_c *MockAPIClient_DoAPIRequestReader_Call) Return(_a0 *http.Response, _a1 error) *MockAPIClient_DoAPIRequestReader_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAPIClient_DoAPIRequestReader_Call) RunAndReturn(run func(context.Context, string, string, io.Reader, map[string]string) (*http.Response, error)) *MockAPIClient_DoAPIRequestReader_Call { + _c.Call.Return(run) + return _c +} + +// NewMockAPIClient creates a new instance of MockAPIClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockAPIClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockAPIClient { + mock := &MockAPIClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/go.mod b/go.mod index b110a71..c2d927a 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( github.com/pion/turn/v2 v2.1.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.1 // indirect github.com/tinylib/msgp v1.1.9 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index 349c34c..dbd5e12 100644 --- a/go.sum +++ b/go.sum @@ -213,12 +213,15 @@ github.com/streamer45/silero-vad-go v0.1.3/go.mod h1:B+2FXs/5fZ6pzl6unUZYhZqkYdO github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=