642 lines
15 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|