|
| 1 | +package acctest |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "strconv" |
| 6 | + "strings" |
| 7 | + "testing" |
| 8 | + "time" |
| 9 | + |
| 10 | + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" |
| 11 | + "github.com/scaleway/terraform-provider-scaleway/v2/internal/meta" |
| 12 | + "github.com/scaleway/terraform-provider-scaleway/v2/internal/transport" |
| 13 | + "github.com/scaleway/terraform-provider-scaleway/v2/scaleway" |
| 14 | + "github.com/stretchr/testify/require" |
| 15 | +) |
| 16 | + |
| 17 | +func PreCheck(_ *testing.T) {} |
| 18 | + |
| 19 | +type TestTools struct { |
| 20 | + T *testing.T |
| 21 | + Meta *meta.Meta |
| 22 | + ProviderFactories map[string]func() (*schema.Provider, error) |
| 23 | + Cleanup func() |
| 24 | +} |
| 25 | + |
| 26 | +func NewTestTools(t *testing.T) *TestTools { |
| 27 | + t.Helper() |
| 28 | + ctx := context.Background() |
| 29 | + // Create a http client with recording capabilities |
| 30 | + httpClient, cleanup, err := getHTTPRecoder(t, *UpdateCassettes) |
| 31 | + require.NoError(t, err) |
| 32 | + |
| 33 | + // Create meta that will be passed in the provider config |
| 34 | + m, err := meta.NewMeta(ctx, &meta.Config{ |
| 35 | + ProviderSchema: nil, |
| 36 | + TerraformVersion: "terraform-tests", |
| 37 | + HTTPClient: httpClient, |
| 38 | + }) |
| 39 | + require.NoError(t, err) |
| 40 | + |
| 41 | + if !*UpdateCassettes { |
| 42 | + tmp := 0 * time.Second |
| 43 | + transport.DefaultWaitRetryInterval = &tmp |
| 44 | + } |
| 45 | + |
| 46 | + return &TestTools{ |
| 47 | + T: t, |
| 48 | + Meta: m, |
| 49 | + ProviderFactories: map[string]func() (*schema.Provider, error){ |
| 50 | + "scaleway": func() (*schema.Provider, error) { |
| 51 | + return scaleway.Provider(&scaleway.ProviderConfig{Meta: m})(), nil |
| 52 | + }, |
| 53 | + }, |
| 54 | + Cleanup: cleanup, |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +// Test Generated name has format: "{prefix}-{generated_number} |
| 59 | +// example: test-acc-scaleway-project-3723338038624371236 |
| 60 | +func extractTestGeneratedNamePrefix(name string) string { |
| 61 | + // {prefix}-{generated} |
| 62 | + // ^ |
| 63 | + dashIndex := strings.LastIndex(name, "-") |
| 64 | + |
| 65 | + generated := name[dashIndex+1:] |
| 66 | + _, generatedToIntErr := strconv.ParseInt(generated, 10, 64) |
| 67 | + |
| 68 | + if dashIndex == -1 || generatedToIntErr != nil { |
| 69 | + // some are only {name} |
| 70 | + return name |
| 71 | + } |
| 72 | + |
| 73 | + // {prefix} |
| 74 | + return name[:dashIndex] |
| 75 | +} |
| 76 | + |
| 77 | +// Generated names have format: "tf-{prefix}-{generated1}-{generated2}" |
| 78 | +// example: tf-sg-gifted-yonath |
| 79 | +func extractGeneratedNamePrefix(name string) string { |
| 80 | + if strings.Count(name, "-") < 3 { |
| 81 | + return name |
| 82 | + } |
| 83 | + // tf-{prefix}-gifted-yonath |
| 84 | + name = strings.TrimPrefix(name, "tf-") |
| 85 | + |
| 86 | + // {prefix}-gifted-yonath |
| 87 | + // ^ |
| 88 | + dashIndex := strings.LastIndex(name, "-") |
| 89 | + name = name[:dashIndex] |
| 90 | + // {prefix}-gifted |
| 91 | + // ^ |
| 92 | + dashIndex = strings.LastIndex(name, "-") |
| 93 | + name = name[:dashIndex] |
| 94 | + return name |
| 95 | +} |
| 96 | + |
| 97 | +// compareJSONFieldsStrings compare two strings from request JSON bodies |
| 98 | +// has special case when string are terraform generated names |
| 99 | +func compareJSONFieldsStrings(expected, actual string) bool { |
| 100 | + expectedHandled := expected |
| 101 | + actualHandled := actual |
| 102 | + |
| 103 | + // Remove s3 url suffix to allow comparison |
| 104 | + if strings.HasSuffix(actual, ".s3-website.fr-par.scw.cloud") { |
| 105 | + actual = strings.TrimSuffix(actual, ".s3-website.fr-par.scw.cloud") |
| 106 | + expected = strings.TrimSuffix(expected, ".s3-website.fr-par.scw.cloud") |
| 107 | + } |
| 108 | + |
| 109 | + // Try to parse test generated name |
| 110 | + if strings.Contains(actual, "-") { |
| 111 | + expectedHandled = extractTestGeneratedNamePrefix(expected) |
| 112 | + actualHandled = extractTestGeneratedNamePrefix(actual) |
| 113 | + } |
| 114 | + |
| 115 | + // Try provider generated name |
| 116 | + if actualHandled == actual && strings.HasPrefix(actual, "tf-") { |
| 117 | + expectedHandled = extractGeneratedNamePrefix(expected) |
| 118 | + actualHandled = extractGeneratedNamePrefix(actual) |
| 119 | + } |
| 120 | + |
| 121 | + return expectedHandled == actualHandled |
| 122 | +} |
| 123 | + |
| 124 | +// compareJSONBodies compare two given maps that represent json bodies |
| 125 | +// returns true if both json are equivalent |
| 126 | +func compareJSONBodies(expected, actual map[string]interface{}) bool { |
| 127 | + // Check for each key in actual requests |
| 128 | + // Compare its value to cassette content if marshal-able to string |
| 129 | + for key := range actual { |
| 130 | + expectedValue, exists := expected[key] |
| 131 | + if !exists { |
| 132 | + // Actual request may contain a field that does not exist in cassette |
| 133 | + // New fields can appear in requests with new api features |
| 134 | + // We do not want to generate new cassettes for each new features |
| 135 | + continue |
| 136 | + } |
| 137 | + if !compareJSONFields(expectedValue, actual[key]) { |
| 138 | + return false |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + for key := range expected { |
| 143 | + _, exists := actual[key] |
| 144 | + if !exists && expected[key] != nil { |
| 145 | + // Fails match if cassettes contains a field not in actual requests |
| 146 | + // Fields should not disappear from requests unless a sdk breaking change |
| 147 | + // We ignore if field is nil in cassette as it could be an old deprecated and unused field |
| 148 | + return false |
| 149 | + } |
| 150 | + } |
| 151 | + return true |
| 152 | +} |
0 commit comments