github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/data/data_manager_test.go (about) 1 // Copyright © 2021 Kaleido, Inc. 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 package data 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 24 "github.com/kaleido-io/firefly/internal/config" 25 "github.com/kaleido-io/firefly/mocks/databasemocks" 26 "github.com/kaleido-io/firefly/mocks/dataexchangemocks" 27 "github.com/kaleido-io/firefly/pkg/fftypes" 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/mock" 30 ) 31 32 func newTestDataManager(t *testing.T) (*dataManager, context.Context, func()) { 33 ctx, cancel := context.WithCancel(context.Background()) 34 mdi := &databasemocks.Plugin{} 35 mdx := &dataexchangemocks.Plugin{} 36 dm, err := NewDataManager(ctx, mdi, mdx) 37 assert.NoError(t, err) 38 return dm.(*dataManager), ctx, cancel 39 } 40 41 func TestValidateE2E(t *testing.T) { 42 43 config.Reset() 44 dm, ctx, cancel := newTestDataManager(t) 45 defer cancel() 46 mdi := dm.database.(*databasemocks.Plugin) 47 data := &fftypes.Data{ 48 Namespace: "ns1", 49 Validator: fftypes.ValidatorTypeJSON, 50 Datatype: &fftypes.DatatypeRef{ 51 Name: "customer", 52 Version: "0.0.1", 53 }, 54 Value: fftypes.Byteable(`{"some":"json"}`), 55 } 56 data.Seal(ctx) 57 dt := &fftypes.Datatype{ 58 ID: fftypes.NewUUID(), 59 Validator: fftypes.ValidatorTypeJSON, 60 Value: fftypes.Byteable(`{ 61 "properties": { 62 "field1": { 63 "type": "string" 64 } 65 }, 66 "additionalProperties": false 67 }`), 68 Namespace: "ns1", 69 Name: "customer", 70 Version: "0.0.1", 71 } 72 mdi.On("GetDatatypeByName", mock.Anything, "ns1", "customer", "0.0.1").Return(dt, nil) 73 isValid, err := dm.ValidateAll(ctx, []*fftypes.Data{data}) 74 assert.Regexp(t, "FF10198", err) 75 assert.False(t, isValid) 76 77 v, err := dm.getValidatorForDatatype(ctx, data.Namespace, data.Validator, data.Datatype) 78 err = v.Validate(ctx, data) 79 assert.Regexp(t, "FF10198", err) 80 81 data.Value = fftypes.Byteable(`{"field1":"value1"}`) 82 data.Seal(context.Background()) 83 err = v.Validate(ctx, data) 84 assert.NoError(t, err) 85 86 isValid, err = dm.ValidateAll(ctx, []*fftypes.Data{data}) 87 assert.NoError(t, err) 88 assert.True(t, isValid) 89 90 } 91 92 func TestInitBadDeps(t *testing.T) { 93 _, err := NewDataManager(context.Background(), nil, nil) 94 assert.Regexp(t, "FF10128", err) 95 } 96 97 func TestValidatorLookupCached(t *testing.T) { 98 99 config.Reset() 100 dm, ctx, cancel := newTestDataManager(t) 101 defer cancel() 102 mdi := dm.database.(*databasemocks.Plugin) 103 ref := &fftypes.DatatypeRef{ 104 Name: "customer", 105 Version: "0.0.1", 106 } 107 dt := &fftypes.Datatype{ 108 ID: fftypes.NewUUID(), 109 Validator: fftypes.ValidatorTypeJSON, 110 Value: fftypes.Byteable(`{}`), 111 Name: "customer", 112 Namespace: "0.0.1", 113 } 114 mdi.On("GetDatatypeByName", mock.Anything, "ns1", "customer", "0.0.1").Return(dt, nil).Once() 115 lookup1, err := dm.getValidatorForDatatype(ctx, "ns1", fftypes.ValidatorTypeJSON, ref) 116 assert.NoError(t, err) 117 assert.Equal(t, "customer", lookup1.(*jsonValidator).datatype.Name) 118 119 lookup2, err := dm.getValidatorForDatatype(ctx, "ns1", fftypes.ValidatorTypeJSON, ref) 120 assert.NoError(t, err) 121 assert.Equal(t, lookup1, lookup2) 122 123 } 124 125 func TestValidateBadHash(t *testing.T) { 126 127 config.Reset() 128 dm, ctx, cancel := newTestDataManager(t) 129 defer cancel() 130 mdi := dm.database.(*databasemocks.Plugin) 131 data := &fftypes.Data{ 132 Namespace: "ns1", 133 Validator: fftypes.ValidatorTypeJSON, 134 Datatype: &fftypes.DatatypeRef{ 135 Name: "customer", 136 Version: "0.0.1", 137 }, 138 Value: fftypes.Byteable(`{}`), 139 Hash: fftypes.NewRandB32(), 140 } 141 dt := &fftypes.Datatype{ 142 ID: fftypes.NewUUID(), 143 Validator: fftypes.ValidatorTypeJSON, 144 Value: fftypes.Byteable(`{}`), 145 Name: "customer", 146 Namespace: "0.0.1", 147 } 148 mdi.On("GetDatatypeByName", mock.Anything, "ns1", "customer", "0.0.1").Return(dt, nil).Once() 149 _, err := dm.ValidateAll(ctx, []*fftypes.Data{data}) 150 assert.Regexp(t, "FF10201", err) 151 152 } 153 154 func TestGetMessageDataDBError(t *testing.T) { 155 156 dm, ctx, cancel := newTestDataManager(t) 157 defer cancel() 158 mdi := dm.database.(*databasemocks.Plugin) 159 mdi.On("GetDataByID", mock.Anything, mock.Anything, true).Return(nil, fmt.Errorf("pop")) 160 data, foundAll, err := dm.GetMessageData(ctx, &fftypes.Message{ 161 Header: fftypes.MessageHeader{ID: fftypes.NewUUID()}, 162 Data: fftypes.DataRefs{{ID: fftypes.NewUUID(), Hash: fftypes.NewRandB32()}}, 163 }, true) 164 assert.Nil(t, data) 165 assert.False(t, foundAll) 166 assert.EqualError(t, err, "pop") 167 168 } 169 170 func TestGetMessageDataNilEntry(t *testing.T) { 171 172 dm, ctx, cancel := newTestDataManager(t) 173 defer cancel() 174 mdi := dm.database.(*databasemocks.Plugin) 175 mdi.On("GetDataByID", mock.Anything, mock.Anything, true).Return(nil, nil) 176 data, foundAll, err := dm.GetMessageData(ctx, &fftypes.Message{ 177 Header: fftypes.MessageHeader{ID: fftypes.NewUUID()}, 178 Data: fftypes.DataRefs{nil}, 179 }, true) 180 assert.Empty(t, data) 181 assert.False(t, foundAll) 182 assert.NoError(t, err) 183 184 } 185 186 func TestGetMessageDataNotFound(t *testing.T) { 187 188 dm, ctx, cancel := newTestDataManager(t) 189 defer cancel() 190 mdi := dm.database.(*databasemocks.Plugin) 191 mdi.On("GetDataByID", mock.Anything, mock.Anything, true).Return(nil, nil) 192 data, foundAll, err := dm.GetMessageData(ctx, &fftypes.Message{ 193 Header: fftypes.MessageHeader{ID: fftypes.NewUUID()}, 194 Data: fftypes.DataRefs{{ID: fftypes.NewUUID(), Hash: fftypes.NewRandB32()}}, 195 }, true) 196 assert.Empty(t, data) 197 assert.False(t, foundAll) 198 assert.NoError(t, err) 199 200 } 201 202 func TestGetMessageDataHashMismatch(t *testing.T) { 203 204 dm, ctx, cancel := newTestDataManager(t) 205 defer cancel() 206 mdi := dm.database.(*databasemocks.Plugin) 207 dataID := fftypes.NewUUID() 208 mdi.On("GetDataByID", mock.Anything, mock.Anything, true).Return(&fftypes.Data{ 209 ID: dataID, 210 Hash: fftypes.NewRandB32(), 211 }, nil) 212 data, foundAll, err := dm.GetMessageData(ctx, &fftypes.Message{ 213 Header: fftypes.MessageHeader{ID: fftypes.NewUUID()}, 214 Data: fftypes.DataRefs{{ID: dataID, Hash: fftypes.NewRandB32()}}, 215 }, true) 216 assert.Empty(t, data) 217 assert.False(t, foundAll) 218 assert.NoError(t, err) 219 220 } 221 222 func TestGetMessageDataOk(t *testing.T) { 223 224 dm, ctx, cancel := newTestDataManager(t) 225 defer cancel() 226 mdi := dm.database.(*databasemocks.Plugin) 227 dataID := fftypes.NewUUID() 228 hash := fftypes.NewRandB32() 229 mdi.On("GetDataByID", mock.Anything, mock.Anything, true).Return(&fftypes.Data{ 230 ID: dataID, 231 Hash: hash, 232 }, nil) 233 data, foundAll, err := dm.GetMessageData(ctx, &fftypes.Message{ 234 Header: fftypes.MessageHeader{ID: fftypes.NewUUID()}, 235 Data: fftypes.DataRefs{{ID: dataID, Hash: hash}}, 236 }, true) 237 assert.NotEmpty(t, data) 238 assert.Equal(t, *dataID, *data[0].ID) 239 assert.True(t, foundAll) 240 assert.NoError(t, err) 241 242 } 243 244 func TestCheckDatatypeVerifiesTheSchema(t *testing.T) { 245 246 dm, ctx, cancel := newTestDataManager(t) 247 defer cancel() 248 err := dm.CheckDatatype(ctx, "ns1", &fftypes.Datatype{}) 249 assert.Regexp(t, "FF10196", err) 250 } 251 252 func TestResolveInputDataEmpty(t *testing.T) { 253 254 dm, ctx, cancel := newTestDataManager(t) 255 defer cancel() 256 refs, err := dm.ResolveInputData(ctx, "ns1", fftypes.InputData{}) 257 assert.NoError(t, err) 258 assert.Empty(t, refs) 259 260 } 261 262 func TestResolveInputDataRefIDOnlyOK(t *testing.T) { 263 dm, ctx, cancel := newTestDataManager(t) 264 defer cancel() 265 mdi := dm.database.(*databasemocks.Plugin) 266 267 dataID := fftypes.NewUUID() 268 dataHash := fftypes.NewRandB32() 269 270 mdi.On("GetDataByID", ctx, dataID, false).Return(&fftypes.Data{ 271 ID: dataID, 272 Namespace: "ns1", 273 Hash: dataHash, 274 }, nil) 275 276 refs, err := dm.ResolveInputData(ctx, "ns1", fftypes.InputData{ 277 {DataRef: fftypes.DataRef{ID: dataID}}, 278 }) 279 assert.NoError(t, err) 280 assert.Len(t, refs, 1) 281 assert.Equal(t, dataID, refs[0].ID) 282 assert.Equal(t, dataHash, refs[0].Hash) 283 } 284 285 func TestResolveInputDataRefBadNamespace(t *testing.T) { 286 dm, ctx, cancel := newTestDataManager(t) 287 defer cancel() 288 mdi := dm.database.(*databasemocks.Plugin) 289 290 dataID := fftypes.NewUUID() 291 dataHash := fftypes.NewRandB32() 292 293 mdi.On("GetDataByID", ctx, dataID, false).Return(&fftypes.Data{ 294 ID: dataID, 295 Namespace: "ns2", 296 Hash: dataHash, 297 }, nil) 298 299 refs, err := dm.ResolveInputData(ctx, "ns1", fftypes.InputData{ 300 {DataRef: fftypes.DataRef{ID: dataID, Hash: dataHash}}, 301 }) 302 assert.Regexp(t, "FF10204", err) 303 assert.Empty(t, refs) 304 } 305 306 func TestResolveInputDataRefBadHash(t *testing.T) { 307 dm, ctx, cancel := newTestDataManager(t) 308 defer cancel() 309 mdi := dm.database.(*databasemocks.Plugin) 310 311 dataID := fftypes.NewUUID() 312 dataHash := fftypes.NewRandB32() 313 314 mdi.On("GetDataByID", ctx, dataID, false).Return(&fftypes.Data{ 315 ID: dataID, 316 Namespace: "ns2", 317 Hash: dataHash, 318 }, nil) 319 320 refs, err := dm.ResolveInputData(ctx, "ns1", fftypes.InputData{ 321 {DataRef: fftypes.DataRef{ID: dataID, Hash: fftypes.NewRandB32()}}, 322 }) 323 assert.Regexp(t, "FF10204", err) 324 assert.Empty(t, refs) 325 } 326 327 func TestResolveInputDataRefLookkupFail(t *testing.T) { 328 dm, ctx, cancel := newTestDataManager(t) 329 defer cancel() 330 mdi := dm.database.(*databasemocks.Plugin) 331 332 dataID := fftypes.NewUUID() 333 334 mdi.On("GetDataByID", ctx, dataID, false).Return(nil, fmt.Errorf("pop")) 335 336 _, err := dm.ResolveInputData(ctx, "ns1", fftypes.InputData{ 337 {DataRef: fftypes.DataRef{ID: dataID, Hash: fftypes.NewRandB32()}}, 338 }) 339 assert.EqualError(t, err, "pop") 340 } 341 342 func TestResolveInputDataValueNoValidatorOK(t *testing.T) { 343 dm, ctx, cancel := newTestDataManager(t) 344 defer cancel() 345 mdi := dm.database.(*databasemocks.Plugin) 346 347 mdi.On("UpsertData", ctx, mock.Anything, false, false).Return(nil) 348 349 refs, err := dm.ResolveInputData(ctx, "ns1", fftypes.InputData{ 350 {Value: fftypes.Byteable(`{"some":"json"}`)}, 351 }) 352 assert.NoError(t, err) 353 assert.Len(t, refs, 1) 354 assert.NotNil(t, refs[0].ID) 355 assert.NotNil(t, refs[0].Hash) 356 } 357 358 func TestResolveInputDataValueNoValidatorStoreFail(t *testing.T) { 359 dm, ctx, cancel := newTestDataManager(t) 360 defer cancel() 361 mdi := dm.database.(*databasemocks.Plugin) 362 363 mdi.On("UpsertData", ctx, mock.Anything, false, false).Return(fmt.Errorf("pop")) 364 365 _, err := dm.ResolveInputData(ctx, "ns1", fftypes.InputData{ 366 {Value: fftypes.Byteable(`{"some":"json"}`)}, 367 }) 368 assert.EqualError(t, err, "pop") 369 } 370 371 func TestResolveInputDataValueWithValidation(t *testing.T) { 372 dm, ctx, cancel := newTestDataManager(t) 373 defer cancel() 374 mdi := dm.database.(*databasemocks.Plugin) 375 376 mdi.On("UpsertData", ctx, mock.Anything, false, false).Return(nil) 377 mdi.On("GetDatatypeByName", ctx, "ns1", "customer", "0.0.1").Return(&fftypes.Datatype{ 378 ID: fftypes.NewUUID(), 379 Validator: fftypes.ValidatorTypeJSON, 380 Namespace: "ns1", 381 Name: "customer", 382 Version: "0.0.1", 383 Value: fftypes.Byteable(`{ 384 "properties": { 385 "field1": { 386 "type": "string" 387 } 388 }, 389 "additionalProperties": false 390 }`), 391 }, nil) 392 393 refs, err := dm.ResolveInputData(ctx, "ns1", fftypes.InputData{ 394 { 395 Datatype: &fftypes.DatatypeRef{ 396 Name: "customer", 397 Version: "0.0.1", 398 }, 399 Value: fftypes.Byteable(`{"field1":"value1"}`), 400 }, 401 }) 402 assert.NoError(t, err) 403 assert.Len(t, refs, 1) 404 assert.NotNil(t, refs[0].ID) 405 assert.NotNil(t, refs[0].Hash) 406 407 _, err = dm.ResolveInputData(ctx, "ns1", fftypes.InputData{ 408 { 409 Datatype: &fftypes.DatatypeRef{ 410 Name: "customer", 411 Version: "0.0.1", 412 }, 413 Value: fftypes.Byteable(`{"not_allowed":"value"}`), 414 }, 415 }) 416 assert.Regexp(t, "FF10198", err) 417 } 418 419 func TestResolveInputDataNoRefOrValue(t *testing.T) { 420 dm, ctx, cancel := newTestDataManager(t) 421 defer cancel() 422 423 _, err := dm.ResolveInputData(ctx, "ns1", fftypes.InputData{ 424 { /* missing */ }, 425 }) 426 assert.Regexp(t, "FF10205", err) 427 } 428 429 func TestUploadJSONLoadDatatypeFail(t *testing.T) { 430 dm, ctx, cancel := newTestDataManager(t) 431 defer cancel() 432 mdi := dm.database.(*databasemocks.Plugin) 433 434 mdi.On("GetDatatypeByName", ctx, "ns1", "customer", "0.0.1").Return(nil, fmt.Errorf("pop")) 435 _, err := dm.UploadJSON(ctx, "ns1", &fftypes.Data{ 436 Datatype: &fftypes.DatatypeRef{ 437 Name: "customer", 438 Version: "0.0.1", 439 }, 440 }) 441 assert.EqualError(t, err, "pop") 442 } 443 444 func TestValidateAndStoreLoadNilRef(t *testing.T) { 445 dm, ctx, cancel := newTestDataManager(t) 446 defer cancel() 447 448 _, err := dm.validateAndStoreInlined(ctx, "ns1", &fftypes.DataRefOrValue{ 449 Validator: fftypes.ValidatorTypeJSON, 450 Datatype: nil, 451 }) 452 assert.Regexp(t, "FF10199", err) 453 } 454 455 func TestValidateAndStoreLoadValidatorUnknown(t *testing.T) { 456 457 dm, ctx, cancel := newTestDataManager(t) 458 defer cancel() 459 mdi := dm.database.(*databasemocks.Plugin) 460 mdi.On("GetDatatypeByName", mock.Anything, "ns1", "customer", "0.0.1").Return(nil, nil) 461 _, err := dm.validateAndStoreInlined(ctx, "ns1", &fftypes.DataRefOrValue{ 462 Validator: "wrong!", 463 Datatype: &fftypes.DatatypeRef{ 464 Name: "customer", 465 Version: "0.0.1", 466 }, 467 }) 468 assert.Regexp(t, "FF10200.*wrong", err) 469 470 } 471 472 func TestValidateAndStoreLoadBadRef(t *testing.T) { 473 474 dm, ctx, cancel := newTestDataManager(t) 475 defer cancel() 476 mdi := dm.database.(*databasemocks.Plugin) 477 mdi.On("GetDatatypeByName", mock.Anything, "ns1", "customer", "0.0.1").Return(nil, nil) 478 _, err := dm.validateAndStoreInlined(ctx, "ns1", &fftypes.DataRefOrValue{ 479 Datatype: &fftypes.DatatypeRef{ 480 // Missing name 481 }, 482 }) 483 assert.Regexp(t, "FF10195", err) 484 } 485 486 func TestValidateAndStoreNotFound(t *testing.T) { 487 488 dm, ctx, cancel := newTestDataManager(t) 489 defer cancel() 490 mdi := dm.database.(*databasemocks.Plugin) 491 mdi.On("GetDatatypeByName", mock.Anything, "ns1", "customer", "0.0.1").Return(nil, nil) 492 _, err := dm.validateAndStoreInlined(ctx, "ns1", &fftypes.DataRefOrValue{ 493 Datatype: &fftypes.DatatypeRef{ 494 Name: "customer", 495 Version: "0.0.1", 496 }, 497 }) 498 assert.Regexp(t, "FF10195", err) 499 } 500 501 func TestValidateAllLookupError(t *testing.T) { 502 503 dm, ctx, cancel := newTestDataManager(t) 504 defer cancel() 505 mdi := dm.database.(*databasemocks.Plugin) 506 mdi.On("GetDatatypeByName", mock.Anything, "ns1", "customer", "0.0.1").Return(nil, fmt.Errorf("pop")) 507 data := &fftypes.Data{ 508 Namespace: "ns1", 509 Validator: fftypes.ValidatorTypeJSON, 510 Datatype: &fftypes.DatatypeRef{ 511 Name: "customer", 512 Version: "0.0.1", 513 }, 514 Value: fftypes.Byteable(`anything`), 515 } 516 data.Seal(ctx) 517 _, err := dm.ValidateAll(ctx, []*fftypes.Data{data}) 518 assert.Regexp(t, "pop", err) 519 520 } 521 522 func TestGetValidatorForDatatypeNilRef(t *testing.T) { 523 524 dm, ctx, cancel := newTestDataManager(t) 525 defer cancel() 526 v, err := dm.getValidatorForDatatype(ctx, "", "", nil) 527 assert.Nil(t, v) 528 assert.NoError(t, err) 529 530 } 531 532 func TestValidateAllStoredValidatorInvalid(t *testing.T) { 533 534 dm, ctx, cancel := newTestDataManager(t) 535 defer cancel() 536 mdi := dm.database.(*databasemocks.Plugin) 537 mdi.On("GetDatatypeByName", mock.Anything, "ns1", "customer", "0.0.1").Return(&fftypes.Datatype{ 538 Value: fftypes.Byteable(`{"not": "a", "schema": true}`), 539 }, nil) 540 data := &fftypes.Data{ 541 Namespace: "ns1", 542 Datatype: &fftypes.DatatypeRef{ 543 Name: "customer", 544 Version: "0.0.1", 545 }, 546 } 547 isValid, err := dm.ValidateAll(ctx, []*fftypes.Data{data}) 548 assert.False(t, isValid) 549 assert.NoError(t, err) 550 mdi.AssertExpectations(t) 551 } 552 553 func TestVerifyNamespaceExistsInvalidFFName(t *testing.T) { 554 dm, ctx, cancel := newTestDataManager(t) 555 defer cancel() 556 err := dm.VerifyNamespaceExists(ctx, "!wrong") 557 assert.Regexp(t, "FF10131", err) 558 } 559 560 func TestVerifyNamespaceExistsLookupErr(t *testing.T) { 561 dm, ctx, cancel := newTestDataManager(t) 562 defer cancel() 563 mdi := dm.database.(*databasemocks.Plugin) 564 mdi.On("GetNamespace", mock.Anything, "ns1").Return(nil, fmt.Errorf("pop")) 565 err := dm.VerifyNamespaceExists(ctx, "ns1") 566 assert.Regexp(t, "pop", err) 567 } 568 569 func TestVerifyNamespaceExistsNotFound(t *testing.T) { 570 dm, ctx, cancel := newTestDataManager(t) 571 defer cancel() 572 mdi := dm.database.(*databasemocks.Plugin) 573 mdi.On("GetNamespace", mock.Anything, "ns1").Return(nil, nil) 574 err := dm.VerifyNamespaceExists(ctx, "ns1") 575 assert.Regexp(t, "FF10187", err) 576 } 577 578 func TestVerifyNamespaceExistsOk(t *testing.T) { 579 dm, ctx, cancel := newTestDataManager(t) 580 defer cancel() 581 mdi := dm.database.(*databasemocks.Plugin) 582 mdi.On("GetNamespace", mock.Anything, "ns1").Return(&fftypes.Namespace{}, nil) 583 err := dm.VerifyNamespaceExists(ctx, "ns1") 584 assert.NoError(t, err) 585 }