github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/processors/query/impl_test.go (about) 1 /* 2 * Copyright (c) 2021-present unTill Pro, Ltd. 3 */ 4 5 package queryprocessor 6 7 import ( 8 "context" 9 "encoding/json" 10 "math" 11 "net/http" 12 "sync" 13 "testing" 14 "time" 15 16 "github.com/stretchr/testify/mock" 17 "github.com/stretchr/testify/require" 18 19 "github.com/voedger/voedger/pkg/appdef" 20 "github.com/voedger/voedger/pkg/appparts" 21 "github.com/voedger/voedger/pkg/iauthnzimpl" 22 "github.com/voedger/voedger/pkg/iprocbus" 23 "github.com/voedger/voedger/pkg/iratesce" 24 "github.com/voedger/voedger/pkg/istorage/mem" 25 istorageimpl "github.com/voedger/voedger/pkg/istorage/provider" 26 "github.com/voedger/voedger/pkg/istructs" 27 "github.com/voedger/voedger/pkg/istructsmem" 28 payloads "github.com/voedger/voedger/pkg/itokens-payloads" 29 "github.com/voedger/voedger/pkg/itokensjwt" 30 imetrics "github.com/voedger/voedger/pkg/metrics" 31 "github.com/voedger/voedger/pkg/pipeline" 32 "github.com/voedger/voedger/pkg/processors" 33 "github.com/voedger/voedger/pkg/state" 34 "github.com/voedger/voedger/pkg/sys/authnz" 35 coreutils "github.com/voedger/voedger/pkg/utils" 36 ibus "github.com/voedger/voedger/staging/src/github.com/untillpro/airs-ibus" 37 ) 38 39 var now = time.Now() 40 41 var timeFunc = coreutils.TimeFunc(func() time.Time { return now }) 42 43 var ( 44 appName istructs.AppQName = istructs.AppQName_test1_app1 45 appEngines = appparts.PoolSize(10, 100, 10) 46 partCount istructs.NumAppPartitions = 10 47 partID istructs.PartitionID = 5 48 wsID istructs.WSID = 15 49 50 qNameFunction = appdef.NewQName("bo", "FindArticlesByModificationTimeStampRange") 51 qNameQryDenied = appdef.NewQName(appdef.SysPackage, "TestDeniedQry") // same as in ACL 52 qNameTestWSDescriptor = appdef.NewQName(appdef.SysPackage, "test_ws") 53 qNameTestWS = appdef.NewQName(appdef.SysPackage, "test_wsWS") 54 ) 55 56 func TestBasicUsage_RowsProcessorFactory(t *testing.T) { 57 require := require.New(t) 58 department := func(name string) istructs.IStateValue { 59 r := &mockRecord{} 60 r. 61 On("AsString", "name").Return(name). 62 On("QName").Return(qNamePosDepartment) 63 sv := &mockStateValue{} 64 sv.On("AsRecord", "").Return(r) 65 return sv 66 } 67 skb := &mockStateKeyBuilder{} 68 skb.On("PutRecordID", mock.Anything, mock.Anything) 69 s := &mockState{} 70 s. 71 On("KeyBuilder", state.Record, appdef.NullQName).Return(skb). 72 On("MustExist", mock.Anything).Return(department("Soft drinks")).Once(). 73 On("MustExist", mock.Anything).Return(department("Alcohol drinks")).Once(). 74 On("MustExist", mock.Anything).Return(department("Alcohol drinks")).Once(). 75 On("MustExist", mock.Anything).Return(department("Sweet")).Once() 76 77 var ( 78 appDef appdef.IAppDef 79 resultMeta appdef.IObject 80 ) 81 t.Run(" should be ok to build appDef and resultMeta", func(t *testing.T) { 82 adb := appdef.New() 83 adb.AddObject(qNamePosDepartment). 84 AddField("name", appdef.DataKind_string, false) 85 resBld := adb.AddObject(qNamePosDepartmentResult) 86 resBld. 87 AddField("id", appdef.DataKind_int64, true). 88 AddField("name", appdef.DataKind_string, false) 89 app, err := adb.Build() 90 require.NoError(err) 91 92 appDef = app 93 resultMeta = app.Object(qNamePosDepartmentResult) 94 }) 95 96 params := queryParams{ 97 elements: []IElement{ 98 element{ 99 path: path{rootDocument}, 100 fields: []IResultField{ 101 resultField{"id"}, 102 resultField{"name"}, 103 }, 104 refs: []IRefField{ 105 refField{field: "id_department", ref: "name", key: "id_department/name"}, 106 }, 107 }, 108 }, 109 count: 1, 110 startFrom: 1, 111 filters: []IFilter{ 112 &EqualsFilter{ 113 field: "id_department/name", 114 value: "Alcohol drinks", 115 }, 116 }, 117 orderBy: []IOrderBy{ 118 orderBy{ 119 field: "name", 120 desc: false, 121 }, 122 }, 123 } 124 work := func(id int64, name string, idDepartment int64) pipeline.IWorkpiece { 125 return rowsWorkpiece{ 126 object: &coreutils.TestObject{ 127 Name: appdef.NewQName("pos", "Article"), 128 Data: map[string]interface{}{"id": id, "name": name, "id_department": istructs.RecordID(idDepartment)}, 129 }, 130 outputRow: &outputRow{ 131 keyToIdx: map[string]int{rootDocument: 0}, 132 values: make([]interface{}, 1), 133 }, 134 enrichedRootFieldsKinds: make(map[string]appdef.DataKind), 135 } 136 } 137 138 result := "" 139 rs := testResultSenderClosable{ 140 startArraySection: func(sectionType string, path []string) {}, 141 sendElement: func(name string, element interface{}) (err error) { 142 bb, err := json.Marshal(element) 143 result = string(bb) 144 return err 145 }, 146 close: func(err error) {}, 147 } 148 processor := ProvideRowsProcessorFactory()(context.Background(), appDef, s, params, resultMeta, rs, &testMetrics{}) 149 150 require.NoError(processor.SendAsync(work(1, "Cola", 10))) 151 require.NoError(processor.SendAsync(work(3, "White wine", 20))) 152 require.NoError(processor.SendAsync(work(2, "Amaretto", 20))) 153 require.NoError(processor.SendAsync(work(4, "Cake", 40))) 154 processor.Close() 155 156 require.Equal(`[[[3,"White wine","Alcohol drinks"]]]`, result) 157 } 158 159 func getTestCfg(require *require.Assertions, prepareAppDef func(adb appdef.IAppDefBuilder, wsb appdef.IWorkspaceBuilder), cfgFunc ...func(cfg *istructsmem.AppConfigType)) (appDef appdef.IAppDef, asp istructs.IAppStructsProvider, appTokens istructs.IAppTokens) { 160 cfgs := make(istructsmem.AppConfigsType) 161 asf := mem.Provide() 162 storageProvider := istorageimpl.Provide(asf) 163 tokens := itokensjwt.ProvideITokens(itokensjwt.SecretKeyExample, timeFunc) 164 165 qNameFindArticlesByModificationTimeStampRangeParams := appdef.NewQName("bo", "FindArticlesByModificationTimeStampRangeParamsDef") 166 qNameDepartment := appdef.NewQName("bo", "Department") 167 qNameArticle := appdef.NewQName("bo", "Article") 168 169 adb := appdef.New() 170 wsb := adb.AddWorkspace(qNameTestWS) 171 adb.AddCDoc(qNameTestWSDescriptor) 172 wsb.SetDescriptor(qNameTestWSDescriptor) 173 174 adb.AddObject(qNameFindArticlesByModificationTimeStampRangeParams). 175 AddField("from", appdef.DataKind_int64, false). 176 AddField("till", appdef.DataKind_int64, false) 177 adb.AddCDoc(qNameDepartment). 178 AddField("name", appdef.DataKind_string, true) 179 adb.AddObject(qNameArticle). 180 AddField("sys.ID", appdef.DataKind_RecordID, true). 181 AddField("name", appdef.DataKind_string, true). 182 AddField("id_department", appdef.DataKind_int64, true) 183 184 // simplified cdoc.sys.WorkspaceDescriptor 185 wsDescBuilder := adb.AddCDoc(authnz.QNameCDocWorkspaceDescriptor) 186 wsDescBuilder. 187 AddField(authnz.Field_WSKind, appdef.DataKind_QName, false). 188 AddField(authnz.Field_Status, appdef.DataKind_int32, false) 189 wsDescBuilder.SetSingleton() 190 191 adb.AddQuery(qNameFunction).SetParam(qNameFindArticlesByModificationTimeStampRangeParams).SetResult(appdef.NewQName("bo", "Article")) 192 adb.AddCommand(istructs.QNameCommandCUD) 193 adb.AddQuery(qNameQryDenied) 194 wsb.AddType(qNameDepartment) 195 wsb.AddType(qNameArticle) 196 wsb.AddType(qNameArticle) 197 wsb.AddType(authnz.QNameCDocWorkspaceDescriptor) 198 wsb.AddType(qNameFunction) 199 wsb.AddType(istructs.QNameCommandCUD) 200 wsb.AddType(qNameQryDenied) 201 202 if prepareAppDef != nil { 203 prepareAppDef(adb, wsb) 204 } 205 206 cfg := cfgs.AddConfig(appName, adb) 207 cfg.SetNumAppWorkspaces(istructs.DefaultNumAppWorkspaces) 208 209 asp = istructsmem.Provide(cfgs, iratesce.TestBucketsFactory, payloads.TestAppTokensFactory(tokens), storageProvider) 210 211 article := func(id, idDepartment istructs.RecordID, name string) istructs.IObject { 212 return &coreutils.TestObject{ 213 Name: appdef.NewQName("bo", "Article"), 214 Data: map[string]interface{}{"sys.ID": id, "name": name, "id_department": idDepartment}, 215 } 216 } 217 cfg.Resources.Add(istructsmem.NewQueryFunction( 218 qNameFunction, 219 func(_ context.Context, args istructs.ExecQueryArgs, callback istructs.ExecQueryCallback) (err error) { 220 require.Equal(int64(1257894000), args.ArgumentObject.AsInt64("from")) 221 require.Equal(int64(2257894000), args.ArgumentObject.AsInt64("till")) 222 objects := []istructs.IObject{ 223 article(1, istructs.MaxRawRecordID+10, "Cola"), 224 article(3, istructs.MaxRawRecordID+20, "White wine"), 225 article(2, istructs.MaxRawRecordID+20, "Amaretto"), 226 article(4, istructs.MaxRawRecordID+40, "Cake"), 227 } 228 for _, object := range objects { 229 err = callback(object) 230 if err != nil { 231 return err 232 } 233 } 234 return err 235 }, 236 )) 237 cfg.Resources.Add(istructsmem.NewCommandFunction(istructs.QNameCommandCUD, istructsmem.NullCommandExec)) 238 cfg.Resources.Add(istructsmem.NewQueryFunction(qNameQryDenied, istructsmem.NullQueryExec)) 239 240 for _, f := range cfgFunc { 241 f(cfg) 242 } 243 244 as, err := asp.AppStructs(appName) 245 require.NoError(err) 246 247 appDef = as.AppDef() 248 249 plogOffset := istructs.FirstOffset 250 wlogOffset := istructs.FirstOffset 251 grebp := istructs.GenericRawEventBuilderParams{ 252 HandlingPartition: partID, 253 Workspace: wsID, 254 QName: istructs.QNameCommandCUD, 255 RegisteredAt: istructs.UnixMilli(time.Now().UnixMilli()), 256 PLogOffset: plogOffset, 257 WLogOffset: wlogOffset, 258 } 259 reb := as.Events().GetSyncRawEventBuilder( 260 istructs.SyncRawEventBuilderParams{ 261 GenericRawEventBuilderParams: grebp, 262 SyncedAt: istructs.UnixMilli(time.Now().UnixMilli()), 263 }, 264 ) 265 266 namedDoc := func(qName appdef.QName, id istructs.RecordID, name string) { 267 doc := reb.CUDBuilder().Create(qName) 268 doc.PutRecordID(appdef.SystemField_ID, id) 269 doc.PutString("name", name) 270 } 271 namedDoc(qNameDepartment, istructs.MaxRawRecordID+10, "Soft drinks") 272 namedDoc(qNameDepartment, istructs.MaxRawRecordID+20, "Alcohol drinks") 273 namedDoc(qNameDepartment, istructs.MaxRawRecordID+40, "Sweet") 274 275 rawEvent, err := reb.BuildRawEvent() 276 require.NoError(err) 277 pLogEvent, err := as.Events().PutPlog(rawEvent, nil, istructsmem.NewIDGenerator()) 278 require.NoError(err) 279 require.NoError(as.Records().Apply(pLogEvent)) 280 err = as.Events().PutWlog(pLogEvent) 281 require.NoError(err) 282 plogOffset++ 283 wlogOffset++ 284 285 appTokens = payloads.TestAppTokensFactory(tokens).New(appName) 286 287 // create stub for cdoc.sys.WorkspaceDescriptor to make query processor work 288 require.NoError(err) 289 now := time.Now() 290 grebp = istructs.GenericRawEventBuilderParams{ 291 HandlingPartition: partID, 292 Workspace: wsID, 293 QName: istructs.QNameCommandCUD, 294 RegisteredAt: istructs.UnixMilli(now.UnixMilli()), 295 PLogOffset: plogOffset, 296 WLogOffset: wlogOffset, 297 } 298 reb = as.Events().GetSyncRawEventBuilder( 299 istructs.SyncRawEventBuilderParams{ 300 GenericRawEventBuilderParams: grebp, 301 SyncedAt: istructs.UnixMilli(now.UnixMilli()), 302 }, 303 ) 304 cdocWSDesc := reb.CUDBuilder().Create(authnz.QNameCDocWorkspaceDescriptor) 305 cdocWSDesc.PutRecordID(appdef.SystemField_ID, 1) 306 cdocWSDesc.PutInt32(authnz.Field_Status, int32(authnz.WorkspaceStatus_Active)) 307 cdocWSDesc.PutQName(authnz.Field_WSKind, qNameTestWSDescriptor) 308 rawEvent, err = reb.BuildRawEvent() 309 require.NoError(err) 310 pLogEvent, err = as.Events().PutPlog(rawEvent, nil, istructsmem.NewIDGenerator()) 311 require.NoError(err) 312 defer pLogEvent.Release() 313 require.NoError(as.Records().Apply(pLogEvent)) 314 require.NoError(as.Events().PutWlog(pLogEvent)) 315 316 return appDef, asp, appTokens 317 } 318 319 func TestBasicUsage_ServiceFactory(t *testing.T) { 320 require := require.New(t) 321 done := make(chan interface{}) 322 result := "" 323 body := []byte(`{ 324 "args":{"from":1257894000,"till":2257894000}, 325 "elements":[ 326 {"path":"","fields":["sys.ID","name"],"refs":[["id_department","name"]]} 327 ], 328 "filters":[ 329 {"expr":"and","args":[{"expr":"eq","args":{"field":"id_department/name","value":"Alcohol drinks"}}]}, 330 {"expr":"or","args":[{"expr":"eq","args":{"field":"id_department/name","value":"Alcohol drinks"}}]} 331 ], 332 "orderBy":[{"field":"name"}], 333 "count":1, 334 "startFrom":1 335 }`) 336 serviceChannel := make(iprocbus.ServiceChannel) 337 rs := testResultSenderClosable{ 338 startArraySection: func(sectionType string, path []string) {}, 339 sendElement: func(name string, element interface{}) (err error) { 340 bb, err := json.Marshal(element) 341 require.NoError(err) 342 result = string(bb) 343 return nil 344 }, 345 close: func(err error) { 346 require.NoError(err) 347 close(done) 348 }, 349 } 350 351 metrics := imetrics.Provide() 352 metricNames := make([]string, 0) 353 354 appDef, appStructsProvider, appTokens := getTestCfg(require, nil) 355 356 appParts, cleanAppParts, err := appparts.New(appStructsProvider) 357 require.NoError(err) 358 defer cleanAppParts() 359 appParts.DeployApp(appName, appDef, partCount, appEngines) 360 appParts.DeployAppPartitions(appName, []istructs.PartitionID{partID}) 361 362 authn := iauthnzimpl.NewDefaultAuthenticator(iauthnzimpl.TestSubjectRolesGetter, iauthnzimpl.TestIsDeviceAllowedFuncs) 363 authz := iauthnzimpl.NewDefaultAuthorizer() 364 queryProcessor := ProvideServiceFactory()( 365 serviceChannel, 366 func(ctx context.Context, sender ibus.ISender) IResultSenderClosable { return rs }, 367 appParts, 368 3, // max concurrent queries 369 metrics, "vvm", authn, authz, itokensjwt.TestTokensJWT(), nil) 370 processorCtx, processorCtxCancel := context.WithCancel(context.Background()) 371 wg := sync.WaitGroup{} 372 wg.Add(1) 373 go func() { 374 queryProcessor.Run(processorCtx) 375 wg.Done() 376 }() 377 systemToken := getSystemToken(appTokens) 378 serviceChannel <- NewQueryMessage(context.Background(), appName, partID, wsID, nil, body, qNameFunction, "127.0.0.1", systemToken) 379 <-done 380 processorCtxCancel() 381 wg.Wait() 382 383 _ = metrics.List(func(metric imetrics.IMetric, metricValue float64) (err error) { 384 metricNames = append(metricNames, metric.Name()) 385 return err 386 }) 387 388 require.Equal(`[[[3,"White wine","Alcohol drinks"]]]`, result) 389 require.Contains(metricNames, queriesTotal) 390 require.Contains(metricNames, queriesSeconds) 391 require.Contains(metricNames, buildSeconds) 392 require.Contains(metricNames, execSeconds) 393 require.Contains(metricNames, execFieldsSeconds) 394 require.Contains(metricNames, execEnrichSeconds) 395 require.Contains(metricNames, execFilterSeconds) 396 require.Contains(metricNames, execOrderSeconds) 397 require.Contains(metricNames, execCountSeconds) 398 require.Contains(metricNames, execSendSeconds) 399 } 400 401 func TestRawMode(t *testing.T) { 402 require := require.New(t) 403 404 var ( 405 appDef appdef.IAppDef 406 resultMeta appdef.IObject 407 ) 408 t.Run(" should be ok to build appDef and resultMeta", func(t *testing.T) { 409 adb := appdef.New() 410 adb.AddObject(istructs.QNameRaw) 411 app, err := adb.Build() 412 require.NoError(err) 413 414 appDef = app 415 resultMeta = app.Object(istructs.QNameRaw) 416 }) 417 418 result := "" 419 rs := testResultSenderClosable{ 420 startArraySection: func(sectionType string, path []string) {}, 421 sendElement: func(name string, element interface{}) (err error) { 422 bb, err := json.Marshal(element) 423 result = string(bb) 424 return err 425 }, 426 close: func(err error) {}, 427 } 428 processor := ProvideRowsProcessorFactory()(context.Background(), appDef, &mockState{}, queryParams{}, resultMeta, rs, &testMetrics{}) 429 430 require.NoError(processor.SendAsync(rowsWorkpiece{ 431 object: &coreutils.TestObject{ 432 Data: map[string]interface{}{ 433 processors.Field_RawObject_Body: `[accepted]`, 434 }, 435 }, 436 outputRow: &outputRow{ 437 keyToIdx: map[string]int{rootDocument: 0}, 438 values: make([]interface{}, 1), 439 }, 440 })) 441 processor.Close() 442 443 require.Equal(`[[["[accepted]"]]]`, result) 444 } 445 446 func Test_epsilon(t *testing.T) { 447 options := func(epsilon interface{}) map[string]interface{} { 448 options := make(map[string]interface{}) 449 if epsilon != nil { 450 options["epsilon"] = epsilon 451 } 452 return options 453 } 454 args := func(options map[string]interface{}) interface{} { 455 args := make(map[string]interface{}) 456 if options != nil { 457 args["options"] = options 458 } 459 return args 460 } 461 t.Run("Should return epsilon", func(t *testing.T) { 462 epsilon, err := epsilon(args(options(math.E))) 463 464 require.Equal(t, math.E, epsilon) 465 require.NoError(t, err) 466 }) 467 t.Run("Should return error when options is nil", func(t *testing.T) { 468 //TODO (FILTER0001) 469 t.Skip("//TODO (FILTER0001)") 470 epsilon, err := epsilon(args(nil)) 471 472 require.Equal(t, 0.0, epsilon) 473 require.ErrorIs(t, err, ErrNotFound) 474 }) 475 t.Run("Should return error when epsilon is nil", func(t *testing.T) { 476 //TODO (FILTER0001) 477 t.Skip("//TODO (FILTER0001)") 478 epsilon, err := epsilon(args(options(nil))) 479 480 require.Equal(t, 0.0, epsilon) 481 require.ErrorIs(t, err, ErrNotFound) 482 }) 483 t.Run("Should return error when epsilon has wrong type", func(t *testing.T) { 484 epsilon, err := epsilon(args(options("0.00000001"))) 485 486 require.Equal(t, 0.0, epsilon) 487 require.ErrorIs(t, err, coreutils.ErrFieldTypeMismatch) 488 }) 489 } 490 491 func Test_nearlyEqual(t *testing.T) { 492 t.Skip("temp skip") 493 tests := []struct { 494 name string 495 first float64 496 second float64 497 epsilon float64 498 want bool 499 }{ 500 { 501 name: "Regular large numbers 1", 502 first: 1000000.0, 503 second: 1000001.0, 504 epsilon: 0.00001, 505 want: true, 506 }, 507 { 508 name: "Regular large numbers 2", 509 first: 1000001.0, 510 second: 1000000.0, 511 epsilon: 0.00001, 512 want: true, 513 }, 514 { 515 name: "Regular large numbers 3", 516 first: 10000.0, 517 second: 10001.0, 518 epsilon: 0.00001, 519 want: false, 520 }, 521 { 522 name: "Regular large numbers 4", 523 first: 10001.0, 524 second: 10000.0, 525 epsilon: 0.00001, 526 want: false, 527 }, 528 { 529 name: "Negative large numbers 1", 530 first: -1000000.0, 531 second: -1000001.0, 532 epsilon: 0.00001, 533 want: true, 534 }, 535 { 536 name: "Negative large numbers 2", 537 first: -1000001.0, 538 second: -1000000.0, 539 epsilon: 0.00001, 540 want: true, 541 }, 542 { 543 name: "Negative large numbers 3", 544 first: -10000.0, 545 second: -10001.0, 546 epsilon: 0.00001, 547 want: false, 548 }, 549 { 550 name: "Negative large numbers 4", 551 first: -10001.0, 552 second: -10000.0, 553 epsilon: 0.00001, 554 want: false, 555 }, 556 { 557 name: "Numbers around one 1", 558 first: 1.0000001, 559 second: 1.0000002, 560 epsilon: 0.00001, 561 want: true, 562 }, 563 { 564 name: "Numbers around one 2", 565 first: 1.0000002, 566 second: 1.0000001, 567 epsilon: 0.00001, 568 want: true, 569 }, 570 { 571 name: "Numbers around one 3", 572 first: 1.0002, 573 second: 1.0001, 574 epsilon: 0.00001, 575 want: false, 576 }, 577 { 578 name: "Numbers around one 4", 579 first: 1.0001, 580 second: 1.0002, 581 epsilon: 0.00001, 582 want: false, 583 }, 584 { 585 name: "Numbers around minus one 1", 586 first: -1.0000001, 587 second: -1.0000002, 588 epsilon: 0.00001, 589 want: true, 590 }, 591 { 592 name: "Numbers around minus one 2", 593 first: -1.0000002, 594 second: -1.0000001, 595 epsilon: 0.00001, 596 want: true, 597 }, 598 { 599 name: "Numbers around minus one 3", 600 first: -1.0002, 601 second: -1.0001, 602 epsilon: 0.00001, 603 want: false, 604 }, 605 { 606 name: "Numbers around minus one 4", 607 first: -1.0001, 608 second: -1.0002, 609 epsilon: 0.00001, 610 want: false, 611 }, 612 { 613 name: "Numbers between one and zero 1", 614 first: 0.000000001000001, 615 second: 0.000000001000002, 616 epsilon: 0.00001, 617 want: true, 618 }, 619 { 620 name: "Numbers between one and zero 2", 621 first: 0.000000001000002, 622 second: 0.000000001000001, 623 epsilon: 0.00001, 624 want: true, 625 }, 626 { 627 name: "Numbers between one and zero 3", 628 first: 0.000000000001002, 629 second: 0.000000000001001, 630 epsilon: 0.00001, 631 want: false, 632 }, 633 { 634 name: "Numbers between one and zero 4", 635 first: 0.000000000001001, 636 second: 0.000000000001002, 637 epsilon: 0.00001, 638 want: false, 639 }, 640 { 641 name: "Numbers between minus one and zero 1", 642 first: -0.000000001000001, 643 second: -0.000000001000002, 644 epsilon: 0.00001, 645 want: true, 646 }, 647 { 648 name: "Numbers between minus one and zero 2", 649 first: -0.000000001000002, 650 second: -0.000000001000001, 651 epsilon: 0.00001, 652 want: true, 653 }, 654 { 655 name: "Numbers between minus one and zero 3", 656 first: -0.000000000001002, 657 second: -0.000000000001001, 658 epsilon: 0.00001, 659 want: false, 660 }, 661 { 662 name: "Numbers between minus one and zero 4", 663 first: -0.000000000001001, 664 second: -0.000000000001002, 665 epsilon: 0.00001, 666 want: false, 667 }, 668 { 669 name: "Small differences away from zero 1", 670 first: 0.3, 671 second: 0.30000003, 672 epsilon: 0.00001, 673 want: true, 674 }, 675 { 676 name: "Small differences away from zero 2", 677 first: -0.3, 678 second: -0.30000003, 679 epsilon: 0.00001, 680 want: true, 681 }, 682 { 683 name: "Comparisons involving zero 1", 684 first: 0.0, 685 second: 0.0, 686 epsilon: 0.00001, 687 want: true, 688 }, 689 { 690 name: "Comparisons involving zero 2", 691 first: 0.00000001, 692 second: 0.0, 693 epsilon: 0.00001, 694 want: false, 695 }, 696 { 697 name: "Comparisons involving zero 3", 698 first: 0.0, 699 second: 0.00000001, 700 epsilon: 0.00001, 701 want: false, 702 }, 703 { 704 name: "Comparisons involving zero 4", 705 first: -0.00000001, 706 second: 0.0, 707 epsilon: 0.00001, 708 want: false, 709 }, 710 { 711 name: "Comparisons involving zero 5", 712 first: 0.0, 713 second: -0.00000001, 714 epsilon: 0.00001, 715 want: false, 716 }, 717 { 718 name: "Comparisons involving zero 6", 719 first: 0.0, 720 second: 1e-40, 721 epsilon: 0.01, 722 want: true, 723 }, 724 { 725 name: "Comparisons involving zero 7", 726 first: 1e-40, 727 second: 0.0, 728 epsilon: 0.01, 729 want: true, 730 }, 731 { 732 name: "Comparisons involving zero 8", 733 first: 0.0, 734 second: 1e-40, 735 epsilon: 0.000001, 736 want: false, 737 }, 738 { 739 name: "Comparisons involving zero 9", 740 first: 1e-40, 741 second: 0.0, 742 epsilon: 0.000001, 743 want: false, 744 }, 745 { 746 name: "Comparisons involving zero 10", 747 first: 0.0, 748 second: -1e-40, 749 epsilon: 0.01, 750 want: true, 751 }, 752 { 753 name: "Comparisons involving zero 11", 754 first: -1e-40, 755 second: 0.0, 756 epsilon: 0.01, 757 want: true, 758 }, 759 { 760 name: "Comparisons involving zero 12", 761 first: 0.0, 762 second: -1e-40, 763 epsilon: 0.000001, 764 want: false, 765 }, 766 { 767 name: "Comparisons involving zero 13", 768 first: -1e-40, 769 second: 0.0, 770 epsilon: 0.000001, 771 want: false, 772 }, 773 { 774 name: "Comparisons involving extreme values 1", 775 first: math.MaxFloat64, 776 second: math.MaxFloat64, 777 epsilon: 0.00001, 778 want: true, 779 }, 780 { 781 name: "Comparisons involving extreme values 2", 782 first: math.MaxFloat64, 783 second: -math.MaxFloat64, 784 epsilon: 0.00001, 785 want: false, 786 }, 787 { 788 name: "Comparisons involving extreme values 3", 789 first: -math.MaxFloat64, 790 second: math.MaxFloat64, 791 epsilon: 0.00001, 792 want: false, 793 }, 794 { 795 name: "Comparisons involving extreme values 4", 796 first: math.MaxFloat64, 797 second: math.MaxFloat64 / 2, 798 epsilon: 0.00001, 799 want: false, 800 }, 801 { 802 name: "Comparisons involving extreme values 5", 803 first: math.MaxFloat64, 804 second: -math.MaxFloat64 / 2, 805 epsilon: 0.00001, 806 want: false, 807 }, 808 { 809 name: "Comparisons involving extreme values 6", 810 first: -math.MaxFloat64, 811 second: math.MaxFloat64 / 2, 812 epsilon: 0.00001, 813 want: false, 814 }, 815 { 816 name: "Comparisons involving infinities 1", 817 first: math.Inf(+1), 818 second: math.Inf(+1), 819 epsilon: 0.00001, 820 want: true, 821 }, 822 { 823 name: "Comparisons involving infinities 2", 824 first: math.Inf(-1), 825 second: math.Inf(-1), 826 epsilon: 0.00001, 827 want: true, 828 }, 829 { 830 name: "Comparisons involving infinities 3", 831 first: math.Inf(-1), 832 second: math.Inf(+1), 833 epsilon: 0.00001, 834 want: false, 835 }, 836 { 837 name: "Comparisons involving infinities 4", 838 first: math.Inf(+1), 839 second: math.MaxFloat64, 840 epsilon: 0.00001, 841 want: false, 842 }, 843 { 844 name: "Comparisons involving infinities 5", 845 first: math.Inf(-1), 846 second: -math.MaxFloat64, 847 epsilon: 0.00001, 848 want: false, 849 }, 850 { 851 name: "Comparisons involving NaN values 1", 852 first: math.NaN(), 853 second: math.NaN(), 854 epsilon: 0.00001, 855 want: false, 856 }, 857 { 858 name: "Comparisons involving NaN values 2", 859 first: math.NaN(), 860 second: 0.0, 861 epsilon: 0.00001, 862 want: false, 863 }, 864 { 865 name: "Comparisons involving NaN values 3", 866 first: 0.0, 867 second: math.NaN(), 868 epsilon: 0.00001, 869 want: false, 870 }, 871 { 872 name: "Comparisons involving NaN values 4", 873 first: math.NaN(), 874 second: math.Inf(+1), 875 epsilon: 0.00001, 876 want: false, 877 }, 878 { 879 name: "Comparisons involving NaN values 5", 880 first: math.Inf(+1), 881 second: math.NaN(), 882 epsilon: 0.00001, 883 want: false, 884 }, 885 { 886 name: "Comparisons involving NaN values 6", 887 first: math.NaN(), 888 second: math.Inf(-1), 889 epsilon: 0.00001, 890 want: false, 891 }, 892 { 893 name: "Comparisons involving NaN values 7", 894 first: math.Inf(-1), 895 second: math.NaN(), 896 epsilon: 0.00001, 897 want: false, 898 }, 899 { 900 name: "Comparisons involving NaN values 8", 901 first: math.NaN(), 902 second: math.MaxFloat64, 903 epsilon: 0.00001, 904 want: false, 905 }, 906 { 907 name: "Comparisons involving NaN values 9", 908 first: math.MaxFloat64, 909 second: math.NaN(), 910 epsilon: 0.00001, 911 want: false, 912 }, 913 { 914 name: "Comparisons involving NaN values 10", 915 first: math.NaN(), 916 second: -math.MaxFloat64, 917 epsilon: 0.00001, 918 want: false, 919 }, 920 { 921 name: "Comparisons involving NaN values 11", 922 first: -math.MaxFloat64, 923 second: math.NaN(), 924 epsilon: 0.00001, 925 want: false, 926 }, 927 { 928 name: "Comparisons involving NaN values 12", 929 first: math.NaN(), 930 second: math.SmallestNonzeroFloat64, 931 epsilon: 0.00001, 932 want: false, 933 }, 934 { 935 name: "Comparisons involving NaN values 13", 936 first: math.SmallestNonzeroFloat64, 937 second: math.NaN(), 938 epsilon: 0.00001, 939 want: false, 940 }, 941 { 942 name: "Comparisons involving NaN values 14", 943 first: math.NaN(), 944 second: -math.SmallestNonzeroFloat64, 945 epsilon: 0.00001, 946 want: false, 947 }, 948 { 949 name: "Comparisons involving NaN values 15", 950 first: -math.SmallestNonzeroFloat64, 951 second: math.NaN(), 952 epsilon: 0.00001, 953 want: false, 954 }, 955 { 956 name: "Comparisons of numbers on opposite sides of zero 1", 957 first: 1.000000001, 958 second: -1.0, 959 epsilon: 0.00001, 960 want: false, 961 }, 962 { 963 name: "Comparisons of numbers on opposite sides of zero 2", 964 first: -1.0, 965 second: 1.000000001, 966 epsilon: 0.00001, 967 want: false, 968 }, 969 { 970 name: "Comparisons of numbers on opposite sides of zero 3", 971 first: -1.000000001, 972 second: 1.0, 973 epsilon: 0.00001, 974 want: false, 975 }, 976 { 977 name: "Comparisons of numbers on opposite sides of zero 4", 978 first: 1.0, 979 second: -1.000000001, 980 epsilon: 0.00001, 981 want: false, 982 }, 983 { 984 name: "Comparisons of numbers on opposite sides of zero 5", 985 first: 10 * math.SmallestNonzeroFloat64, 986 second: 10 * -math.SmallestNonzeroFloat64, 987 epsilon: 0.00001, 988 want: true, 989 }, 990 { 991 name: "Comparisons of numbers on opposite sides of zero 6", 992 first: 10000 * math.SmallestNonzeroFloat64, 993 second: 10000 * -math.SmallestNonzeroFloat64, 994 epsilon: 0.00001, 995 want: false, 996 }, 997 { 998 name: "Comparisons of numbers very close to zero 1", 999 first: math.SmallestNonzeroFloat64, 1000 second: math.SmallestNonzeroFloat64, 1001 epsilon: 0.00001, 1002 want: true, 1003 }, 1004 { 1005 name: "Comparisons of numbers very close to zero 2", 1006 first: math.SmallestNonzeroFloat64, 1007 second: -math.SmallestNonzeroFloat64, 1008 epsilon: 0.00001, 1009 want: true, 1010 }, 1011 { 1012 name: "Comparisons of numbers very close to zero 3", 1013 first: -math.SmallestNonzeroFloat64, 1014 second: math.SmallestNonzeroFloat64, 1015 epsilon: 0.00001, 1016 want: true, 1017 }, 1018 { 1019 name: "Comparisons of numbers very close to zero 4", 1020 first: math.SmallestNonzeroFloat64, 1021 second: 0.0, 1022 epsilon: 0.00001, 1023 want: true, 1024 }, 1025 { 1026 name: "Comparisons of numbers very close to zero 5", 1027 first: 0.0, 1028 second: math.SmallestNonzeroFloat64, 1029 epsilon: 0.00001, 1030 want: true, 1031 }, 1032 { 1033 name: "Comparisons of numbers very close to zero 6", 1034 first: -math.SmallestNonzeroFloat64, 1035 second: 0.0, 1036 epsilon: 0.00001, 1037 want: true, 1038 }, 1039 { 1040 name: "Comparisons of numbers very close to zero 7", 1041 first: 0.0, 1042 second: -math.SmallestNonzeroFloat64, 1043 epsilon: 0.00001, 1044 want: true, 1045 }, 1046 { 1047 name: "Comparisons of numbers very close to zero 8", 1048 first: 0.000000001, 1049 second: -math.SmallestNonzeroFloat64, 1050 epsilon: 0.00001, 1051 want: false, 1052 }, 1053 { 1054 name: "Comparisons of numbers very close to zero 9", 1055 first: 0.000000001, 1056 second: math.SmallestNonzeroFloat64, 1057 epsilon: 0.00001, 1058 want: false, 1059 }, 1060 { 1061 name: "Comparisons of numbers very close to zero 10", 1062 first: math.SmallestNonzeroFloat64, 1063 second: 0.000000001, 1064 epsilon: 0.00001, 1065 want: false, 1066 }, 1067 { 1068 name: "Comparisons of numbers very close to zero 11", 1069 first: -math.SmallestNonzeroFloat64, 1070 second: 0.000000001, 1071 epsilon: 0.00001, 1072 want: false, 1073 }, 1074 } 1075 for _, test := range tests { 1076 t.Run(test.name, func(t *testing.T) { 1077 require.Equal(t, test.want, nearlyEqual(test.first, test.second, test.epsilon)) 1078 }) 1079 } 1080 } 1081 1082 func TestRateLimiter(t *testing.T) { 1083 require := require.New(t) 1084 errs := make(chan error) 1085 serviceChannel := make(iprocbus.ServiceChannel) 1086 rs := testResultSenderClosable{ 1087 startArraySection: func(sectionType string, path []string) {}, 1088 sendElement: func(name string, element interface{}) (err error) { return nil }, 1089 close: func(err error) { 1090 errs <- err 1091 }, 1092 } 1093 1094 qNameMyFuncParams := appdef.NewQName(appdef.SysPackage, "myFuncParams") 1095 qNameMyFuncResults := appdef.NewQName(appdef.SysPackage, "results") 1096 qName := appdef.NewQName(appdef.SysPackage, "myFunc") 1097 appDef, appStructsProvider, appTokens := getTestCfg(require, 1098 func(appDef appdef.IAppDefBuilder, wsb appdef.IWorkspaceBuilder) { 1099 appDef.AddObject(qNameMyFuncParams) 1100 appDef.AddObject(qNameMyFuncResults). 1101 AddField("fld", appdef.DataKind_string, false) 1102 qry := appDef.AddQuery(qName) 1103 qry.SetParam(qNameMyFuncParams).SetResult(qNameMyFuncResults) 1104 wsb.AddType(qName) 1105 }, 1106 func(cfg *istructsmem.AppConfigType) { 1107 myFunc := istructsmem.NewQueryFunction(qName, istructsmem.NullQueryExec) 1108 // declare a test func 1109 1110 cfg.Resources.Add(myFunc) 1111 1112 // declare rate limits 1113 cfg.FunctionRateLimits.AddWorkspaceLimit(qName, istructs.RateLimit{ 1114 Period: time.Minute, 1115 MaxAllowedPerDuration: 2, 1116 }) 1117 }) 1118 1119 appParts, cleanAppParts, err := appparts.New(appStructsProvider) 1120 require.NoError(err) 1121 defer cleanAppParts() 1122 appParts.DeployApp(appName, appDef, partCount, appEngines) 1123 appParts.DeployAppPartitions(appName, []istructs.PartitionID{partID}) 1124 1125 // create aquery processor 1126 metrics := imetrics.Provide() 1127 authn := iauthnzimpl.NewDefaultAuthenticator(iauthnzimpl.TestSubjectRolesGetter, iauthnzimpl.TestIsDeviceAllowedFuncs) 1128 authz := iauthnzimpl.NewDefaultAuthorizer() 1129 queryProcessor := ProvideServiceFactory()( 1130 serviceChannel, 1131 func(ctx context.Context, sender ibus.ISender) IResultSenderClosable { return rs }, 1132 appParts, 1133 3, // max concurrent queries 1134 metrics, "vvm", authn, authz, itokensjwt.TestTokensJWT(), nil) 1135 go queryProcessor.Run(context.Background()) 1136 1137 systemToken := getSystemToken(appTokens) 1138 body := []byte(`{ 1139 "args":{}, 1140 "elements":[{"path":"","fields":["fld"]}] 1141 }`) 1142 1143 // execute query 1144 // first 2 - ok 1145 serviceChannel <- NewQueryMessage(context.Background(), appName, partID, wsID, nil, body, qName, "127.0.0.1", systemToken) 1146 require.NoError(<-errs) 1147 serviceChannel <- NewQueryMessage(context.Background(), appName, partID, wsID, nil, body, qName, "127.0.0.1", systemToken) 1148 require.NoError(<-errs) 1149 1150 // 3rd exceeds the limit - not often than twice per minute 1151 serviceChannel <- NewQueryMessage(context.Background(), appName, partID, wsID, nil, body, qName, "127.0.0.1", systemToken) 1152 require.Error(<-errs) 1153 } 1154 1155 func TestAuthnz(t *testing.T) { 1156 require := require.New(t) 1157 errs := make(chan error) 1158 body := []byte(`{}`) 1159 serviceChannel := make(iprocbus.ServiceChannel) 1160 rs := testResultSenderClosable{ 1161 startArraySection: func(sectionType string, path []string) {}, 1162 sendElement: func(name string, element interface{}) (err error) { 1163 t.Fail() 1164 return nil 1165 }, 1166 close: func(err error) { 1167 errs <- err 1168 }, 1169 } 1170 1171 metrics := imetrics.Provide() 1172 1173 appDef, appStructsProvider, appTokens := getTestCfg(require, nil) 1174 1175 appParts, cleanAppParts, err := appparts.New(appStructsProvider) 1176 require.NoError(err) 1177 defer cleanAppParts() 1178 1179 appParts.DeployApp(appName, appDef, partCount, appEngines) 1180 appParts.DeployAppPartitions(appName, []istructs.PartitionID{partID}) 1181 1182 authn := iauthnzimpl.NewDefaultAuthenticator(iauthnzimpl.TestSubjectRolesGetter, iauthnzimpl.TestIsDeviceAllowedFuncs) 1183 authz := iauthnzimpl.NewDefaultAuthorizer() 1184 queryProcessor := ProvideServiceFactory()( 1185 serviceChannel, 1186 func(ctx context.Context, sender ibus.ISender) IResultSenderClosable { return rs }, 1187 appParts, 1188 3, // max concurrent queries 1189 metrics, "vvm", authn, authz, itokensjwt.TestTokensJWT(), nil) 1190 go queryProcessor.Run(context.Background()) 1191 1192 t.Run("no token for a query that requires authorization -> 403 unauthorized", func(t *testing.T) { 1193 serviceChannel <- NewQueryMessage(context.Background(), appName, partID, wsID, nil, body, qNameFunction, "127.0.0.1", "") 1194 var se coreutils.SysError 1195 require.ErrorAs(<-errs, &se) 1196 require.Equal(http.StatusForbidden, se.HTTPStatus) 1197 }) 1198 1199 t.Run("expired token -> 401 unauthorized", func(t *testing.T) { 1200 systemToken := getSystemToken(appTokens) 1201 // make the token be expired 1202 now = now.Add(2 * time.Minute) 1203 serviceChannel <- NewQueryMessage(context.Background(), appName, partID, wsID, nil, body, qNameFunction, "127.0.0.1", systemToken) 1204 var se coreutils.SysError 1205 require.ErrorAs(<-errs, &se) 1206 require.Equal(http.StatusUnauthorized, se.HTTPStatus) 1207 }) 1208 1209 t.Run("token provided, query a denied func -> 403 forbidden", func(t *testing.T) { 1210 token := getTestToken(appTokens, wsID) 1211 serviceChannel <- NewQueryMessage(context.Background(), appName, partID, wsID, nil, body, qNameQryDenied, "127.0.0.1", token) 1212 var se coreutils.SysError 1213 require.ErrorAs(<-errs, &se) 1214 require.Equal(http.StatusForbidden, se.HTTPStatus) 1215 }) 1216 } 1217 1218 type testOutputRow struct { 1219 fields []string 1220 fieldToIdx map[string]int 1221 values []interface{} 1222 } 1223 1224 func (r *testOutputRow) Set(alias string, value interface{}) { 1225 if r.values == nil { 1226 r.values = make([]interface{}, len(r.fields)) 1227 r.fieldToIdx = make(map[string]int) 1228 for i, field := range r.fields { 1229 r.fieldToIdx[field] = i 1230 } 1231 } 1232 r.values[r.fieldToIdx[alias]] = value 1233 } 1234 1235 func (r testOutputRow) Value(alias string) interface{} { return r.values[r.fieldToIdx[alias]] } 1236 func (r testOutputRow) Values() []interface{} { return r.values } 1237 1238 type testFilter struct { 1239 match bool 1240 err error 1241 } 1242 1243 func (f testFilter) IsMatch(FieldsKinds, IOutputRow) (bool, error) { 1244 return f.match, f.err 1245 } 1246 1247 type testWorkpiece struct { 1248 object istructs.IObject 1249 outputRow IOutputRow 1250 release func() 1251 } 1252 1253 func (w testWorkpiece) Object() istructs.IObject { return w.object } 1254 func (w testWorkpiece) OutputRow() IOutputRow { return w.outputRow } 1255 func (w testWorkpiece) EnrichedRootFieldsKinds() FieldsKinds { 1256 return FieldsKinds{} 1257 } 1258 func (w testWorkpiece) PutEnrichedRootFieldKind(string, appdef.DataKind) { 1259 panic("implement me") 1260 } 1261 func (w testWorkpiece) Release() { 1262 if w.release != nil { 1263 w.release() 1264 } 1265 } 1266 1267 type testResultSenderClosable struct { 1268 startArraySection func(sectionType string, path []string) 1269 objectSection func(sectionType string, path []string, element interface{}) (err error) 1270 sendElement func(name string, element interface{}) (err error) 1271 close func(err error) 1272 } 1273 1274 func (s testResultSenderClosable) StartArraySection(sectionType string, path []string) { 1275 s.startArraySection(sectionType, path) 1276 } 1277 func (s testResultSenderClosable) StartMapSection(string, []string) { panic("implement me") } 1278 func (s testResultSenderClosable) ObjectSection(sectionType string, path []string, element interface{}) (err error) { 1279 return s.objectSection(sectionType, path, element) 1280 } 1281 func (s testResultSenderClosable) SendElement(name string, element interface{}) (err error) { 1282 return s.sendElement(name, element) 1283 } 1284 func (s testResultSenderClosable) Close(err error) { s.close(err) } 1285 1286 type testMetrics struct{} 1287 1288 func (m *testMetrics) Increase(string, float64) {} 1289 1290 func getTestToken(appTokens istructs.IAppTokens, wsid istructs.WSID) string { 1291 pp := payloads.PrincipalPayload{ 1292 Login: "syslogin", 1293 SubjectKind: istructs.SubjectKind_User, 1294 ProfileWSID: wsid, 1295 } 1296 token, err := appTokens.IssueToken(time.Minute, &pp) 1297 if err != nil { 1298 panic(err) 1299 } 1300 return token 1301 } 1302 1303 func getSystemToken(appTokens istructs.IAppTokens) string { 1304 pp := payloads.PrincipalPayload{ 1305 Login: "syslogin", 1306 SubjectKind: istructs.SubjectKind_User, 1307 ProfileWSID: istructs.NullWSID, 1308 } 1309 token, err := appTokens.IssueToken(time.Minute, &pp) 1310 if err != nil { 1311 panic(err) 1312 } 1313 return token 1314 } 1315 1316 type mockState struct { 1317 istructs.IState 1318 mock.Mock 1319 } 1320 1321 func (s *mockState) KeyBuilder(storage, entity appdef.QName) (builder istructs.IStateKeyBuilder, err error) { 1322 return s.Called(storage, entity).Get(0).(istructs.IStateKeyBuilder), err 1323 } 1324 1325 func (s *mockState) MustExist(key istructs.IStateKeyBuilder) (value istructs.IStateValue, err error) { 1326 return s.Called(key).Get(0).(istructs.IStateValue), err 1327 } 1328 1329 type mockStateKeyBuilder struct { 1330 istructs.IStateKeyBuilder 1331 mock.Mock 1332 } 1333 1334 func (b *mockStateKeyBuilder) PutRecordID(name string, value istructs.RecordID) { 1335 b.Called(name, value) 1336 } 1337 1338 type mockStateValue struct { 1339 istructs.IStateValue 1340 mock.Mock 1341 } 1342 1343 func (o *mockStateValue) AsRecord(name string) istructs.IRecord { 1344 return o.Called(name).Get(0).(istructs.IRecord) 1345 } 1346 1347 type mockRecord struct { 1348 istructs.IRecord 1349 mock.Mock 1350 } 1351 1352 func (r *mockRecord) AsString(name string) string { return r.Called(name).String(0) } 1353 func (r *mockRecord) QName() appdef.QName { return r.Called().Get(0).(appdef.QName) }