package container import ( "context" "fmt" "io" "io/ioutil" "net" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" ) type mockConn struct { WData []byte } func (mc mockConn) Read(b []byte) (n int, err error) { return len(b), nil } func (mc mockConn) Write(b []byte) (n int, err error) { copy(mc.WData, b) return len(b), nil } func (mc mockConn) Close() error { return nil } func (mc mockConn) LocalAddr() net.Addr { return nil } func (mc mockConn) RemoteAddr() net.Addr { return nil } func (mc mockConn) SetDeadline(t time.Time) error { return nil } func (mc mockConn) SetReadDeadline(t time.Time) error { return nil } func (mc mockConn) SetWriteDeadline(t time.Time) error { return nil } type mockDockerClient struct { imageInspectWithRaw func() (types.ImageInspect, []byte, error) imageList func() ([]types.ImageSummary, error) containerAttach func() (types.HijackedResponse, error) imagePull func() (io.ReadCloser, error) containerStart func() error containerWait func() (<-chan container.ContainerWaitOKBody, <-chan error) containerLogs func() (io.ReadCloser, error) } func (mdc *mockDockerClient) ImageInspectWithRaw(context.Context, string) (types.ImageInspect, []byte, error) { return mdc.imageInspectWithRaw() } func (mdc *mockDockerClient) ImageList(context.Context, types.ImageListOptions) ([]types.ImageSummary, error) { return mdc.imageList() } func (mdc *mockDockerClient) ImagePull( context.Context, string, types.ImagePullOptions, ) (io.ReadCloser, error) { return mdc.imagePull() } func (mdc *mockDockerClient) ContainerCreate( context.Context, *container.Config, *container.HostConfig, *network.NetworkingConfig, string, ) (container.ContainerCreateCreatedBody, error) { return container.ContainerCreateCreatedBody{ID: "testID"}, nil } func (mdc *mockDockerClient) ContainerAttach( context.Context, string, types.ContainerAttachOptions, ) (types.HijackedResponse, error) { return mdc.containerAttach() } func (mdc *mockDockerClient) ContainerStart(context.Context, string, types.ContainerStartOptions) error { if mdc.containerStart != nil { return mdc.containerStart() } return nil } func (mdc *mockDockerClient) ContainerWait( context.Context, string, container.WaitCondition, ) (<-chan container.ContainerWaitOKBody, <-chan error) { if mdc.containerWait != nil { return mdc.containerWait() } resC := make(chan container.ContainerWaitOKBody) go func() { resC <- container.ContainerWaitOKBody{StatusCode: 0} }() return resC, nil } func (mdc *mockDockerClient) ContainerLogs(context.Context, string, types.ContainerLogsOptions) (io.ReadCloser, error) { if mdc.containerLogs != nil { return mdc.containerLogs() } return ioutil.NopCloser(strings.NewReader("")), nil } func (mdc *mockDockerClient) ContainerRemove(context.Context, string, types.ContainerRemoveOptions) error { return nil } func getDockerContainerMock(mdc mockDockerClient) *DockerContainer { ctx := context.Background() cnt := &DockerContainer{ dockerClient: &mdc, ctx: &ctx, } return cnt } func TestGetCmd(t *testing.T) { testError := fmt.Errorf("inspect error") tests := []struct { cmd []string mockDockerClient mockDockerClient expectedResult []string expectedErr error }{ { cmd: []string{"testCmd"}, mockDockerClient: mockDockerClient{}, expectedResult: []string{"testCmd"}, expectedErr: nil, }, { cmd: []string{}, mockDockerClient: mockDockerClient{ imageList: func() ([]types.ImageSummary, error) { return []types.ImageSummary{{ID: "imgid"}}, nil }, imageInspectWithRaw: func() (types.ImageInspect, []byte, error) { return types.ImageInspect{ Config: &container.Config{ Cmd: []string{"testCmd"}, }, }, nil, nil }, }, expectedResult: []string{"testCmd"}, expectedErr: nil, }, { cmd: []string{}, mockDockerClient: mockDockerClient{ imageList: func() ([]types.ImageSummary, error) { return []types.ImageSummary{{ID: "imgid"}}, nil }, imageInspectWithRaw: func() (types.ImageInspect, []byte, error) { return types.ImageInspect{}, nil, testError }, }, expectedResult: nil, expectedErr: testError, }, } for _, tt := range tests { cnt := getDockerContainerMock(tt.mockDockerClient) actualRes, actualErr := cnt.getCmd(tt.cmd) assert.Equal(t, tt.expectedErr, actualErr) assert.Equal(t, tt.expectedResult, actualRes) } } func TestGetImageId(t *testing.T) { testError := fmt.Errorf("img list error") tests := []struct { url string mockDockerClient mockDockerClient expectedResult string expectedErr error }{ { url: "test:latest", mockDockerClient: mockDockerClient{ imageList: func() ([]types.ImageSummary, error) { return nil, testError }, }, expectedResult: "", expectedErr: testError, }, { url: "test:latest", mockDockerClient: mockDockerClient{ imageList: func() ([]types.ImageSummary, error) { return []types.ImageSummary{}, nil }, }, expectedResult: "", expectedErr: ErrEmptyImageList{}, }, } for _, tt := range tests { cnt := getDockerContainerMock(tt.mockDockerClient) actualRes, actualErr := cnt.getImageId(tt.url) assert.Equal(t, tt.expectedErr, actualErr) assert.Equal(t, tt.expectedResult, actualRes) } } func TestImagePull(t *testing.T) { testError := fmt.Errorf("image pull rror") tests := []struct { mockDockerClient mockDockerClient expectedErr error }{ { mockDockerClient: mockDockerClient{ imagePull: func() (io.ReadCloser, error) { return ioutil.NopCloser(strings.NewReader("test")), nil }, }, expectedErr: nil, }, { mockDockerClient: mockDockerClient{ imagePull: func() (io.ReadCloser, error) { return nil, testError }, }, expectedErr: testError, }, } for _, tt := range tests { cnt := getDockerContainerMock(tt.mockDockerClient) actualErr := cnt.ImagePull() assert.Equal(t, tt.expectedErr, actualErr) } } func TestGetId(t *testing.T) { cnt := getDockerContainerMock(mockDockerClient{}) err := cnt.RunCommand([]string{"testCmd"}, nil, nil, []string{}, false) require.NoError(t, err) actualId := cnt.GetId() assert.Equal(t, "testID", actualId) } func TestRunCommand(t *testing.T) { imageListError := fmt.Errorf("image list error") attachError := fmt.Errorf("attach error") containerStartError := fmt.Errorf("container start error") containerWaitError := fmt.Errorf("container wait error") tests := []struct { cmd []string containerInput io.Reader volumeMounts []string debug bool mockDockerClient mockDockerClient expectedErr error assertF func(*testing.T) }{ { cmd: []string{"testCmd"}, containerInput: nil, volumeMounts: nil, debug: false, mockDockerClient: mockDockerClient{}, expectedErr: nil, assertF: func(t *testing.T) {}, }, { cmd: []string{}, containerInput: nil, volumeMounts: nil, debug: false, mockDockerClient: mockDockerClient{ imageList: func() ([]types.ImageSummary, error) { return nil, imageListError }, }, expectedErr: imageListError, assertF: func(t *testing.T) {}, }, { cmd: []string{"testCmd"}, containerInput: strings.NewReader("testInput"), volumeMounts: nil, debug: false, mockDockerClient: mockDockerClient{ containerAttach: func() (types.HijackedResponse, error) { conn := types.HijackedResponse{ Conn: mockConn{WData: make([]byte, len([]byte("testInput")))}, } return conn, nil }, }, expectedErr: nil, assertF: func(t *testing.T) {}, }, { cmd: []string{"testCmd"}, containerInput: strings.NewReader("testInput"), volumeMounts: nil, debug: false, mockDockerClient: mockDockerClient{ containerAttach: func() (types.HijackedResponse, error) { return types.HijackedResponse{}, attachError }, }, expectedErr: attachError, assertF: func(t *testing.T) {}, }, { cmd: []string{"testCmd"}, containerInput: nil, volumeMounts: nil, debug: false, mockDockerClient: mockDockerClient{ containerStart: func() error { return containerStartError }, }, expectedErr: containerStartError, assertF: func(t *testing.T) {}, }, { cmd: []string{"testCmd"}, containerInput: nil, volumeMounts: nil, debug: false, mockDockerClient: mockDockerClient{ containerWait: func() (<-chan container.ContainerWaitOKBody, <-chan error) { errC := make(chan error) go func() { errC <- containerWaitError }() return nil, errC }, }, expectedErr: containerWaitError, assertF: func(t *testing.T) {}, }, { cmd: []string{"testCmd"}, containerInput: nil, volumeMounts: nil, debug: false, mockDockerClient: mockDockerClient{ containerWait: func() (<-chan container.ContainerWaitOKBody, <-chan error) { resC := make(chan container.ContainerWaitOKBody) go func() { resC <- container.ContainerWaitOKBody{StatusCode: 1} }() return resC, nil }, }, expectedErr: ErrRunContainerCommand{Cmd: "docker logs testID"}, assertF: func(t *testing.T) {}, }, } for _, tt := range tests { cnt := getDockerContainerMock(tt.mockDockerClient) actualErr := cnt.RunCommand(tt.cmd, tt.containerInput, tt.volumeMounts, []string{}, tt.debug) assert.Equal(t, tt.expectedErr, actualErr) tt.assertF(t) } } func TestRunCommandOutput(t *testing.T) { testError := fmt.Errorf("img list error") tests := []struct { cmd []string containerInput io.Reader volumeMounts []string mockDockerClient mockDockerClient expectedResult string expectedErr error }{ { cmd: []string{"testCmd"}, containerInput: nil, volumeMounts: nil, mockDockerClient: mockDockerClient{}, expectedResult: "", expectedErr: nil, }, { cmd: []string{}, containerInput: nil, volumeMounts: nil, mockDockerClient: mockDockerClient{ imageList: func() ([]types.ImageSummary, error) { return nil, testError }, }, expectedResult: "", expectedErr: testError, }, } for _, tt := range tests { cnt := getDockerContainerMock(tt.mockDockerClient) actualRes, actualErr := cnt.RunCommandOutput(tt.cmd, tt.containerInput, tt.volumeMounts, []string{}) assert.Equal(t, tt.expectedErr, actualErr) var actualResBytes []byte if actualRes != nil { var err error actualResBytes, err = ioutil.ReadAll(actualRes) require.NoError(t, err) } else { actualResBytes = []byte{} } assert.Equal(t, tt.expectedResult, string(actualResBytes)) } } func TestNewDockerContainer(t *testing.T) { testError := fmt.Errorf("image pull error") type resultStruct struct { tag string imageUrl string id string } tests := []struct { url string ctx context.Context cli mockDockerClient expectedErr error expectedResult resultStruct }{ { url: "testPrefix/testImage:testTag", ctx: context.Background(), cli: mockDockerClient{ imagePull: func() (io.ReadCloser, error) { return ioutil.NopCloser(strings.NewReader("test")), nil }, }, expectedErr: nil, expectedResult: resultStruct{ tag: "testTag", imageUrl: "testPrefix/testImage:testTag", id: "", }, }, { url: "testPrefix/testImage:testTag", ctx: context.Background(), cli: mockDockerClient{ imagePull: func() (io.ReadCloser, error) { return nil, testError }, }, expectedErr: testError, expectedResult: resultStruct{}, }, } for _, tt := range tests { actualRes, actualErr := NewDockerContainer(&(tt.ctx), tt.url, &(tt.cli)) assert.Equal(t, tt.expectedErr, actualErr) var actualResStruct resultStruct if actualRes == nil { actualResStruct = resultStruct{} } else { actualResStruct = resultStruct{ tag: actualRes.tag, imageUrl: actualRes.imageUrl, id: actualRes.id, } } assert.Equal(t, tt.expectedResult, actualResStruct) } } func TestRmContainer(t *testing.T) { tests := []struct { mockDockerClient mockDockerClient expectedErr error }{ { mockDockerClient: mockDockerClient{}, expectedErr: nil, }, } for _, tt := range tests { cnt := getDockerContainerMock(tt.mockDockerClient) actualErr := cnt.RmContainer() assert.Equal(t, tt.expectedErr, actualErr) } }