Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions internal/workflows/eks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package workflows

import (
"errors"
"testing"

"github.com/Amertz08/gitops-example/internal/activities"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"go.temporal.io/sdk/temporal"
"go.temporal.io/sdk/testsuite"
)

type EKSWorkflowTestSuite struct {
suite.Suite
testsuite.WorkflowTestSuite
env *testsuite.TestWorkflowEnvironment
}

func (s *EKSWorkflowTestSuite) SetupTest() {
s.env = s.NewTestWorkflowEnvironment()
}

func (s *EKSWorkflowTestSuite) AfterTest(_, _ string) {
s.env.AssertExpectations(s.T())
}

func TestEKSWorkflowSuite(t *testing.T) {
suite.Run(t, new(EKSWorkflowTestSuite))
}

func validEKSInput() SpinUpEKSInput {
return SpinUpEKSInput{
Region: "us-east-1",
ClusterName: "my-cluster",
ClusterRoleARN: "arn:aws:iam::123:role/cluster-role",
NodeRoleARN: "arn:aws:iam::123:role/node-role",
VpcID: "vpc-123",
SubnetIDs: []string{"subnet-a", "subnet-b"},
NodeCount: 2,
NodeInstanceType: "t3.medium",
Environment: "prod",
Team: "platform",
}
}

func (s *EKSWorkflowTestSuite) Test_SpinUpEKS_InvalidInput() {
s.env.ExecuteWorkflow(SpinUpEKSWorkflow, SpinUpEKSInput{})

s.True(s.env.IsWorkflowCompleted())
err := s.env.GetWorkflowError()
s.Error(err)
var appErr *temporal.ApplicationError
s.True(errors.As(err, &appErr))
s.Equal("InvalidInput", appErr.Type())
}

func (s *EKSWorkflowTestSuite) Test_SpinUpEKS_Success() {
aws := &activities.AWSActivities{}
s.env.OnActivity(aws.CreateEKSCluster, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.CreateNodeGroup, mock.Anything, mock.Anything).Return(nil)

s.env.ExecuteWorkflow(SpinUpEKSWorkflow, validEKSInput())

s.True(s.env.IsWorkflowCompleted())
s.NoError(s.env.GetWorkflowError())
}

// CreateNodeGroup failing must trigger saga compensation to delete the cluster.
func (s *EKSWorkflowTestSuite) Test_SpinUpEKS_NodeGroupFailure_CompensatesCluster() {
aws := &activities.AWSActivities{}
s.env.OnActivity(aws.CreateEKSCluster, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.CreateNodeGroup, mock.Anything, mock.Anything).
Return(errors.New("node group quota exceeded"))
s.env.OnActivity(aws.DeleteEKSCluster, mock.Anything, mock.Anything).Return(nil).Once()

s.env.ExecuteWorkflow(SpinUpEKSWorkflow, validEKSInput())

s.True(s.env.IsWorkflowCompleted())
s.Error(s.env.GetWorkflowError())
}

func (s *EKSWorkflowTestSuite) Test_SpinDownEKS_InvalidInput() {
s.env.ExecuteWorkflow(SpinDownEKSWorkflow, SpinDownEKSInput{})

s.True(s.env.IsWorkflowCompleted())
err := s.env.GetWorkflowError()
s.Error(err)
var appErr *temporal.ApplicationError
s.True(errors.As(err, &appErr))
s.Equal("InvalidInput", appErr.Type())
}

func (s *EKSWorkflowTestSuite) Test_SpinDownEKS_Success() {
aws := &activities.AWSActivities{}
s.env.OnActivity(aws.DeleteNodeGroup, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.DeleteEKSCluster, mock.Anything, mock.Anything).Return(nil)

s.env.ExecuteWorkflow(SpinDownEKSWorkflow, SpinDownEKSInput{
Region: "us-east-1",
ClusterName: "my-cluster",
})

s.True(s.env.IsWorkflowCompleted())
s.NoError(s.env.GetWorkflowError())
}
109 changes: 109 additions & 0 deletions internal/workflows/iam_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package workflows

import (
"errors"
"testing"

"github.com/Amertz08/gitops-example/internal/activities"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"go.temporal.io/sdk/temporal"
"go.temporal.io/sdk/testsuite"
)

type IAMWorkflowTestSuite struct {
suite.Suite
testsuite.WorkflowTestSuite
env *testsuite.TestWorkflowEnvironment
}

func (s *IAMWorkflowTestSuite) SetupTest() {
s.env = s.NewTestWorkflowEnvironment()
}

func (s *IAMWorkflowTestSuite) AfterTest(_, _ string) {
s.env.AssertExpectations(s.T())
}

func TestIAMWorkflowSuite(t *testing.T) {
suite.Run(t, new(IAMWorkflowTestSuite))
}

func (s *IAMWorkflowTestSuite) Test_SpinUpIAM_InvalidInput() {
s.env.ExecuteWorkflow(SpinUpIAMWorkflow, SpinUpEKSIAMInput{})

s.True(s.env.IsWorkflowCompleted())
err := s.env.GetWorkflowError()
s.Error(err)
var appErr *temporal.ApplicationError
s.True(errors.As(err, &appErr))
s.Equal("InvalidInput", appErr.Type())
}

func (s *IAMWorkflowTestSuite) Test_SpinUpIAM_Success() {
aws := &activities.AWSActivities{}
s.env.OnActivity(aws.CreateIAMRole, mock.Anything, mock.Anything).
Return("arn:aws:iam::123:role/cluster-role", nil).Once()
s.env.OnActivity(aws.CreateIAMRole, mock.Anything, mock.Anything).
Return("arn:aws:iam::123:role/node-role", nil).Once()

s.env.ExecuteWorkflow(SpinUpIAMWorkflow, SpinUpEKSIAMInput{
ClusterName: "my-cluster",
Environment: "prod",
Team: "platform",
})

s.True(s.env.IsWorkflowCompleted())
s.NoError(s.env.GetWorkflowError())

var out SpinUpEKSIAMOutput
s.NoError(s.env.GetWorkflowResult(&out))
s.Equal("arn:aws:iam::123:role/cluster-role", out.ClusterRoleARN)
s.Equal("my-cluster-eks-cluster-role", out.ClusterRoleName)
s.Equal("arn:aws:iam::123:role/node-role", out.NodeRoleARN)
s.Equal("my-cluster-eks-node-role", out.NodeRoleName)
}

// When node role creation fails, the saga must compensate the already-created cluster role.
func (s *IAMWorkflowTestSuite) Test_SpinUpIAM_NodeRoleFailure_CompensatesClusterRole() {
aws := &activities.AWSActivities{}
s.env.OnActivity(aws.CreateIAMRole, mock.Anything, mock.Anything).
Return("arn:aws:iam::123:role/cluster-role", nil).Once()
s.env.OnActivity(aws.CreateIAMRole, mock.Anything, mock.Anything).
Return("", errors.New("IAM quota exceeded"))
s.env.OnActivity(aws.DeleteIAMRole, mock.Anything, mock.Anything).
Return(nil).Once()

s.env.ExecuteWorkflow(SpinUpIAMWorkflow, SpinUpEKSIAMInput{
ClusterName: "my-cluster",
Environment: "prod",
Team: "platform",
})

s.True(s.env.IsWorkflowCompleted())
s.Error(s.env.GetWorkflowError())
}

func (s *IAMWorkflowTestSuite) Test_SpinDownIAM_InvalidInput() {
s.env.ExecuteWorkflow(SpinDownIAMWorkflow, SpinDownEKSIAMInput{})

s.True(s.env.IsWorkflowCompleted())
err := s.env.GetWorkflowError()
s.Error(err)
var appErr *temporal.ApplicationError
s.True(errors.As(err, &appErr))
s.Equal("InvalidInput", appErr.Type())
}

func (s *IAMWorkflowTestSuite) Test_SpinDownIAM_Success() {
aws := &activities.AWSActivities{}
s.env.OnActivity(aws.DeleteIAMRole, mock.Anything, mock.Anything).Return(nil).Times(2)

s.env.ExecuteWorkflow(SpinDownIAMWorkflow, SpinDownEKSIAMInput{
ClusterRoleName: "my-cluster-eks-cluster-role",
NodeRoleName: "my-cluster-eks-node-role",
})

s.True(s.env.IsWorkflowCompleted())
s.NoError(s.env.GetWorkflowError())
}
160 changes: 160 additions & 0 deletions internal/workflows/infrastructure_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package workflows

import (
"errors"
"testing"

"github.com/Amertz08/gitops-example/internal/activities"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"go.temporal.io/sdk/temporal"
"go.temporal.io/sdk/testsuite"
)

type InfraWorkflowTestSuite struct {
suite.Suite
testsuite.WorkflowTestSuite
env *testsuite.TestWorkflowEnvironment
}

func (s *InfraWorkflowTestSuite) SetupTest() {
s.env = s.NewTestWorkflowEnvironment()
s.env.RegisterWorkflow(SpinUpNetworkWorkflow)
s.env.RegisterWorkflow(SpinUpIAMWorkflow)
s.env.RegisterWorkflow(SpinUpEKSWorkflow)
s.env.RegisterWorkflow(SpinDownEKSWorkflow)
s.env.RegisterWorkflow(SpinDownNetworkWorkflow)
s.env.RegisterWorkflow(SpinDownIAMWorkflow)
}

func (s *InfraWorkflowTestSuite) AfterTest(_, _ string) {
s.env.AssertExpectations(s.T())
}

func TestInfraWorkflowSuite(t *testing.T) {
suite.Run(t, new(InfraWorkflowTestSuite))
}

// mockHappyPathNetworkActivities mocks all network activities to return success.
func (s *InfraWorkflowTestSuite) mockHappyPathNetworkActivities() {
aws := &activities.AWSActivities{}
s.env.OnActivity(aws.CreateVPC, mock.Anything, mock.Anything).Return("vpc-123", nil)
s.env.OnActivity(aws.CreateSubnets, mock.Anything, mock.Anything).
Return([]string{"subnet-a", "subnet-b"}, nil)
s.env.OnActivity(aws.CreateInternetGateway, mock.Anything, mock.Anything).Return("igw-123", nil)
s.env.OnActivity(aws.ConfigureRouteTables, mock.Anything, mock.Anything).Return(nil)
}

// mockHappyPathEKSActivities mocks all EKS activities to return success.
func (s *InfraWorkflowTestSuite) mockHappyPathEKSActivities() {
aws := &activities.AWSActivities{}
s.env.OnActivity(aws.CreateEKSCluster, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.CreateNodeGroup, mock.Anything, mock.Anything).Return(nil)
}

func validSpinUpInput() SpinUpInput {
return SpinUpInput{
Region: "us-east-1",
ClusterName: "my-cluster",
NodeCount: 2,
NodeInstanceType: "t3.medium",
Environment: "prod",
Team: "platform",
}
}

func (s *InfraWorkflowTestSuite) Test_SpinUp_InvalidInput() {
s.env.ExecuteWorkflow(SpinUpWorkflow, SpinUpInput{})

s.True(s.env.IsWorkflowCompleted())
err := s.env.GetWorkflowError()
s.Error(err)
var appErr *temporal.ApplicationError
s.True(errors.As(err, &appErr))
s.Equal("InvalidInput", appErr.Type())
}

// When no role ARNs are supplied, SpinUpWorkflow must run SpinUpIAMWorkflow concurrently with
// SpinUpNetworkWorkflow, then use the resulting ARNs for SpinUpEKSWorkflow.
func (s *InfraWorkflowTestSuite) Test_SpinUp_Success_WithoutPreSuppliedARNs() {
aws := &activities.AWSActivities{}
s.env.OnActivity(aws.CreateIAMRole, mock.Anything, mock.Anything).
Return("arn:aws:iam::123:role/cluster-role", nil).Once()
s.env.OnActivity(aws.CreateIAMRole, mock.Anything, mock.Anything).
Return("arn:aws:iam::123:role/node-role", nil).Once()
s.mockHappyPathNetworkActivities()
s.mockHappyPathEKSActivities()

s.env.ExecuteWorkflow(SpinUpWorkflow, validSpinUpInput())

s.True(s.env.IsWorkflowCompleted())
s.NoError(s.env.GetWorkflowError())
}

// When role ARNs are pre-supplied, SpinUpWorkflow must skip IAM creation entirely.
func (s *InfraWorkflowTestSuite) Test_SpinUp_Success_WithPreSuppliedARNs() {
s.mockHappyPathNetworkActivities()
s.mockHappyPathEKSActivities()

input := validSpinUpInput()
input.ClusterRoleARN = "arn:aws:iam::123:role/existing-cluster-role"
input.NodeRoleARN = "arn:aws:iam::123:role/existing-node-role"
s.env.ExecuteWorkflow(SpinUpWorkflow, input)

s.True(s.env.IsWorkflowCompleted())
s.NoError(s.env.GetWorkflowError())
}

func (s *InfraWorkflowTestSuite) Test_SpinDown_InvalidInput() {
s.env.ExecuteWorkflow(SpinDownWorkflow, SpinDownInput{})

s.True(s.env.IsWorkflowCompleted())
err := s.env.GetWorkflowError()
s.Error(err)
var appErr *temporal.ApplicationError
s.True(errors.As(err, &appErr))
s.Equal("InvalidInput", appErr.Type())
}

// SpinDownWorkflow tears down EKS, network, and (when role names are provided) IAM.
func (s *InfraWorkflowTestSuite) Test_SpinDown_Success_WithRoles() {
aws := &activities.AWSActivities{}
s.env.OnActivity(aws.DeleteNodeGroup, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.DeleteEKSCluster, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.DeleteSubnets, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.DeleteRouteTables, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.DetachDeleteInternetGateway, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.DeleteVPC, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.DeleteIAMRole, mock.Anything, mock.Anything).Return(nil).Times(2)

s.env.ExecuteWorkflow(SpinDownWorkflow, SpinDownInput{
Region: "us-east-1",
ClusterName: "my-cluster",
VpcID: "vpc-123",
ClusterRoleName: "my-cluster-eks-cluster-role",
NodeRoleName: "my-cluster-eks-node-role",
})

s.True(s.env.IsWorkflowCompleted())
s.NoError(s.env.GetWorkflowError())
}

// When role names are empty, SpinDownWorkflow must skip the IAM teardown child workflow.
func (s *InfraWorkflowTestSuite) Test_SpinDown_Success_WithoutRoles() {
aws := &activities.AWSActivities{}
s.env.OnActivity(aws.DeleteNodeGroup, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.DeleteEKSCluster, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.DeleteSubnets, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.DeleteRouteTables, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.DetachDeleteInternetGateway, mock.Anything, mock.Anything).Return(nil)
s.env.OnActivity(aws.DeleteVPC, mock.Anything, mock.Anything).Return(nil)

s.env.ExecuteWorkflow(SpinDownWorkflow, SpinDownInput{
Region: "us-east-1",
ClusterName: "my-cluster",
VpcID: "vpc-123",
})

s.True(s.env.IsWorkflowCompleted())
s.NoError(s.env.GetWorkflowError())
}
Loading
Loading