resource/docker/volume/volume_test.go
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !solaris
package volume_test
import (
"errors"
"testing"
"github.com/asteris-llc/converge/helpers/comparison"
"github.com/asteris-llc/converge/helpers/fakerenderer"
"github.com/asteris-llc/converge/helpers/logging"
"github.com/asteris-llc/converge/resource"
"github.com/asteris-llc/converge/resource/docker/volume"
dc "github.com/fsouza/go-dockerclient"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
// TestVolumeInterface verifies that Volume implements the resource.Task
// interface
func TestVolumeInterface(t *testing.T) {
t.Parallel()
defer logging.HideLogs(t)()
assert.Implements(t, (*resource.Task)(nil), new(volume.Volume))
}
// TestVolumeCheck tests the Volume.Check function
func TestVolumeCheck(t *testing.T) {
t.Parallel()
defer logging.HideLogs(t)()
t.Run("state: absent", func(t *testing.T) {
t.Run("volume does not exist", func(t *testing.T) {
vol := &volume.Volume{Name: "test-volume", State: "absent"}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").Return(nil, nil)
status, err := vol.Check(context.Background(), fakerenderer.New())
assert.Nil(t, err)
assert.False(t, status.HasChanges())
assert.Equal(t, 1, len(status.Diffs()))
comparison.AssertDiff(t, status.Diffs(), "test-volume", "absent", "absent")
})
t.Run("volume exists", func(t *testing.T) {
vol := &volume.Volume{Name: "test-volume", State: "absent"}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").Return(&dc.Volume{Name: "test-volume"}, nil)
status, err := vol.Check(context.Background(), fakerenderer.New())
assert.Nil(t, err)
assert.True(t, status.HasChanges())
assert.Equal(t, 1, len(status.Diffs()))
comparison.AssertDiff(t, status.Diffs(), "test-volume", "present", "absent")
})
})
t.Run("state: present", func(t *testing.T) {
t.Run("volume does not exist", func(t *testing.T) {
vol := &volume.Volume{
Name: "test-volume",
State: "present",
}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").Return(nil, nil)
status, err := vol.Check(context.Background(), fakerenderer.New())
assert.Nil(t, err)
assert.True(t, status.HasChanges())
assert.Equal(t, 1, len(status.Diffs()))
comparison.AssertDiff(t, status.Diffs(), "test-volume", "absent", "present")
})
t.Run("volume exists", func(t *testing.T) {
vol := &volume.Volume{
Name: "test-volume",
State: "present",
}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").Return(&dc.Volume{Name: "test-volume"}, nil)
status, err := vol.Check(context.Background(), fakerenderer.New())
assert.Nil(t, err)
assert.False(t, status.HasChanges())
assert.Equal(t, 1, len(status.Diffs()))
comparison.AssertDiff(t, status.Diffs(), "test-volume", "present", "present")
})
t.Run("volume exists, force: true", func(t *testing.T) {
t.Run("labels", func(t *testing.T) {
vol := &volume.Volume{
Name: "test-volume",
State: "present",
Labels: map[string]string{"key": "val", "test": "val2"},
Force: true,
}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").Return(&dc.Volume{Name: "test-volume"}, nil)
status, err := vol.Check(context.Background(), fakerenderer.New())
assert.Nil(t, err)
assert.True(t, status.HasChanges())
assert.True(t, len(status.Diffs()) > 1)
comparison.AssertDiff(t, status.Diffs(), "test-volume", "present", "present")
comparison.AssertDiff(t, status.Diffs(), "labels", "", "key=val, test=val2")
})
t.Run("driver", func(t *testing.T) {
vol := &volume.Volume{
Name: "test-volume",
State: "present",
Driver: "flocker",
Force: true,
}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").
Return(&dc.Volume{Name: "test-volume", Driver: "local"}, nil)
vol.SetClient(c)
status, err := vol.Check(context.Background(), fakerenderer.New())
assert.Nil(t, err)
assert.True(t, status.HasChanges())
assert.True(t, len(status.Diffs()) > 1)
comparison.AssertDiff(t, status.Diffs(), "test-volume", "present", "present")
comparison.AssertDiff(t, status.Diffs(), "driver", "local", "flocker")
})
})
})
t.Run("docker api error", func(t *testing.T) {
vol := &volume.Volume{Name: "test-volume", State: "present"}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").Return(nil, errors.New("error"))
status, err := vol.Check(context.Background(), fakerenderer.New())
require.Error(t, err)
assert.Equal(t, resource.StatusFatal, status.StatusCode())
})
}
// TestVolumeApply tests the Volume.Apply function
func TestVolumeApply(t *testing.T) {
t.Parallel()
defer logging.HideLogs(t)()
t.Run("docker find volume error", func(t *testing.T) {
vol := &volume.Volume{Name: "test-volume"}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").Return(nil, errors.New("error"))
status, err := vol.Apply(context.Background())
require.Error(t, err)
assert.Equal(t, resource.StatusFatal, status.StatusCode())
})
t.Run("state: absent", func(t *testing.T) {
t.Run("volume exists", func(t *testing.T) {
vol := &volume.Volume{Name: "test-volume", State: "absent"}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").Return(&dc.Volume{Name: "test-volume"}, nil)
c.On("RemoveVolume", mock.AnythingOfType("string")).Return(nil)
status, err := vol.Apply(context.Background())
require.NoError(t, err)
assert.True(t, status.HasChanges())
c.AssertCalled(t, "RemoveVolume", "test-volume")
})
t.Run("volume does not exist", func(t *testing.T) {
vol := &volume.Volume{Name: "test-volume", State: "absent"}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").Return(nil, nil)
status, err := vol.Apply(context.Background())
require.NoError(t, err)
assert.False(t, status.HasChanges())
c.AssertNotCalled(t, "RemoveVolume", "test-volume")
})
t.Run("docker remove volume error", func(t *testing.T) {
vol := &volume.Volume{Name: "test-volume", State: "absent"}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").Return(&dc.Volume{Name: "test-volume"}, nil)
c.On("RemoveVolume", mock.AnythingOfType("string")).Return(errors.New("test-volume"))
status, err := vol.Apply(context.Background())
require.Error(t, err)
assert.Equal(t, resource.StatusFatal, status.StatusCode())
})
})
t.Run("state: present", func(t *testing.T) {
t.Run("volume does not exist", func(t *testing.T) {
vol := &volume.Volume{Name: "test-volume", State: "present"}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").Return(nil, nil)
c.On("CreateVolume", mock.AnythingOfType("docker.CreateVolumeOptions")).
Return(&dc.Volume{Name: "test-volume"}, nil)
status, err := vol.Apply(context.Background())
require.NoError(t, err)
assert.True(t, status.HasChanges())
c.AssertCalled(t, "CreateVolume", mock.AnythingOfType("docker.CreateVolumeOptions"))
c.AssertNotCalled(t, "RemoveVolume", "test-volume")
})
t.Run("volume exists, force: false", func(t *testing.T) {
vol := &volume.Volume{Name: "test-volume", State: "present"}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").Return(&dc.Volume{Name: "test-volume"}, nil)
status, err := vol.Apply(context.Background())
require.NoError(t, err)
assert.False(t, status.HasChanges())
c.AssertNotCalled(t, "CreateVolume", mock.AnythingOfType("docker.CreateVolumeOptions"))
c.AssertNotCalled(t, "RemoveVolume", "test-volume")
})
t.Run("volume exists, force: true", func(t *testing.T) {
vol := &volume.Volume{Name: "test-volume", State: "present", Force: true}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").
Return(&dc.Volume{Name: "test-volume", Driver: "flocker"}, nil)
c.On("RemoveVolume", mock.AnythingOfType("string")).Return(nil)
c.On("CreateVolume", mock.AnythingOfType("docker.CreateVolumeOptions")).
Return(&dc.Volume{Name: "test-volume"}, nil)
status, err := vol.Apply(context.Background())
require.NoError(t, err)
assert.True(t, status.HasChanges())
c.AssertCalled(t, "RemoveVolume", "test-volume")
c.AssertCalled(t, "CreateVolume", mock.AnythingOfType("docker.CreateVolumeOptions"))
})
t.Run("docker create volume error", func(t *testing.T) {
vol := &volume.Volume{Name: "test-volume", State: "present"}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").Return(nil, nil)
c.On("CreateVolume", mock.AnythingOfType("docker.CreateVolumeOptions")).
Return(nil, errors.New("error"))
status, err := vol.Apply(context.Background())
require.Error(t, err)
assert.Equal(t, resource.StatusFatal, status.StatusCode())
})
t.Run("docker remove volume error", func(t *testing.T) {
vol := &volume.Volume{Name: "test-volume", State: "present", Force: true}
c := &mockClient{}
vol.SetClient(c)
c.On("FindVolume", "test-volume").
Return(&dc.Volume{Name: "test-volume", Driver: "flocker"}, nil)
c.On("RemoveVolume", mock.AnythingOfType("string")).Return(errors.New("error"))
status, err := vol.Apply(context.Background())
require.Error(t, err)
assert.Equal(t, resource.StatusFatal, status.StatusCode())
})
})
}
type mockClient struct {
mock.Mock
}
func (m *mockClient) FindVolume(name string) (*dc.Volume, error) {
args := m.Called(name)
ret := args.Get(0)
if ret == nil {
return nil, args.Error(1)
}
return ret.(*dc.Volume), args.Error(1)
}
func (m *mockClient) CreateVolume(opts dc.CreateVolumeOptions) (*dc.Volume, error) {
args := m.Called(opts)
ret := args.Get(0)
if ret == nil {
return nil, args.Error(1)
}
return ret.(*dc.Volume), args.Error(1)
}
func (m *mockClient) RemoveVolume(name string) error {
args := m.Called(name)
return args.Error(0)
}