| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: BUSL-1.1 |
| |
| package graph |
| |
| import ( |
| "bytes" |
| "context" |
| "fmt" |
| "strings" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| "github.com/hashicorp/hcl/v2" |
| "github.com/hashicorp/hcl/v2/hclparse" |
| "github.com/hashicorp/terraform/internal/configs" |
| "github.com/hashicorp/terraform/internal/moduletest" |
| ) |
| |
| func TestTransformForTest(t *testing.T) { |
| |
| str := func(providers map[string]string) string { |
| var buffer bytes.Buffer |
| for key, config := range providers { |
| buffer.WriteString(fmt.Sprintf("%s: %s\n", key, config)) |
| } |
| return buffer.String() |
| } |
| |
| convertToProviders := func(t *testing.T, contents map[string]string) map[string]*configs.Provider { |
| t.Helper() |
| |
| providers := make(map[string]*configs.Provider) |
| for key, content := range contents { |
| parser := hclparse.NewParser() |
| file, diags := parser.ParseHCL([]byte(content), fmt.Sprintf("%s.hcl", key)) |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| provider := &configs.Provider{ |
| Config: file.Body, |
| } |
| |
| parts := strings.Split(key, ".") |
| provider.Name = parts[0] |
| if len(parts) > 1 { |
| provider.Alias = parts[1] |
| } |
| |
| providers[key] = provider |
| } |
| return providers |
| } |
| |
| tcs := map[string]struct { |
| configProviders map[string]string |
| fileProviders map[string]string |
| runProviders []configs.PassedProviderConfig |
| expectedProviders map[string]string |
| expectedErrors []string |
| }{ |
| "empty": { |
| configProviders: make(map[string]string), |
| expectedProviders: make(map[string]string), |
| }, |
| "only providers in config": { |
| configProviders: map[string]string{ |
| "foo": "source = \"config\"", |
| "bar": "source = \"config\"", |
| }, |
| expectedProviders: map[string]string{ |
| "foo": "source = \"config\"", |
| "bar": "source = \"config\"", |
| }, |
| }, |
| "only providers in test file": { |
| configProviders: make(map[string]string), |
| fileProviders: map[string]string{ |
| "foo": "source = \"testfile\"", |
| "bar": "source = \"testfile\"", |
| }, |
| expectedProviders: map[string]string{ |
| "foo": "source = \"testfile\"", |
| "bar": "source = \"testfile\"", |
| }, |
| }, |
| "only providers in run block": { |
| configProviders: make(map[string]string), |
| runProviders: []configs.PassedProviderConfig{ |
| { |
| InChild: &configs.ProviderConfigRef{ |
| Name: "foo", |
| }, |
| InParent: &configs.ProviderConfigRef{ |
| Name: "bar", |
| }, |
| }, |
| }, |
| expectedProviders: make(map[string]string), |
| expectedErrors: []string{ |
| ":0,0-0: Missing provider definition for bar; This provider block references a provider definition that does not exist.", |
| }, |
| }, |
| "subset of providers in test file": { |
| configProviders: make(map[string]string), |
| fileProviders: map[string]string{ |
| "bar": "source = \"testfile\"", |
| }, |
| runProviders: []configs.PassedProviderConfig{ |
| { |
| InChild: &configs.ProviderConfigRef{ |
| Name: "foo", |
| }, |
| InParent: &configs.ProviderConfigRef{ |
| Name: "bar", |
| }, |
| }, |
| }, |
| expectedProviders: map[string]string{ |
| "foo": "source = \"testfile\"", |
| }, |
| }, |
| "overrides providers in config": { |
| configProviders: map[string]string{ |
| "foo": "source = \"config\"", |
| "bar": "source = \"config\"", |
| }, |
| fileProviders: map[string]string{ |
| "bar": "source = \"testfile\"", |
| }, |
| expectedProviders: map[string]string{ |
| "foo": "source = \"config\"", |
| "bar": "source = \"testfile\"", |
| }, |
| }, |
| "overrides subset of providers in config": { |
| configProviders: map[string]string{ |
| "foo": "source = \"config\"", |
| "bar": "source = \"config\"", |
| }, |
| fileProviders: map[string]string{ |
| "foo": "source = \"testfile\"", |
| "bar": "source = \"testfile\"", |
| }, |
| runProviders: []configs.PassedProviderConfig{ |
| { |
| InChild: &configs.ProviderConfigRef{ |
| Name: "bar", |
| }, |
| InParent: &configs.ProviderConfigRef{ |
| Name: "bar", |
| }, |
| }, |
| }, |
| expectedProviders: map[string]string{ |
| "foo": "source = \"config\"", |
| "bar": "source = \"testfile\"", |
| }, |
| }, |
| "handles aliases": { |
| configProviders: map[string]string{ |
| "foo.primary": "source = \"config\"", |
| "foo.secondary": "source = \"config\"", |
| }, |
| fileProviders: map[string]string{ |
| "foo": "source = \"testfile\"", |
| }, |
| runProviders: []configs.PassedProviderConfig{ |
| { |
| InChild: &configs.ProviderConfigRef{ |
| Name: "foo.secondary", |
| }, |
| InParent: &configs.ProviderConfigRef{ |
| Name: "foo", |
| }, |
| }, |
| }, |
| expectedProviders: map[string]string{ |
| "foo.primary": "source = \"config\"", |
| "foo.secondary": "source = \"testfile\"", |
| }, |
| }, |
| "ignores unexpected providers in test file": { |
| configProviders: make(map[string]string), |
| fileProviders: map[string]string{ |
| "foo": "source = \"testfile\"", |
| "bar": "source = \"testfile\"", |
| }, |
| expectedProviders: map[string]string{ |
| "foo": "source = \"testfile\"", |
| }, |
| }, |
| } |
| for name, tc := range tcs { |
| t.Run(name, func(t *testing.T) { |
| config := &configs.Config{ |
| Module: &configs.Module{ |
| ProviderConfigs: convertToProviders(t, tc.configProviders), |
| }, |
| } |
| |
| file := &moduletest.File{ |
| Config: &configs.TestFile{ |
| Providers: convertToProviders(t, tc.fileProviders), |
| }, |
| } |
| |
| run := &moduletest.Run{ |
| Config: &configs.TestRun{ |
| Providers: tc.runProviders, |
| }, |
| ModuleConfig: config, |
| } |
| |
| availableProviders := make(map[string]bool, len(tc.expectedProviders)) |
| for provider := range tc.expectedProviders { |
| availableProviders[provider] = true |
| } |
| |
| ctx := NewEvalContext(&EvalContextOpts{ |
| CancelCtx: context.Background(), |
| StopCtx: context.Background(), |
| }) |
| ctx.configProviders = map[string]map[string]bool{ |
| run.GetModuleConfigID(): availableProviders, |
| } |
| |
| diags := TransformConfigForRun(ctx, run, file) |
| |
| var actualErrs []string |
| for _, err := range diags.Errs() { |
| actualErrs = append(actualErrs, err.Error()) |
| } |
| if diff := cmp.Diff(actualErrs, tc.expectedErrors, cmpopts.IgnoreUnexported()); len(diff) > 0 { |
| t.Errorf("unmatched errors\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", strings.Join(tc.expectedErrors, "\n"), strings.Join(actualErrs, "\n"), diff) |
| } |
| |
| converted := make(map[string]string) |
| for key, provider := range config.Module.ProviderConfigs { |
| content, err := provider.Config.Content(&hcl.BodySchema{ |
| Attributes: []hcl.AttributeSchema{ |
| {Name: "source", Required: true}, |
| }, |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| source, diags := content.Attributes["source"].Expr.Value(nil) |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| converted[key] = fmt.Sprintf("source = %q", source.AsString()) |
| } |
| |
| if diff := cmp.Diff(tc.expectedProviders, converted); len(diff) > 0 { |
| t.Errorf("%s\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", "after transform mismatch", str(tc.expectedProviders), str(converted), diff) |
| } |
| }) |
| } |
| } |