github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/selfregmanager/selfreg_manager_test.go (about) 1 package selfregmanager_test 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net/http" 8 "testing" 9 "time" 10 11 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 12 13 "github.com/kyma-incubator/compass/components/director/internal/selfregmanager" 14 "github.com/kyma-incubator/compass/components/director/internal/selfregmanager/automock" 15 "github.com/kyma-incubator/compass/components/director/internal/selfregmanager/selfregmngrtest" 16 "github.com/kyma-incubator/compass/components/director/pkg/resource" 17 18 "github.com/kyma-incubator/compass/components/director/pkg/config" 19 "github.com/kyma-incubator/compass/components/hydrator/pkg/oathkeeper" 20 21 "github.com/kyma-incubator/compass/components/director/pkg/consumer" 22 "github.com/stretchr/testify/require" 23 ) 24 25 const ( 26 selfRegisterDistinguishLabelKey = "test-distinguish-label-key" 27 distinguishLblVal = "test-value" 28 testUUID = "b3ea1977-582e-4d61-ae12-b3a837a3858e" 29 testRegion = "test-region" 30 fakeRegion = "fake-region" 31 missingSaaSAppRegion = "missing-saas-app-region" 32 emptySaaSAppRegion = "empty-saas-app-region" 33 consumerID = "test-consumer-id" 34 testSaaSAppName = "testSaaSAppName-1" 35 secondTestSaaSAppName = "testSaaSAppName-2" 36 ) 37 38 var ( 39 testConfig = config.SelfRegConfig{ 40 SelfRegisterDistinguishLabelKey: selfRegisterDistinguishLabelKey, 41 SelfRegisterLabelKey: "test-label-key", 42 SelfRegisterLabelValuePrefix: "test-prefix", 43 SelfRegisterResponseKey: selfregmngrtest.ResponseLabelKey, 44 SaaSAppNameLabelKey: "test-CMPSaaSAppName", 45 SelfRegisterPath: "test-path", 46 SelfRegisterNameQueryParam: "testNameQuery", 47 SelfRegisterTenantQueryParam: "testTenantQuery", 48 SelfRegisterRequestBodyPattern: `{"%s":"test"}`, 49 SelfRegisterSecretPath: "testdata/TestSelfRegisterManager_PrepareRuntimeForSelfRegistration.golden", 50 SelfRegSaaSAppSecretPath: "testdata/TestSelfRegisterManager_SaaSAppName.golden", 51 InstanceClientIDPath: "clientId", 52 InstanceClientSecretPath: "clientSecret", 53 InstanceURLPath: "url", 54 InstanceTokenURLPath: "tokenUrl", 55 InstanceCertPath: "clientCert", 56 InstanceKeyPath: "clientKey", 57 RegionToInstanceConfig: map[string]config.InstanceConfig{ 58 "test-region": { 59 ClientID: "client_id", 60 ClientSecret: "client_secret", 61 URL: "https://test-url-second.com", 62 TokenURL: "https://test-token-url-second.com", 63 Cert: "cert", 64 Key: "key", 65 }, 66 "fake-region": { 67 ClientID: "client_id_2", 68 ClientSecret: "client_secret_2", 69 URL: "https://test-url -second.com", 70 TokenURL: "https://test-token-url-second.com", 71 Cert: "cert2", 72 Key: "key2", 73 }, 74 "missing-saas-app-region": { 75 ClientID: "client_id", 76 ClientSecret: "client_secret", 77 URL: "https://test-url-second.com", 78 TokenURL: "https://test-token-url-second.com", 79 Cert: "cert", 80 Key: "key", 81 }, 82 "empty-saas-app-region": { 83 ClientID: "client_id", 84 ClientSecret: "client_secret", 85 URL: "https://test-url-second.com", 86 TokenURL: "https://test-token-url-second.com", 87 Cert: "cert", 88 Key: "key", 89 }, 90 }, 91 SaaSAppNamePath: "localSaaSAppNamePath", 92 RegionToSaaSAppName: map[string]string{ 93 "test-region": testSaaSAppName, 94 "second-region": secondTestSaaSAppName, 95 "empty-saas-app-region": "", 96 }, 97 ClientTimeout: 5 * time.Second, 98 } 99 100 tokenConsumer = consumer.Consumer{ 101 ConsumerID: consumerID, 102 Flow: oathkeeper.OAuth2Flow, 103 Region: testRegion, 104 } 105 106 certConsumer = consumer.Consumer{ 107 ConsumerID: consumerID, 108 Flow: oathkeeper.CertificateFlow, 109 Region: testRegion, 110 } 111 112 certConsumerWithoutRegion = consumer.Consumer{ 113 ConsumerID: consumerID, 114 Flow: oathkeeper.CertificateFlow, 115 } 116 117 certConsumerWithFakeRegion = consumer.Consumer{ 118 ConsumerID: consumerID, 119 Flow: oathkeeper.CertificateFlow, 120 Region: fakeRegion, 121 } 122 123 certConsumerWithMissingSaaSAppRegion = consumer.Consumer{ 124 ConsumerID: consumerID, 125 Flow: oathkeeper.CertificateFlow, 126 Region: missingSaaSAppRegion, 127 } 128 129 certConsumerWithEmptySaaSAppRegion = consumer.Consumer{ 130 ConsumerID: consumerID, 131 Flow: oathkeeper.CertificateFlow, 132 Region: emptySaaSAppRegion, 133 } 134 ) 135 136 func TestSelfRegisterManager_IsSelfRegistrationFlow(t *testing.T) { 137 contextWithTenant := tenant.SaveToContext(context.TODO(), "internalTenantID", "externalTenantID") 138 ctxWithTokenConsumer := consumer.SaveToContext(contextWithTenant, tokenConsumer) 139 ctxWithCertConsumer := consumer.SaveToContext(contextWithTenant, certConsumer) 140 141 testCases := []struct { 142 Name string 143 Config config.SelfRegConfig 144 Region string 145 InputLabels map[string]interface{} 146 Context context.Context 147 ExpectedErr error 148 ExpectedOutput bool 149 }{ 150 { 151 Name: "Success", 152 Config: testConfig, 153 InputLabels: fixLblInput(), 154 Region: testRegion, 155 Context: ctxWithCertConsumer, 156 ExpectedErr: nil, 157 ExpectedOutput: true, 158 }, 159 { 160 Name: "Success for non-matching consumer", 161 Config: testConfig, 162 Region: testRegion, 163 InputLabels: fixLblWithoutRegion(), 164 Context: ctxWithTokenConsumer, 165 ExpectedErr: nil, 166 ExpectedOutput: false, 167 }, 168 { 169 Name: "Error for missing distinguished label", 170 Config: testConfig, 171 Region: testRegion, 172 InputLabels: map[string]interface{}{}, 173 Context: ctxWithCertConsumer, 174 ExpectedErr: fmt.Errorf("missing %q label", selfRegisterDistinguishLabelKey), 175 ExpectedOutput: false, 176 }, 177 { 178 Name: "Error when context does not contain consumer", 179 Config: testConfig, 180 Region: testRegion, 181 InputLabels: map[string]interface{}{}, 182 Context: context.TODO(), 183 ExpectedErr: consumer.NoConsumerError, 184 ExpectedOutput: false, 185 }, 186 { 187 Name: "False when context does not contain tenant", 188 Config: testConfig, 189 Region: testRegion, 190 InputLabels: map[string]interface{}{}, 191 Context: consumer.SaveToContext(context.TODO(), tokenConsumer), 192 ExpectedErr: nil, 193 ExpectedOutput: false, 194 }, 195 } 196 197 for _, testCase := range testCases { 198 t.Run(testCase.Name, func(t *testing.T) { 199 manager, err := selfregmanager.NewSelfRegisterManager(testCase.Config, nil) 200 require.NoError(t, err) 201 202 output, err := manager.IsSelfRegistrationFlow(testCase.Context, testCase.InputLabels) 203 if testCase.ExpectedErr != nil { 204 require.Error(t, err) 205 require.Contains(t, err.Error(), testCase.ExpectedErr.Error()) 206 } else { 207 require.NoError(t, err) 208 } 209 require.Equal(t, testCase.ExpectedOutput, output) 210 }) 211 } 212 } 213 214 func TestSelfRegisterManager_PrepareForSelfRegistration(t *testing.T) { 215 contextWithTenant := tenant.SaveToContext(context.TODO(), "internalTenantID", "externalTenantID") 216 ctxWithTokenConsumer := consumer.SaveToContext(contextWithTenant, tokenConsumer) 217 ctxWithCertConsumer := consumer.SaveToContext(contextWithTenant, certConsumer) 218 ctxWithCertConsumerWithoutRegion := consumer.SaveToContext(contextWithTenant, certConsumerWithoutRegion) 219 ctxWithCertConsumerWithFakeRegion := consumer.SaveToContext(contextWithTenant, certConsumerWithFakeRegion) 220 ctxWithCertConsumerWithMissingSaaSAppRegion := consumer.SaveToContext(contextWithTenant, certConsumerWithMissingSaaSAppRegion) 221 ctxWithCertConsumerWithEmptySaaSAppRegion := consumer.SaveToContext(contextWithTenant, certConsumerWithEmptySaaSAppRegion) 222 223 testCases := []struct { 224 Name string 225 Config config.SelfRegConfig 226 CallerProvider func(*testing.T, config.SelfRegConfig, string) *automock.ExternalSvcCallerProvider 227 Region string 228 InputLabels map[string]interface{} 229 Context context.Context 230 ResourceType resource.Type 231 Validation func() error 232 ExpectedErr error 233 ExpectedOutput map[string]interface{} 234 }{ 235 { 236 Name: "Success", 237 Config: testConfig, 238 InputLabels: fixLblWithoutRegion(), 239 CallerProvider: selfregmngrtest.CallerThatGetsCalledOnce(http.StatusCreated), 240 Region: testRegion, 241 Context: ctxWithCertConsumer, 242 ResourceType: resource.Runtime, 243 Validation: func() error { return nil }, 244 ExpectedErr: nil, 245 ExpectedOutput: fixLblInputAfterPrepForRuntime(), 246 }, 247 { 248 Name: "Success with subaccount label for application templates", 249 Config: testConfig, 250 InputLabels: fixLblWithoutRegion(), 251 CallerProvider: selfregmngrtest.CallerThatGetsCalledOnce(http.StatusCreated), 252 Region: testRegion, 253 Context: ctxWithCertConsumer, 254 ResourceType: resource.ApplicationTemplate, 255 Validation: func() error { return nil }, 256 ExpectedErr: nil, 257 ExpectedOutput: fixLblInputAfterPrep(), 258 }, 259 { 260 Name: "Success for non-matching consumer", 261 Config: testConfig, 262 CallerProvider: selfregmngrtest.CallerThatDoesNotGetCalled, 263 Region: testRegion, 264 InputLabels: fixLblWithoutRegion(), 265 Context: ctxWithTokenConsumer, 266 ResourceType: resource.Runtime, 267 Validation: func() error { return nil }, 268 ExpectedErr: nil, 269 ExpectedOutput: fixLblWithDistinguish(), 270 }, 271 { 272 Name: "Success for missing distinguished label but resource is Runtime", 273 Config: testConfig, 274 CallerProvider: selfregmngrtest.CallerThatDoesNotGetCalled, 275 Region: testRegion, 276 InputLabels: map[string]interface{}{}, 277 Context: ctxWithCertConsumer, 278 ResourceType: resource.Runtime, 279 Validation: func() error { return nil }, 280 ExpectedErr: nil, 281 ExpectedOutput: map[string]interface{}{}, 282 }, 283 { 284 Name: "Error validation failed", 285 Config: testConfig, 286 CallerProvider: selfregmngrtest.CallerThatDoesNotGetCalled, 287 Region: testRegion, 288 InputLabels: fixLblInput(), 289 Context: ctxWithCertConsumer, 290 ResourceType: resource.ApplicationTemplate, 291 Validation: func() error { return errors.New("validation failed") }, 292 ExpectedErr: errors.New("validation failed"), 293 ExpectedOutput: nil, 294 }, 295 { 296 Name: "Error for missing distinguished label and resource is App Template", 297 Config: testConfig, 298 CallerProvider: selfregmngrtest.CallerThatDoesNotGetCalled, 299 Region: testRegion, 300 InputLabels: map[string]interface{}{}, 301 Context: ctxWithCertConsumer, 302 ResourceType: resource.ApplicationTemplate, 303 Validation: func() error { return nil }, 304 ExpectedErr: fmt.Errorf("missing %q label", selfRegisterDistinguishLabelKey), 305 ExpectedOutput: nil, 306 }, 307 { 308 Name: "Error during region check when tenant region is unable to be retrieved from context", 309 Config: testConfig, 310 InputLabels: fixLblWithoutRegion(), 311 CallerProvider: selfregmngrtest.CallerThatDoesNotGetCalled, 312 Region: testRegion, 313 Context: ctxWithCertConsumerWithoutRegion, 314 ResourceType: resource.Runtime, 315 Validation: func() error { return nil }, 316 ExpectedErr: fmt.Errorf("missing %s value in consumer context", selfregmanager.RegionLabel), 317 ExpectedOutput: nil, 318 }, 319 { 320 Name: "Error when region label is provided", 321 Config: testConfig, 322 InputLabels: fixLblInput(), 323 CallerProvider: selfregmngrtest.CallerThatDoesNotGetCalled, 324 Region: testRegion, 325 Context: ctxWithCertConsumer, 326 ResourceType: resource.Runtime, 327 Validation: func() error { return nil }, 328 ExpectedErr: fmt.Errorf("providing %q label and value is forbidden", selfregmanager.RegionLabel), 329 ExpectedOutput: nil, 330 }, 331 { 332 Name: "Error when caller provider fails", 333 Config: testConfig, 334 CallerProvider: selfregmngrtest.CallerProviderThatFails, 335 Region: testRegion, 336 InputLabels: fixLblWithoutRegion(), 337 Context: ctxWithCertConsumer, 338 ResourceType: resource.Runtime, 339 Validation: func() error { return nil }, 340 ExpectedErr: errors.New("while getting caller"), 341 ExpectedOutput: nil, 342 }, 343 { 344 Name: "Error when context does not contain consumer", 345 Config: testConfig, 346 CallerProvider: selfregmngrtest.CallerThatDoesNotGetCalled, 347 Region: testRegion, 348 InputLabels: map[string]interface{}{}, 349 Context: context.TODO(), 350 ResourceType: resource.Runtime, 351 Validation: func() error { return nil }, 352 ExpectedErr: consumer.NoConsumerError, 353 ExpectedOutput: nil, 354 }, 355 { 356 Name: "Error when can't create URL for preparation of self-registration", 357 Config: testConfig, 358 CallerProvider: selfregmngrtest.CallerThatDoesNotGetCalled, 359 Region: fakeRegion, 360 InputLabels: map[string]interface{}{selfRegisterDistinguishLabelKey: "invalid value"}, 361 Context: ctxWithCertConsumerWithFakeRegion, 362 ResourceType: resource.Runtime, 363 Validation: func() error { return nil }, 364 ExpectedErr: errors.New("while creating url for preparation of self-registered resource"), 365 ExpectedOutput: nil, 366 }, 367 { 368 Name: "Error when Call doesn't succeed", 369 Config: testConfig, 370 CallerProvider: selfregmngrtest.CallerThatDoesNotSucceed, 371 Region: testRegion, 372 InputLabels: fixLblWithoutRegion(), 373 Context: ctxWithCertConsumer, 374 ResourceType: resource.Runtime, 375 Validation: func() error { return nil }, 376 ExpectedErr: selfregmngrtest.TestError, 377 ExpectedOutput: nil, 378 }, 379 { 380 Name: "Error when status code is unexpected", 381 Config: testConfig, 382 CallerProvider: selfregmngrtest.CallerThatReturnsBadStatus, 383 Region: testRegion, 384 InputLabels: fixLblWithoutRegion(), 385 Context: ctxWithCertConsumer, 386 ResourceType: resource.Runtime, 387 Validation: func() error { return nil }, 388 ExpectedErr: errors.New("received unexpected status"), 389 ExpectedOutput: nil, 390 }, 391 { 392 Name: "Error when SaaS application name is not found for a given region", 393 Config: testConfig, 394 CallerProvider: selfregmngrtest.CallerThatGetsCalledOnce(http.StatusCreated), 395 Region: missingSaaSAppRegion, 396 InputLabels: fixLblWithoutRegion(), 397 Context: ctxWithCertConsumerWithMissingSaaSAppRegion, 398 ResourceType: resource.Runtime, 399 Validation: func() error { return nil }, 400 ExpectedErr: fmt.Errorf("missing SaaS application name for region: \"%s\"", missingSaaSAppRegion), 401 ExpectedOutput: nil, 402 }, 403 { 404 Name: "Error when SaaS application name is empty", 405 Config: testConfig, 406 CallerProvider: selfregmngrtest.CallerThatGetsCalledOnce(http.StatusCreated), 407 Region: emptySaaSAppRegion, 408 InputLabels: fixLblWithoutRegion(), 409 Context: ctxWithCertConsumerWithEmptySaaSAppRegion, 410 ResourceType: resource.Runtime, 411 Validation: func() error { return nil }, 412 ExpectedErr: fmt.Errorf("SaaS application name for region: \"%s\" could not be empty", emptySaaSAppRegion), 413 ExpectedOutput: nil, 414 }, 415 } 416 417 for _, testCase := range testCases { 418 t.Run(testCase.Name, func(t *testing.T) { 419 svcCallerProvider := testCase.CallerProvider(t, testCase.Config, testCase.Region) 420 manager, err := selfregmanager.NewSelfRegisterManager(testCase.Config, svcCallerProvider) 421 require.NoError(t, err) 422 423 output, err := manager.PrepareForSelfRegistration(testCase.Context, testCase.ResourceType, testCase.InputLabels, testUUID, testCase.Validation) 424 if testCase.ExpectedErr != nil { 425 require.Error(t, err) 426 require.Contains(t, err.Error(), testCase.ExpectedErr.Error()) 427 } else { 428 require.NoError(t, err) 429 } 430 require.Equal(t, testCase.ExpectedOutput, output) 431 432 svcCallerProvider.AssertExpectations(t) 433 }) 434 } 435 } 436 437 func TestSelfRegisterManager_CleanupSelfRegistration(t *testing.T) { 438 tokenConsumer := consumer.Consumer{ 439 ConsumerID: consumerID, 440 Flow: oathkeeper.OAuth2Flow, 441 } 442 certConsumer := consumer.Consumer{ 443 ConsumerID: consumerID, 444 Flow: oathkeeper.CertificateFlow, 445 } 446 447 contextWithTenant := tenant.SaveToContext(context.TODO(), "internalTenantID", "externalTenantID") 448 ctxWithTokenConsumer := consumer.SaveToContext(contextWithTenant, tokenConsumer) 449 ctxWithCertConsumer := consumer.SaveToContext(contextWithTenant, certConsumer) 450 451 testCases := []struct { 452 Name string 453 Config config.SelfRegConfig 454 CallerProvider func(*testing.T, config.SelfRegConfig, string) *automock.ExternalSvcCallerProvider 455 Region string 456 SelfRegisteredDistinguishLabelValue string 457 Context context.Context 458 ExpectedErr error 459 }{ 460 { 461 Name: "Success", 462 CallerProvider: selfregmngrtest.CallerThatGetsCalledOnce(http.StatusOK), 463 Region: testRegion, 464 Config: testConfig, 465 SelfRegisteredDistinguishLabelValue: distinguishLblVal, 466 Context: ctxWithCertConsumer, 467 ExpectedErr: nil, 468 }, 469 { 470 Name: "Success when resoource is not self-registered", 471 CallerProvider: selfregmngrtest.CallerThatDoesNotGetCalled, 472 Region: testRegion, 473 Config: testConfig, 474 SelfRegisteredDistinguishLabelValue: "", 475 Context: ctxWithCertConsumer, 476 ExpectedErr: nil, 477 }, 478 { 479 Name: "Error when can't create URL for cleanup of self-registered resource", 480 CallerProvider: selfregmngrtest.CallerThatDoesNotGetCalled, 481 Region: fakeRegion, 482 Config: testConfig, 483 SelfRegisteredDistinguishLabelValue: "invalid value", 484 Context: ctxWithCertConsumer, 485 ExpectedErr: errors.New("while creating url for cleanup of self-registered resource"), 486 }, 487 { 488 Name: "Error when region doesn't exist", 489 Config: testConfig, 490 CallerProvider: selfregmngrtest.CallerThatDoesNotGetCalled, 491 Region: "not-valid", 492 SelfRegisteredDistinguishLabelValue: distinguishLblVal, 493 Context: ctxWithCertConsumer, 494 ExpectedErr: errors.New("missing configuration for region"), 495 }, 496 { 497 Name: "Error when caller provider fails", 498 Config: testConfig, 499 CallerProvider: selfregmngrtest.CallerProviderThatFails, 500 Region: testRegion, 501 SelfRegisteredDistinguishLabelValue: distinguishLblVal, 502 Context: ctxWithCertConsumer, 503 ExpectedErr: errors.New("while getting caller"), 504 }, 505 { 506 Name: "Error when Call doesn't succeed", 507 CallerProvider: selfregmngrtest.CallerThatDoesNotSucceed, 508 Region: testRegion, 509 Config: testConfig, 510 SelfRegisteredDistinguishLabelValue: distinguishLblVal, 511 Context: ctxWithCertConsumer, 512 ExpectedErr: selfregmngrtest.TestError, 513 }, 514 { 515 Name: "Error when Call doesn't succeed", 516 CallerProvider: selfregmngrtest.CallerThatReturnsBadStatus, 517 Region: testRegion, 518 Config: testConfig, 519 SelfRegisteredDistinguishLabelValue: distinguishLblVal, 520 Context: ctxWithCertConsumer, 521 ExpectedErr: errors.New("received unexpected status code"), 522 }, 523 { 524 Name: "Success when token consumer is used", 525 CallerProvider: selfregmngrtest.CallerThatDoesNotGetCalled, 526 Region: testRegion, 527 Config: testConfig, 528 SelfRegisteredDistinguishLabelValue: "", 529 Context: ctxWithTokenConsumer, 530 ExpectedErr: nil, 531 }, 532 } 533 534 for _, testCase := range testCases { 535 t.Run(testCase.Name, func(t *testing.T) { 536 svcCallerProvider := testCase.CallerProvider(t, testCase.Config, testCase.Region) 537 manager, err := selfregmanager.NewSelfRegisterManager(testCase.Config, svcCallerProvider) 538 require.NoError(t, err) 539 540 err = manager.CleanupSelfRegistration(testCase.Context, testCase.SelfRegisteredDistinguishLabelValue, testCase.Region) 541 if testCase.ExpectedErr != nil { 542 require.Error(t, err) 543 require.Contains(t, err.Error(), testCase.ExpectedErr.Error()) 544 } else { 545 require.NoError(t, err) 546 } 547 }) 548 } 549 } 550 551 func TestNewSelfRegisterManager(t *testing.T) { 552 t.Run("Error when creating self register manager fails", func(t *testing.T) { 553 cfg := config.SelfRegConfig{} 554 manager, err := selfregmanager.NewSelfRegisterManager(cfg, nil) 555 require.Error(t, err) 556 require.Contains(t, err.Error(), "config path cannot be empty") 557 require.Nil(t, manager) 558 }) 559 } 560 561 func fixLblInputAfterPrep() map[string]interface{} { 562 return map[string]interface{}{ 563 testConfig.SelfRegisterLabelKey: selfregmngrtest.ResponseLabelValue, 564 selfregmanager.RegionLabel: testRegion, 565 selfRegisterDistinguishLabelKey: distinguishLblVal, 566 } 567 } 568 569 func fixLblInputAfterPrepForRuntime() map[string]interface{} { 570 return map[string]interface{}{ 571 testConfig.SelfRegisterLabelKey: selfregmngrtest.ResponseLabelValue, 572 selfregmanager.RegionLabel: testRegion, 573 selfRegisterDistinguishLabelKey: distinguishLblVal, 574 testConfig.SaaSAppNameLabelKey: testSaaSAppName, 575 } 576 } 577 578 func fixLblWithDistinguish() map[string]interface{} { 579 return map[string]interface{}{ 580 selfRegisterDistinguishLabelKey: distinguishLblVal, 581 } 582 } 583 584 func fixLblInput() map[string]interface{} { 585 return map[string]interface{}{ 586 selfRegisterDistinguishLabelKey: distinguishLblVal, 587 selfregmanager.RegionLabel: testRegion, 588 } 589 } 590 591 func fixLblWithoutRegion() map[string]interface{} { 592 return map[string]interface{}{ 593 selfRegisterDistinguishLabelKey: distinguishLblVal, 594 } 595 }