diff --git a/Makefile b/Makefile index 75cd617fa..a3341a4d8 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ test: go test -count=1 -cover ./... go test -coverprofile=coverage.txt -covermode=atomic ./... -mock: mock_airflow mock_houston mock_astro mock_pkg +mock: mock_airflow mock_houston mock_astro mock_pkg mock_sql_cli mock_houston: mockery --filename=ClientInterface.go --output=houston/mocks --dir=houston --outpkg=houston_mocks --name ClientInterface @@ -39,5 +39,8 @@ mock_astro: mock_pkg: mockery --filename=Azure.go --output=pkg/azure/mocks --dir=pkg/azure --outpkg=azure_mocks --name Azure +mock_sql_cli: + mockery --filename="flow.go" --output="sql/mocks" --dir=sql/ --outpkg=mocks --name DockerBind + codecov: @eval $$(curl -s https://codecov.io/bash) diff --git a/cmd/sql/flow_test.go b/cmd/sql/flow_test.go index 604f33906..f6bc23dbd 100644 --- a/cmd/sql/flow_test.go +++ b/cmd/sql/flow_test.go @@ -1,12 +1,65 @@ package sql import ( + "errors" + "io" "os" + "strings" "testing" + sql "github.com/astronomer/astro-cli/sql" + "github.com/astronomer/astro-cli/sql/mocks" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) +var ( + errMock = errors.New("mock error") + imageBuildResponse = types.ImageBuildResponse{ + Body: io.NopCloser(strings.NewReader("Image built")), + } + containerCreateCreatedBody = container.ContainerCreateCreatedBody{ID: "123"} + sampleLog = io.NopCloser(strings.NewReader("Sample log")) + mockDockerBinder = new(mocks.DockerBind) + _ = patchDockerClientInit() +) + +func getContainerWaitResponse(raiseError bool) (bodyCh <-chan container.ContainerWaitOKBody, errCh <-chan error) { + containerWaitOkBodyChannel := make(chan container.ContainerWaitOKBody) + errChannel := make(chan error, 1) + go func() { + if raiseError { + errChannel <- errMock + return + } + res := container.ContainerWaitOKBody{StatusCode: 200, Error: nil} + containerWaitOkBodyChannel <- res + errChannel <- nil + close(containerWaitOkBodyChannel) + close(errChannel) + }() + // converting bidirectional channel to read only channels for ContainerWait to consume + var readOnlyStatusCh <-chan container.ContainerWaitOKBody + var readOnlyErrCh <-chan error + readOnlyStatusCh = containerWaitOkBodyChannel + readOnlyErrCh = errChannel + return readOnlyStatusCh, readOnlyErrCh +} + +func patchDockerClientInit() error { + sql.DockerClientInit = func() (sql.DockerBind, error) { + mockDockerBinder.On("ImageBuild", mock.Anything, mock.Anything, mock.Anything).Return(imageBuildResponse, nil) + mockDockerBinder.On("ContainerCreate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(containerCreateCreatedBody, nil) + mockDockerBinder.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(nil) + mockDockerBinder.On("ContainerWait", mock.Anything, mock.Anything, mock.Anything).Return(getContainerWaitResponse(false)) + mockDockerBinder.On("ContainerLogs", mock.Anything, mock.Anything, mock.Anything).Return(sampleLog, nil) + return mockDockerBinder, nil + } + return nil +} + // chdir changes the current working directory to the named directory and // returns a function that, when called, restores the original working // directory. diff --git a/codecov.yml b/codecov.yml index a322199a5..09c6c92ec 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,4 +1,6 @@ --- +ignore: + - "sql/docker_interface.go" coverage: range: 85..100 round: down diff --git a/sql/docker_interface.go b/sql/docker_interface.go new file mode 100644 index 000000000..b5b8e6da8 --- /dev/null +++ b/sql/docker_interface.go @@ -0,0 +1,52 @@ +package sql + +import ( + "context" + "io" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + +type DockerBinder struct { + cli *client.Client +} + +type DockerBind interface { + ImageBuild(ctx context.Context, buildContext io.Reader, options *types.ImageBuildOptions) (types.ImageBuildResponse, error) + ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error) + ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error + ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error) + ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) +} + +func (d DockerBinder) ImageBuild(ctx context.Context, buildContext io.Reader, options *types.ImageBuildOptions) (types.ImageBuildResponse, error) { + return d.cli.ImageBuild(ctx, buildContext, *options) +} + +func (d DockerBinder) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { + return d.cli.ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, containerName) +} + +func (d DockerBinder) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error { + return d.cli.ContainerStart(ctx, containerID, options) +} + +func (d DockerBinder) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (body <-chan container.ContainerWaitOKBody, err <-chan error) { + return d.cli.ContainerWait(ctx, containerID, condition) +} + +func (d DockerBinder) ContainerLogs(ctx context.Context, containerID string, options types.ContainerLogsOptions) (io.ReadCloser, error) { + return d.cli.ContainerLogs(ctx, containerID, options) +} + +func NewDockerClient() (DockerBind, error) { + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return nil, err + } + return &DockerBinder{cli: cli}, nil +} diff --git a/sql/errors.go b/sql/errors.go index 5d3876536..2616e74ee 100644 --- a/sql/errors.go +++ b/sql/errors.go @@ -5,14 +5,7 @@ import ( "fmt" ) -var ( - errEnvVarNotSetError = errors.New("environment Variable not set") - errArgNotSetError = errors.New("argument not set") -) - -func EnvVarNotSetError(envVar string) error { - return fmt.Errorf("%w:%s", errEnvVarNotSetError, envVar) -} +var errArgNotSetError = errors.New("argument not set") func ArgNotSetError(argument string) error { return fmt.Errorf("%w:%s", errArgNotSetError, argument) diff --git a/sql/errors_test.go b/sql/errors_test.go new file mode 100644 index 000000000..79d0df4a5 --- /dev/null +++ b/sql/errors_test.go @@ -0,0 +1,13 @@ +package sql + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestArgNotSetError(t *testing.T) { + errorMessage := ArgNotSetError("sample_argument") + expectedErrorMesssage := "argument not set:sample_argument" + assert.EqualError(t, errorMessage, expectedErrorMesssage) +} diff --git a/sql/flow.go b/sql/flow.go index a4d634511..6aaec6301 100644 --- a/sql/flow.go +++ b/sql/flow.go @@ -10,10 +10,7 @@ import ( "github.com/astronomer/astro-cli/sql/include" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/client" "github.com/docker/docker/pkg/archive" - specs "github.com/opencontainers/image-spec/specs-go/v1" ) type MountVolume struct { @@ -28,45 +25,10 @@ const ( PythonVersion = "3.9" ) -type DockerBinder struct { - cli *client.Client -} - -type DockerBind interface { - ImageBuild(ctx context.Context, buildContext io.Reader, options *types.ImageBuildOptions) (types.ImageBuildResponse, error) - ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error) - ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error - ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error) - ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) -} - -func (d DockerBinder) ImageBuild(ctx context.Context, buildContext io.Reader, options *types.ImageBuildOptions) (types.ImageBuildResponse, error) { - return d.cli.ImageBuild(ctx, buildContext, *options) -} - -func (d DockerBinder) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { - return d.cli.ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, containerName) -} - -func (d DockerBinder) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error { - return d.cli.ContainerStart(ctx, containerID, options) -} - -func (d DockerBinder) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (body <-chan container.ContainerWaitOKBody, err <-chan error) { - return d.cli.ContainerWait(ctx, containerID, condition) -} - -func (d DockerBinder) ContainerLogs(ctx context.Context, containerID string, options types.ContainerLogsOptions) (io.ReadCloser, error) { - return d.cli.ContainerLogs(ctx, containerID, options) -} - -var newDockerClient = func() (DockerBind, error) { - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return nil, err - } - return &DockerBinder{cli: cli}, nil -} +var ( + DockerClientInit = NewDockerClient + ioCopy = io.Copy +) func getContext(filePath string) io.Reader { ctx, _ := archive.TarWithOptions(filePath, &archive.TarOptions{}) @@ -75,13 +37,13 @@ func getContext(filePath string) io.Reader { func CommonDockerUtil(cmd, args []string, flags map[string]string, mountDirs []string) error { ctx := context.Background() - cli, err := newDockerClient() + cli, err := DockerClientInit() if err != nil { err = fmt.Errorf("docker client initialization failed %w", err) return err } - astroSQLCliVersion, err := GetPypiVersion(astroSQLCliProjectURL) + astroSQLCliVersion, err := getPypiVersion(astroSQLCliProjectURL) if err != nil { return err } @@ -100,11 +62,11 @@ func CommonDockerUtil(cmd, args []string, flags map[string]string, mountDirs []s body, err := cli.ImageBuild(ctx, getContext(SQLCliDockerfilePath), &opts) if err != nil { - err = fmt.Errorf("image building failed %w ", err) + err = fmt.Errorf("image building failed %w", err) return err } buf := new(strings.Builder) - _, err = io.Copy(buf, body.Body) + _, err = ioCopy(buf, body.Body) if err != nil { err = fmt.Errorf("image build response read failed %w", err) return err @@ -142,7 +104,7 @@ func CommonDockerUtil(cmd, args []string, flags map[string]string, mountDirs []s select { case err := <-errCh: if err != nil { - err = fmt.Errorf("docker client run failed %w", err) + err = fmt.Errorf("docker container wait failed %w", err) return err } case <-statusCh: @@ -154,7 +116,7 @@ func CommonDockerUtil(cmd, args []string, flags map[string]string, mountDirs []s return err } - _, err = io.Copy(os.Stdout, cout) + _, err = ioCopy(os.Stdout, cout) if err != nil { err = fmt.Errorf("docker logs forwarding failed %w", err) diff --git a/sql/flow_test.go b/sql/flow_test.go new file mode 100644 index 000000000..ad8db9e2e --- /dev/null +++ b/sql/flow_test.go @@ -0,0 +1,188 @@ +package sql + +import ( + "errors" + "fmt" + "io" + "strings" + "testing" + + "github.com/astronomer/astro-cli/sql/mocks" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +var ( + testCommand = []string{"flow", "test"} + errMock = errors.New("mock error") + imageBuildResponse = types.ImageBuildResponse{ + Body: io.NopCloser(strings.NewReader("Image built")), + } + containerCreateCreatedBody = container.ContainerCreateCreatedBody{ID: "123"} + sampleLog = io.NopCloser(strings.NewReader("Sample log")) +) + +func getContainerWaitResponse(raiseError bool) (bodyCh <-chan container.ContainerWaitOKBody, errCh <-chan error) { + containerWaitOkBodyChannel := make(chan container.ContainerWaitOKBody) + errChannel := make(chan error, 1) + go func() { + if raiseError { + errChannel <- errMock + return + } + res := container.ContainerWaitOKBody{StatusCode: 200, Error: nil} + containerWaitOkBodyChannel <- res + errChannel <- nil + }() + // converting bidirectional channel to read only channels for ContainerWait to consume + var readOnlyStatusCh <-chan container.ContainerWaitOKBody + var readOnlyErrCh <-chan error + readOnlyStatusCh = containerWaitOkBodyChannel + readOnlyErrCh = errChannel + return readOnlyStatusCh, readOnlyErrCh +} + +func TestCommonDockerUtilSuccess(t *testing.T) { + mockDockerBinder := new(mocks.DockerBind) + DockerClientInit = func() (DockerBind, error) { + mockDockerBinder.On("ImageBuild", mock.Anything, mock.Anything, mock.Anything).Return(imageBuildResponse, nil) + mockDockerBinder.On("ContainerCreate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(containerCreateCreatedBody, nil) + mockDockerBinder.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(nil) + mockDockerBinder.On("ContainerWait", mock.Anything, mock.Anything, mock.Anything).Return(getContainerWaitResponse(false)) + mockDockerBinder.On("ContainerLogs", mock.Anything, mock.Anything, mock.Anything).Return(sampleLog, nil) + return mockDockerBinder, nil + } + err := CommonDockerUtil(testCommand, nil, map[string]string{"flag": "value"}, []string{"mountDirectory"}) + assert.NoError(t, err) + mockDockerBinder.AssertExpectations(t) +} + +func TestDockerClientInitFailure(t *testing.T) { + DockerClientInit = func() (DockerBind, error) { + return nil, errMock + } + err := CommonDockerUtil(testCommand, nil, map[string]string{"flag": "value"}, []string{"mountDirectory"}) + expectedErr := fmt.Errorf("docker client initialization failed %w", errMock) + assert.Equal(t, expectedErr, err) +} + +func TestGetPypiVersionFailure(t *testing.T) { + getPypiVersion = func(projectURL string) (string, error) { + return "", errMock + } + err := CommonDockerUtil(testCommand, nil, nil, nil) + assert.ErrorIs(t, err, errMock) + getPypiVersion = GetPypiVersion +} + +func TestImageBuildFailure(t *testing.T) { + mockDockerBinder := new(mocks.DockerBind) + DockerClientInit = func() (DockerBind, error) { + mockDockerBinder.On("ImageBuild", mock.Anything, mock.Anything, mock.Anything).Return(imageBuildResponse, errMock) + return mockDockerBinder, nil + } + err := CommonDockerUtil(testCommand, nil, nil, nil) + expectedErr := fmt.Errorf("image building failed %w", errMock) + assert.Equal(t, expectedErr, err) + mockDockerBinder.AssertExpectations(t) +} + +func TestImageBuildResponseReadFailure(t *testing.T) { + mockDockerBinder := new(mocks.DockerBind) + DockerClientInit = func() (DockerBind, error) { + mockDockerBinder.On("ImageBuild", mock.Anything, mock.Anything, mock.Anything).Return(imageBuildResponse, nil) + return mockDockerBinder, nil + } + ioCopy = func(dst io.Writer, src io.Reader) (written int64, err error) { + return 0, errMock + } + err := CommonDockerUtil(testCommand, nil, nil, nil) + expectedErr := fmt.Errorf("image build response read failed %w", errMock) + assert.Equal(t, expectedErr, err) + mockDockerBinder.AssertExpectations(t) + ioCopy = io.Copy +} + +func TestContainerCreateFailure(t *testing.T) { + mockDockerBinder := new(mocks.DockerBind) + DockerClientInit = func() (DockerBind, error) { + mockDockerBinder.On("ImageBuild", mock.Anything, mock.Anything, mock.Anything).Return(imageBuildResponse, nil) + mockDockerBinder.On("ContainerCreate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(containerCreateCreatedBody, errMock) + return mockDockerBinder, nil + } + err := CommonDockerUtil(testCommand, nil, nil, nil) + expectedErr := fmt.Errorf("docker container creation failed %w", errMock) + assert.Equal(t, expectedErr, err) + mockDockerBinder.AssertExpectations(t) +} + +func TestContainerStartFailure(t *testing.T) { + mockDockerBinder := new(mocks.DockerBind) + DockerClientInit = func() (DockerBind, error) { + mockDockerBinder.On("ImageBuild", mock.Anything, mock.Anything, mock.Anything).Return(imageBuildResponse, nil) + mockDockerBinder.On("ContainerCreate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(containerCreateCreatedBody, nil) + mockDockerBinder.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errMock) + return mockDockerBinder, nil + } + err := CommonDockerUtil(testCommand, nil, nil, nil) + expectedErr := fmt.Errorf("docker container start failed %w", errMock) + assert.Equal(t, expectedErr, err) + mockDockerBinder.AssertExpectations(t) +} + +func TestContainerWaitFailure(t *testing.T) { + mockDockerBinder := new(mocks.DockerBind) + DockerClientInit = func() (DockerBind, error) { + mockDockerBinder.On("ImageBuild", mock.Anything, mock.Anything, mock.Anything).Return(imageBuildResponse, nil) + mockDockerBinder.On("ContainerCreate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(containerCreateCreatedBody, nil) + mockDockerBinder.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(nil) + mockDockerBinder.On("ContainerWait", mock.Anything, mock.Anything, mock.Anything).Return(getContainerWaitResponse(true)) + return mockDockerBinder, nil + } + err := CommonDockerUtil(testCommand, nil, nil, nil) + expectedErr := fmt.Errorf("docker container wait failed %w", errMock) + assert.Equal(t, expectedErr, err) + mockDockerBinder.AssertExpectations(t) +} + +func TestContainerLogsFailure(t *testing.T) { + mockDockerBinder := new(mocks.DockerBind) + DockerClientInit = func() (DockerBind, error) { + mockDockerBinder.On("ImageBuild", mock.Anything, mock.Anything, mock.Anything).Return(imageBuildResponse, nil) + mockDockerBinder.On("ContainerCreate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(containerCreateCreatedBody, nil) + mockDockerBinder.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(nil) + mockDockerBinder.On("ContainerWait", mock.Anything, mock.Anything, mock.Anything).Return(getContainerWaitResponse(false)) + mockDockerBinder.On("ContainerLogs", mock.Anything, mock.Anything, mock.Anything).Return(sampleLog, errMock) + return mockDockerBinder, nil + } + err := CommonDockerUtil(testCommand, nil, nil, nil) + expectedErr := fmt.Errorf("docker container logs fetching failed %w", errMock) + assert.Equal(t, expectedErr, err) + mockDockerBinder.AssertExpectations(t) +} + +func TestCommonDockerUtilLogsCopyFailure(t *testing.T) { + mockDockerBinder := new(mocks.DockerBind) + DockerClientInit = func() (DockerBind, error) { + mockDockerBinder.On("ImageBuild", mock.Anything, mock.Anything, mock.Anything).Return(imageBuildResponse, nil) + mockDockerBinder.On("ContainerCreate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(containerCreateCreatedBody, nil) + mockDockerBinder.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(nil) + mockDockerBinder.On("ContainerWait", mock.Anything, mock.Anything, mock.Anything).Return(getContainerWaitResponse(false)) + mockDockerBinder.On("ContainerLogs", mock.Anything, mock.Anything, mock.Anything).Return(sampleLog, nil) + return mockDockerBinder, nil + } + ioCopyCallCounter := 1 + ioCopy = func(dst io.Writer, src io.Reader) (written int64, err error) { + if ioCopyCallCounter == 2 { + return 0, errMock + } + ioCopyCallCounter++ + return 0, nil + } + err := CommonDockerUtil(testCommand, nil, nil, nil) + expectedErr := fmt.Errorf("docker logs forwarding failed %w", errMock) + assert.Equal(t, expectedErr, err) + mockDockerBinder.AssertExpectations(t) +} diff --git a/sql/mocks/flow.go b/sql/mocks/flow.go new file mode 100644 index 000000000..c3a669538 --- /dev/null +++ b/sql/mocks/flow.go @@ -0,0 +1,143 @@ +// Code generated by mockery v2.14.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + container "github.com/docker/docker/api/types/container" + + io "io" + + mock "github.com/stretchr/testify/mock" + + network "github.com/docker/docker/api/types/network" + + types "github.com/docker/docker/api/types" + + v1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +// DockerBind is an autogenerated mock type for the DockerBind type +type DockerBind struct { + mock.Mock +} + +// ContainerCreate provides a mock function with given fields: ctx, config, hostConfig, networkingConfig, platform, containerName +func (_m *DockerBind) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *v1.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { + ret := _m.Called(ctx, config, hostConfig, networkingConfig, platform, containerName) + + var r0 container.ContainerCreateCreatedBody + if rf, ok := ret.Get(0).(func(context.Context, *container.Config, *container.HostConfig, *network.NetworkingConfig, *v1.Platform, string) container.ContainerCreateCreatedBody); ok { + r0 = rf(ctx, config, hostConfig, networkingConfig, platform, containerName) + } else { + r0 = ret.Get(0).(container.ContainerCreateCreatedBody) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *container.Config, *container.HostConfig, *network.NetworkingConfig, *v1.Platform, string) error); ok { + r1 = rf(ctx, config, hostConfig, networkingConfig, platform, containerName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ContainerLogs provides a mock function with given fields: ctx, _a1, options +func (_m *DockerBind) ContainerLogs(ctx context.Context, _a1 string, options types.ContainerLogsOptions) (io.ReadCloser, error) { + ret := _m.Called(ctx, _a1, options) + + var r0 io.ReadCloser + if rf, ok := ret.Get(0).(func(context.Context, string, types.ContainerLogsOptions) io.ReadCloser); ok { + r0 = rf(ctx, _a1, options) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(io.ReadCloser) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, types.ContainerLogsOptions) error); ok { + r1 = rf(ctx, _a1, options) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ContainerStart provides a mock function with given fields: ctx, containerID, options +func (_m *DockerBind) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error { + ret := _m.Called(ctx, containerID, options) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, types.ContainerStartOptions) error); ok { + r0 = rf(ctx, containerID, options) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ContainerWait provides a mock function with given fields: ctx, containerID, condition +func (_m *DockerBind) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error) { + ret := _m.Called(ctx, containerID, condition) + + var r0 <-chan container.ContainerWaitOKBody + if rf, ok := ret.Get(0).(func(context.Context, string, container.WaitCondition) <-chan container.ContainerWaitOKBody); ok { + r0 = rf(ctx, containerID, condition) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan container.ContainerWaitOKBody) + } + } + + var r1 <-chan error + if rf, ok := ret.Get(1).(func(context.Context, string, container.WaitCondition) <-chan error); ok { + r1 = rf(ctx, containerID, condition) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(<-chan error) + } + } + + return r0, r1 +} + +// ImageBuild provides a mock function with given fields: ctx, buildContext, options +func (_m *DockerBind) ImageBuild(ctx context.Context, buildContext io.Reader, options *types.ImageBuildOptions) (types.ImageBuildResponse, error) { + ret := _m.Called(ctx, buildContext, options) + + var r0 types.ImageBuildResponse + if rf, ok := ret.Get(0).(func(context.Context, io.Reader, *types.ImageBuildOptions) types.ImageBuildResponse); ok { + r0 = rf(ctx, buildContext, options) + } else { + r0 = ret.Get(0).(types.ImageBuildResponse) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, io.Reader, *types.ImageBuildOptions) error); ok { + r1 = rf(ctx, buildContext, options) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewDockerBind interface { + mock.TestingT + Cleanup(func()) +} + +// NewDockerBind creates a new instance of DockerBind. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewDockerBind(t mockConstructorTestingTNewDockerBind) *DockerBind { + mock := &DockerBind{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/sql/pypi_client.go b/sql/pypi_client.go index 30e35f78b..2565c9e61 100644 --- a/sql/pypi_client.go +++ b/sql/pypi_client.go @@ -14,6 +14,8 @@ type pypiVersionResponse struct { const astroSQLCliProjectURL = "https://pypi.org/pypi/astro-sql-cli/json" +var getPypiVersion = GetPypiVersion + func GetPypiVersion(projectURL string) (string, error) { httpClient := &http.Client{} req, err := http.NewRequest(http.MethodGet, projectURL, http.NoBody) diff --git a/sql/pypi_client_test.go b/sql/pypi_client_test.go new file mode 100644 index 000000000..cb1e67678 --- /dev/null +++ b/sql/pypi_client_test.go @@ -0,0 +1,13 @@ +package sql + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetPypiVersionInvalidHostFailure(t *testing.T) { + _, err := GetPypiVersion("http://abcd") + expectedErrContains := "error getting latest release version for project url http://abcd, Get \"http://abcd\": dial tcp: lookup abcd" + assert.ErrorContains(t, err, expectedErrContains) +}