github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/backend/remote/backend_test.go (about)

     1  package remote
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"strings"
     8  	"testing"
     9  
    10  	tfe "github.com/hashicorp/go-tfe"
    11  	version "github.com/hashicorp/go-version"
    12  	"github.com/hashicorp/terraform-svchost/disco"
    13  	"github.com/eliastor/durgaform/internal/backend"
    14  	"github.com/eliastor/durgaform/internal/tfdiags"
    15  	tfversion "github.com/eliastor/durgaform/version"
    16  	"github.com/zclconf/go-cty/cty"
    17  
    18  	backendLocal "github.com/eliastor/durgaform/internal/backend/local"
    19  )
    20  
    21  func TestRemote(t *testing.T) {
    22  	var _ backend.Enhanced = New(nil)
    23  	var _ backend.CLI = New(nil)
    24  }
    25  
    26  func TestRemote_backendDefault(t *testing.T) {
    27  	b, bCleanup := testBackendDefault(t)
    28  	defer bCleanup()
    29  
    30  	backend.TestBackendStates(t, b)
    31  	backend.TestBackendStateLocks(t, b, b)
    32  	backend.TestBackendStateForceUnlock(t, b, b)
    33  }
    34  
    35  func TestRemote_backendNoDefault(t *testing.T) {
    36  	b, bCleanup := testBackendNoDefault(t)
    37  	defer bCleanup()
    38  
    39  	backend.TestBackendStates(t, b)
    40  }
    41  
    42  func TestRemote_config(t *testing.T) {
    43  	cases := map[string]struct {
    44  		config  cty.Value
    45  		confErr string
    46  		valErr  string
    47  	}{
    48  		"with_a_nonexisting_organization": {
    49  			config: cty.ObjectVal(map[string]cty.Value{
    50  				"hostname":     cty.NullVal(cty.String),
    51  				"organization": cty.StringVal("nonexisting"),
    52  				"token":        cty.NullVal(cty.String),
    53  				"workspaces": cty.ObjectVal(map[string]cty.Value{
    54  					"name":   cty.StringVal("prod"),
    55  					"prefix": cty.NullVal(cty.String),
    56  				}),
    57  			}),
    58  			confErr: "organization \"nonexisting\" at host app.durgaform.io not found",
    59  		},
    60  		"with_an_unknown_host": {
    61  			config: cty.ObjectVal(map[string]cty.Value{
    62  				"hostname":     cty.StringVal("nonexisting.local"),
    63  				"organization": cty.StringVal("hashicorp"),
    64  				"token":        cty.NullVal(cty.String),
    65  				"workspaces": cty.ObjectVal(map[string]cty.Value{
    66  					"name":   cty.StringVal("prod"),
    67  					"prefix": cty.NullVal(cty.String),
    68  				}),
    69  			}),
    70  			confErr: "Failed to request discovery document",
    71  		},
    72  		// localhost advertises TFE services, but has no token in the credentials
    73  		"without_a_token": {
    74  			config: cty.ObjectVal(map[string]cty.Value{
    75  				"hostname":     cty.StringVal("localhost"),
    76  				"organization": cty.StringVal("hashicorp"),
    77  				"token":        cty.NullVal(cty.String),
    78  				"workspaces": cty.ObjectVal(map[string]cty.Value{
    79  					"name":   cty.StringVal("prod"),
    80  					"prefix": cty.NullVal(cty.String),
    81  				}),
    82  			}),
    83  			confErr: "durgaform login localhost",
    84  		},
    85  		"with_a_name": {
    86  			config: cty.ObjectVal(map[string]cty.Value{
    87  				"hostname":     cty.NullVal(cty.String),
    88  				"organization": cty.StringVal("hashicorp"),
    89  				"token":        cty.NullVal(cty.String),
    90  				"workspaces": cty.ObjectVal(map[string]cty.Value{
    91  					"name":   cty.StringVal("prod"),
    92  					"prefix": cty.NullVal(cty.String),
    93  				}),
    94  			}),
    95  		},
    96  		"with_a_prefix": {
    97  			config: cty.ObjectVal(map[string]cty.Value{
    98  				"hostname":     cty.NullVal(cty.String),
    99  				"organization": cty.StringVal("hashicorp"),
   100  				"token":        cty.NullVal(cty.String),
   101  				"workspaces": cty.ObjectVal(map[string]cty.Value{
   102  					"name":   cty.NullVal(cty.String),
   103  					"prefix": cty.StringVal("my-app-"),
   104  				}),
   105  			}),
   106  		},
   107  		"without_either_a_name_and_a_prefix": {
   108  			config: cty.ObjectVal(map[string]cty.Value{
   109  				"hostname":     cty.NullVal(cty.String),
   110  				"organization": cty.StringVal("hashicorp"),
   111  				"token":        cty.NullVal(cty.String),
   112  				"workspaces": cty.ObjectVal(map[string]cty.Value{
   113  					"name":   cty.NullVal(cty.String),
   114  					"prefix": cty.NullVal(cty.String),
   115  				}),
   116  			}),
   117  			valErr: `Either workspace "name" or "prefix" is required`,
   118  		},
   119  		"with_both_a_name_and_a_prefix": {
   120  			config: cty.ObjectVal(map[string]cty.Value{
   121  				"hostname":     cty.NullVal(cty.String),
   122  				"organization": cty.StringVal("hashicorp"),
   123  				"token":        cty.NullVal(cty.String),
   124  				"workspaces": cty.ObjectVal(map[string]cty.Value{
   125  					"name":   cty.StringVal("prod"),
   126  					"prefix": cty.StringVal("my-app-"),
   127  				}),
   128  			}),
   129  			valErr: `Only one of workspace "name" or "prefix" is allowed`,
   130  		},
   131  		"null config": {
   132  			config: cty.NullVal(cty.EmptyObject),
   133  		},
   134  	}
   135  
   136  	for name, tc := range cases {
   137  		s := testServer(t)
   138  		b := New(testDisco(s))
   139  
   140  		// Validate
   141  		_, valDiags := b.PrepareConfig(tc.config)
   142  		if (valDiags.Err() != nil || tc.valErr != "") &&
   143  			(valDiags.Err() == nil || !strings.Contains(valDiags.Err().Error(), tc.valErr)) {
   144  			t.Fatalf("%s: unexpected validation result: %v", name, valDiags.Err())
   145  		}
   146  
   147  		// Configure
   148  		confDiags := b.Configure(tc.config)
   149  		if (confDiags.Err() != nil || tc.confErr != "") &&
   150  			(confDiags.Err() == nil || !strings.Contains(confDiags.Err().Error(), tc.confErr)) {
   151  			t.Fatalf("%s: unexpected configure result: %v", name, confDiags.Err())
   152  		}
   153  	}
   154  }
   155  
   156  func TestRemote_versionConstraints(t *testing.T) {
   157  	cases := map[string]struct {
   158  		config     cty.Value
   159  		prerelease string
   160  		version    string
   161  		result     string
   162  	}{
   163  		"compatible version": {
   164  			config: cty.ObjectVal(map[string]cty.Value{
   165  				"hostname":     cty.NullVal(cty.String),
   166  				"organization": cty.StringVal("hashicorp"),
   167  				"token":        cty.NullVal(cty.String),
   168  				"workspaces": cty.ObjectVal(map[string]cty.Value{
   169  					"name":   cty.StringVal("prod"),
   170  					"prefix": cty.NullVal(cty.String),
   171  				}),
   172  			}),
   173  			version: "0.11.1",
   174  		},
   175  		"version too old": {
   176  			config: cty.ObjectVal(map[string]cty.Value{
   177  				"hostname":     cty.NullVal(cty.String),
   178  				"organization": cty.StringVal("hashicorp"),
   179  				"token":        cty.NullVal(cty.String),
   180  				"workspaces": cty.ObjectVal(map[string]cty.Value{
   181  					"name":   cty.StringVal("prod"),
   182  					"prefix": cty.NullVal(cty.String),
   183  				}),
   184  			}),
   185  			version: "0.0.1",
   186  			result:  "upgrade Durgaform to >= 0.1.0",
   187  		},
   188  		"version too new": {
   189  			config: cty.ObjectVal(map[string]cty.Value{
   190  				"hostname":     cty.NullVal(cty.String),
   191  				"organization": cty.StringVal("hashicorp"),
   192  				"token":        cty.NullVal(cty.String),
   193  				"workspaces": cty.ObjectVal(map[string]cty.Value{
   194  					"name":   cty.StringVal("prod"),
   195  					"prefix": cty.NullVal(cty.String),
   196  				}),
   197  			}),
   198  			version: "10.0.1",
   199  			result:  "downgrade Durgaform to <= 10.0.0",
   200  		},
   201  	}
   202  
   203  	// Save and restore the actual version.
   204  	p := tfversion.Prerelease
   205  	v := tfversion.Version
   206  	defer func() {
   207  		tfversion.Prerelease = p
   208  		tfversion.Version = v
   209  	}()
   210  
   211  	for name, tc := range cases {
   212  		s := testServer(t)
   213  		b := New(testDisco(s))
   214  
   215  		// Set the version for this test.
   216  		tfversion.Prerelease = tc.prerelease
   217  		tfversion.Version = tc.version
   218  
   219  		// Validate
   220  		_, valDiags := b.PrepareConfig(tc.config)
   221  		if valDiags.HasErrors() {
   222  			t.Fatalf("%s: unexpected validation result: %v", name, valDiags.Err())
   223  		}
   224  
   225  		// Configure
   226  		confDiags := b.Configure(tc.config)
   227  		if (confDiags.Err() != nil || tc.result != "") &&
   228  			(confDiags.Err() == nil || !strings.Contains(confDiags.Err().Error(), tc.result)) {
   229  			t.Fatalf("%s: unexpected configure result: %v", name, confDiags.Err())
   230  		}
   231  	}
   232  }
   233  
   234  func TestRemote_localBackend(t *testing.T) {
   235  	b, bCleanup := testBackendDefault(t)
   236  	defer bCleanup()
   237  
   238  	local, ok := b.local.(*backendLocal.Local)
   239  	if !ok {
   240  		t.Fatalf("expected b.local to be \"*local.Local\", got: %T", b.local)
   241  	}
   242  
   243  	remote, ok := local.Backend.(*Remote)
   244  	if !ok {
   245  		t.Fatalf("expected local.Backend to be *remote.Remote, got: %T", remote)
   246  	}
   247  }
   248  
   249  func TestRemote_addAndRemoveWorkspacesDefault(t *testing.T) {
   250  	b, bCleanup := testBackendDefault(t)
   251  	defer bCleanup()
   252  
   253  	if _, err := b.Workspaces(); err != backend.ErrWorkspacesNotSupported {
   254  		t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
   255  	}
   256  
   257  	if _, err := b.StateMgr(backend.DefaultStateName); err != nil {
   258  		t.Fatalf("expected no error, got %v", err)
   259  	}
   260  
   261  	if _, err := b.StateMgr("prod"); err != backend.ErrWorkspacesNotSupported {
   262  		t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
   263  	}
   264  
   265  	if err := b.DeleteWorkspace(backend.DefaultStateName); err != nil {
   266  		t.Fatalf("expected no error, got %v", err)
   267  	}
   268  
   269  	if err := b.DeleteWorkspace("prod"); err != backend.ErrWorkspacesNotSupported {
   270  		t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
   271  	}
   272  }
   273  
   274  func TestRemote_addAndRemoveWorkspacesNoDefault(t *testing.T) {
   275  	b, bCleanup := testBackendNoDefault(t)
   276  	defer bCleanup()
   277  
   278  	states, err := b.Workspaces()
   279  	if err != nil {
   280  		t.Fatal(err)
   281  	}
   282  
   283  	expectedWorkspaces := []string(nil)
   284  	if !reflect.DeepEqual(states, expectedWorkspaces) {
   285  		t.Fatalf("expected states %#+v, got %#+v", expectedWorkspaces, states)
   286  	}
   287  
   288  	if _, err := b.StateMgr(backend.DefaultStateName); err != backend.ErrDefaultWorkspaceNotSupported {
   289  		t.Fatalf("expected error %v, got %v", backend.ErrDefaultWorkspaceNotSupported, err)
   290  	}
   291  
   292  	expectedA := "test_A"
   293  	if _, err := b.StateMgr(expectedA); err != nil {
   294  		t.Fatal(err)
   295  	}
   296  
   297  	states, err = b.Workspaces()
   298  	if err != nil {
   299  		t.Fatal(err)
   300  	}
   301  
   302  	expectedWorkspaces = append(expectedWorkspaces, expectedA)
   303  	if !reflect.DeepEqual(states, expectedWorkspaces) {
   304  		t.Fatalf("expected %#+v, got %#+v", expectedWorkspaces, states)
   305  	}
   306  
   307  	expectedB := "test_B"
   308  	if _, err := b.StateMgr(expectedB); err != nil {
   309  		t.Fatal(err)
   310  	}
   311  
   312  	states, err = b.Workspaces()
   313  	if err != nil {
   314  		t.Fatal(err)
   315  	}
   316  
   317  	expectedWorkspaces = append(expectedWorkspaces, expectedB)
   318  	if !reflect.DeepEqual(states, expectedWorkspaces) {
   319  		t.Fatalf("expected %#+v, got %#+v", expectedWorkspaces, states)
   320  	}
   321  
   322  	if err := b.DeleteWorkspace(backend.DefaultStateName); err != backend.ErrDefaultWorkspaceNotSupported {
   323  		t.Fatalf("expected error %v, got %v", backend.ErrDefaultWorkspaceNotSupported, err)
   324  	}
   325  
   326  	if err := b.DeleteWorkspace(expectedA); err != nil {
   327  		t.Fatal(err)
   328  	}
   329  
   330  	states, err = b.Workspaces()
   331  	if err != nil {
   332  		t.Fatal(err)
   333  	}
   334  
   335  	expectedWorkspaces = []string{expectedB}
   336  	if !reflect.DeepEqual(states, expectedWorkspaces) {
   337  		t.Fatalf("expected %#+v got %#+v", expectedWorkspaces, states)
   338  	}
   339  
   340  	if err := b.DeleteWorkspace(expectedB); err != nil {
   341  		t.Fatal(err)
   342  	}
   343  
   344  	states, err = b.Workspaces()
   345  	if err != nil {
   346  		t.Fatal(err)
   347  	}
   348  
   349  	expectedWorkspaces = []string(nil)
   350  	if !reflect.DeepEqual(states, expectedWorkspaces) {
   351  		t.Fatalf("expected %#+v, got %#+v", expectedWorkspaces, states)
   352  	}
   353  }
   354  
   355  func TestRemote_checkConstraints(t *testing.T) {
   356  	b, bCleanup := testBackendDefault(t)
   357  	defer bCleanup()
   358  
   359  	cases := map[string]struct {
   360  		constraints *disco.Constraints
   361  		prerelease  string
   362  		version     string
   363  		result      string
   364  	}{
   365  		"compatible version": {
   366  			constraints: &disco.Constraints{
   367  				Minimum: "0.11.0",
   368  				Maximum: "0.11.11",
   369  			},
   370  			version: "0.11.1",
   371  			result:  "",
   372  		},
   373  		"version too old": {
   374  			constraints: &disco.Constraints{
   375  				Minimum: "0.11.0",
   376  				Maximum: "0.11.11",
   377  			},
   378  			version: "0.10.1",
   379  			result:  "upgrade Durgaform to >= 0.11.0",
   380  		},
   381  		"version too new": {
   382  			constraints: &disco.Constraints{
   383  				Minimum: "0.11.0",
   384  				Maximum: "0.11.11",
   385  			},
   386  			version: "0.12.0",
   387  			result:  "downgrade Durgaform to <= 0.11.11",
   388  		},
   389  		"version excluded - ordered": {
   390  			constraints: &disco.Constraints{
   391  				Minimum:   "0.11.0",
   392  				Excluding: []string{"0.11.7", "0.11.8"},
   393  				Maximum:   "0.11.11",
   394  			},
   395  			version: "0.11.7",
   396  			result:  "upgrade Durgaform to > 0.11.8",
   397  		},
   398  		"version excluded - unordered": {
   399  			constraints: &disco.Constraints{
   400  				Minimum:   "0.11.0",
   401  				Excluding: []string{"0.11.8", "0.11.6"},
   402  				Maximum:   "0.11.11",
   403  			},
   404  			version: "0.11.6",
   405  			result:  "upgrade Durgaform to > 0.11.8",
   406  		},
   407  		"list versions": {
   408  			constraints: &disco.Constraints{
   409  				Minimum: "0.11.0",
   410  				Maximum: "0.11.11",
   411  			},
   412  			version: "0.10.1",
   413  			result:  "versions >= 0.11.0, <= 0.11.11.",
   414  		},
   415  		"list exclusion": {
   416  			constraints: &disco.Constraints{
   417  				Minimum:   "0.11.0",
   418  				Excluding: []string{"0.11.6"},
   419  				Maximum:   "0.11.11",
   420  			},
   421  			version: "0.11.6",
   422  			result:  "excluding version 0.11.6.",
   423  		},
   424  		"list exclusions": {
   425  			constraints: &disco.Constraints{
   426  				Minimum:   "0.11.0",
   427  				Excluding: []string{"0.11.8", "0.11.6"},
   428  				Maximum:   "0.11.11",
   429  			},
   430  			version: "0.11.6",
   431  			result:  "excluding versions 0.11.6, 0.11.8.",
   432  		},
   433  	}
   434  
   435  	// Save and restore the actual version.
   436  	p := tfversion.Prerelease
   437  	v := tfversion.Version
   438  	defer func() {
   439  		tfversion.Prerelease = p
   440  		tfversion.Version = v
   441  	}()
   442  
   443  	for name, tc := range cases {
   444  		// Set the version for this test.
   445  		tfversion.Prerelease = tc.prerelease
   446  		tfversion.Version = tc.version
   447  
   448  		// Check the constraints.
   449  		diags := b.checkConstraints(tc.constraints)
   450  		if (diags.Err() != nil || tc.result != "") &&
   451  			(diags.Err() == nil || !strings.Contains(diags.Err().Error(), tc.result)) {
   452  			t.Fatalf("%s: unexpected constraints result: %v", name, diags.Err())
   453  		}
   454  	}
   455  }
   456  
   457  func TestRemote_StateMgr_versionCheck(t *testing.T) {
   458  	b, bCleanup := testBackendDefault(t)
   459  	defer bCleanup()
   460  
   461  	// Some fixed versions for testing with. This logic is a simple string
   462  	// comparison, so we don't need many test cases.
   463  	v0135 := version.Must(version.NewSemver("0.13.5"))
   464  	v0140 := version.Must(version.NewSemver("0.14.0"))
   465  
   466  	// Save original local version state and restore afterwards
   467  	p := tfversion.Prerelease
   468  	v := tfversion.Version
   469  	s := tfversion.SemVer
   470  	defer func() {
   471  		tfversion.Prerelease = p
   472  		tfversion.Version = v
   473  		tfversion.SemVer = s
   474  	}()
   475  
   476  	// For this test, the local Durgaform version is set to 0.14.0
   477  	tfversion.Prerelease = ""
   478  	tfversion.Version = v0140.String()
   479  	tfversion.SemVer = v0140
   480  
   481  	// Update the mock remote workspace Durgaform version to match the local
   482  	// Durgaform version
   483  	if _, err := b.client.Workspaces.Update(
   484  		context.Background(),
   485  		b.organization,
   486  		b.workspace,
   487  		tfe.WorkspaceUpdateOptions{
   488  			DurgaformVersion: tfe.String(v0140.String()),
   489  		},
   490  	); err != nil {
   491  		t.Fatalf("error: %v", err)
   492  	}
   493  
   494  	// This should succeed
   495  	if _, err := b.StateMgr(backend.DefaultStateName); err != nil {
   496  		t.Fatalf("expected no error, got %v", err)
   497  	}
   498  
   499  	// Now change the remote workspace to a different Durgaform version
   500  	if _, err := b.client.Workspaces.Update(
   501  		context.Background(),
   502  		b.organization,
   503  		b.workspace,
   504  		tfe.WorkspaceUpdateOptions{
   505  			DurgaformVersion: tfe.String(v0135.String()),
   506  		},
   507  	); err != nil {
   508  		t.Fatalf("error: %v", err)
   509  	}
   510  
   511  	// This should fail
   512  	want := `Remote workspace Durgaform version "0.13.5" does not match local Terraform version "0.14.0"`
   513  	if _, err := b.StateMgr(backend.DefaultStateName); err.Error() != want {
   514  		t.Fatalf("wrong error\n got: %v\nwant: %v", err.Error(), want)
   515  	}
   516  }
   517  
   518  func TestRemote_StateMgr_versionCheckLatest(t *testing.T) {
   519  	b, bCleanup := testBackendDefault(t)
   520  	defer bCleanup()
   521  
   522  	v0140 := version.Must(version.NewSemver("0.14.0"))
   523  
   524  	// Save original local version state and restore afterwards
   525  	p := tfversion.Prerelease
   526  	v := tfversion.Version
   527  	s := tfversion.SemVer
   528  	defer func() {
   529  		tfversion.Prerelease = p
   530  		tfversion.Version = v
   531  		tfversion.SemVer = s
   532  	}()
   533  
   534  	// For this test, the local Durgaform version is set to 0.14.0
   535  	tfversion.Prerelease = ""
   536  	tfversion.Version = v0140.String()
   537  	tfversion.SemVer = v0140
   538  
   539  	// Update the remote workspace to the pseudo-version "latest"
   540  	if _, err := b.client.Workspaces.Update(
   541  		context.Background(),
   542  		b.organization,
   543  		b.workspace,
   544  		tfe.WorkspaceUpdateOptions{
   545  			DurgaformVersion: tfe.String("latest"),
   546  		},
   547  	); err != nil {
   548  		t.Fatalf("error: %v", err)
   549  	}
   550  
   551  	// This should succeed despite not being a string match
   552  	if _, err := b.StateMgr(backend.DefaultStateName); err != nil {
   553  		t.Fatalf("expected no error, got %v", err)
   554  	}
   555  }
   556  
   557  func TestRemote_VerifyWorkspaceDurgaformVersion(t *testing.T) {
   558  	testCases := []struct {
   559  		local         string
   560  		remote        string
   561  		executionMode string
   562  		wantErr       bool
   563  	}{
   564  		{"0.13.5", "0.13.5", "remote", false},
   565  		{"0.14.0", "0.13.5", "remote", true},
   566  		{"0.14.0", "0.13.5", "local", false},
   567  		{"0.14.0", "0.14.1", "remote", false},
   568  		{"0.14.0", "1.0.99", "remote", false},
   569  		{"0.14.0", "1.1.0", "remote", false},
   570  		{"0.14.0", "1.3.0", "remote", true},
   571  		{"1.2.0", "1.2.99", "remote", false},
   572  		{"1.2.0", "1.3.0", "remote", true},
   573  		{"0.15.0", "latest", "remote", false},
   574  	}
   575  	for _, tc := range testCases {
   576  		t.Run(fmt.Sprintf("local %s, remote %s", tc.local, tc.remote), func(t *testing.T) {
   577  			b, bCleanup := testBackendDefault(t)
   578  			defer bCleanup()
   579  
   580  			local := version.Must(version.NewSemver(tc.local))
   581  
   582  			// Save original local version state and restore afterwards
   583  			p := tfversion.Prerelease
   584  			v := tfversion.Version
   585  			s := tfversion.SemVer
   586  			defer func() {
   587  				tfversion.Prerelease = p
   588  				tfversion.Version = v
   589  				tfversion.SemVer = s
   590  			}()
   591  
   592  			// Override local version as specified
   593  			tfversion.Prerelease = ""
   594  			tfversion.Version = local.String()
   595  			tfversion.SemVer = local
   596  
   597  			// Update the mock remote workspace Durgaform version to the
   598  			// specified remote version
   599  			if _, err := b.client.Workspaces.Update(
   600  				context.Background(),
   601  				b.organization,
   602  				b.workspace,
   603  				tfe.WorkspaceUpdateOptions{
   604  					ExecutionMode:    &tc.executionMode,
   605  					DurgaformVersion: tfe.String(tc.remote),
   606  				},
   607  			); err != nil {
   608  				t.Fatalf("error: %v", err)
   609  			}
   610  
   611  			diags := b.VerifyWorkspaceDurgaformVersion(backend.DefaultStateName)
   612  			if tc.wantErr {
   613  				if len(diags) != 1 {
   614  					t.Fatal("expected diag, but none returned")
   615  				}
   616  				if got := diags.Err().Error(); !strings.Contains(got, "Durgaform version mismatch") {
   617  					t.Fatalf("unexpected error: %s", got)
   618  				}
   619  			} else {
   620  				if len(diags) != 0 {
   621  					t.Fatalf("unexpected diags: %s", diags.Err())
   622  				}
   623  			}
   624  		})
   625  	}
   626  }
   627  
   628  func TestRemote_VerifyWorkspaceDurgaformVersion_workspaceErrors(t *testing.T) {
   629  	b, bCleanup := testBackendDefault(t)
   630  	defer bCleanup()
   631  
   632  	// Attempting to check the version against a workspace which doesn't exist
   633  	// should result in no errors
   634  	diags := b.VerifyWorkspaceDurgaformVersion("invalid-workspace")
   635  	if len(diags) != 0 {
   636  		t.Fatalf("unexpected error: %s", diags.Err())
   637  	}
   638  
   639  	// Use a special workspace ID to trigger a 500 error, which should result
   640  	// in a failed check
   641  	diags = b.VerifyWorkspaceDurgaformVersion("network-error")
   642  	if len(diags) != 1 {
   643  		t.Fatal("expected diag, but none returned")
   644  	}
   645  	if got := diags.Err().Error(); !strings.Contains(got, "Error looking up workspace: Workspace read failed") {
   646  		t.Fatalf("unexpected error: %s", got)
   647  	}
   648  
   649  	// Update the mock remote workspace Durgaform version to an invalid version
   650  	if _, err := b.client.Workspaces.Update(
   651  		context.Background(),
   652  		b.organization,
   653  		b.workspace,
   654  		tfe.WorkspaceUpdateOptions{
   655  			DurgaformVersion: tfe.String("1.0.cheetarah"),
   656  		},
   657  	); err != nil {
   658  		t.Fatalf("error: %v", err)
   659  	}
   660  	diags = b.VerifyWorkspaceDurgaformVersion(backend.DefaultStateName)
   661  
   662  	if len(diags) != 1 {
   663  		t.Fatal("expected diag, but none returned")
   664  	}
   665  	if got := diags.Err().Error(); !strings.Contains(got, "Error looking up workspace: Invalid Durgaform version") {
   666  		t.Fatalf("unexpected error: %s", got)
   667  	}
   668  }
   669  
   670  func TestRemote_VerifyWorkspaceDurgaformVersion_ignoreFlagSet(t *testing.T) {
   671  	b, bCleanup := testBackendDefault(t)
   672  	defer bCleanup()
   673  
   674  	// If the ignore flag is set, the behaviour changes
   675  	b.IgnoreVersionConflict()
   676  
   677  	// Different local & remote versions to cause an error
   678  	local := version.Must(version.NewSemver("0.14.0"))
   679  	remote := version.Must(version.NewSemver("0.13.5"))
   680  
   681  	// Save original local version state and restore afterwards
   682  	p := tfversion.Prerelease
   683  	v := tfversion.Version
   684  	s := tfversion.SemVer
   685  	defer func() {
   686  		tfversion.Prerelease = p
   687  		tfversion.Version = v
   688  		tfversion.SemVer = s
   689  	}()
   690  
   691  	// Override local version as specified
   692  	tfversion.Prerelease = ""
   693  	tfversion.Version = local.String()
   694  	tfversion.SemVer = local
   695  
   696  	// Update the mock remote workspace Durgaform version to the
   697  	// specified remote version
   698  	if _, err := b.client.Workspaces.Update(
   699  		context.Background(),
   700  		b.organization,
   701  		b.workspace,
   702  		tfe.WorkspaceUpdateOptions{
   703  			DurgaformVersion: tfe.String(remote.String()),
   704  		},
   705  	); err != nil {
   706  		t.Fatalf("error: %v", err)
   707  	}
   708  
   709  	diags := b.VerifyWorkspaceDurgaformVersion(backend.DefaultStateName)
   710  	if len(diags) != 1 {
   711  		t.Fatal("expected diag, but none returned")
   712  	}
   713  
   714  	if got, want := diags[0].Severity(), tfdiags.Warning; got != want {
   715  		t.Errorf("wrong severity: got %#v, want %#v", got, want)
   716  	}
   717  	if got, want := diags[0].Description().Summary, "Durgaform version mismatch"; got != want {
   718  		t.Errorf("wrong summary: got %s, want %s", got, want)
   719  	}
   720  	wantDetail := "The local Durgaform version (0.14.0) does not match the configured version for remote workspace hashicorp/prod (0.13.5)."
   721  	if got := diags[0].Description().Detail; got != wantDetail {
   722  		t.Errorf("wrong summary: got %s, want %s", got, wantDetail)
   723  	}
   724  }