github.com/crossplane/upjet@v1.3.0/pkg/controller/external_async_tfpluginsdk_test.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package controller
     6  
     7  import (
     8  	"context"
     9  	"testing"
    10  
    11  	"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
    12  	xpresource "github.com/crossplane/crossplane-runtime/pkg/resource"
    13  	"github.com/crossplane/crossplane-runtime/pkg/test"
    14  	"github.com/google/go-cmp/cmp"
    15  	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
    16  	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    17  	tf "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
    18  	"sigs.k8s.io/controller-runtime/pkg/client"
    19  
    20  	"github.com/crossplane/upjet/pkg/config"
    21  	"github.com/crossplane/upjet/pkg/resource/fake"
    22  	"github.com/crossplane/upjet/pkg/terraform"
    23  )
    24  
    25  var (
    26  	cfgAsync = &config.Resource{
    27  		TerraformResource: &schema.Resource{
    28  			Timeouts: &schema.ResourceTimeout{
    29  				Create: &timeout,
    30  				Read:   &timeout,
    31  				Update: &timeout,
    32  				Delete: &timeout,
    33  			},
    34  			Schema: map[string]*schema.Schema{
    35  				"name": {
    36  					Type:     schema.TypeString,
    37  					Required: true,
    38  				},
    39  				"id": {
    40  					Type:     schema.TypeString,
    41  					Computed: true,
    42  					Required: false,
    43  				},
    44  				"map": {
    45  					Type: schema.TypeMap,
    46  					Elem: &schema.Schema{
    47  						Type: schema.TypeString,
    48  					},
    49  				},
    50  				"list": {
    51  					Type: schema.TypeList,
    52  					Elem: &schema.Schema{
    53  						Type: schema.TypeString,
    54  					},
    55  				},
    56  			},
    57  		},
    58  		ExternalName: config.IdentifierFromProvider,
    59  		Sensitive: config.Sensitive{AdditionalConnectionDetailsFn: func(attr map[string]any) (map[string][]byte, error) {
    60  			return nil, nil
    61  		}},
    62  	}
    63  	objAsync = &fake.Terraformed{
    64  		Parameterizable: fake.Parameterizable{
    65  			Parameters: map[string]any{
    66  				"name": "example",
    67  				"map": map[string]any{
    68  					"key": "value",
    69  				},
    70  				"list": []any{"elem1", "elem2"},
    71  			},
    72  		},
    73  		Observable: fake.Observable{
    74  			Observation: map[string]any{},
    75  		},
    76  	}
    77  )
    78  
    79  func prepareTerraformPluginSDKAsyncExternal(r Resource, cfg *config.Resource, fns CallbackFns) *terraformPluginSDKAsyncExternal {
    80  	schemaBlock := cfg.TerraformResource.CoreConfigSchema()
    81  	rawConfig, err := schema.JSONMapToStateValue(map[string]any{"name": "example"}, schemaBlock)
    82  	if err != nil {
    83  		panic(err)
    84  	}
    85  	return &terraformPluginSDKAsyncExternal{
    86  		terraformPluginSDKExternal: &terraformPluginSDKExternal{
    87  			ts:             terraform.Setup{},
    88  			resourceSchema: r,
    89  			config:         cfg,
    90  			params: map[string]any{
    91  				"name": "example",
    92  			},
    93  			rawConfig: rawConfig,
    94  			logger:    logTest,
    95  			opTracker: NewAsyncTracker(),
    96  		},
    97  		callback: fns,
    98  	}
    99  }
   100  
   101  func TestAsyncTerraformPluginSDKConnect(t *testing.T) {
   102  	type args struct {
   103  		setupFn terraform.SetupFn
   104  		cfg     *config.Resource
   105  		ots     *OperationTrackerStore
   106  		obj     xpresource.Managed
   107  	}
   108  	type want struct {
   109  		err error
   110  	}
   111  	cases := map[string]struct {
   112  		args
   113  		want
   114  	}{
   115  		"Successful": {
   116  			args: args{
   117  				setupFn: func(_ context.Context, _ client.Client, _ xpresource.Managed) (terraform.Setup, error) {
   118  					return terraform.Setup{}, nil
   119  				},
   120  				cfg: cfgAsync,
   121  				obj: objAsync,
   122  				ots: ots,
   123  			},
   124  		},
   125  	}
   126  	for name, tc := range cases {
   127  		t.Run(name, func(t *testing.T) {
   128  			c := NewTerraformPluginSDKAsyncConnector(nil, tc.args.ots, tc.args.setupFn, tc.args.cfg, WithTerraformPluginSDKAsyncLogger(logTest))
   129  			_, err := c.Connect(context.TODO(), tc.args.obj)
   130  			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
   131  				t.Errorf("\n%s\nConnect(...): -want error, +got error:\n", diff)
   132  			}
   133  		})
   134  	}
   135  }
   136  
   137  func TestAsyncTerraformPluginSDKObserve(t *testing.T) {
   138  	type args struct {
   139  		r   Resource
   140  		cfg *config.Resource
   141  		obj xpresource.Managed
   142  	}
   143  	type want struct {
   144  		obs managed.ExternalObservation
   145  		err error
   146  	}
   147  	cases := map[string]struct {
   148  		args
   149  		want
   150  	}{
   151  		"NotExists": {
   152  			args: args{
   153  				r: mockResource{
   154  					RefreshWithoutUpgradeFn: func(ctx context.Context, s *tf.InstanceState, meta interface{}) (*tf.InstanceState, diag.Diagnostics) {
   155  						return nil, nil
   156  					},
   157  				},
   158  				cfg: cfgAsync,
   159  				obj: objAsync,
   160  			},
   161  			want: want{
   162  				obs: managed.ExternalObservation{
   163  					ResourceExists:          false,
   164  					ResourceUpToDate:        false,
   165  					ResourceLateInitialized: false,
   166  					ConnectionDetails:       nil,
   167  					Diff:                    "",
   168  				},
   169  			},
   170  		},
   171  		"UpToDate": {
   172  			args: args{
   173  				r: mockResource{
   174  					RefreshWithoutUpgradeFn: func(ctx context.Context, s *tf.InstanceState, meta interface{}) (*tf.InstanceState, diag.Diagnostics) {
   175  						return &tf.InstanceState{ID: "example-id", Attributes: map[string]string{"name": "example"}}, nil
   176  					},
   177  				},
   178  				cfg: cfgAsync,
   179  				obj: objAsync,
   180  			},
   181  			want: want{
   182  				obs: managed.ExternalObservation{
   183  					ResourceExists:          true,
   184  					ResourceUpToDate:        true,
   185  					ResourceLateInitialized: true,
   186  					ConnectionDetails:       nil,
   187  					Diff:                    "",
   188  				},
   189  			},
   190  		},
   191  	}
   192  	for name, tc := range cases {
   193  		t.Run(name, func(t *testing.T) {
   194  			terraformPluginSDKAsyncExternal := prepareTerraformPluginSDKAsyncExternal(tc.args.r, tc.args.cfg, CallbackFns{})
   195  			observation, err := terraformPluginSDKAsyncExternal.Observe(context.TODO(), tc.args.obj)
   196  			if diff := cmp.Diff(tc.want.obs, observation); diff != "" {
   197  				t.Errorf("\n%s\nObserve(...): -want observation, +got observation:\n", diff)
   198  			}
   199  			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
   200  				t.Errorf("\n%s\nConnect(...): -want error, +got error:\n", diff)
   201  			}
   202  		})
   203  	}
   204  }
   205  
   206  func TestAsyncTerraformPluginSDKCreate(t *testing.T) {
   207  	type args struct {
   208  		r   Resource
   209  		cfg *config.Resource
   210  		obj xpresource.Managed
   211  		fns CallbackFns
   212  	}
   213  	type want struct {
   214  		err error
   215  	}
   216  	cases := map[string]struct {
   217  		args
   218  		want
   219  	}{
   220  		"Successful": {
   221  			args: args{
   222  				r: mockResource{
   223  					ApplyFn: func(ctx context.Context, s *tf.InstanceState, d *tf.InstanceDiff, meta interface{}) (*tf.InstanceState, diag.Diagnostics) {
   224  						return &tf.InstanceState{ID: "example-id"}, nil
   225  					},
   226  				},
   227  				cfg: cfgAsync,
   228  				obj: objAsync,
   229  				fns: CallbackFns{
   230  					CreateFn: func(s string) terraform.CallbackFn {
   231  						return func(err error, ctx context.Context) error {
   232  							return nil
   233  						}
   234  					},
   235  				},
   236  			},
   237  		},
   238  	}
   239  	for name, tc := range cases {
   240  		t.Run(name, func(t *testing.T) {
   241  			terraformPluginSDKAsyncExternal := prepareTerraformPluginSDKAsyncExternal(tc.args.r, tc.args.cfg, tc.args.fns)
   242  			_, err := terraformPluginSDKAsyncExternal.Create(context.TODO(), tc.args.obj)
   243  			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
   244  				t.Errorf("\n%s\nConnect(...): -want error, +got error:\n", diff)
   245  			}
   246  		})
   247  	}
   248  }
   249  
   250  func TestAsyncTerraformPluginSDKUpdate(t *testing.T) {
   251  	type args struct {
   252  		r   Resource
   253  		cfg *config.Resource
   254  		obj xpresource.Managed
   255  		fns CallbackFns
   256  	}
   257  	type want struct {
   258  		err error
   259  	}
   260  	cases := map[string]struct {
   261  		args
   262  		want
   263  	}{
   264  		"Successful": {
   265  			args: args{
   266  				r: mockResource{
   267  					ApplyFn: func(ctx context.Context, s *tf.InstanceState, d *tf.InstanceDiff, meta interface{}) (*tf.InstanceState, diag.Diagnostics) {
   268  						return &tf.InstanceState{ID: "example-id"}, nil
   269  					},
   270  				},
   271  				cfg: cfgAsync,
   272  				obj: objAsync,
   273  				fns: CallbackFns{
   274  					UpdateFn: func(s string) terraform.CallbackFn {
   275  						return func(err error, ctx context.Context) error {
   276  							return nil
   277  						}
   278  					},
   279  				},
   280  			},
   281  		},
   282  	}
   283  	for name, tc := range cases {
   284  		t.Run(name, func(t *testing.T) {
   285  			terraformPluginSDKAsyncExternal := prepareTerraformPluginSDKAsyncExternal(tc.args.r, tc.args.cfg, tc.args.fns)
   286  			_, err := terraformPluginSDKAsyncExternal.Update(context.TODO(), tc.args.obj)
   287  			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
   288  				t.Errorf("\n%s\nConnect(...): -want error, +got error:\n", diff)
   289  			}
   290  		})
   291  	}
   292  }
   293  
   294  func TestAsyncTerraformPluginSDKDelete(t *testing.T) {
   295  	type args struct {
   296  		r   Resource
   297  		cfg *config.Resource
   298  		obj xpresource.Managed
   299  		fns CallbackFns
   300  	}
   301  	type want struct {
   302  		err error
   303  	}
   304  	cases := map[string]struct {
   305  		args
   306  		want
   307  	}{
   308  		"Successful": {
   309  			args: args{
   310  				r: mockResource{
   311  					ApplyFn: func(ctx context.Context, s *tf.InstanceState, d *tf.InstanceDiff, meta interface{}) (*tf.InstanceState, diag.Diagnostics) {
   312  						return &tf.InstanceState{ID: "example-id"}, nil
   313  					},
   314  				},
   315  				cfg: cfgAsync,
   316  				obj: objAsync,
   317  				fns: CallbackFns{
   318  					DestroyFn: func(s string) terraform.CallbackFn {
   319  						return func(err error, ctx context.Context) error {
   320  							return nil
   321  						}
   322  					},
   323  				},
   324  			},
   325  		},
   326  	}
   327  	for name, tc := range cases {
   328  		t.Run(name, func(t *testing.T) {
   329  			terraformPluginSDKAsyncExternal := prepareTerraformPluginSDKAsyncExternal(tc.args.r, tc.args.cfg, tc.args.fns)
   330  			err := terraformPluginSDKAsyncExternal.Delete(context.TODO(), tc.args.obj)
   331  			if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
   332  				t.Errorf("\n%s\nConnect(...): -want error, +got error:\n", diff)
   333  			}
   334  		})
   335  	}
   336  }