-
Notifications
You must be signed in to change notification settings - Fork 17
Add ActionChecks
#570
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add ActionChecks
#570
Changes from all commits
ca6d123
d7d486d
3a712c0
a3af31e
c4b9ee5
2a60496
68fc022
bba37b0
ed6e9a1
653ee5a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
| } | ||
| 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 |
| 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:"), | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
The examples as written do not work because the provider does not have access to the alias name so you can only reference messages by action type name. |
||||||
|
|
||||||
| // Check that we get exactly 2 progress messages (success + logs) | ||||||
| resource.TestCheckProgressMessageCount("aws_lambda_invoke.test", 2), | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| // Check that messages appear in the expected sequence | ||||||
| resource.TestCheckProgressMessageSequence("aws_lambda_invoke.test", []string{ | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| "invoked successfully", | ||||||
| "Lambda function logs:", | ||||||
| }), | ||||||
| }, | ||||||
| }, | ||||||
| }, | ||||||
| } | ||||||
| } | ||||||
| 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 | ||
| } | ||
|
Comment on lines
+21
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right now, if the provider developer mistypes the action name in the check or if the expected action never produces any progress messages, the check will still pass without an error. I think that we need to pass the entire map of messages from |
||
|
|
||
| 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, | ||
| } | ||
| } | ||
| 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, | ||
| } | ||
| } |
| 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") | ||
| } | ||
| } |
| 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, | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I wonder if the interface name should make it clear that these checks assert against intercepted provider messages rather than a standard Terraform artifact. Maybe there could be a future where there is a terraform-produced artifact for Actions and we would like a different interface for those types of checks? I don't have a strong opinion on this right now 🤔