mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-21 18:10:30 -04:00
151 lines
5.7 KiB
Go
151 lines
5.7 KiB
Go
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package command
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/apparentlymart/go-versions/versions"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/hashicorp/cli"
|
|
tfaddr "github.com/hashicorp/terraform-registry-address"
|
|
"github.com/hashicorp/terraform/internal/depsfile"
|
|
"github.com/hashicorp/terraform/internal/getproviders/providerreqs"
|
|
)
|
|
|
|
// This tests combining locks from config and state. Locks derived from state are always unconstrained, i.e. no version constraint data,
|
|
// so this test
|
|
func Test_mergeLockedDependencies_config_and_state(t *testing.T) {
|
|
|
|
providerA := tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "my-org", "providerA")
|
|
providerB := tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "my-org", "providerB")
|
|
v1_0_0 := providerreqs.MustParseVersion("1.0.0")
|
|
versionConstraintv1, _ := providerreqs.ParseVersionConstraints("1.0.0")
|
|
hashesProviderA := []providerreqs.Hash{providerreqs.MustParseHash("providerA:this-is-providerA")}
|
|
hashesProviderB := []providerreqs.Hash{providerreqs.MustParseHash("providerB:this-is-providerB")}
|
|
|
|
var versionUnconstrained providerreqs.VersionConstraints = nil
|
|
noVersion := versions.Version{}
|
|
|
|
cases := map[string]struct {
|
|
configLocks *depsfile.Locks
|
|
stateLocks *depsfile.Locks
|
|
expectedLocks *depsfile.Locks
|
|
}{
|
|
"no locks when all inputs empty": {
|
|
configLocks: depsfile.NewLocks(),
|
|
stateLocks: depsfile.NewLocks(),
|
|
expectedLocks: depsfile.NewLocks(),
|
|
},
|
|
"when provider only described in config, output locks have matching constraints": {
|
|
configLocks: func() *depsfile.Locks {
|
|
configLocks := depsfile.NewLocks()
|
|
configLocks.SetProvider(providerA, v1_0_0, versionConstraintv1, hashesProviderA)
|
|
return configLocks
|
|
}(),
|
|
stateLocks: depsfile.NewLocks(),
|
|
expectedLocks: func() *depsfile.Locks {
|
|
combinedLocks := depsfile.NewLocks()
|
|
combinedLocks.SetProvider(providerA, v1_0_0, versionConstraintv1, hashesProviderA)
|
|
return combinedLocks
|
|
}(),
|
|
},
|
|
"when provider only described in state, output locks are unconstrained": {
|
|
configLocks: depsfile.NewLocks(),
|
|
stateLocks: func() *depsfile.Locks {
|
|
stateLocks := depsfile.NewLocks()
|
|
stateLocks.SetProvider(providerA, noVersion, versionUnconstrained, hashesProviderA)
|
|
return stateLocks
|
|
}(),
|
|
expectedLocks: func() *depsfile.Locks {
|
|
combinedLocks := depsfile.NewLocks()
|
|
combinedLocks.SetProvider(providerA, noVersion, versionUnconstrained, hashesProviderA)
|
|
return combinedLocks
|
|
}(),
|
|
},
|
|
"different providers present in state and config are combined, with version constraints kept on config providers": {
|
|
configLocks: func() *depsfile.Locks {
|
|
configLocks := depsfile.NewLocks()
|
|
configLocks.SetProvider(providerA, v1_0_0, versionConstraintv1, hashesProviderA)
|
|
return configLocks
|
|
}(),
|
|
stateLocks: func() *depsfile.Locks {
|
|
stateLocks := depsfile.NewLocks()
|
|
|
|
// Imagine that the state locks contain:
|
|
// 1) provider for resources in the config
|
|
stateLocks.SetProvider(providerA, noVersion, versionUnconstrained, hashesProviderA)
|
|
|
|
// 2) also, a provider that's deleted from the config and only present in state
|
|
stateLocks.SetProvider(providerB, noVersion, versionUnconstrained, hashesProviderB)
|
|
|
|
return stateLocks
|
|
}(),
|
|
expectedLocks: func() *depsfile.Locks {
|
|
combinedLocks := depsfile.NewLocks()
|
|
combinedLocks.SetProvider(providerA, v1_0_0, versionConstraintv1, hashesProviderA) // version constraint preserved
|
|
combinedLocks.SetProvider(providerB, noVersion, versionUnconstrained, hashesProviderB) // sourced from state only
|
|
return combinedLocks
|
|
}(),
|
|
},
|
|
}
|
|
|
|
for tn, tc := range cases {
|
|
t.Run(tn, func(t *testing.T) {
|
|
// Use tmp dir as we're creating lock files in the test
|
|
td := t.TempDir()
|
|
t.Chdir(td)
|
|
|
|
ui := new(cli.MockUi)
|
|
view, _ := testView(t)
|
|
|
|
m := Meta{
|
|
Ui: ui,
|
|
View: view,
|
|
}
|
|
|
|
// Code under test - combine deps from state with prior deps from config
|
|
// mergedLocks := m.mergeLockedDependencies(tc.configLocks, tc.stateLocks)
|
|
mergedLocks := m.mergeLockedDependencies(tc.configLocks, tc.stateLocks)
|
|
|
|
// We cannot use (l *depsfile.Locks) Equal here as it doesn't compare version constraints
|
|
// Instead, inspect entries directly
|
|
if len(mergedLocks.AllProviders()) != len(tc.expectedLocks.AllProviders()) {
|
|
t.Fatalf("expected merged dependencies to include %d providers, but got %d:\n %#v",
|
|
len(tc.expectedLocks.AllProviders()),
|
|
len(mergedLocks.AllProviders()),
|
|
mergedLocks.AllProviders(),
|
|
)
|
|
}
|
|
for _, lock := range tc.expectedLocks.AllProviders() {
|
|
match := mergedLocks.Provider(lock.Provider())
|
|
if match == nil {
|
|
t.Fatalf("expected merged dependencies to include provider %s, but it's missing", lock.Provider())
|
|
}
|
|
if len(match.VersionConstraints()) != len(lock.VersionConstraints()) {
|
|
t.Fatalf("detected a problem with version constraints for provider %s, got: %d, want %d",
|
|
lock.Provider(),
|
|
len(match.VersionConstraints()),
|
|
len(lock.VersionConstraints()),
|
|
)
|
|
}
|
|
if len(match.VersionConstraints()) > 0 && len(lock.VersionConstraints()) > 0 {
|
|
gotConstraints := match.VersionConstraints()[0]
|
|
wantConstraints := lock.VersionConstraints()[0]
|
|
|
|
if gotConstraints.Boundary.String() != wantConstraints.Boundary.String() {
|
|
t.Fatalf("expected merged dependencies to include provider %s with version constraint %v, but instead got %v",
|
|
lock.Provider(),
|
|
gotConstraints.Boundary.String(),
|
|
wantConstraints.Boundary.String(),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
if diff := cmp.Diff(tc.expectedLocks, mergedLocks); diff != "" {
|
|
t.Errorf("difference in file contents detected\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|