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 }