Skip to content
Draft
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
39 changes: 39 additions & 0 deletions actioncheck/action_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package actioncheck

import (
"context"
"time"
)

// ActionCheck defines an interface for implementing test logic that checks action progress messages.
type ActionCheck interface {
// CheckAction should perform the action check.
CheckAction(context.Context, CheckActionRequest, *CheckActionResponse)
}

// CheckActionRequest is a request for an invoke of the CheckAction function.
type CheckActionRequest struct {
// ActionName is the name of the action being checked (e.g., "aws_lambda_invoke.test").
ActionName string

// Messages contains all progress messages captured for this action.
Messages []ProgressMessage
}

// CheckActionResponse is a response to an invoke of the CheckAction function.
type CheckActionResponse struct {
// Error is used to report the failure of an action check assertion.
Error error
}

// ProgressMessage represents a single progress message from an action.
type ProgressMessage struct {
// Message is the progress message content.
Message string

// Timestamp is when the message was captured.
Timestamp time.Time
}
5 changes: 5 additions & 0 deletions actioncheck/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

// Package actioncheck contains checks for Terraform action progress messages.
package actioncheck
42 changes: 42 additions & 0 deletions actioncheck/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package actioncheck_test

import (
"github.com/hashicorp/terraform-plugin-testing/actioncheck"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

// Example of how to use action checks in a test case
func ExampleExpectProgressMessageContains() {
// This is how you would use action checks in a real test
_ = resource.TestCase{
Steps: []resource.TestStep{
{
Config: `
action "aws_lambda_invoke" "test" {
config {
function_name = "my-function"
payload = "{\"key\":\"value\"}"
log_type = "Tail"
}
}
`,
ActionChecks: []actioncheck.ActionCheck{
// Check that the action produces a message containing log output
resource.TestCheckProgressMessageContains("aws_lambda_invoke.test", "Lambda function logs:"),

// Check that we get exactly 2 progress messages (success + logs)
resource.TestCheckProgressMessageCount("aws_lambda_invoke.test", 2),

// Check that messages appear in the expected sequence
resource.TestCheckProgressMessageSequence("aws_lambda_invoke.test", []string{
"invoked successfully",
"Lambda function logs:",
}),
},
},
},
}
}
37 changes: 37 additions & 0 deletions actioncheck/expect_progress_count.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package actioncheck

import (
"context"
"fmt"
)

var _ ActionCheck = expectProgressCount{}

// expectProgressCount is an ActionCheck that verifies the expected number of progress messages.
type expectProgressCount struct {
actionName string
expectedCount int
}

// CheckAction implements the ActionCheck interface.
func (e expectProgressCount) CheckAction(ctx context.Context, req CheckActionRequest, resp *CheckActionResponse) {
if req.ActionName != e.actionName {
return
}

actualCount := len(req.Messages)
if actualCount != e.expectedCount {
resp.Error = fmt.Errorf("expected action %s to have %d progress messages, but got %d", e.actionName, e.expectedCount, actualCount)
}
}

// ExpectProgressCount returns an ActionCheck that verifies the expected number of progress messages.
func ExpectProgressCount(actionName string, expectedCount int) ActionCheck {
return expectProgressCount{
actionName: actionName,
expectedCount: expectedCount,
}
}
41 changes: 41 additions & 0 deletions actioncheck/expect_progress_message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package actioncheck

import (
"context"
"fmt"
"strings"
)

var _ ActionCheck = expectProgressMessageContains{}

// expectProgressMessageContains is an ActionCheck that verifies that at least one progress message contains the expected content.
type expectProgressMessageContains struct {
actionName string
expectedContent string
}

// CheckAction implements the ActionCheck interface.
func (e expectProgressMessageContains) CheckAction(ctx context.Context, req CheckActionRequest, resp *CheckActionResponse) {
if req.ActionName != e.actionName {
return
}

for _, message := range req.Messages {
if strings.Contains(message.Message, e.expectedContent) {
return // Found the expected content
}
}

resp.Error = fmt.Errorf("expected action %s to have progress message containing %q, but no matching message found", e.actionName, e.expectedContent)
}

// ExpectProgressMessageContains returns an ActionCheck that verifies that at least one progress message contains the expected content.
func ExpectProgressMessageContains(actionName, expectedContent string) ActionCheck {
return expectProgressMessageContains{
actionName: actionName,
expectedContent: expectedContent,
}
}
50 changes: 50 additions & 0 deletions actioncheck/expect_progress_message_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package actioncheck_test

import (
"context"
"testing"
"time"

"github.com/hashicorp/terraform-plugin-testing/actioncheck"
)

func TestExpectProgressMessageContains_Success(t *testing.T) {
t.Parallel()
check := actioncheck.ExpectProgressMessageContains("test_action", "expected content")

req := actioncheck.CheckActionRequest{
ActionName: "test_action",
Messages: []actioncheck.ProgressMessage{
{Message: "some expected content here", Timestamp: time.Now()},
},
}

resp := &actioncheck.CheckActionResponse{}
check.CheckAction(context.Background(), req, resp)

if resp.Error != nil {
t.Errorf("Expected no error, got: %v", resp.Error)
}
}

func TestExpectProgressMessageContains_Failure(t *testing.T) {
t.Parallel()
check := actioncheck.ExpectProgressMessageContains("test_action", "missing content")

req := actioncheck.CheckActionRequest{
ActionName: "test_action",
Messages: []actioncheck.ProgressMessage{
{Message: "some other content", Timestamp: time.Now()},
},
}

resp := &actioncheck.CheckActionResponse{}
check.CheckAction(context.Background(), req, resp)

if resp.Error == nil {
t.Error("Expected error, got none")
}
}
45 changes: 45 additions & 0 deletions actioncheck/expect_progress_sequence.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package actioncheck

import (
"context"
"fmt"
"strings"
)

var _ ActionCheck = expectProgressSequence{}

// expectProgressSequence is an ActionCheck that verifies progress messages appear in the expected sequence.
type expectProgressSequence struct {
actionName string
expectedSequence []string
}

// CheckAction implements the ActionCheck interface.
func (e expectProgressSequence) CheckAction(ctx context.Context, req CheckActionRequest, resp *CheckActionResponse) {
if req.ActionName != e.actionName {
return
}

sequenceIndex := 0
for _, message := range req.Messages {
if sequenceIndex < len(e.expectedSequence) && strings.Contains(message.Message, e.expectedSequence[sequenceIndex]) {
sequenceIndex++
}
}

if sequenceIndex != len(e.expectedSequence) {
resp.Error = fmt.Errorf("expected action %s to have progress messages in sequence %v, but only found %d of %d expected messages",
e.actionName, e.expectedSequence, sequenceIndex, len(e.expectedSequence))
}
}

// ExpectProgressSequence returns an ActionCheck that verifies progress messages appear in the expected sequence.
func ExpectProgressSequence(actionName string, expectedSequence []string) ActionCheck {
return expectProgressSequence{
actionName: actionName,
expectedSequence: expectedSequence,
}
}
2 changes: 1 addition & 1 deletion helper/acctest/random.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func RandString(strlen int) string {
// the charset provided
func RandStringFromCharSet(strlen int, charSet string) string {
result := make([]byte, strlen)
for i := 0; i < strlen; i++ {
for i := range strlen {
result[i] = charSet[RandIntRange(0, len(charSet))]
}
return string(result)
Expand Down
4 changes: 2 additions & 2 deletions helper/resource/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (
// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.NotFoundError.
type NotFoundError struct {
LastError error
LastRequest interface{}
LastResponse interface{}
LastRequest any
LastResponse any
Message string
Retries int
}
Expand Down
2 changes: 1 addition & 1 deletion helper/resource/id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestUniqueId(t *testing.T) {
iterations := 10000
ids := make(map[string]struct{})
var id, lastId string
for i := 0; i < iterations; i++ {
for range iterations {
id = UniqueId()

if _, ok := ids[id]; ok {
Expand Down
2 changes: 1 addition & 1 deletion helper/resource/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"encoding/json"
)

func unmarshalJSON(data []byte, v interface{}) error {
func unmarshalJSON(data []byte, v any) error {
dec := json.NewDecoder(bytes.NewReader(data))
dec.UseNumber()
return dec.Decode(v)
Expand Down
Loading