Skip to content

Commit

Permalink
Sql cli 0.1 tests (#849)
Browse files Browse the repository at this point in the history
* Draft

* Add mocks generated by mockery

* Add tests for common docker util

* Remove comments to pass the lint errors

* Add back test for ContainerWait

* Cast return values

* Add tests for error scenarios

* Separate out docker interface and add file to codecov ignore

* Use error object for testing

* Pass dummy flag and mount directory

* Add test for error

* Add pypi client invalid host failure test

* Check error contains substring

* Add more tests for error returns

* Fix test runs

* Address @neel-astro's comments
  • Loading branch information
pankajkoti authored and neel-astro committed Nov 4, 2022
1 parent d5ba8ec commit 581cf8b
Show file tree
Hide file tree
Showing 11 changed files with 481 additions and 57 deletions.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
53 changes: 53 additions & 0 deletions cmd/sql/flow_test.go
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 2 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
---
ignore:
- "sql/docker_interface.go"
coverage:
range: 85..100
round: down
Expand Down
52 changes: 52 additions & 0 deletions sql/docker_interface.go
Original file line number Diff line number Diff line change
@@ -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
}
9 changes: 1 addition & 8 deletions sql/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions sql/errors_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
58 changes: 10 additions & 48 deletions sql/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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{})
Expand All @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down
Loading

0 comments on commit 581cf8b

Please sign in to comment.