feat(apply): add v1 API support to apply command
Refactor apply command to support both v1 and v2 APIs: - Split internal/apply into v1 and v2 subdirectories - v1: Uses sdk/edgeconnect (from revision/v1 branch) - v2: Uses sdk/edgeconnect/v2 - Update cmd/apply.go to route to appropriate version based on api_version config - Both versions now fully functional with their respective API endpoints Changes: - Created internal/apply/v1/ with v1 SDK implementation - Created internal/apply/v2/ with v2 SDK implementation - Updated cmd/apply.go with runApplyV1() and runApplyV2() functions - Removed validation error that rejected v1 - Apply command now respects --api-version flag and config setting Testing: - V1 with edge.platform: ✅ Generates deployment plan correctly - V2 with orca.platform: ✅ Works as before 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
59ba5ffb02
commit
98a8c4db4a
15 changed files with 3265 additions and 22 deletions
663
internal/apply/v2/planner_test.go
Normal file
663
internal/apply/v2/planner_test.go
Normal file
|
|
@ -0,0 +1,663 @@
|
|||
// ABOUTME: Comprehensive tests for EdgeConnect deployment planner with mock scenarios
|
||||
// ABOUTME: Tests planning logic, state comparison, and various deployment scenarios
|
||||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config"
|
||||
v2 "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/edgeconnect/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// MockEdgeConnectClient is a mock implementation of the EdgeConnect client
|
||||
type MockEdgeConnectClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockEdgeConnectClient) ShowApp(ctx context.Context, appKey v2.AppKey, region string) (v2.App, error) {
|
||||
args := m.Called(ctx, appKey, region)
|
||||
if args.Get(0) == nil {
|
||||
return v2.App{}, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(v2.App), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockEdgeConnectClient) ShowAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) (v2.AppInstance, error) {
|
||||
args := m.Called(ctx, instanceKey, region)
|
||||
if args.Get(0) == nil {
|
||||
return v2.AppInstance{}, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(v2.AppInstance), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockEdgeConnectClient) CreateApp(ctx context.Context, input *v2.NewAppInput) error {
|
||||
args := m.Called(ctx, input)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockEdgeConnectClient) CreateAppInstance(ctx context.Context, input *v2.NewAppInstanceInput) error {
|
||||
args := m.Called(ctx, input)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockEdgeConnectClient) DeleteApp(ctx context.Context, appKey v2.AppKey, region string) error {
|
||||
args := m.Called(ctx, appKey, region)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockEdgeConnectClient) UpdateApp(ctx context.Context, input *v2.UpdateAppInput) error {
|
||||
args := m.Called(ctx, input)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockEdgeConnectClient) UpdateAppInstance(ctx context.Context, input *v2.UpdateAppInstanceInput) error {
|
||||
args := m.Called(ctx, input)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockEdgeConnectClient) DeleteAppInstance(ctx context.Context, instanceKey v2.AppInstanceKey, region string) error {
|
||||
args := m.Called(ctx, instanceKey, region)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockEdgeConnectClient) ShowApps(ctx context.Context, appKey v2.AppKey, region string) ([]v2.App, error) {
|
||||
args := m.Called(ctx, appKey, region)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]v2.App), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockEdgeConnectClient) ShowAppInstances(ctx context.Context, instanceKey v2.AppInstanceKey, region string) ([]v2.AppInstance, error) {
|
||||
args := m.Called(ctx, instanceKey, region)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]v2.AppInstance), args.Error(1)
|
||||
}
|
||||
|
||||
func TestNewPlanner(t *testing.T) {
|
||||
mockClient := &MockEdgeConnectClient{}
|
||||
planner := NewPlanner(mockClient)
|
||||
|
||||
assert.NotNil(t, planner)
|
||||
assert.IsType(t, &EdgeConnectPlanner{}, planner)
|
||||
}
|
||||
|
||||
func TestDefaultPlanOptions(t *testing.T) {
|
||||
opts := DefaultPlanOptions()
|
||||
|
||||
assert.False(t, opts.DryRun)
|
||||
assert.False(t, opts.Force)
|
||||
assert.False(t, opts.SkipStateCheck)
|
||||
assert.True(t, opts.ParallelQueries)
|
||||
assert.Equal(t, 30*time.Second, opts.Timeout)
|
||||
}
|
||||
|
||||
func createTestConfig(t *testing.T) *config.EdgeConnectConfig {
|
||||
// Create temporary manifest file
|
||||
tempDir := t.TempDir()
|
||||
manifestFile := filepath.Join(tempDir, "test-manifest.yaml")
|
||||
manifestContent := "apiVersion: v1\nkind: Pod\nmetadata:\n name: test\n"
|
||||
err := os.WriteFile(manifestFile, []byte(manifestContent), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
return &config.EdgeConnectConfig{
|
||||
Kind: "edgeconnect-deployment",
|
||||
Metadata: config.Metadata{
|
||||
Name: "test-app",
|
||||
AppVersion: "1.0.0",
|
||||
Organization: "testorg",
|
||||
},
|
||||
Spec: config.Spec{
|
||||
K8sApp: &config.K8sApp{
|
||||
ManifestFile: manifestFile,
|
||||
},
|
||||
InfraTemplate: []config.InfraTemplate{
|
||||
{
|
||||
Region: "US",
|
||||
CloudletOrg: "TestCloudletOrg",
|
||||
CloudletName: "TestCloudlet",
|
||||
FlavorName: "small",
|
||||
},
|
||||
},
|
||||
Network: &config.NetworkConfig{
|
||||
OutboundConnections: []config.OutboundConnection{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
PortRangeMin: 80,
|
||||
PortRangeMax: 80,
|
||||
RemoteCIDR: "0.0.0.0/0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanNewDeployment(t *testing.T) {
|
||||
mockClient := &MockEdgeConnectClient{}
|
||||
planner := NewPlanner(mockClient)
|
||||
testConfig := createTestConfig(t)
|
||||
|
||||
// Mock API calls to return "not found" errors
|
||||
mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US").
|
||||
Return(nil, &v2.APIError{StatusCode: 404, Messages: []string{"App not found"}})
|
||||
|
||||
mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "US").
|
||||
Return(nil, &v2.APIError{StatusCode: 404, Messages: []string{"Instance not found"}})
|
||||
|
||||
ctx := context.Background()
|
||||
result, err := planner.Plan(ctx, testConfig)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
require.NotNil(t, result.Plan)
|
||||
require.NoError(t, result.Error)
|
||||
|
||||
plan := result.Plan
|
||||
assert.Equal(t, "test-app", plan.ConfigName)
|
||||
assert.Equal(t, ActionCreate, plan.AppAction.Type)
|
||||
assert.Equal(t, "Application does not exist", plan.AppAction.Reason)
|
||||
|
||||
require.Len(t, plan.InstanceActions, 1)
|
||||
assert.Equal(t, ActionCreate, plan.InstanceActions[0].Type)
|
||||
assert.Equal(t, "Instance does not exist", plan.InstanceActions[0].Reason)
|
||||
|
||||
assert.Equal(t, 2, plan.TotalActions) // 1 app + 1 instance
|
||||
assert.False(t, plan.IsEmpty())
|
||||
|
||||
mockClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestPlanExistingDeploymentNoChanges(t *testing.T) {
|
||||
mockClient := &MockEdgeConnectClient{}
|
||||
planner := NewPlanner(mockClient)
|
||||
testConfig := createTestConfig(t)
|
||||
|
||||
// Note: We would calculate expected manifest hash here when API supports it
|
||||
|
||||
// Mock existing app with same manifest hash and outbound connections
|
||||
manifestContent := "apiVersion: v1\nkind: Pod\nmetadata:\n name: test\n"
|
||||
existingApp := &v2.App{
|
||||
Key: v2.AppKey{
|
||||
Organization: "testorg",
|
||||
Name: "test-app",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Deployment: "kubernetes",
|
||||
DeploymentManifest: manifestContent,
|
||||
RequiredOutboundConnections: []v2.SecurityRule{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
PortRangeMin: 80,
|
||||
PortRangeMax: 80,
|
||||
RemoteCIDR: "0.0.0.0/0",
|
||||
},
|
||||
},
|
||||
// Note: Manifest hash tracking would be implemented when API supports annotations
|
||||
}
|
||||
|
||||
// Mock existing instance
|
||||
existingInstance := &v2.AppInstance{
|
||||
Key: v2.AppInstanceKey{
|
||||
Organization: "testorg",
|
||||
Name: "test-app-1.0.0-instance",
|
||||
CloudletKey: v2.CloudletKey{
|
||||
Organization: "TestCloudletOrg",
|
||||
Name: "TestCloudlet",
|
||||
},
|
||||
},
|
||||
AppKey: v2.AppKey{
|
||||
Organization: "testorg",
|
||||
Name: "test-app",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Flavor: v2.Flavor{
|
||||
Name: "small",
|
||||
},
|
||||
State: "Ready",
|
||||
PowerState: "PowerOn",
|
||||
}
|
||||
|
||||
mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US").
|
||||
Return(*existingApp, nil)
|
||||
|
||||
mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "US").
|
||||
Return(*existingInstance, nil)
|
||||
|
||||
ctx := context.Background()
|
||||
result, err := planner.Plan(ctx, testConfig)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
require.NotNil(t, result.Plan)
|
||||
|
||||
plan := result.Plan
|
||||
assert.Equal(t, ActionNone, plan.AppAction.Type)
|
||||
assert.Len(t, plan.InstanceActions, 1)
|
||||
assert.Equal(t, ActionNone, plan.InstanceActions[0].Type)
|
||||
assert.Equal(t, 0, plan.TotalActions)
|
||||
assert.True(t, plan.IsEmpty())
|
||||
assert.Contains(t, plan.Summary, "No changes required")
|
||||
|
||||
mockClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestPlanWithOptions(t *testing.T) {
|
||||
mockClient := &MockEdgeConnectClient{}
|
||||
planner := NewPlanner(mockClient)
|
||||
testConfig := createTestConfig(t)
|
||||
|
||||
opts := PlanOptions{
|
||||
DryRun: true,
|
||||
SkipStateCheck: true,
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
result, err := planner.PlanWithOptions(ctx, testConfig, opts)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
require.NotNil(t, result.Plan)
|
||||
|
||||
plan := result.Plan
|
||||
assert.True(t, plan.DryRun)
|
||||
assert.Equal(t, ActionCreate, plan.AppAction.Type)
|
||||
assert.Contains(t, plan.AppAction.Reason, "state check skipped")
|
||||
|
||||
// No API calls should be made when SkipStateCheck is true
|
||||
mockClient.AssertNotCalled(t, "ShowApp")
|
||||
mockClient.AssertNotCalled(t, "ShowAppInstance")
|
||||
}
|
||||
|
||||
func TestPlanMultipleInfrastructures(t *testing.T) {
|
||||
mockClient := &MockEdgeConnectClient{}
|
||||
planner := NewPlanner(mockClient)
|
||||
testConfig := createTestConfig(t)
|
||||
|
||||
// Add a second infrastructure target
|
||||
testConfig.Spec.InfraTemplate = append(testConfig.Spec.InfraTemplate, config.InfraTemplate{
|
||||
Region: "EU",
|
||||
CloudletOrg: "EUCloudletOrg",
|
||||
CloudletName: "EUCloudlet",
|
||||
FlavorName: "medium",
|
||||
})
|
||||
|
||||
// Mock API calls to return "not found" errors
|
||||
mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US").
|
||||
Return(nil, &v2.APIError{StatusCode: 404, Messages: []string{"App not found"}})
|
||||
|
||||
mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "US").
|
||||
Return(nil, &v2.APIError{StatusCode: 404, Messages: []string{"Instance not found"}})
|
||||
|
||||
mockClient.On("ShowAppInstance", mock.Anything, mock.AnythingOfType("v2.AppInstanceKey"), "EU").
|
||||
Return(nil, &v2.APIError{StatusCode: 404, Messages: []string{"Instance not found"}})
|
||||
|
||||
ctx := context.Background()
|
||||
result, err := planner.Plan(ctx, testConfig)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
require.NotNil(t, result.Plan)
|
||||
|
||||
plan := result.Plan
|
||||
assert.Equal(t, ActionCreate, plan.AppAction.Type)
|
||||
|
||||
// Should have 2 instance actions, one for each infrastructure
|
||||
require.Len(t, plan.InstanceActions, 2)
|
||||
assert.Equal(t, ActionCreate, plan.InstanceActions[0].Type)
|
||||
assert.Equal(t, ActionCreate, plan.InstanceActions[1].Type)
|
||||
|
||||
assert.Equal(t, 3, plan.TotalActions) // 1 app + 2 instances
|
||||
|
||||
// Test cloudlet and region aggregation
|
||||
cloudlets := plan.GetTargetCloudlets()
|
||||
regions := plan.GetTargetRegions()
|
||||
assert.Len(t, cloudlets, 2)
|
||||
assert.Len(t, regions, 2)
|
||||
|
||||
mockClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestCalculateManifestHash(t *testing.T) {
|
||||
planner := &EdgeConnectPlanner{}
|
||||
tempDir := t.TempDir()
|
||||
|
||||
// Create test file
|
||||
testFile := filepath.Join(tempDir, "test.yaml")
|
||||
content := "test content for hashing"
|
||||
err := os.WriteFile(testFile, []byte(content), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
hash1, err := planner.calculateManifestHash(testFile)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, hash1)
|
||||
assert.Len(t, hash1, 64) // SHA256 hex string length
|
||||
|
||||
// Same content should produce same hash
|
||||
hash2, err := planner.calculateManifestHash(testFile)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, hash1, hash2)
|
||||
|
||||
// Different content should produce different hash
|
||||
err = os.WriteFile(testFile, []byte("different content"), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
hash3, err := planner.calculateManifestHash(testFile)
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, hash1, hash3)
|
||||
|
||||
// Empty file path should return empty hash
|
||||
hash4, err := planner.calculateManifestHash("")
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, hash4)
|
||||
|
||||
// Non-existent file should return error
|
||||
_, err = planner.calculateManifestHash("/non/existent/file")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestCompareAppStates(t *testing.T) {
|
||||
planner := &EdgeConnectPlanner{}
|
||||
|
||||
current := &AppState{
|
||||
Name: "test-app",
|
||||
Version: "1.0.0",
|
||||
AppType: AppTypeK8s,
|
||||
ManifestHash: "old-hash",
|
||||
}
|
||||
|
||||
desired := &AppState{
|
||||
Name: "test-app",
|
||||
Version: "1.0.0",
|
||||
AppType: AppTypeK8s,
|
||||
ManifestHash: "new-hash",
|
||||
}
|
||||
|
||||
changes, manifestChanged := planner.compareAppStates(current, desired)
|
||||
assert.Len(t, changes, 1)
|
||||
assert.True(t, manifestChanged)
|
||||
assert.Contains(t, changes[0], "Manifest hash changed")
|
||||
|
||||
// Test no changes
|
||||
desired.ManifestHash = "old-hash"
|
||||
changes, manifestChanged = planner.compareAppStates(current, desired)
|
||||
assert.Empty(t, changes)
|
||||
assert.False(t, manifestChanged)
|
||||
|
||||
// Test app type change
|
||||
desired.AppType = AppTypeDocker
|
||||
changes, manifestChanged = planner.compareAppStates(current, desired)
|
||||
assert.Len(t, changes, 1)
|
||||
assert.False(t, manifestChanged)
|
||||
assert.Contains(t, changes[0], "App type changed")
|
||||
}
|
||||
|
||||
func TestCompareAppStatesOutboundConnections(t *testing.T) {
|
||||
planner := &EdgeConnectPlanner{}
|
||||
|
||||
// Test with no outbound connections
|
||||
current := &AppState{
|
||||
Name: "test-app",
|
||||
Version: "1.0.0",
|
||||
AppType: AppTypeK8s,
|
||||
OutboundConnections: nil,
|
||||
}
|
||||
|
||||
desired := &AppState{
|
||||
Name: "test-app",
|
||||
Version: "1.0.0",
|
||||
AppType: AppTypeK8s,
|
||||
OutboundConnections: nil,
|
||||
}
|
||||
|
||||
changes, _ := planner.compareAppStates(current, desired)
|
||||
assert.Empty(t, changes, "No changes expected when both have no outbound connections")
|
||||
|
||||
// Test adding outbound connections
|
||||
desired.OutboundConnections = []SecurityRule{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
PortRangeMin: 80,
|
||||
PortRangeMax: 80,
|
||||
RemoteCIDR: "0.0.0.0/0",
|
||||
},
|
||||
}
|
||||
|
||||
changes, _ = planner.compareAppStates(current, desired)
|
||||
assert.Len(t, changes, 1)
|
||||
assert.Contains(t, changes[0], "Outbound connections changed")
|
||||
|
||||
// Test identical outbound connections
|
||||
current.OutboundConnections = []SecurityRule{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
PortRangeMin: 80,
|
||||
PortRangeMax: 80,
|
||||
RemoteCIDR: "0.0.0.0/0",
|
||||
},
|
||||
}
|
||||
|
||||
changes, _ = planner.compareAppStates(current, desired)
|
||||
assert.Empty(t, changes, "No changes expected when outbound connections are identical")
|
||||
|
||||
// Test different outbound connections (different port)
|
||||
desired.OutboundConnections[0].PortRangeMin = 443
|
||||
desired.OutboundConnections[0].PortRangeMax = 443
|
||||
|
||||
changes, _ = planner.compareAppStates(current, desired)
|
||||
assert.Len(t, changes, 1)
|
||||
assert.Contains(t, changes[0], "Outbound connections changed")
|
||||
|
||||
// Test same connections but different order (should be considered equal)
|
||||
current.OutboundConnections = []SecurityRule{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
PortRangeMin: 80,
|
||||
PortRangeMax: 80,
|
||||
RemoteCIDR: "0.0.0.0/0",
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
PortRangeMin: 443,
|
||||
PortRangeMax: 443,
|
||||
RemoteCIDR: "0.0.0.0/0",
|
||||
},
|
||||
}
|
||||
|
||||
desired.OutboundConnections = []SecurityRule{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
PortRangeMin: 443,
|
||||
PortRangeMax: 443,
|
||||
RemoteCIDR: "0.0.0.0/0",
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
PortRangeMin: 80,
|
||||
PortRangeMax: 80,
|
||||
RemoteCIDR: "0.0.0.0/0",
|
||||
},
|
||||
}
|
||||
|
||||
changes, _ = planner.compareAppStates(current, desired)
|
||||
assert.Empty(t, changes, "No changes expected when outbound connections are same but in different order")
|
||||
|
||||
// Test removing outbound connections
|
||||
desired.OutboundConnections = nil
|
||||
|
||||
changes, _ = planner.compareAppStates(current, desired)
|
||||
assert.Len(t, changes, 1)
|
||||
assert.Contains(t, changes[0], "Outbound connections changed")
|
||||
}
|
||||
|
||||
func TestCompareInstanceStates(t *testing.T) {
|
||||
planner := &EdgeConnectPlanner{}
|
||||
|
||||
current := &InstanceState{
|
||||
Name: "test-instance",
|
||||
FlavorName: "small",
|
||||
CloudletName: "oldcloudlet",
|
||||
CloudletOrg: "oldorg",
|
||||
}
|
||||
|
||||
desired := &InstanceState{
|
||||
Name: "test-instance",
|
||||
FlavorName: "medium",
|
||||
CloudletName: "newcloudlet",
|
||||
CloudletOrg: "neworg",
|
||||
}
|
||||
|
||||
changes := planner.compareInstanceStates(current, desired)
|
||||
assert.Len(t, changes, 3)
|
||||
assert.Contains(t, changes[0], "Flavor changed")
|
||||
assert.Contains(t, changes[1], "Cloudlet changed")
|
||||
assert.Contains(t, changes[2], "Cloudlet org changed")
|
||||
|
||||
// Test no changes
|
||||
desired.FlavorName = "small"
|
||||
desired.CloudletName = "oldcloudlet"
|
||||
desired.CloudletOrg = "oldorg"
|
||||
changes = planner.compareInstanceStates(current, desired)
|
||||
assert.Empty(t, changes)
|
||||
}
|
||||
|
||||
func TestDeploymentPlanMethods(t *testing.T) {
|
||||
plan := &DeploymentPlan{
|
||||
ConfigName: "test-plan",
|
||||
AppAction: AppAction{
|
||||
Type: ActionCreate,
|
||||
Desired: &AppState{Name: "test-app"},
|
||||
},
|
||||
InstanceActions: []InstanceAction{
|
||||
{
|
||||
Type: ActionCreate,
|
||||
Target: config.InfraTemplate{
|
||||
CloudletOrg: "org1",
|
||||
CloudletName: "cloudlet1",
|
||||
Region: "US",
|
||||
},
|
||||
InstanceName: "instance1",
|
||||
Desired: &InstanceState{Name: "instance1"},
|
||||
},
|
||||
{
|
||||
Type: ActionUpdate,
|
||||
Target: config.InfraTemplate{
|
||||
CloudletOrg: "org2",
|
||||
CloudletName: "cloudlet2",
|
||||
Region: "EU",
|
||||
},
|
||||
InstanceName: "instance2",
|
||||
Desired: &InstanceState{Name: "instance2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Test IsEmpty
|
||||
assert.False(t, plan.IsEmpty())
|
||||
|
||||
// Test GetTargetCloudlets
|
||||
cloudlets := plan.GetTargetCloudlets()
|
||||
assert.Len(t, cloudlets, 2)
|
||||
assert.Contains(t, cloudlets, "org1:cloudlet1")
|
||||
assert.Contains(t, cloudlets, "org2:cloudlet2")
|
||||
|
||||
// Test GetTargetRegions
|
||||
regions := plan.GetTargetRegions()
|
||||
assert.Len(t, regions, 2)
|
||||
assert.Contains(t, regions, "US")
|
||||
assert.Contains(t, regions, "EU")
|
||||
|
||||
// Test GenerateSummary
|
||||
summary := plan.GenerateSummary()
|
||||
assert.Contains(t, summary, "test-plan")
|
||||
assert.Contains(t, summary, "CREATE application")
|
||||
assert.Contains(t, summary, "CREATE 1 instance")
|
||||
assert.Contains(t, summary, "UPDATE 1 instance")
|
||||
|
||||
// Test Validate
|
||||
err := plan.Validate()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test validation failure
|
||||
plan.AppAction.Desired = nil
|
||||
err = plan.Validate()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "must have desired state")
|
||||
}
|
||||
|
||||
func TestEstimateDeploymentDuration(t *testing.T) {
|
||||
planner := &EdgeConnectPlanner{}
|
||||
|
||||
plan := &DeploymentPlan{
|
||||
AppAction: AppAction{Type: ActionCreate},
|
||||
InstanceActions: []InstanceAction{
|
||||
{Type: ActionCreate},
|
||||
{Type: ActionUpdate},
|
||||
},
|
||||
}
|
||||
|
||||
duration := planner.estimateDeploymentDuration(plan)
|
||||
assert.Greater(t, duration, time.Duration(0))
|
||||
assert.Less(t, duration, 10*time.Minute) // Reasonable upper bound
|
||||
|
||||
// Test with no actions
|
||||
emptyPlan := &DeploymentPlan{
|
||||
AppAction: AppAction{Type: ActionNone},
|
||||
InstanceActions: []InstanceAction{},
|
||||
}
|
||||
|
||||
emptyDuration := planner.estimateDeploymentDuration(emptyPlan)
|
||||
assert.Greater(t, emptyDuration, time.Duration(0))
|
||||
assert.Less(t, emptyDuration, duration) // Should be less than plan with actions
|
||||
}
|
||||
|
||||
func TestIsResourceNotFoundError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
expected bool
|
||||
}{
|
||||
{"nil error", nil, false},
|
||||
{"not found error", &v2.APIError{StatusCode: 404, Messages: []string{"Resource not found"}}, true},
|
||||
{"does not exist error", &v2.APIError{Messages: []string{"App does not exist"}}, true},
|
||||
{"404 in message", &v2.APIError{Messages: []string{"HTTP 404 error"}}, true},
|
||||
{"other error", &v2.APIError{StatusCode: 500, Messages: []string{"Server error"}}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := isResourceNotFoundError(tt.err)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanErrorHandling(t *testing.T) {
|
||||
mockClient := &MockEdgeConnectClient{}
|
||||
planner := NewPlanner(mockClient)
|
||||
testConfig := createTestConfig(t)
|
||||
|
||||
// Mock API call to return a non-404 error
|
||||
mockClient.On("ShowApp", mock.Anything, mock.AnythingOfType("v2.AppKey"), "US").
|
||||
Return(nil, &v2.APIError{StatusCode: 500, Messages: []string{"Server error"}})
|
||||
|
||||
ctx := context.Background()
|
||||
result, err := planner.Plan(ctx, testConfig)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.NotNil(t, result.Error)
|
||||
assert.Contains(t, err.Error(), "failed to query current app state")
|
||||
|
||||
mockClient.AssertExpectations(t)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue