package awsinv import ( "context" "errors" "testing" "github.com/aws/aws-sdk-go-v2/aws" awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/aws/smithy-go/middleware" ) type exactEC2 struct { out *ec2.DescribeInstancesOutput err error in *ec2.DescribeInstancesInput region string } func (f *exactEC2) DescribeInstances(_ context.Context, in *ec2.DescribeInstancesInput, options ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) { f.in = in var opts ec2.Options for _, option := range options { option(&opts) } f.region = opts.Region return f.out, f.err } func exactOutput(owner, id string, state types.InstanceStateName) *ec2.DescribeInstancesOutput { out := &ec2.DescribeInstancesOutput{Reservations: []types.Reservation{{ OwnerId: aws.String(owner), Instances: []types.Instance{{InstanceId: aws.String(id), State: &types.InstanceState{Name: state}}}, }}} awsmiddleware.SetRequestIDMetadata(&out.ResultMetadata, "aws-request-123") return out } func TestVerifyExactStoppedInstanceAcceptsOnlyStoppedOrTerminated(t *testing.T) { for _, state := range []types.InstanceStateName{types.InstanceStateNameStopped, types.InstanceStateNameTerminated} { t.Run(string(state), func(t *testing.T) { api := &exactEC2{out: exactOutput("123456789012", "i-123", state)} got, err := VerifyExactStoppedInstance(t.Context(), api, "123456789012", "eu-west-1", "i-123") if err != nil { t.Fatal(err) } if got.Account != "123456789012" || got.Region != "eu-west-1" || got.InstanceID != "i-123" || got.State != string(state) || got.RequestID != "aws-request-123" { t.Fatalf("observation = %+v", got) } if len(api.in.InstanceIds) != 1 || api.in.InstanceIds[0] != "i-123" { t.Fatalf("DescribeInstances input = %+v", api.in) } if api.region != "eu-west-1" { t.Fatalf("DescribeInstances region = %q", api.region) } }) } } func TestVerifyExactStoppedInstanceRefusesEveryIncompleteOrLiveAnswer(t *testing.T) { notFound := errors.New("InvalidInstanceID.NotFound") cases := map[string]*exactEC2{ "missing-request-id": {out: func() *ec2.DescribeInstancesOutput { o := exactOutput("123456789012", "i-123", types.InstanceStateNameStopped) o.ResultMetadata = middleware.Metadata{} return o }()}, "empty-request-id": {out: func() *ec2.DescribeInstancesOutput { o := exactOutput("123456789012", "i-123", types.InstanceStateNameStopped) awsmiddleware.SetRequestIDMetadata(&o.ResultMetadata, "") return o }()}, "blank-request-id": {out: func() *ec2.DescribeInstancesOutput { o := exactOutput("123456789012", "i-123", types.InstanceStateNameStopped) awsmiddleware.SetRequestIDMetadata(&o.ResultMetadata, " ") return o }()}, "not-found": {err: notFound}, "nil": {}, "no-reservation": {out: &ec2.DescribeInstancesOutput{}}, "partial-page": {out: func() *ec2.DescribeInstancesOutput { o := exactOutput("123456789012", "i-123", types.InstanceStateNameStopped) o.NextToken = aws.String("more") return o }()}, "wrong-owner": {out: exactOutput("999999999999", "i-123", types.InstanceStateNameStopped)}, "missing-owner": {out: exactOutput("", "i-123", types.InstanceStateNameStopped)}, "wrong-instance": {out: exactOutput("123456789012", "i-other", types.InstanceStateNameStopped)}, "running": {out: exactOutput("123456789012", "i-123", types.InstanceStateNameRunning)}, "missing-state": {out: &ec2.DescribeInstancesOutput{Reservations: []types.Reservation{{OwnerId: aws.String("123456789012"), Instances: []types.Instance{{InstanceId: aws.String("i-123")}}}}}}, "two-reservations": {out: &ec2.DescribeInstancesOutput{Reservations: []types.Reservation{{OwnerId: aws.String("123456789012")}, {OwnerId: aws.String("123456789012")}}}}, "two-instances": {out: &ec2.DescribeInstancesOutput{Reservations: []types.Reservation{{OwnerId: aws.String("123456789012"), Instances: []types.Instance{{InstanceId: aws.String("i-123")}, {InstanceId: aws.String("i-123")}}}}}}, } for name, api := range cases { t.Run(name, func(t *testing.T) { if _, err := VerifyExactStoppedInstance(t.Context(), api, "123456789012", "eu-west-1", "i-123"); !errors.Is(err, ErrExactInstance) { t.Fatalf("error = %v, want ErrExactInstance", err) } }) } }