github.com/Axway/agent-sdk@v1.1.101/pkg/agent/handler/accessrequest_test.go (about)

     1  package handler
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  
     8  	agentcache "github.com/Axway/agent-sdk/pkg/agent/cache"
     9  	v1 "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/api/v1"
    10  	management "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/management/v1alpha1"
    11  	defs "github.com/Axway/agent-sdk/pkg/apic/definitions"
    12  	prov "github.com/Axway/agent-sdk/pkg/apic/provisioning"
    13  	"github.com/Axway/agent-sdk/pkg/apic/provisioning/mock"
    14  	"github.com/Axway/agent-sdk/pkg/config"
    15  	"github.com/Axway/agent-sdk/pkg/util"
    16  	"github.com/Axway/agent-sdk/pkg/watchmanager/proto"
    17  	"github.com/stretchr/testify/assert"
    18  )
    19  
    20  func TestAccessRequestHandler(t *testing.T) {
    21  	ardRI, _ := ard.AsInstance()
    22  
    23  	tests := []struct {
    24  		action           proto.Event_Type
    25  		expectedProvType string
    26  		getErr           error
    27  		hasError         bool
    28  		inboundStatus    string
    29  		name             string
    30  		outboundStatus   string
    31  		references       []v1.Reference
    32  		subError         error
    33  		appStatus        string
    34  		getARDErr        error
    35  		state            string
    36  		finalizers       []v1.Finalizer
    37  	}{
    38  		{
    39  			action:           proto.Event_CREATED,
    40  			inboundStatus:    prov.Pending.String(),
    41  			name:             "should handle a create event for an AccessRequest when status is pending",
    42  			outboundStatus:   prov.Success.String(),
    43  			expectedProvType: provision,
    44  			references:       accessReq.Metadata.References,
    45  		},
    46  		{
    47  			action:           proto.Event_UPDATED,
    48  			inboundStatus:    prov.Pending.String(),
    49  			name:             "should handle an update event for an AccessRequest when status is pending",
    50  			outboundStatus:   prov.Success.String(),
    51  			expectedProvType: provision,
    52  			references:       accessReq.Metadata.References,
    53  		},
    54  		{
    55  			action:         proto.Event_CREATED,
    56  			inboundStatus:  prov.Pending.String(),
    57  			name:           "should return nil with the appStatus is not success",
    58  			outboundStatus: prov.Error.String(),
    59  			references:     accessReq.Metadata.References,
    60  			appStatus:      prov.Error.String(),
    61  		},
    62  		{
    63  			action: proto.Event_SUBRESOURCEUPDATED,
    64  			name:   "should return nil when the event is for subresources",
    65  		},
    66  		{
    67  			action:        proto.Event_UPDATED,
    68  			inboundStatus: prov.Error.String(),
    69  			name:          "should return nil and not process anything when status is set to Error",
    70  			references:    accessReq.Metadata.References,
    71  		},
    72  		{
    73  			action:        proto.Event_UPDATED,
    74  			inboundStatus: prov.Success.String(),
    75  			name:          "should return nil and not process anything when the status is set to Success",
    76  			references:    accessReq.Metadata.References,
    77  		},
    78  		{
    79  			action:        proto.Event_CREATED,
    80  			inboundStatus: "",
    81  			name:          "should return nil and not process anything when the status field is empty",
    82  			references:    accessReq.Metadata.References,
    83  		},
    84  		{
    85  			action:         proto.Event_CREATED,
    86  			getErr:         fmt.Errorf("error getting managed app"),
    87  			inboundStatus:  prov.Pending.String(),
    88  			name:           "should handle an error when retrieving the managed app, and set a failed status",
    89  			outboundStatus: prov.Error.String(),
    90  			references:     accessReq.Metadata.References,
    91  		},
    92  		{
    93  			action:         proto.Event_CREATED,
    94  			inboundStatus:  prov.Pending.String(),
    95  			name:           "should handle an error when retrieving the access request definition, and set a failed status",
    96  			outboundStatus: prov.Error.String(),
    97  			references:     accessReq.Metadata.References,
    98  			getARDErr:      fmt.Errorf("could not get access request definition"),
    99  		},
   100  		{
   101  			action:           proto.Event_CREATED,
   102  			hasError:         true,
   103  			inboundStatus:    prov.Pending.String(),
   104  			name:             "should handle an error when updating the AccessRequest subresources",
   105  			outboundStatus:   prov.Success.String(),
   106  			expectedProvType: provision,
   107  			references:       accessReq.Metadata.References,
   108  			subError:         fmt.Errorf("error updating subresources"),
   109  		},
   110  		{
   111  			action:         proto.Event_CREATED,
   112  			inboundStatus:  prov.Pending.String(),
   113  			name:           "should handle an error when the instance is not found in the cache, and set a failed status",
   114  			outboundStatus: prov.Error.String(),
   115  		},
   116  		{
   117  			action:         proto.Event_DELETED,
   118  			inboundStatus:  prov.Success.String(),
   119  			name:           "should handle an error when the instance is not found in the cache for a delete event",
   120  			outboundStatus: prov.Success.String(),
   121  			state:          v1.ResourceDeleting,
   122  			finalizers:     []v1.Finalizer{{Name: "abc"}},
   123  		},
   124  	}
   125  
   126  	for _, tc := range tests {
   127  		t.Run(tc.name, func(t *testing.T) {
   128  			mApp.SubResources["status"].(map[string]interface{})["level"] = prov.Success.String()
   129  			if tc.appStatus != "" {
   130  				mApp.SubResources["status"].(map[string]interface{})["level"] = tc.appStatus
   131  			}
   132  
   133  			cm := agentcache.NewAgentCacheManager(&config.CentralConfiguration{}, false)
   134  
   135  			ar := accessReq
   136  			ar.Status.Level = tc.inboundStatus
   137  			ar.Metadata.References = tc.references
   138  			if tc.state != "" {
   139  				ar.Metadata.State = tc.state
   140  			}
   141  			if tc.finalizers != nil {
   142  				ar.Finalizers = tc.finalizers
   143  			}
   144  
   145  			instanceRI, _ := instance.AsInstance()
   146  			cm.AddAPIServiceInstance(instanceRI)
   147  
   148  			status := mock.MockRequestStatus{
   149  				Status: prov.Success,
   150  				Msg:    "msg",
   151  				Properties: map[string]string{
   152  					"status_key": "status_val",
   153  				},
   154  			}
   155  
   156  			arp := &mockARProvision{
   157  				expectedAccessDetails: util.GetAgentDetails(&ar),
   158  				expectedAPIID:         instRefID,
   159  				expectedAppDetails:    util.GetAgentDetails(mApp),
   160  				expectedAppName:       managedAppRefName,
   161  				expectedStatus:        status,
   162  				t:                     t,
   163  			}
   164  
   165  			c := &mockClient{
   166  				expectedStatus: tc.outboundStatus,
   167  				getErr:         tc.getErr,
   168  				getARDErr:      tc.getARDErr,
   169  				getRI:          mApp,
   170  				subError:       tc.subError,
   171  				t:              t,
   172  				ard:            ardRI,
   173  			}
   174  
   175  			if tc.state == v1.ResourceDeleting {
   176  				c.isDeleting = true
   177  			}
   178  
   179  			handler := NewAccessRequestHandler(arp, cm, c)
   180  			v := handler.(*accessRequestHandler)
   181  			v.encryptSchema = func(_, _ map[string]interface{}, _, _, _ string) (map[string]interface{}, error) {
   182  				return map[string]interface{}{}, nil
   183  			}
   184  
   185  			ri, _ := ar.AsInstance()
   186  			err := handler.Handle(NewEventContext(tc.action, nil, ri.Kind, ri.Name), nil, ri)
   187  
   188  			if tc.hasError {
   189  				assert.Error(t, err)
   190  			} else {
   191  				assert.Nil(t, err)
   192  			}
   193  
   194  			assert.Equal(t, tc.expectedProvType, arp.expectedProvType)
   195  			if tc.inboundStatus == prov.Pending.String() {
   196  				assert.True(t, c.createSubCalled)
   197  			} else {
   198  				assert.False(t, c.createSubCalled)
   199  			}
   200  
   201  		})
   202  	}
   203  }
   204  
   205  func TestAccessRequestHandler_deleting(t *testing.T) {
   206  	tests := []struct {
   207  		name           string
   208  		outboundStatus prov.Status
   209  	}{
   210  		{
   211  			name:           "should deprovision with no error",
   212  			outboundStatus: prov.Success,
   213  		},
   214  		{
   215  			name:           "should fail to deprovision and set the status to error",
   216  			outboundStatus: prov.Error,
   217  		},
   218  	}
   219  
   220  	for _, tc := range tests {
   221  		t.Run(tc.name, func(t *testing.T) {
   222  			cm := agentcache.NewAgentCacheManager(&config.CentralConfiguration{}, false)
   223  			ar := accessReq
   224  			ar.Status.Level = prov.Success.String()
   225  			ar.Metadata.State = v1.ResourceDeleting
   226  			ar.Finalizers = []v1.Finalizer{{Name: arFinalizer}}
   227  
   228  			instanceRI, _ := instance.AsInstance()
   229  			cm.AddAPIServiceInstance(instanceRI)
   230  
   231  			arp := &mockARProvision{
   232  				t:                     t,
   233  				expectedAPIID:         instRefID,
   234  				expectedAppName:       managedAppRefName,
   235  				expectedAccessDetails: util.GetAgentDetails(&ar),
   236  				expectedAppDetails:    util.GetAgentDetails(mApp),
   237  				expectedStatus: mock.MockRequestStatus{
   238  					Status: tc.outboundStatus,
   239  					Msg:    "msg",
   240  					Properties: map[string]string{
   241  						"status_key": "status_val",
   242  					},
   243  				},
   244  			}
   245  
   246  			c := &mockClient{
   247  				expectedStatus: tc.outboundStatus.String(),
   248  				getRI:          mApp,
   249  				isDeleting:     true,
   250  				t:              t,
   251  			}
   252  
   253  			handler := NewAccessRequestHandler(arp, cm, c)
   254  
   255  			ri, _ := ar.AsInstance()
   256  
   257  			err := handler.Handle(NewEventContext(proto.Event_UPDATED, nil, ri.Kind, ri.Name), nil, ri)
   258  			assert.Nil(t, err)
   259  			assert.Equal(t, deprovision, arp.expectedProvType)
   260  
   261  			if tc.outboundStatus.String() == prov.Success.String() {
   262  				assert.False(t, c.createSubCalled)
   263  			} else {
   264  				assert.True(t, c.createSubCalled)
   265  			}
   266  		})
   267  	}
   268  }
   269  
   270  func TestAccessRequestHandler_wrong_kind(t *testing.T) {
   271  	cm := agentcache.NewAgentCacheManager(&config.CentralConfiguration{}, false)
   272  	c := &mockClient{
   273  		t: t,
   274  	}
   275  	ar := &mockARProvision{}
   276  	handler := NewAccessRequestHandler(ar, cm, c)
   277  	ri := &v1.ResourceInstance{
   278  		ResourceMeta: v1.ResourceMeta{
   279  			GroupVersionKind: management.EnvironmentGVK(),
   280  		},
   281  	}
   282  	err := handler.Handle(NewEventContext(proto.Event_CREATED, nil, ri.Kind, ri.Name), nil, ri)
   283  	assert.Nil(t, err)
   284  }
   285  
   286  func Test_arReq(t *testing.T) {
   287  	r := provAccReq{
   288  		appDetails: map[string]interface{}{
   289  			"app_details_key": "app_details_value",
   290  		},
   291  		accessDetails: map[string]interface{}{
   292  			"access_details_key": "access_details_value",
   293  		},
   294  		requestData: map[string]interface{}{
   295  			"key": "val",
   296  		},
   297  		managedApp: "managed-app-name",
   298  		instanceDetails: map[string]interface{}{
   299  			defs.AttrExternalAPIStage: "api-stage",
   300  			defs.AttrExternalAPIID:    "123",
   301  		},
   302  		id: "ar-id",
   303  		provData: map[string]interface{}{
   304  			"key1": "val1",
   305  		},
   306  	}
   307  
   308  	assert.Equal(t, r.managedApp, r.GetApplicationName())
   309  	assert.Equal(t, r.id, r.GetID())
   310  	assert.Equal(t, r.provData, r.GetAccessRequestProvisioningData().(map[string]interface{}))
   311  	assert.Equal(t, r.appDetails["app_details_key"], r.GetApplicationDetailsValue("app_details_key"))
   312  	assert.Equal(t, r.accessDetails["access_details_key"], r.GetAccessRequestDetailsValue("access_details_key"))
   313  	assert.Equal(t, r.requestData, r.GetAccessRequestData())
   314  
   315  	r.accessDetails = nil
   316  	r.appDetails = nil
   317  	assert.Empty(t, r.GetApplicationDetailsValue("app_details_key"))
   318  	assert.Empty(t, r.GetAccessRequestDetailsValue("access_details_key"))
   319  }
   320  
   321  type mockClient struct {
   322  	createSubCalled bool
   323  	expectedStatus  string
   324  	getErr          error
   325  	getARDErr       error
   326  	getRI           *v1.ResourceInstance
   327  	ard             *v1.ResourceInstance
   328  	isDeleting      bool
   329  	subError        error
   330  	t               *testing.T
   331  }
   332  
   333  func (m *mockClient) GetResource(url string) (*v1.ResourceInstance, error) {
   334  	if strings.Contains(url, "/accessrequestdefinitions") {
   335  		return m.ard, m.getARDErr
   336  	}
   337  	return m.getRI, m.getErr
   338  }
   339  
   340  func (m *mockClient) CreateSubResource(_ v1.ResourceMeta, subs map[string]interface{}) error {
   341  	if statusI, ok := subs["status"]; ok {
   342  		status := statusI.(*v1.ResourceStatus)
   343  		assert.Equal(m.t, m.expectedStatus, status.Level, status.Reasons)
   344  	}
   345  	m.createSubCalled = true
   346  	return m.subError
   347  }
   348  
   349  func (m *mockClient) UpdateResourceFinalizer(_ *v1.ResourceInstance, _, _ string, addAction bool) (*v1.ResourceInstance, error) {
   350  	if m.isDeleting {
   351  		assert.False(m.t, addAction, "addAction should be false when the resource is deleting")
   352  	} else {
   353  		assert.True(m.t, addAction, "addAction should be true when the resource is not deleting")
   354  	}
   355  
   356  	return nil, nil
   357  }
   358  
   359  func (m *mockClient) UpdateResourceInstance(ri v1.Interface) (*v1.ResourceInstance, error) {
   360  	return nil, nil
   361  }
   362  
   363  type mockARProvision struct {
   364  	expectedAccessDetails map[string]interface{}
   365  	expectedAPIID         string
   366  	expectedAppDetails    map[string]interface{}
   367  	expectedAppName       string
   368  	expectedProvType      string
   369  	expectedStatus        mock.MockRequestStatus
   370  	t                     *testing.T
   371  }
   372  
   373  func (m *mockARProvision) AccessRequestProvision(ar prov.AccessRequest) (status prov.RequestStatus, data prov.AccessData) {
   374  	m.expectedProvType = provision
   375  	v := ar.(*provAccReq)
   376  	assert.Equal(m.t, m.expectedAPIID, v.instanceDetails[defs.AttrExternalAPIID])
   377  	assert.Equal(m.t, m.expectedAppName, v.managedApp)
   378  	assert.Equal(m.t, m.expectedAppDetails, v.appDetails)
   379  	assert.Equal(m.t, m.expectedAccessDetails, v.accessDetails)
   380  	return m.expectedStatus, prov.NewAccessDataBuilder().SetData(nil)
   381  }
   382  
   383  func (m *mockARProvision) AccessRequestDeprovision(ar prov.AccessRequest) (status prov.RequestStatus) {
   384  	m.expectedProvType = deprovision
   385  	v := ar.(*provAccReq)
   386  	assert.Equal(m.t, m.expectedAPIID, v.instanceDetails[defs.AttrExternalAPIID])
   387  	assert.Equal(m.t, m.expectedAppName, v.managedApp)
   388  	assert.Equal(m.t, m.expectedAppDetails, v.appDetails)
   389  	assert.Equal(m.t, m.expectedAccessDetails, v.accessDetails)
   390  	return m.expectedStatus
   391  }
   392  
   393  const instRefID = "inst-id-1"
   394  const instRefName = "inst-name-1"
   395  const managedAppRefName = "managed-app-name"
   396  
   397  var instance = &management.APIServiceInstance{
   398  	ResourceMeta: v1.ResourceMeta{
   399  		Name: instRefName,
   400  		Metadata: v1.Metadata{
   401  			ID: instRefID,
   402  		},
   403  		SubResources: map[string]interface{}{
   404  			defs.XAgentDetails: map[string]interface{}{
   405  				defs.AttrExternalAPIID: instRefID,
   406  			},
   407  		},
   408  	},
   409  	Spec: management.ApiServiceInstanceSpec{
   410  		AccessRequestDefinition: "ard",
   411  	},
   412  }
   413  
   414  var mApp = &v1.ResourceInstance{
   415  	ResourceMeta: v1.ResourceMeta{
   416  		Name: managedAppRefName,
   417  		SubResources: map[string]interface{}{
   418  			defs.XAgentDetails: map[string]interface{}{
   419  				"sub_managed_app_key": "sub_managed_app_val",
   420  			},
   421  			"status": map[string]interface{}{
   422  				"level": prov.Success.String(),
   423  			},
   424  		},
   425  	},
   426  }
   427  
   428  var accessReq = management.AccessRequest{
   429  	ResourceMeta: v1.ResourceMeta{
   430  		Metadata: v1.Metadata{
   431  			ID: "11",
   432  			References: []v1.Reference{
   433  				{
   434  					Group: management.APIServiceInstanceGVK().Group,
   435  					Kind:  management.APIServiceInstanceGVK().Kind,
   436  					ID:    instRefID,
   437  					Name:  instRefName,
   438  				},
   439  			},
   440  			Scope: v1.MetadataScope{
   441  				Kind: management.EnvironmentGVK().Kind,
   442  				Name: "env-1",
   443  			},
   444  		},
   445  		SubResources: map[string]interface{}{
   446  			defs.XAgentDetails: map[string]interface{}{
   447  				"sub_access_request_key": "sub_access_request_val",
   448  			},
   449  		},
   450  	},
   451  	Spec: management.AccessRequestSpec{
   452  		ApiServiceInstance: instRefName,
   453  		ManagedApplication: managedAppRefName,
   454  		Data:               map[string]interface{}{},
   455  	},
   456  	Status: &v1.ResourceStatus{
   457  		Level: prov.Pending.String(),
   458  	},
   459  }
   460  
   461  var ard = &management.AccessRequestDefinition{
   462  	ResourceMeta: v1.ResourceMeta{
   463  		Name: credAppRefName,
   464  		SubResources: map[string]interface{}{
   465  			defs.XAgentDetails: map[string]interface{}{
   466  				"sub_crd_key": "sub_crd_val",
   467  			},
   468  		},
   469  	},
   470  	Owner: nil,
   471  	Spec: management.AccessRequestDefinitionSpec{
   472  		Schema: nil,
   473  		Provision: &management.AccessRequestDefinitionSpecProvision{
   474  			Schema: map[string]interface{}{
   475  				"properties": map[string]interface{}{},
   476  			},
   477  		},
   478  	},
   479  }