// Copyright (c) The OpenTofu Authors // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package getproviders import ( "context" "testing" svchost "github.com/hashicorp/terraform-svchost" "github.com/opentofu/opentofu/internal/addrs" ) func TestMissingProviderSuggestion(t *testing.T) { // Most of these test cases rely on specific "magic" provider addresses // that are implemented by the fake registry source returned by // testRegistrySource. Refer to that function for more details on how // they work. t.Run("happy path", func(t *testing.T) { ctx := context.Background() source, _, close := testRegistrySource(t) defer close() // testRegistrySource handles -/legacy as a valid legacy provider // lookup mapping to legacycorp/legacy. legacyAddr := addrs.NewDefaultProvider("legacy") got := MissingProviderSuggestion( ctx, addrs.NewDefaultProvider("legacy"), source, Requirements{ legacyAddr: MustParseVersionConstraints(">= 1.0.0"), }, ) want := addrs.Provider{ Hostname: defaultRegistryHost, Namespace: "legacycorp", Type: "legacy", } if got != want { t.Errorf("wrong result\ngot: %s\nwant: %s", got, want) } }) t.Run("provider moved", func(t *testing.T) { ctx := context.Background() source, _, close := testRegistrySource(t) defer close() // testRegistrySource handles -/moved as a valid legacy provider // lookup mapping to hashicorp/moved but with an additional "redirect" // to acme/moved. This mimics how for some providers there is both // a copy under terraform-providers for v0.12 compatibility _and_ a // copy in some other namespace for v0.13 or later to use. Our naming // suggestions ignore the v0.12-compatible one and suggest the // other one. moved := addrs.NewDefaultProvider("moved") want := addrs.Provider{ Hostname: defaultRegistryHost, Namespace: "acme", Type: "moved", } got := MissingProviderSuggestion( ctx, moved, source, Requirements{ moved: MustParseVersionConstraints(">= 1.0.0"), }, ) if got != want { t.Errorf("wrong result\ngot: %s\nwant: %s", got, want) } // If a provider has moved, but there's provider requirements // for something of the same type, we'll return that one // and skip the legacy lookup process. In practice, // hopefully this is also "acme" but it's "zcme" here to // exercise the codepath want2 := addrs.Provider{ Hostname: defaultRegistryHost, Namespace: "zcme", Type: "moved", } got2 := MissingProviderSuggestion( ctx, moved, source, Requirements{ moved: MustParseVersionConstraints(">= 1.0.0"), want2: MustParseVersionConstraints(">= 1.0.0"), }, ) if got2 != want2 { t.Errorf("wrong result\ngot: %s\nwant: %s", got2, want2) } }) t.Run("invalid response", func(t *testing.T) { ctx := context.Background() source, _, close := testRegistrySource(t) defer close() // testRegistrySource handles -/invalid by returning an invalid // provider address, which MissingProviderSuggestion should reject // and behave as if there was no suggestion available. want := addrs.NewDefaultProvider("invalid") got := MissingProviderSuggestion( ctx, want, source, Requirements{ want: MustParseVersionConstraints(">= 1.0.0"), }, ) if got != want { t.Errorf("wrong result\ngot: %s\nwant: %s", got, want) } }) t.Run("another registry", func(t *testing.T) { ctx := context.Background() source, _, close := testRegistrySource(t) defer close() // Because this provider address isn't on registry.opentofu.org, // MissingProviderSuggestion won't even attempt to make a suggestion // for it. want := addrs.Provider{ Hostname: svchost.Hostname("example.com"), Namespace: "whatever", Type: "foo", } got := MissingProviderSuggestion( ctx, want, source, Requirements{ want: MustParseVersionConstraints(">= 1.0.0"), }, ) if got != want { t.Errorf("wrong result\ngot: %s\nwant: %s", got, want) } }) t.Run("another namespace", func(t *testing.T) { ctx := context.Background() source, _, close := testRegistrySource(t) defer close() // Because this provider address isn't in // registry.opentofu.org/hashicorp/..., MissingProviderSuggestion // will provide the same addr since there's no alternative in Requirements want := addrs.Provider{ Hostname: defaultRegistryHost, Namespace: "whatever", Type: "foo", } got := MissingProviderSuggestion( ctx, want, source, Requirements{ want: MustParseVersionConstraints(">= 1.0.0"), }, ) if got != want { t.Errorf("wrong result\ngot: %s\nwant: %s", got, want) } // If there is a provider required that has the same type, // but different namespace, we can suggest that foo := addrs.Provider{ Hostname: defaultRegistryHost, Namespace: "hashicorp", Type: "foo", } realFoo := addrs.Provider{ Hostname: defaultRegistryHost, Namespace: "acme", Type: "foo", } got2 := MissingProviderSuggestion( ctx, foo, source, Requirements{ foo: MustParseVersionConstraints(">= 1.0.0"), realFoo: MustParseVersionConstraints(">= 1.0.0"), }, ) if got2 != realFoo { t.Errorf("wrong result\ngot: %s\nwant: %s", got2, realFoo) } }) }