2023-06-05 14:18:53 -07:00

642 lines
15 KiB
Go

package state
import (
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/wrapperspb"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk"
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
)
func TestTarget_Create(t *testing.T) {
t.Run("Requires name and project", func(t *testing.T) {
require, db := requireAndDB(t)
result := db.Save(&Target{})
require.Error(result.Error)
require.ErrorContains(result.Error, "Name:")
require.ErrorContains(result.Error, "Project:")
})
t.Run("Requires name", func(t *testing.T) {
require, db := requireAndDB(t)
result := db.Save(
&Target{
Project: testProject(t, db),
},
)
require.Error(result.Error)
require.ErrorContains(result.Error, "Name:")
})
t.Run("Requires project", func(t *testing.T) {
require, db := requireAndDB(t)
result := db.Save(
&Target{
Name: "default",
},
)
require.Error(result.Error)
require.ErrorContains(result.Error, "Project:")
})
t.Run("Sets resource ID", func(t *testing.T) {
require, db := requireAndDB(t)
target := Target{
Name: "default",
Project: testProject(t, db),
}
result := db.Save(&target)
require.NoError(result.Error)
require.NotEmpty(target.ResourceId)
})
t.Run("Retains resource ID", func(t *testing.T) {
require, db := requireAndDB(t)
rid := "RESOURCE_ID"
target := Target{
Name: "default",
ResourceId: rid,
Project: testProject(t, db),
}
result := db.Save(&target)
require.NoError(result.Error)
require.NotNil(target.ResourceId)
require.EqualValues(rid, target.ResourceId)
})
t.Run("Does not allow duplicate name in same project", func(t *testing.T) {
require, db := requireAndDB(t)
project := testProject(t, db)
result := db.Save(
&Target{
Name: "default",
Project: project,
},
)
require.NoError(result.Error)
result = db.Save(
&Target{
Name: "default",
Project: project,
},
)
require.Error(result.Error)
require.ErrorContains(result.Error, "Name:")
})
t.Run("Allows duplicate name in different projects", func(t *testing.T) {
require, db := requireAndDB(t)
result := db.Save(
&Target{
Name: "default",
Project: testProject(t, db),
},
)
require.NoError(result.Error)
result = db.Save(
&Target{
Name: "default",
Project: testProject(t, db),
},
)
require.NoError(result.Error)
})
t.Run("Does not allow duplicate resource IDs", func(t *testing.T) {
require, db := requireAndDB(t)
rid := "RESOURCE ID"
result := db.Save(
&Target{
Name: "default",
ResourceId: rid,
Project: testProject(t, db),
},
)
require.NoError(result.Error)
result = db.Save(
&Target{
Name: "other",
ResourceId: rid,
Project: testProject(t, db),
},
)
require.Error(result.Error)
require.ErrorContains(result.Error, "ResourceId:")
})
t.Run("Does not allow duplicate UUIDs", func(t *testing.T) {
require, db := requireAndDB(t)
uuid := "UUID VALUE"
result := db.Save(
&Target{
Name: "default",
Uuid: &uuid,
Project: testProject(t, db),
},
)
require.NoError(result.Error)
result = db.Save(
&Target{
Name: "other",
Uuid: &uuid,
Project: testProject(t, db),
},
)
require.Error(result.Error)
require.ErrorContains(result.Error, "Uuid:")
})
t.Run("Stores a record when set", func(t *testing.T) {
require, db := requireAndDB(t)
record := &vagrant_server.Target_Machine{
Id: "MACHINE_ID",
}
result := db.Save(
&Target{
Name: "default",
Project: testProject(t, db),
Record: &ProtoValue{Message: record},
},
)
require.NoError(result.Error)
var target Target
result = db.First(&target, &Target{Name: "default"})
require.NoError(result.Error)
require.Equal(record.Id, target.Record.Message.(*vagrant_server.Target_Machine).Id)
})
t.Run("Properly creates child targets", func(t *testing.T) {
require, db := requireAndDB(t)
project := testProject(t, db)
result := db.Save(
&Target{
Name: "parent",
Project: project,
Subtargets: []*Target{
{
Name: "subtarget1",
Project: project,
},
{
Name: "subtarget2",
Project: project,
},
{
Name: "subtarget3",
Project: project,
},
},
},
)
require.NoError(result.Error)
var target Target
result = db.Preload(clause.Associations).
First(&target, &Target{Name: "parent"})
require.NoError(result.Error)
require.Equal(3, len(target.Subtargets))
})
}
func TestTarget_Update(t *testing.T) {
t.Run("Requires name", func(t *testing.T) {
require, db := requireAndDB(t)
target := &Target{Name: "default", Project: testProject(t, db)}
result := db.Save(target)
require.NoError(result.Error)
target.Name = ""
result = db.Save(target)
require.Error(result.Error)
require.ErrorContains(result.Error, "Name:")
})
t.Run("Does not update resource ID", func(t *testing.T) {
require, db := requireAndDB(t)
target := Target{Name: "default", Project: testProject(t, db)}
result := db.Save(&target)
require.NoError(result.Error)
require.NotEmpty(target.ResourceId)
var reloadTarget Basis
result = db.First(&reloadTarget, &Target{Model: Model{ID: target.ID}})
require.NoError(result.Error)
reloadTarget.ResourceId = "NEW VALUE"
result = db.Save(&reloadTarget)
require.Error(result.Error)
require.ErrorContains(result.Error, "ResourceId:")
})
t.Run("Updates the state", func(t *testing.T) {
require, db := requireAndDB(t)
target := Target{
Name: "default",
Project: testProject(t, db),
State: vagrant_server.Operation_NOT_CREATED,
}
result := db.Save(&target)
require.NoError(result.Error)
require.Equal(vagrant_server.Operation_NOT_CREATED, target.State)
target.State = vagrant_server.Operation_UNKNOWN
result = db.Save(&target)
require.NoError(result.Error)
result = db.First(&target, &Target{Model: Model{ID: target.ID}})
require.NoError(result.Error)
require.Equal(vagrant_server.Operation_UNKNOWN, target.State)
})
t.Run("Adds subtarget", func(t *testing.T) {
require, db := requireAndDB(t)
project := testProject(t, db)
target := Target{
Name: "parent",
Project: project,
Subtargets: []*Target{
{
Name: "subtarget1",
Project: project,
},
},
}
result := db.Save(&target)
require.NoError(result.Error)
result = db.Preload(clause.Associations).First(&target, &Target{Name: "parent"})
require.NoError(result.Error)
require.Equal(1, len(target.Subtargets))
target.Subtargets = append(target.Subtargets, &Target{
Name: "subtarget2",
Project: project,
})
result = db.Save(&target)
require.NoError(result.Error)
result = db.Preload(clause.Associations).First(&target, &Target{Name: "parent"})
require.NoError(result.Error)
require.Equal(2, len(target.Subtargets))
})
t.Run("It fails to add subtarget with different project", func(t *testing.T) {
require, db := requireAndDB(t)
target := Target{
Name: "parent",
Project: testProject(t, db),
}
result := db.Save(&target)
require.NoError(result.Error)
result = db.First(&target, &Target{Name: "parent"})
require.NoError(result.Error)
target.Subtargets = append(target.Subtargets, &Target{
Name: "subtarget",
Project: testProject(t, db),
})
result = db.Save(&target)
require.Error(result.Error)
})
}
func TestTarget_Delete(t *testing.T) {
t.Run("Deletes target", func(t *testing.T) {
require, db := requireAndDB(t)
result := db.Save(&Target{Name: "default", Project: testProject(t, db)})
require.NoError(result.Error)
var target Target
result = db.First(&target, &Target{Name: "default"})
require.NoError(result.Error)
result = db.Where(&Target{ResourceId: target.ResourceId}).
Delete(&Target{})
require.NoError(result.Error)
result = db.First(&Target{}, &Target{ResourceId: target.ResourceId})
require.Error(result.Error)
require.ErrorIs(result.Error, gorm.ErrRecordNotFound)
})
t.Run("Deletes subtargets", func(t *testing.T) {
require, db := requireAndDB(t)
project := testProject(t, db)
result := db.Save(
&Target{
Name: "parent",
Project: project,
Subtargets: []*Target{
{
Name: "subtarget1",
Project: project,
},
{
Name: "subtarget2",
Project: project,
},
},
},
)
require.NoError(result.Error)
var count int64
result = db.Model(&Target{}).Count(&count)
require.NoError(result.Error)
require.Equal(int64(3), count)
result = db.Where(&Target{Name: "parent"}).
Delete(&Target{})
require.NoError(result.Error)
result = db.Model(&Target{}).Count(&count)
require.NoError(result.Error)
require.Equal(int64(0), count)
})
}
func TestTarget_State(t *testing.T) {
t.Run("Get returns not found error if not exist", func(t *testing.T) {
require := require.New(t)
s := TestState(t)
defer s.Close()
_, err := s.TargetGet(&vagrant_plugin_sdk.Ref_Target{
ResourceId: "foo",
})
require.Error(err)
require.Equal(codes.NotFound, status.Code(err))
})
t.Run("Simple update", func(t *testing.T) {
require := require.New(t)
s := TestState(t)
defer s.Close()
resp, err := s.TargetPut(&vagrant_server.Target{
Name: "default",
Project: testProject(t, s.db).ToProtoRef(),
State: vagrant_server.Operation_NOT_CREATED,
})
require.NoError(err)
require.Equal(vagrant_server.Operation_NOT_CREATED, resp.State)
target, err := s.TargetGet(&vagrant_plugin_sdk.Ref_Target{
ResourceId: resp.ResourceId,
})
require.NoError(err)
require.Equal(vagrant_server.Operation_NOT_CREATED, target.State)
target.State = vagrant_server.Operation_UNKNOWN
resp, err = s.TargetPut(target)
require.NoError(err)
require.Equal(vagrant_server.Operation_UNKNOWN, resp.State)
target, err = s.TargetGet(&vagrant_plugin_sdk.Ref_Target{
ResourceId: resp.ResourceId,
})
require.NoError(err)
require.Equal(vagrant_server.Operation_UNKNOWN, target.State)
})
t.Run("Put and Get", func(t *testing.T) {
require := require.New(t)
s := TestState(t)
defer s.Close()
projectRef := testProjectProto(t, s)
// Set
result, err := s.TargetPut(&vagrant_server.Target{
Project: projectRef,
Name: "test",
})
require.NoError(err)
// Ensure there is one entry
resp, err := s.TargetList()
require.NoError(err)
require.Len(resp, 1)
// Try to insert duplicate entry
doubleResult, err := s.TargetPut(&vagrant_server.Target{
ResourceId: result.ResourceId,
Project: projectRef,
Name: "test",
})
require.NoError(err)
require.Equal(doubleResult.ResourceId, result.ResourceId)
require.Equal(doubleResult.Project, result.Project)
// Ensure there is still one entry
resp, err = s.TargetList()
require.NoError(err)
require.Len(resp, 1)
// Try to insert duplicate entry by just name and project
_, err = s.TargetPut(&vagrant_server.Target{
Project: projectRef,
Name: "test",
})
require.NoError(err)
// Ensure there is still one entry
resp, err = s.TargetList()
require.NoError(err)
require.Len(resp, 1)
// Try to insert duplicate config
key, _ := anypb.New(&wrapperspb.StringValue{Value: "vm"})
value, _ := anypb.New(&wrapperspb.StringValue{Value: "value"})
_, err = s.TargetPut(&vagrant_server.Target{
ResourceId: result.ResourceId,
Configuration: &vagrant_plugin_sdk.Args_ConfigData{
Data: &vagrant_plugin_sdk.Args_Hash{
Entries: []*vagrant_plugin_sdk.Args_HashEntry{
{
Key: key,
Value: value,
},
},
},
},
})
require.NoError(err)
_, err = s.TargetPut(&vagrant_server.Target{
ResourceId: result.ResourceId,
Configuration: &vagrant_plugin_sdk.Args_ConfigData{
Data: &vagrant_plugin_sdk.Args_Hash{
Entries: []*vagrant_plugin_sdk.Args_HashEntry{
{
Key: key,
Value: value,
},
},
},
},
})
require.NoError(err)
// Ensure there is still one entry
resp, err = s.TargetList()
require.NoError(err)
require.Len(resp, 1)
// Ensure the config did not merge
targetResp, err := s.TargetGet(&vagrant_plugin_sdk.Ref_Target{
ResourceId: result.ResourceId,
})
require.NoError(err)
require.NotNil(targetResp.Configuration)
require.NotNil(targetResp.Configuration.Data)
require.Len(targetResp.Configuration.Data.Entries, 1)
vmAny := targetResp.Configuration.Data.Entries[0].Value
vmString := wrapperspb.StringValue{}
_ = vmAny.UnmarshalTo(&vmString)
require.Equal(vmString.Value, "value")
// Get exact
{
resp, err := s.TargetGet(&vagrant_plugin_sdk.Ref_Target{
ResourceId: result.ResourceId,
})
require.NoError(err)
require.NotNil(resp)
require.Equal(resp.ResourceId, result.ResourceId)
}
// List
{
resp, err := s.TargetList()
require.NoError(err)
require.Len(resp, 1)
}
})
t.Run("Delete", func(t *testing.T) {
require := require.New(t)
s := TestState(t)
defer s.Close()
projectRef := testProjectProto(t, s)
// Set
result, err := s.TargetPut(&vagrant_server.Target{
Project: projectRef,
Name: "test",
})
require.NoError(err)
// Read
resp, err := s.TargetGet(&vagrant_plugin_sdk.Ref_Target{
ResourceId: result.ResourceId,
})
require.NoError(err)
require.NotNil(resp)
// Delete
{
err := s.TargetDelete(&vagrant_plugin_sdk.Ref_Target{
ResourceId: result.ResourceId,
Project: projectRef,
})
require.NoError(err)
}
// Read
{
_, err := s.TargetGet(&vagrant_plugin_sdk.Ref_Target{
ResourceId: result.ResourceId,
})
require.Error(err)
require.Equal(codes.NotFound, status.Code(err))
}
// List
{
resp, err := s.TargetList()
require.NoError(err)
require.Len(resp, 0)
}
})
t.Run("Find", func(t *testing.T) {
require := require.New(t)
s := TestState(t)
defer s.Close()
projectRef := testProjectProto(t, s)
// Set
result, err := s.TargetPut(&vagrant_server.Target{
Project: projectRef,
Name: "test",
})
require.NoError(err)
// Find by resource id
{
resp, err := s.TargetFind(&vagrant_server.Target{
ResourceId: result.ResourceId,
})
require.NoError(err)
require.NotNil(resp)
require.Equal(resp.ResourceId, result.ResourceId)
}
// Find by resource name without project
{
resp, err := s.TargetFind(&vagrant_server.Target{
Name: "test",
})
require.Error(err)
require.Nil(resp)
}
// Find by resource name+project
{
resp, err := s.TargetFind(&vagrant_server.Target{
Name: "test", Project: projectRef,
})
require.NoError(err)
require.NotNil(resp)
require.Equal(resp.ResourceId, result.ResourceId)
}
// Don't find nonexistent project
{
resp, err := s.TargetFind(&vagrant_server.Target{
Name: "test", Project: &vagrant_plugin_sdk.Ref_Project{ResourceId: "idontexist"},
})
require.Nil(resp)
require.Error(err)
}
// Don't find just by project
{
resp, err := s.TargetFind(&vagrant_server.Target{
Project: projectRef,
})
require.Error(err)
require.Nil(resp)
}
})
}