Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
92 changes: 48 additions & 44 deletions test/e2e/item_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/hashicorp/terraform-plugin-testing/terraform"

tfconfig "github.com/1Password/terraform-provider-onepassword/v2/test/e2e/terraform/config"
"github.com/1Password/terraform-provider-onepassword/v2/test/e2e/utils/checks"
"github.com/1Password/terraform-provider-onepassword/v2/test/e2e/utils/uuid"
)

type testResourceItem struct {
Expand Down Expand Up @@ -115,17 +117,17 @@ func TestAccItemResource(t *testing.T) {
// Build check functions for create step
createChecks := []resource.TestCheckFunc{
logStep(t, "CREATE"),
captureItemUUID(t, "onepassword_item.test_item", &itemUUID),
uuid.CaptureItemUUID(t, "onepassword_item.test_item", &itemUUID),
}
bcCreate := buildItemChecks("onepassword_item.test_item", item.Attrs)
bcCreate := checks.BuildItemChecks("onepassword_item.test_item", item.Attrs)
createChecks = append(createChecks, bcCreate...)

// Build checks for update step
updateChecks := []resource.TestCheckFunc{
logStep(t, "UPDATE"),
verifyItemUUIDUnchanged(t, "onepassword_item.test_item", &itemUUID),
uuid.VerifyItemUUIDUnchanged(t, "onepassword_item.test_item", &itemUUID),
}
bcUpdate := buildItemChecks("onepassword_item.test_item", testItemsUpdatedAttrs[tc.category])
bcUpdate := checks.BuildItemChecks("onepassword_item.test_item", testItemsUpdatedAttrs[tc.category])
updateChecks = append(updateChecks, bcUpdate...)

resource.Test(t, resource.TestCase{
Expand Down Expand Up @@ -178,57 +180,59 @@ func TestAccItemResource(t *testing.T) {
}
}

// logStep logs the current test step for easier test debugging
func logStep(t *testing.T, step string) resource.TestCheckFunc {
return func(s *terraform.State) error {
t.Log(step)
return nil
func TestAccItemResourcePasswordGeneration(t *testing.T) {
testCases := []struct {
name string
recipe map[string]any
}{
{name: "Length32", recipe: map[string]any{"length": 32, "symbols": false, "digits": false, "letters": true}},
{name: "Length16", recipe: map[string]any{"length": 16, "symbols": false, "digits": false, "letters": true}},
{name: "WithSymbols", recipe: map[string]any{"length": 20, "symbols": true, "digits": false, "letters": false}},
{name: "WithoutSymbols", recipe: map[string]any{"length": 20, "symbols": false, "digits": true, "letters": true}},
{name: "WithDigits", recipe: map[string]any{"length": 20, "symbols": false, "digits": true, "letters": false}},
{name: "WithoutDigits", recipe: map[string]any{"length": 20, "symbols": true, "digits": false, "letters": true}},
{name: "WithLetters", recipe: map[string]any{"length": 20, "symbols": false, "digits": false, "letters": true}},
{name: "WithoutLetters", recipe: map[string]any{"length": 20, "symbols": true, "digits": true, "letters": false}},
}
}

// buildItemChecks creates a list of test assertions to verify item attributes
func buildItemChecks(resourceName string, attrs map[string]any) []resource.TestCheckFunc {
checks := []resource.TestCheckFunc{
resource.TestCheckResourceAttrSet(resourceName, "uuid"),
resource.TestCheckResourceAttrSet(resourceName, "id"),
}
// Test both Login and Password items
items := []op.ItemCategory{op.Login, op.Password}

for attr, expectedValue := range attrs {
// Check if the value is a slice and iterate over it
if slice, ok := expectedValue.([]string); ok {
checks = append(checks, resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.#", attr), fmt.Sprintf("%d", len(slice))))
for _, item := range items {
item := testItemsToCreate[item]

for i, val := range slice {
checks = append(checks, resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.%d", attr, i), val))
}
} else {
checks = append(checks, resource.TestCheckResourceAttr(resourceName, attr, fmt.Sprintf("%v", expectedValue)))
}
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("%s_%s", tc.name, item.Attrs["category"]), func(t *testing.T) {

return checks
}
attrs := map[string]any{
"title": item.Attrs["title"],
"category": item.Attrs["category"],
"password_recipe": tc.recipe,
}

// captureItemUUID captures the UUID of a resource item
func captureItemUUID(t *testing.T, resourceName string, uuidPtr *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs := s.RootModule().Resources[resourceName]
*uuidPtr = rs.Primary.Attributes["uuid"]
checks := checks.BuildPasswordRecipeChecks("onepassword_item.test_item", tc.recipe)

return nil
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: tfconfig.CreateConfigBuilder()(
tfconfig.ProviderConfig(),
tfconfig.ItemResourceConfig(testVaultID, attrs),
),
Check: resource.ComposeAggregateTestCheckFunc(checks...),
},
},
})
})
}
}
}

// verifyItemUUIDUnchanged verifies that the resource UUID matches the expected UUID
func verifyItemUUIDUnchanged(t *testing.T, resourceName string, expectedUUID *string) resource.TestCheckFunc {
// logStep logs the current test step for easier test debugging
func logStep(t *testing.T, step string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs := s.RootModule().Resources[resourceName]
currentUUID := rs.Primary.Attributes["uuid"]

if currentUUID != *expectedUUID {
return fmt.Errorf("UUID changed from %s to %s - resource was replaced instead of updated", *expectedUUID, currentUUID)
}

t.Log(step)
return nil
}
}
11 changes: 11 additions & 0 deletions test/e2e/terraform/config/item_resource_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ func formatTerraformAttribute(key string, value any) string {
}
return fmt.Sprintf("\n %s = [%s]", key, strings.Join(quotedItems, ", "))

case reflect.Map:
blockStr := fmt.Sprintf("\n %s {", key)
attributes := value.(map[string]any)

for k, v := range attributes {
blockStr += formatTerraformAttribute(k, v)
}

blockStr += "\n }"
return blockStr

case reflect.Bool:
return fmt.Sprintf("\n %s = %t", key, value)

Expand Down
30 changes: 30 additions & 0 deletions test/e2e/utils/checks/attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package checks

import (
"fmt"

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

// BuildItemChecks creates a list of test assertions to verify item attributes
func BuildItemChecks(resourceName string, attrs map[string]any) []resource.TestCheckFunc {
checks := []resource.TestCheckFunc{
resource.TestCheckResourceAttrSet(resourceName, "uuid"),
resource.TestCheckResourceAttrSet(resourceName, "id"),
}

for attr, expectedValue := range attrs {
// Check if the value is a slice and iterate over it
if slice, ok := expectedValue.([]string); ok {
checks = append(checks, resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.#", attr), fmt.Sprintf("%d", len(slice))))

for i, val := range slice {
checks = append(checks, resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.%d", attr, i), val))
}
} else {
checks = append(checks, resource.TestCheckResourceAttr(resourceName, attr, fmt.Sprintf("%v", expectedValue)))
}
}

return checks
}
63 changes: 63 additions & 0 deletions test/e2e/utils/checks/password_generation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package checks

import (
"fmt"
"regexp"

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

// BuildPasswordRecipeChecks creates a list of test assertions to verify password recipe attributes
func BuildPasswordRecipeChecks(resourceName string, recipe map[string]any) []resource.TestCheckFunc {
checks := []resource.TestCheckFunc{
resource.TestCheckResourceAttr(resourceName, "password_recipe.#", "1"),
}

length, _ := recipe["length"].(int)
symbols, _ := recipe["symbols"].(bool)
digits, _ := recipe["digits"].(bool)
letters, _ := recipe["letters"].(bool)

if length > 0 {
checks = append(checks, checkPasswordPattern(resourceName, fmt.Sprintf("^.{%d}$", length), "length"))
}

if symbols {
checks = append(checks, checkPasswordPattern(resourceName, `[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~`+"`"+`]`, "symbols"))
} else {
checks = append(checks, checkPasswordPattern(resourceName, `^[^!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~\`+"`"+`]+$`, "symbols"))
}

if digits {
checks = append(checks, checkPasswordPattern(resourceName, `[0-9]`, "digits"))
} else {
checks = append(checks, checkPasswordPattern(resourceName, `^[^0-9]+$`, "digits"))
}

if letters {
checks = append(checks, checkPasswordPattern(resourceName, `[a-zA-Z]`, "letters"))
} else {
checks = append(checks, checkPasswordPattern(resourceName, `^[^a-zA-Z]+$`, "letters"))
}

return checks
}

// checkPasswordPattern creates a test assertion to verify password pattern with regex
func checkPasswordPattern(resourceName, pattern, description string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("resource not found: %s", resourceName)
}

password := rs.Primary.Attributes["password"]
matched, _ := regexp.MatchString(pattern, password)

if !matched {
return fmt.Errorf("password does not match expected pattern: %s", description)
}
return nil
}
}
51 changes: 51 additions & 0 deletions test/e2e/utils/uuid/uuid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package uuid

import (
"fmt"
"testing"

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

// CaptureItemUUID captures the UUID of a resource item
func CaptureItemUUID(t *testing.T, resourceName string, uuidPtr *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]

if !ok {
return fmt.Errorf("resource not found: %s", resourceName)
}

*uuidPtr = rs.Primary.Attributes["uuid"]

if *uuidPtr == "" {
return fmt.Errorf("UUID is empty")
}

return nil
}
}

// VerifyItemUUIDUnchanged verifies that the resource UUID matches the expected UUID
func VerifyItemUUIDUnchanged(t *testing.T, resourceName string, expectedUUID *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]

if !ok {
return fmt.Errorf("resource not found: %s", resourceName)
}

currentUUID := rs.Primary.Attributes["uuid"]

if currentUUID == "" {
return fmt.Errorf("UUID is empty")
}

if currentUUID != *expectedUUID {
return fmt.Errorf("UUID changed from %s to %s - resource was replaced instead of updated", *expectedUUID, currentUUID)
}

return nil
}
}
Loading