Skip to content

Commit 529bf18

Browse files
authored
Merge pull request #1771 from gruntwork-io/feat/aws-acm-mock-tests
test(aws): add mock-based unit tests for 6 previously-untested modules
2 parents 2d45416 + 8564211 commit 529bf18

12 files changed

Lines changed: 678 additions & 13 deletions

modules/aws/acm.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ import (
99
"github.com/gruntwork-io/terratest/modules/testing"
1010
)
1111

12+
// AcmAPI is the subset of *acm.Client operations used by the helpers in this file.
13+
// It is declared as an interface so tests can substitute a mock without an AWS
14+
// account. A real *acm.Client satisfies this interface automatically.
15+
type AcmAPI interface {
16+
ListCertificates(ctx context.Context, params *acm.ListCertificatesInput, optFns ...func(*acm.Options)) (*acm.ListCertificatesOutput, error)
17+
}
18+
1219
// GetAcmCertificateArnContextE gets the ACM certificate for the given domain name in the given region.
1320
// The ctx parameter supports cancellation and timeouts.
1421
func GetAcmCertificateArnContextE(t testing.TestingT, ctx context.Context, awsRegion string, certDomainName string) (string, error) {
@@ -17,7 +24,15 @@ func GetAcmCertificateArnContextE(t testing.TestingT, ctx context.Context, awsRe
1724
return "", err
1825
}
1926

20-
result, err := acmClient.ListCertificates(ctx, &acm.ListCertificatesInput{})
27+
return GetAcmCertificateArnWithClientContextE(t, ctx, acmClient, certDomainName)
28+
}
29+
30+
// GetAcmCertificateArnWithClientContextE gets the ACM certificate for the given domain name using
31+
// the provided ACM client. Useful when a pre-configured client is available or in unit tests with
32+
// a mock.
33+
// The ctx parameter supports cancellation and timeouts.
34+
func GetAcmCertificateArnWithClientContextE(t testing.TestingT, ctx context.Context, client AcmAPI, certDomainName string) (string, error) {
35+
result, err := client.ListCertificates(ctx, &acm.ListCertificatesInput{})
2136
if err != nil {
2237
return "", err
2338
}

modules/aws/acm_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package aws_test
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
8+
awsSDK "github.com/aws/aws-sdk-go-v2/aws"
9+
"github.com/aws/aws-sdk-go-v2/service/acm"
10+
"github.com/aws/aws-sdk-go-v2/service/acm/types"
11+
"github.com/stretchr/testify/require"
12+
13+
aws "github.com/gruntwork-io/terratest/modules/aws"
14+
)
15+
16+
// mockAcmClient is a test double for aws.AcmAPI that returns canned responses.
17+
type mockAcmClient struct {
18+
ListCertificatesOutput *acm.ListCertificatesOutput
19+
ListCertificatesErr error
20+
}
21+
22+
func (m *mockAcmClient) ListCertificates(_ context.Context, _ *acm.ListCertificatesInput, _ ...func(*acm.Options)) (*acm.ListCertificatesOutput, error) {
23+
if m.ListCertificatesErr != nil {
24+
return nil, m.ListCertificatesErr
25+
}
26+
27+
return m.ListCertificatesOutput, nil
28+
}
29+
30+
func TestGetAcmCertificateArnWithClientContextE(t *testing.T) {
31+
t.Parallel()
32+
33+
const (
34+
arn1 = "arn:aws:acm:us-east-1:123456789012:certificate/cert-1"
35+
arn2 = "arn:aws:acm:us-east-1:123456789012:certificate/cert-2"
36+
domain1 = "foo.example.com"
37+
domain2 = "bar.example.com"
38+
)
39+
40+
twoCerts := &acm.ListCertificatesOutput{
41+
CertificateSummaryList: []types.CertificateSummary{
42+
{DomainName: awsSDK.String(domain1), CertificateArn: awsSDK.String(arn1)},
43+
{DomainName: awsSDK.String(domain2), CertificateArn: awsSDK.String(arn2)},
44+
},
45+
}
46+
47+
tests := map[string]struct {
48+
client *mockAcmClient
49+
query string
50+
expectedArn string
51+
expectErr bool
52+
}{
53+
"returns arn when domain matches": {
54+
client: &mockAcmClient{ListCertificatesOutput: twoCerts},
55+
query: domain2,
56+
expectedArn: arn2,
57+
},
58+
"returns first match when listed first": {
59+
client: &mockAcmClient{ListCertificatesOutput: twoCerts},
60+
query: domain1,
61+
expectedArn: arn1,
62+
},
63+
"returns empty string when no domain matches": {
64+
client: &mockAcmClient{ListCertificatesOutput: twoCerts},
65+
query: "nonexistent.example.com",
66+
expectedArn: "",
67+
},
68+
"returns empty string on empty list": {
69+
client: &mockAcmClient{ListCertificatesOutput: &acm.ListCertificatesOutput{}},
70+
query: domain1,
71+
expectedArn: "",
72+
},
73+
"propagates api error": {
74+
client: &mockAcmClient{ListCertificatesErr: errors.New("AccessDenied")},
75+
query: domain1,
76+
expectErr: true,
77+
},
78+
}
79+
80+
for name, tc := range tests {
81+
t.Run(name, func(t *testing.T) {
82+
t.Parallel()
83+
84+
arn, err := aws.GetAcmCertificateArnWithClientContextE(t, context.Background(), tc.client, tc.query)
85+
if tc.expectErr {
86+
require.Error(t, err)
87+
88+
return
89+
}
90+
91+
require.NoError(t, err)
92+
require.Equal(t, tc.expectedArn, arn)
93+
})
94+
}
95+
}

modules/aws/cloudwatch.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ import (
99
"github.com/stretchr/testify/require"
1010
)
1111

12+
// CloudWatchLogsAPI is the subset of *cloudwatchlogs.Client operations used by the helpers in this
13+
// file. Declared as an interface so tests can substitute a mock; a real *cloudwatchlogs.Client
14+
// satisfies it automatically.
15+
type CloudWatchLogsAPI interface {
16+
GetLogEvents(ctx context.Context, params *cloudwatchlogs.GetLogEventsInput, optFns ...func(*cloudwatchlogs.Options)) (*cloudwatchlogs.GetLogEventsOutput, error)
17+
}
18+
1219
// GetCloudWatchLogEntriesContextE returns the CloudWatch log messages in the given region for the given log stream and log group.
1320
// The ctx parameter supports cancellation and timeouts.
1421
func GetCloudWatchLogEntriesContextE(t testing.TestingT, ctx context.Context, awsRegion string, logStreamName string, logGroupName string) ([]string, error) {
@@ -17,6 +24,13 @@ func GetCloudWatchLogEntriesContextE(t testing.TestingT, ctx context.Context, aw
1724
return nil, err
1825
}
1926

27+
return GetCloudWatchLogEntriesWithClientContextE(t, ctx, client, logStreamName, logGroupName)
28+
}
29+
30+
// GetCloudWatchLogEntriesWithClientContextE returns the CloudWatch log messages for the given log
31+
// stream and log group using the provided CloudWatch Logs client.
32+
// The ctx parameter supports cancellation and timeouts.
33+
func GetCloudWatchLogEntriesWithClientContextE(t testing.TestingT, ctx context.Context, client CloudWatchLogsAPI, logStreamName string, logGroupName string) ([]string, error) {
2034
output, err := client.GetLogEvents(ctx, &cloudwatchlogs.GetLogEventsInput{
2135
LogGroupName: aws.String(logGroupName),
2236
LogStreamName: aws.String(logStreamName),

modules/aws/cloudwatch_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package aws_test
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
8+
awsSDK "github.com/aws/aws-sdk-go-v2/aws"
9+
"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
10+
"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
11+
"github.com/stretchr/testify/require"
12+
13+
aws "github.com/gruntwork-io/terratest/modules/aws"
14+
)
15+
16+
// mockCloudWatchLogsClient is a test double for aws.CloudWatchLogsAPI that returns canned responses.
17+
type mockCloudWatchLogsClient struct {
18+
GetLogEventsOutput *cloudwatchlogs.GetLogEventsOutput
19+
GetLogEventsErr error
20+
}
21+
22+
func (m *mockCloudWatchLogsClient) GetLogEvents(_ context.Context, _ *cloudwatchlogs.GetLogEventsInput, _ ...func(*cloudwatchlogs.Options)) (*cloudwatchlogs.GetLogEventsOutput, error) {
23+
if m.GetLogEventsErr != nil {
24+
return nil, m.GetLogEventsErr
25+
}
26+
27+
return m.GetLogEventsOutput, nil
28+
}
29+
30+
func TestGetCloudWatchLogEntriesWithClientContextE(t *testing.T) {
31+
t.Parallel()
32+
33+
tests := map[string]struct {
34+
client *mockCloudWatchLogsClient
35+
expected []string
36+
wantErr bool
37+
}{
38+
"returns messages preserving order": {
39+
client: &mockCloudWatchLogsClient{
40+
GetLogEventsOutput: &cloudwatchlogs.GetLogEventsOutput{
41+
Events: []types.OutputLogEvent{
42+
{Message: awsSDK.String("first line")},
43+
{Message: awsSDK.String("second line")},
44+
{Message: awsSDK.String("third line")},
45+
},
46+
},
47+
},
48+
expected: []string{"first line", "second line", "third line"},
49+
},
50+
"returns nil slice on empty events": {
51+
client: &mockCloudWatchLogsClient{
52+
GetLogEventsOutput: &cloudwatchlogs.GetLogEventsOutput{},
53+
},
54+
expected: nil,
55+
},
56+
"propagates api error": {
57+
client: &mockCloudWatchLogsClient{GetLogEventsErr: errors.New("ResourceNotFoundException")},
58+
wantErr: true,
59+
},
60+
}
61+
62+
for name, tc := range tests {
63+
t.Run(name, func(t *testing.T) {
64+
t.Parallel()
65+
66+
got, err := aws.GetCloudWatchLogEntriesWithClientContextE(t, context.Background(), tc.client, "stream", "group")
67+
if tc.wantErr {
68+
require.Error(t, err)
69+
70+
return
71+
}
72+
73+
require.NoError(t, err)
74+
require.Equal(t, tc.expected, got)
75+
})
76+
}
77+
}

modules/aws/dynamodb.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,31 @@ import (
1010
"github.com/stretchr/testify/require"
1111
)
1212

13+
// DynamoDBAPI is the subset of *dynamodb.Client operations used by the helpers in this file.
14+
// Declared as an interface so tests can substitute a mock; a real *dynamodb.Client satisfies it
15+
// automatically.
16+
type DynamoDBAPI interface {
17+
DescribeTable(ctx context.Context, params *dynamodb.DescribeTableInput, optFns ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error)
18+
DescribeTimeToLive(ctx context.Context, params *dynamodb.DescribeTimeToLiveInput, optFns ...func(*dynamodb.Options)) (*dynamodb.DescribeTimeToLiveOutput, error)
19+
ListTagsOfResource(ctx context.Context, params *dynamodb.ListTagsOfResourceInput, optFns ...func(*dynamodb.Options)) (*dynamodb.ListTagsOfResourceOutput, error)
20+
}
21+
1322
// GetDynamoDBTableTagsContextE fetches resource tags of a specified dynamoDB table.
1423
// The ctx parameter supports cancellation and timeouts.
1524
func GetDynamoDBTableTagsContextE(t testing.TestingT, ctx context.Context, region string, tableName string) ([]types.Tag, error) {
16-
table, err := GetDynamoDBTableContextE(t, ctx, region, tableName)
25+
client, err := NewDynamoDBClientContextE(t, ctx, region)
1726
if err != nil {
1827
return nil, err
1928
}
2029

21-
client, err := NewDynamoDBClientContextE(t, ctx, region)
30+
return GetDynamoDBTableTagsWithClientContextE(t, ctx, client, tableName)
31+
}
32+
33+
// GetDynamoDBTableTagsWithClientContextE fetches resource tags of a specified dynamoDB table using
34+
// the provided DynamoDB client.
35+
// The ctx parameter supports cancellation and timeouts.
36+
func GetDynamoDBTableTagsWithClientContextE(t testing.TestingT, ctx context.Context, client DynamoDBAPI, tableName string) ([]types.Tag, error) {
37+
table, err := GetDynamoDBTableWithClientContextE(t, ctx, client, tableName)
2238
if err != nil {
2339
return nil, err
2440
}
@@ -81,6 +97,13 @@ func GetDynamoDBTableTimeToLiveContextE(t testing.TestingT, ctx context.Context,
8197
return nil, err
8298
}
8399

100+
return GetDynamoDBTableTimeToLiveWithClientContextE(t, ctx, client, tableName)
101+
}
102+
103+
// GetDynamoDBTableTimeToLiveWithClientContextE fetches the TTL configuration of a specified
104+
// dynamoDB table using the provided DynamoDB client.
105+
// The ctx parameter supports cancellation and timeouts.
106+
func GetDynamoDBTableTimeToLiveWithClientContextE(t testing.TestingT, ctx context.Context, client DynamoDBAPI, tableName string) (*types.TimeToLiveDescription, error) {
84107
out, err := client.DescribeTimeToLive(ctx, &dynamodb.DescribeTimeToLiveInput{
85108
TableName: aws.String(tableName),
86109
})
@@ -124,6 +147,13 @@ func GetDynamoDBTableContextE(t testing.TestingT, ctx context.Context, region st
124147
return nil, err
125148
}
126149

150+
return GetDynamoDBTableWithClientContextE(t, ctx, client, tableName)
151+
}
152+
153+
// GetDynamoDBTableWithClientContextE fetches information about the specified dynamoDB table using
154+
// the provided DynamoDB client.
155+
// The ctx parameter supports cancellation and timeouts.
156+
func GetDynamoDBTableWithClientContextE(t testing.TestingT, ctx context.Context, client DynamoDBAPI, tableName string) (*types.TableDescription, error) {
127157
out, err := client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
128158
TableName: aws.String(tableName),
129159
})

0 commit comments

Comments
 (0)