github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/sys/collection/collection_test.go (about) 1 /* 2 * Copyright (c) 2021-present unTill Pro, Ltd. 3 * 4 * @author Michael Saigachenko 5 */ 6 7 package collection 8 9 import ( 10 "context" 11 "encoding/json" 12 "fmt" 13 "testing" 14 "time" 15 16 "github.com/stretchr/testify/require" 17 18 "github.com/voedger/voedger/pkg/appdef" 19 "github.com/voedger/voedger/pkg/appparts" 20 "github.com/voedger/voedger/pkg/iauthnzimpl" 21 "github.com/voedger/voedger/pkg/in10n" 22 "github.com/voedger/voedger/pkg/in10nmem" 23 "github.com/voedger/voedger/pkg/iprocbus" 24 "github.com/voedger/voedger/pkg/iratesce" 25 "github.com/voedger/voedger/pkg/isecretsimpl" 26 "github.com/voedger/voedger/pkg/istorage/mem" 27 istorageimpl "github.com/voedger/voedger/pkg/istorage/provider" 28 "github.com/voedger/voedger/pkg/istructs" 29 "github.com/voedger/voedger/pkg/istructsmem" 30 payloads "github.com/voedger/voedger/pkg/itokens-payloads" 31 "github.com/voedger/voedger/pkg/itokensjwt" 32 imetrics "github.com/voedger/voedger/pkg/metrics" 33 queryprocessor "github.com/voedger/voedger/pkg/processors/query" 34 "github.com/voedger/voedger/pkg/projectors" 35 "github.com/voedger/voedger/pkg/state" 36 wsdescutil "github.com/voedger/voedger/pkg/utils/testwsdesc" 37 "github.com/voedger/voedger/pkg/vvm/engines" 38 ibus "github.com/voedger/voedger/staging/src/github.com/untillpro/airs-ibus" 39 ) 40 41 var cocaColaDocID istructs.RecordID 42 var qNameTestWSKind = appdef.NewQName(appdef.SysPackage, "test_ws") 43 44 const maxPrepareQueries = 10 45 46 func deployTestApp(t *testing.T) (appParts appparts.IAppPartitions, appStructs istructs.IAppStructs, cleanup func()) { 47 require := require.New(t) 48 49 cfgs := make(istructsmem.AppConfigsType, 1) 50 asp := istorageimpl.Provide(mem.Provide()) 51 52 // конфиг приложения airs-bp 53 adb := appdef.New() 54 cfg := cfgs.AddConfig(test.appQName, adb) 55 cfg.SetNumAppWorkspaces(istructs.DefaultNumAppWorkspaces) 56 { 57 58 adb.AddPackage("test", "test.org/test") 59 60 // this should be done in tests only. Runtime -> the projector is defined in sys.vsql already 61 adb.AddCDoc(istructs.QNameCDoc) 62 adb.AddODoc(istructs.QNameODoc) 63 adb.AddWDoc(istructs.QNameWDoc) 64 adb.AddCRecord(istructs.QNameCRecord) 65 adb.AddORecord(istructs.QNameORecord) 66 adb.AddWRecord(istructs.QNameWRecord) 67 68 prj := adb.AddProjector(QNameProjectorCollection) 69 prj.SetSync(true). 70 Events().Add(istructs.QNameCRecord, appdef.ProjectorEventKind_Insert, appdef.ProjectorEventKind_Update) 71 prj.Intents(). 72 Add(state.View, QNameCollectionView) // this view will be added below 73 } 74 { 75 // fill IAppDef with funcs. That is done here manually because we o not use sys.vsql here 76 qNameCollectionParams := appdef.NewQName(appdef.SysPackage, "CollectionParams") 77 78 // will add func definitions to AppDef manually because local test does not use sql. In runtime these definitions will come from sys.vsql 79 adb.AddObject(qNameCollectionParams). 80 AddField(field_Schema, appdef.DataKind_string, true). 81 AddField(field_ID, appdef.DataKind_RecordID, false) 82 83 adb.AddQuery(qNameQueryCollection). 84 SetParam(qNameCollectionParams). 85 SetResult(appdef.QNameANY) 86 87 qNameGetCDocParams := appdef.NewQName(appdef.SysPackage, "GetCDocParams") 88 adb.AddObject(qNameGetCDocParams). 89 AddField(field_ID, appdef.DataKind_int64, true) 90 91 qNameGetCDocResult := appdef.NewQName(appdef.SysPackage, "GetCDocResult") 92 adb.AddObject(qNameGetCDocResult). 93 AddField("Result", appdef.DataKind_string, true) 94 95 adb.AddQuery(qNameQueryGetCDoc). 96 SetParam(qNameGetCDocParams). 97 SetResult(qNameGetCDocResult) 98 99 qNameStateParams := appdef.NewQName(appdef.SysPackage, "StateParams") 100 adb.AddObject(qNameStateParams). 101 AddField(field_After, appdef.DataKind_int64, true) 102 103 qNameStateResult := appdef.NewQName(appdef.SysPackage, "StateResult") 104 adb.AddObject(qNameStateResult). 105 AddField(field_State, appdef.DataKind_string, true) 106 107 adb.AddQuery(qNameQueryState). 108 SetParam(qNameStateParams). 109 SetResult(qNameStateResult) 110 111 wsdescutil.AddWorkspaceDescriptorStubDef(adb) 112 113 adb.AddCDoc(qNameTestWSKind).SetSingleton() 114 } 115 { // "modify" function 116 adb.AddCommand(test.modifyCmdName) 117 cfg.Resources.Add(istructsmem.NewCommandFunction(test.modifyCmdName, istructsmem.NullCommandExec)) 118 } 119 { // CDoc: articles 120 articles := adb.AddCDoc(test.tableArticles) 121 articles. 122 AddField(test.articleNameIdent, appdef.DataKind_string, true). 123 AddField(test.articleNumberIdent, appdef.DataKind_int32, false). 124 AddField(test.articleDeptIdent, appdef.DataKind_RecordID, false) 125 articles. 126 AddContainer(test.tableArticlePrices.Entity(), test.tableArticlePrices, appdef.Occurs(0), appdef.Occurs(100)) 127 } 128 { // CDoc: departments 129 departments := adb.AddCDoc(test.tableDepartments) 130 departments. 131 AddField(test.depNameIdent, appdef.DataKind_string, true). 132 AddField(test.depNumberIdent, appdef.DataKind_int32, false) 133 } 134 { // CDoc: periods 135 periods := adb.AddCDoc(test.tablePeriods) 136 periods. 137 AddField(test.periodNameIdent, appdef.DataKind_string, true). 138 AddField(test.periodNumberIdent, appdef.DataKind_int32, false) 139 } 140 { // CDoc: prices 141 prices := adb.AddCDoc(test.tablePrices) 142 prices. 143 AddField(test.priceNameIdent, appdef.DataKind_string, true). 144 AddField(test.priceNumberIdent, appdef.DataKind_int32, false) 145 } 146 147 { // CDoc: article prices 148 articlesPrices := adb.AddCRecord(test.tableArticlePrices) 149 articlesPrices. 150 AddField(test.articlePricesPriceIdIdent, appdef.DataKind_RecordID, true). 151 AddField(test.articlePricesPriceIdent, appdef.DataKind_float32, true) 152 articlesPrices. 153 AddContainer(test.tableArticlePriceExceptions.Entity(), test.tableArticlePriceExceptions, appdef.Occurs(0), appdef.Occurs(100)) 154 } 155 156 { // CDoc: article price exceptions 157 articlesPricesExceptions := adb.AddCRecord(test.tableArticlePriceExceptions) 158 articlesPricesExceptions. 159 AddField(test.articlePriceExceptionsPeriodIdIdent, appdef.DataKind_RecordID, true). 160 AddField(test.articlePriceExceptionsPriceIdent, appdef.DataKind_float32, true) 161 } 162 163 // kept here to keep local tests working without sql 164 projectors.ProvideViewDef(adb, QNameCollectionView, func(b appdef.IViewBuilder) { 165 b.Key().PartKey().AddField(Field_PartKey, appdef.DataKind_int32) 166 b.Key().ClustCols(). 167 AddField(Field_DocQName, appdef.DataKind_QName). 168 AddRefField(field_DocID). 169 AddRefField(field_ElementID) 170 b.Value(). 171 AddField(Field_Record, appdef.DataKind_Record, true). 172 AddField(state.ColOffset, appdef.DataKind_int64, true) 173 }) 174 175 { 176 // Workspace 177 wsBuilder := adb.AddWorkspace(appdef.NewQName(appdef.SysPackage, "test_wsWS")) 178 wsBuilder.SetDescriptor(qNameTestWSKind) 179 wsBuilder.AddType(qNameQueryCollection) 180 wsBuilder.AddType(qNameQueryGetCDoc) 181 wsBuilder.AddType(qNameQueryState) 182 wsBuilder.AddType(test.modifyCmdName) 183 wsBuilder.AddType(test.tableArticles) 184 wsBuilder.AddType(test.tableDepartments) 185 wsBuilder.AddType(test.tablePeriods) 186 wsBuilder.AddType(test.tablePrices) 187 wsBuilder.AddType(test.tableArticlePrices) 188 wsBuilder.AddType(test.tableArticlePriceExceptions) 189 wsBuilder.AddType(QNameCollectionView) 190 } 191 192 // TODO: remove it after https://github.com/voedger/voedger/issues/56 193 appDef, err := adb.Build() 194 require.NoError(err) 195 196 Provide(cfg) 197 198 appStructsProvider := istructsmem.Provide(cfgs, iratesce.TestBucketsFactory, 199 payloads.ProvideIAppTokensFactory(itokensjwt.TestTokensJWT()), asp) 200 201 secretReader := isecretsimpl.ProvideSecretReader() 202 n10nBroker, n10nBrokerCleanup := in10nmem.ProvideEx2(in10n.Quotas{ 203 Channels: 1000, 204 ChannelsPerSubject: 10, 205 Subscriptions: 1000, 206 SubscriptionsPerSubject: 10, 207 }, time.Now) 208 209 appParts, appPartsCleanup, err := appparts.NewWithActualizerWithExtEnginesFactories(appStructsProvider, 210 projectors.NewSyncActualizerFactoryFactory(projectors.ProvideSyncActualizerFactory(), secretReader, n10nBroker), 211 engines.ProvideExtEngineFactories( 212 engines.ExtEngineFactoriesConfig{ 213 AppConfigs: cfgs, 214 WASMCompile: false, 215 })) 216 require.NoError(err) 217 appParts.DeployApp(test.appQName, appDef, test.totalPartitions, test.appEngines) 218 appParts.DeployAppPartitions(test.appQName, []istructs.PartitionID{test.partition}) 219 220 // create stub for cdoc.sys.WorkspaceDescriptor to make query processor work 221 as, err := appStructsProvider.AppStructs(test.appQName) 222 require.NoError(err) 223 err = wsdescutil.CreateCDocWorkspaceDescriptorStub(as, test.partition, test.workspace, qNameTestWSKind, 1, 1) 224 require.NoError(err) 225 226 cleanup = func() { 227 appPartsCleanup() 228 n10nBrokerCleanup() 229 } 230 231 return appParts, as, cleanup 232 } 233 234 // Test executes 3 operations with CUDs: 235 // - insert coca-cola with 2 prices 236 // - modify coca-cola and 1 price 237 // - insert fanta with 2 prices 238 // ...then launches Collection actualizer and waits until it reads all the log. 239 // Then projection values checked. 240 func TestBasicUsage_Collection(t *testing.T) { 241 require := require.New(t) 242 243 appParts, appStructs, cleanup := deployTestApp(t) 244 defer cleanup() 245 246 // Command processor 247 processor := testProcessor(appParts) 248 249 // ID and Offset generators 250 idGen := newIdsGenerator() 251 252 normalPriceID, happyHourPriceID, _ := insertPrices(require, appStructs, &idGen) 253 coldDrinks, _ := insertDepartments(require, appStructs, &idGen) 254 255 { // CUDs: Insert coca-cola 256 event := saveEvent(require, appStructs, &idGen, newModify(appStructs, &idGen, func(event istructs.IRawEventBuilder) { 257 newArticleCUD(event, 1, coldDrinks, test.cocaColaNumber, "Coca-cola") 258 newArPriceCUD(event, 1, 2, normalPriceID, 2.4) 259 newArPriceCUD(event, 1, 3, happyHourPriceID, 1.8) 260 })) 261 err := processor.SendSync(event) 262 require.NoError(err) 263 } 264 265 cocaColaDocID = idGen.idmap[1] 266 cocaColaNormalPriceElementId := idGen.idmap[2] 267 cocaColaHappyHourPriceElementId := idGen.idmap[3] 268 269 { // CUDs: modify coca-cola number and normal price 270 event := saveEvent(require, appStructs, &idGen, newModify(appStructs, &idGen, func(event istructs.IRawEventBuilder) { 271 updateArticleCUD(event, appStructs, cocaColaDocID, test.cocaColaNumber2, "Coca-cola") 272 updateArPriceCUD(event, appStructs, cocaColaNormalPriceElementId, normalPriceID, 2.2) 273 })) 274 require.NoError(processor.SendSync(event)) 275 } 276 277 { // CUDs: insert fanta 278 event := saveEvent(require, appStructs, &idGen, newModify(appStructs, &idGen, func(event istructs.IRawEventBuilder) { 279 newArticleCUD(event, 7, coldDrinks, test.fantaNumber, "Fanta") 280 newArPriceCUD(event, 7, 8, normalPriceID, 2.1) 281 newArPriceCUD(event, 7, 9, happyHourPriceID, 1.7) 282 })) 283 require.NoError(processor.SendSync(event)) 284 } 285 fantaDocID := idGen.idmap[7] 286 fantaNormalPriceElementId := idGen.idmap[8] 287 fantaHappyHourPriceElementId := idGen.idmap[9] 288 289 // Check expected projection values 290 { // coca-cola 291 requireArticle(require, "Coca-cola", test.cocaColaNumber2, appStructs, cocaColaDocID) 292 requireArPrice(require, normalPriceID, 2.2, appStructs, cocaColaDocID, cocaColaNormalPriceElementId) 293 requireArPrice(require, happyHourPriceID, 1.8, appStructs, cocaColaDocID, cocaColaHappyHourPriceElementId) 294 } 295 { // fanta 296 requireArticle(require, "Fanta", test.fantaNumber, appStructs, fantaDocID) 297 requireArPrice(require, normalPriceID, 2.1, appStructs, fantaDocID, fantaNormalPriceElementId) 298 requireArPrice(require, happyHourPriceID, 1.7, appStructs, fantaDocID, fantaHappyHourPriceElementId) 299 } 300 301 } 302 303 func Test_updateChildRecord(t *testing.T) { 304 require := require.New(t) 305 306 _, appStructs, cleanup := deployTestApp(t) 307 defer cleanup() 308 309 // ID and Offset generators 310 idGen := newIdsGenerator() 311 312 normalPriceID, _, _ := insertPrices(require, appStructs, &idGen) 313 coldDrinks, _ := insertDepartments(require, appStructs, &idGen) 314 315 { // CUDs: Insert coca-cola 316 saveEvent(require, appStructs, &idGen, newModify(appStructs, &idGen, func(event istructs.IRawEventBuilder) { 317 newArticleCUD(event, 1, coldDrinks, test.cocaColaNumber, "Coca-cola") 318 newArPriceCUD(event, 1, 2, normalPriceID, 2.4) 319 })) 320 } 321 322 cocaColaNormalPriceElementId := idGen.idmap[2] 323 324 { // CUDs: modify normal price 325 saveEvent(require, appStructs, &idGen, newModify(appStructs, &idGen, func(event istructs.IRawEventBuilder) { 326 updateArPriceCUD(event, appStructs, cocaColaNormalPriceElementId, normalPriceID, 2.2) 327 })) 328 } 329 330 rec, err := appStructs.Records().Get(test.workspace, true, cocaColaNormalPriceElementId) 331 require.NoError(err) 332 require.NotNil(rec) 333 require.Equal(float32(2.2), rec.AsFloat32(test.articlePricesPriceIdent)) 334 } 335 336 /* 337 coca-cola 338 339 normal 2.0 340 happy_hour 1.5 341 exception: holiday 1.0 342 exception: new year 0.8 343 344 fanta 345 346 normal 2.1 347 exception: holiday 1.6 348 exception: new year 1.2 349 happy_hour 1.6 350 exception: holiday 1.1 351 352 update coca-cola: 353 354 +exception for normal: 355 - exception: holiday 1.8 356 update exception for happy_hour: 357 - holiday: 0.9 358 */ 359 360 func cp_Collection_3levels(t *testing.T, appParts appparts.IAppPartitions, appStructs istructs.IAppStructs) { 361 require := require.New(t) 362 363 // Command processor 364 processor := testProcessor(appParts) 365 366 // ID and Offset generators 367 idGen := newIdsGenerator() 368 369 normalPriceID, happyHourPriceID, eventPrices := insertPrices(require, appStructs, &idGen) 370 coldDrinks, eventDepartments := insertDepartments(require, appStructs, &idGen) 371 holiday, newyear, eventPeriods := insertPeriods(require, appStructs, &idGen) 372 373 for _, event := range []istructs.IPLogEvent{eventPrices, eventDepartments, eventPeriods} { 374 require.NoError(processor.SendSync(event)) 375 } 376 377 // insert coca-cola 378 { 379 event := saveEvent(require, appStructs, &idGen, newModify(appStructs, &idGen, func(event istructs.IRawEventBuilder) { 380 newArticleCUD(event, 1, coldDrinks, test.cocaColaNumber, "Coca-cola") 381 newArPriceCUD(event, 1, 2, normalPriceID, 2.0) 382 newArPriceCUD(event, 1, 3, happyHourPriceID, 1.5) 383 { 384 newArPriceExceptionCUD(event, 3, 4, holiday, 1.0) 385 newArPriceExceptionCUD(event, 3, 5, newyear, 0.8) 386 } 387 })) 388 require.NoError(processor.SendSync(event)) 389 } 390 391 cocaColaDocID = idGen.idmap[1] 392 cocaColaNormalPriceElementId := idGen.idmap[2] 393 cocaColaHappyHourPriceElementId := idGen.idmap[3] 394 cocaColaHappyHourExceptionHolidayElementId := idGen.idmap[4] 395 cocaColaHappyHourExceptionNewYearElementId := idGen.idmap[5] 396 397 // insert fanta 398 { 399 event := saveEvent(require, appStructs, &idGen, newModify(appStructs, &idGen, func(event istructs.IRawEventBuilder) { 400 newArticleCUD(event, 6, coldDrinks, test.fantaNumber, "Fanta") 401 newArPriceCUD(event, 6, 7, normalPriceID, 2.1) 402 { 403 newArPriceExceptionCUD(event, 7, 9, holiday, 1.6) 404 newArPriceExceptionCUD(event, 7, 10, newyear, 1.2) 405 } 406 newArPriceCUD(event, 6, 8, happyHourPriceID, 1.6) 407 { 408 newArPriceExceptionCUD(event, 8, 11, holiday, 1.1) 409 } 410 })) 411 require.NoError(processor.SendSync(event)) 412 } 413 414 fantaDocID := idGen.idmap[6] 415 fantaNormalPriceElementId := idGen.idmap[7] 416 fantaNormalExceptionHolidayElementId := idGen.idmap[9] 417 fantaNormalExceptionNewYearElementId := idGen.idmap[10] 418 fantaHappyHourPriceElementId := idGen.idmap[8] 419 fantaHappyHourExceptionHolidayElementId := idGen.idmap[11] 420 421 // modify coca-cola 422 { 423 event := saveEvent(require, appStructs, &idGen, newModify(appStructs, &idGen, func(event istructs.IRawEventBuilder) { 424 newArPriceExceptionCUD(event, cocaColaNormalPriceElementId, 15, holiday, 1.8) 425 updateArPriceExceptionCUD(event, appStructs, cocaColaHappyHourExceptionHolidayElementId, holiday, 0.9) 426 })) 427 require.NoError(processor.SendSync(event)) 428 } 429 cocaColaNormalExceptionHolidayElementId := idGen.idmap[15] 430 require.NotEqual(istructs.NullRecordID, cocaColaNormalExceptionHolidayElementId) 431 432 // Check expected projection values 433 { // coca-cola 434 docId := cocaColaDocID 435 requireArticle(require, "Coca-cola", test.cocaColaNumber, appStructs, docId) 436 requireArPrice(require, normalPriceID, 2.0, appStructs, docId, cocaColaNormalPriceElementId) 437 { 438 requireArPriceException(require, holiday, 1.8, appStructs, docId, cocaColaNormalExceptionHolidayElementId) 439 } 440 requireArPrice(require, happyHourPriceID, 1.5, appStructs, docId, cocaColaHappyHourPriceElementId) 441 { 442 requireArPriceException(require, holiday, 0.9, appStructs, docId, cocaColaHappyHourExceptionHolidayElementId) 443 requireArPriceException(require, newyear, 0.8, appStructs, docId, cocaColaHappyHourExceptionNewYearElementId) 444 } 445 } 446 { // fanta 447 docId := fantaDocID 448 requireArticle(require, "Fanta", test.fantaNumber, appStructs, docId) 449 requireArPrice(require, normalPriceID, 2.1, appStructs, docId, fantaNormalPriceElementId) 450 { 451 requireArPriceException(require, holiday, 1.6, appStructs, docId, fantaNormalExceptionHolidayElementId) 452 requireArPriceException(require, newyear, 1.2, appStructs, docId, fantaNormalExceptionNewYearElementId) 453 } 454 requireArPrice(require, happyHourPriceID, 1.6, appStructs, docId, fantaHappyHourPriceElementId) 455 { 456 requireArPriceException(require, holiday, 1.1, appStructs, docId, fantaHappyHourExceptionHolidayElementId) 457 } 458 } 459 } 460 461 func Test_Collection_3levels(t *testing.T) { 462 appParts, appStructs, cleanup := deployTestApp(t) 463 defer cleanup() 464 465 cp_Collection_3levels(t, appParts, appStructs) 466 } 467 468 func TestBasicUsage_QueryFunc_Collection(t *testing.T) { 469 require := require.New(t) 470 471 appParts, appStructs, cleanup := deployTestApp(t) 472 defer cleanup() 473 474 // Fill the collection projection 475 cp_Collection_3levels(t, appParts, appStructs) 476 477 request := []byte(`{ 478 "args":{ 479 "Schema":"test.articles" 480 }, 481 "elements": [ 482 { 483 "fields": ["name", "number"], 484 "refs": [["id_department", "name"]] 485 }, 486 { 487 "path": "article_prices", 488 "fields": ["price"], 489 "refs": [["id_prices", "name"]] 490 } 491 ], 492 "orderBy":[{"field":"name"}] 493 }`) 494 serviceChannel := make(iprocbus.ServiceChannel) 495 out := newTestSender() 496 497 authn := iauthnzimpl.NewDefaultAuthenticator(iauthnzimpl.TestSubjectRolesGetter, iauthnzimpl.TestIsDeviceAllowedFuncs) 498 authz := iauthnzimpl.NewDefaultAuthorizer() 499 tokens := itokensjwt.ProvideITokens(itokensjwt.SecretKeyExample, time.Now) 500 appTokens := payloads.ProvideIAppTokensFactory(tokens).New(test.appQName) 501 queryProcessor := queryprocessor.ProvideServiceFactory()( 502 serviceChannel, 503 func(ctx context.Context, sender ibus.ISender) queryprocessor.IResultSenderClosable { return out }, 504 appParts, 505 maxPrepareQueries, 506 imetrics.Provide(), "vvm", authn, authz, tokens, nil) 507 go queryProcessor.Run(context.Background()) 508 sysToken, err := payloads.GetSystemPrincipalTokenApp(appTokens) 509 require.NoError(err) 510 serviceChannel <- queryprocessor.NewQueryMessage(context.Background(), test.appQName, test.partition, test.workspace, nil, request, qNameQueryCollection, "", sysToken) 511 <-out.done 512 513 out.requireNoError(require) 514 require.Len(out.resultRows, 2) // 2 rows 515 516 json, err := json.Marshal(out.resultRows) 517 require.NoError(err) 518 require.NotNil(json) 519 520 { 521 row := 0 522 require.Len(out.resultRows[row], 2) // 2 elements in a row 523 { 524 elem := 0 525 require.Len(out.resultRows[row][elem], 1) // 1 element row in 1st element 526 require.Len(out.resultRows[row][elem][0], 3) // 3 cell in a row element 527 name := out.resultRows[row][elem][0][0] 528 number := out.resultRows[row][elem][0][1] 529 department := out.resultRows[row][elem][0][2] 530 require.Equal("Coca-cola", name) 531 require.Equal(int32(10), number) 532 require.Equal("Cold Drinks", department) 533 } 534 { 535 elem := 1 536 require.Len(out.resultRows[row][elem], 2) // 2 element rows in 2nd element 537 { 538 elemRow := 0 539 require.Len(out.resultRows[row][elem][elemRow], 2) // 2 cells in a row element 540 price := out.resultRows[row][elem][elemRow][0] 541 pricename := out.resultRows[row][elem][elemRow][1] 542 require.Equal(float32(2.0), price) 543 require.Equal("Normal Price", pricename) 544 } 545 { 546 elemRow := 1 547 require.Len(out.resultRows[row][elem][elemRow], 2) // 2 cells in a row element 548 price := out.resultRows[row][elem][elemRow][0] 549 pricename := out.resultRows[row][elem][elemRow][1] 550 require.Equal(float32(1.5), price) 551 require.Equal("Happy Hour Price", pricename) 552 } 553 } 554 } 555 { 556 row := 1 557 require.Len(out.resultRows[row], 2) // 2 elements in a row 558 { 559 elem := 0 560 require.Len(out.resultRows[row][elem], 1) // 1 element row in 1st element 561 require.Len(out.resultRows[row][elem][0], 3) // 3 cell in a row element 562 name := out.resultRows[row][elem][0][0] 563 number := out.resultRows[row][elem][0][1] 564 department := out.resultRows[row][elem][0][2] 565 require.Equal("Fanta", name) 566 require.Equal(int32(12), number) 567 require.Equal("Cold Drinks", department) 568 } 569 { 570 elem := 1 571 require.Len(out.resultRows[row][elem], 2) // 2 element rows in 2nd element 572 { 573 elemRow := 0 574 require.Len(out.resultRows[row][elem][elemRow], 2) // 2 cells in a row element 575 price := out.resultRows[row][elem][elemRow][0] 576 pricename := out.resultRows[row][elem][elemRow][1] 577 require.Equal(float32(2.1), price) 578 require.Equal("Normal Price", pricename) 579 } 580 { 581 elemRow := 1 582 require.Len(out.resultRows[row][elem][elemRow], 2) // 2 cells in a row element 583 price := out.resultRows[row][elem][elemRow][0] 584 pricename := out.resultRows[row][elem][elemRow][1] 585 require.Equal(float32(1.6), price) 586 require.Equal("Happy Hour Price", pricename) 587 } 588 } 589 } 590 } 591 592 func TestBasicUsage_QueryFunc_CDoc(t *testing.T) { 593 require := require.New(t) 594 595 appParts, appStructs, cleanup := deployTestApp(t) 596 defer cleanup() 597 598 // Fill the collection projection 599 cp_Collection_3levels(t, appParts, appStructs) 600 601 request := fmt.Sprintf(`{ 602 "args":{ 603 "ID":%d 604 }, 605 "elements": [ 606 { 607 "fields": ["Result"] 608 } 609 ] 610 }`, int64(cocaColaDocID)) 611 612 serviceChannel := make(iprocbus.ServiceChannel) 613 out := newTestSender() 614 615 authn := iauthnzimpl.NewDefaultAuthenticator(iauthnzimpl.TestSubjectRolesGetter, iauthnzimpl.TestIsDeviceAllowedFuncs) 616 authz := iauthnzimpl.NewDefaultAuthorizer() 617 tokens := itokensjwt.ProvideITokens(itokensjwt.SecretKeyExample, time.Now) 618 appTokens := payloads.ProvideIAppTokensFactory(tokens).New(test.appQName) 619 queryProcessor := queryprocessor.ProvideServiceFactory()(serviceChannel, func(ctx context.Context, sender ibus.ISender) queryprocessor.IResultSenderClosable { 620 return out 621 }, appParts, maxPrepareQueries, imetrics.Provide(), "vvm", authn, authz, tokens, nil) 622 623 go queryProcessor.Run(context.Background()) 624 sysToken, err := payloads.GetSystemPrincipalTokenApp(appTokens) 625 require.NoError(err) 626 serviceChannel <- queryprocessor.NewQueryMessage(context.Background(), test.appQName, test.partition, test.workspace, nil, []byte(request), qNameQueryGetCDoc, "", sysToken) 627 <-out.done 628 629 out.requireNoError(require) 630 require.Len(out.resultRows, 1) // 1 row 631 require.Len(out.resultRows[0], 1) // 1 element in a row 632 require.Len(out.resultRows[0][0], 1) // 1 row element in an element 633 require.Len(out.resultRows[0][0][0], 1) // 1 cell in a row element 634 635 value := out.resultRows[0][0][0][0] 636 expected := `{ 637 638 "article_prices":[ 639 { 640 "article_price_exceptions":[ 641 { 642 "id_periods":3.22685000131076e+14, 643 "price":1.8, 644 "sys.ID":3.22685000131089e+14, 645 "sys.IsActive":true 646 } 647 ], 648 "id_prices":3.22685000131072e+14, 649 "price":2, 650 "sys.ID":3.22685000131079e+14, 651 "sys.IsActive":true 652 }, 653 { 654 "article_price_exceptions":[ 655 { 656 "id_periods":3.22685000131076e+14, 657 "price":0.9, 658 "sys.ID":3.22685000131081e+14, 659 "sys.IsActive":true 660 }, 661 { 662 "id_periods":3.22685000131077e+14, 663 "price":0.8, 664 "sys.ID":3.22685000131082e+14, 665 "sys.IsActive":true 666 } 667 ], 668 "id_prices":3.22685000131073e+14, 669 "price":1.5, 670 "sys.ID":3.2268500013108e+14, 671 "sys.IsActive":true 672 } 673 ], 674 "id_department":3.22685000131074e+14, 675 "name":"Coca-cola", 676 "number":10, 677 "sys.ID":3.22685000131078e+14, 678 "sys.IsActive":true, 679 "xrefs":{ 680 "test.departments":{ 681 "322685000131074":{ 682 "name":"Cold Drinks", 683 "number":1, 684 "sys.ID":3.22685000131074e+14, 685 "sys.IsActive":true 686 } 687 }, 688 "test.periods":{ 689 "322685000131076":{ 690 "name":"Holiday", 691 "number":1, 692 "sys.ID":322685000131076, 693 "sys.IsActive":true 694 }, 695 "322685000131077":{ 696 "name":"New Year", 697 "number":2, 698 "sys.ID":322685000131077, 699 "sys.IsActive":true 700 } 701 }, 702 "test.prices":{ 703 "322685000131072":{ 704 "name":"Normal Price", 705 "number":1, 706 "sys.ID":322685000131072, 707 "sys.IsActive":true 708 }, 709 "322685000131073":{ 710 "name":"Happy Hour Price", 711 "number":2, 712 "sys.ID":322685000131073, 713 "sys.IsActive":true 714 } 715 } 716 } 717 718 }` 719 require.JSONEq(expected, value.(string)) 720 } 721 722 func TestBasicUsage_State(t *testing.T) { 723 require := require.New(t) 724 725 appParts, appStructs, cleanup := deployTestApp(t) 726 defer cleanup() 727 728 // Fill the collection projection 729 cp_Collection_3levels(t, appParts, appStructs) 730 731 serviceChannel := make(iprocbus.ServiceChannel) 732 out := newTestSender() 733 734 authn := iauthnzimpl.NewDefaultAuthenticator(iauthnzimpl.TestSubjectRolesGetter, iauthnzimpl.TestIsDeviceAllowedFuncs) 735 authz := iauthnzimpl.NewDefaultAuthorizer() 736 tokens := itokensjwt.ProvideITokens(itokensjwt.SecretKeyExample, time.Now) 737 appTokens := payloads.ProvideIAppTokensFactory(tokens).New(test.appQName) 738 queryProcessor := queryprocessor.ProvideServiceFactory()(serviceChannel, func(ctx context.Context, sender ibus.ISender) queryprocessor.IResultSenderClosable { 739 return out 740 }, appParts, maxPrepareQueries, imetrics.Provide(), "vvm", authn, authz, tokens, nil) 741 742 go queryProcessor.Run(context.Background()) 743 sysToken, err := payloads.GetSystemPrincipalTokenApp(appTokens) 744 require.NoError(err) 745 serviceChannel <- queryprocessor.NewQueryMessage(context.Background(), test.appQName, test.partition, test.workspace, nil, []byte(`{"args":{"After":0},"elements":[{"fields":["State"]}]}`), 746 qNameQueryState, "", sysToken) 747 <-out.done 748 749 out.requireNoError(require) 750 require.Len(out.resultRows, 1) // 1 row 751 require.Len(out.resultRows[0], 1) // 1 element in a row 752 require.Len(out.resultRows[0][0], 1) // 1 row element in an element 753 require.Len(out.resultRows[0][0][0], 1) // 1 cell in a row element 754 expected := `{ 755 "test.article_price_exceptions":{ 756 "322685000131081":{ 757 "id_periods":3.22685000131076e+14, 758 "price":0.9, 759 "sys.ID":322685000131081, 760 "sys.IsActive":true, 761 "sys.ParentID":3.2268500013108e+14 762 }, 763 "322685000131082":{ 764 "id_periods": 3.22685000131077e+14, 765 "price":0.8, 766 "sys.ID":322685000131082, 767 "sys.IsActive":true, 768 "sys.ParentID":3.2268500013108e+14 769 }, 770 "322685000131085":{ 771 "id_periods":3.22685000131076e+14, 772 "price":1.6, 773 "sys.ID":322685000131085, 774 "sys.IsActive":true, 775 "sys.ParentID":3.22685000131084e+14 776 }, 777 "322685000131086":{ 778 "id_periods":3.22685000131077e+14, 779 "price":1.2, 780 "sys.ID":322685000131086, 781 "sys.IsActive":true, 782 "sys.ParentID":3.22685000131084e+14 783 }, 784 "322685000131088":{ 785 "id_periods":3.22685000131076e+14, 786 "price":1.1, 787 "sys.ID":322685000131088, 788 "sys.IsActive":true, 789 "sys.ParentID":3.22685000131087e+14 790 }, 791 "322685000131089":{ 792 "id_periods":3.22685000131076e+14, 793 "price":1.8, 794 "sys.ID":322685000131089, 795 "sys.IsActive":true, 796 "sys.ParentID":3.22685000131079e+14 797 } 798 }, 799 "test.article_prices":{ 800 "322685000131079":{ 801 "id_prices":3.22685000131072e+14, 802 "price":2, 803 "sys.ID":322685000131079, 804 "sys.IsActive":true, 805 "sys.ParentID":3.22685000131078e+14 806 }, 807 "322685000131080":{ 808 "id_prices":3.22685000131073e+14, 809 "price":1.5, 810 "sys.ID":322685000131080, 811 "sys.IsActive":true, 812 "sys.ParentID":3.22685000131078e+14 813 }, 814 "322685000131084":{ 815 "id_prices":3.22685000131072e+14, 816 "price":2.1, 817 "sys.ID":322685000131084, 818 "sys.IsActive":true, 819 "sys.ParentID":3.22685000131083e+14 820 }, 821 "322685000131087":{ 822 "id_prices":3.22685000131073e+14, 823 "price":1.6, 824 "sys.ID":322685000131087, 825 "sys.IsActive":true, 826 "sys.ParentID":3.22685000131083e+14 827 } 828 }, 829 "test.articles":{ 830 "322685000131078":{ 831 "id_department":3.22685000131074e+14, 832 "name":"Coca-cola", 833 "number":10, 834 "sys.ID":322685000131078, 835 "sys.IsActive":true 836 }, 837 "322685000131083":{ 838 "id_department":3.22685000131074e+14, 839 "name":"Fanta", 840 "number":12, 841 "sys.ID":322685000131083, 842 "sys.IsActive":true 843 } 844 }, 845 "test.departments":{ 846 "322685000131074":{ 847 "name":"Cold Drinks", 848 "number":1, 849 "sys.ID":322685000131074, 850 "sys.IsActive":true 851 }, 852 "322685000131075":{ 853 "name":"Hot Drinks", 854 "number":2, 855 "sys.ID":322685000131075, 856 "sys.IsActive":true 857 } 858 }, 859 "test.periods":{ 860 "322685000131076":{ 861 "name":"Holiday", 862 "number":1, 863 "sys.ID":322685000131076, 864 "sys.IsActive":true 865 }, 866 "322685000131077":{ 867 "name":"New Year", 868 "number":2, 869 "sys.ID":322685000131077, 870 "sys.IsActive":true 871 } 872 }, 873 "test.prices":{ 874 "322685000131072":{ 875 "name":"Normal Price", 876 "number":1, 877 "sys.ID":322685000131072, 878 "sys.IsActive":true 879 }, 880 "322685000131073":{ 881 "name":"Happy Hour Price", 882 "number":2, 883 "sys.ID":322685000131073, 884 "sys.IsActive":true 885 } 886 } 887 }` 888 require.JSONEq(expected, out.resultRows[0][0][0][0].(string)) 889 } 890 891 func TestState_withAfterArgument(t *testing.T) { 892 require := require.New(t) 893 894 appParts, appStructs, cleanup := deployTestApp(t) 895 defer cleanup() 896 897 // Fill the collection projection 898 cp_Collection_3levels(t, appParts, appStructs) 899 900 serviceChannel := make(iprocbus.ServiceChannel) 901 out := newTestSender() 902 903 authn := iauthnzimpl.NewDefaultAuthenticator(iauthnzimpl.TestSubjectRolesGetter, iauthnzimpl.TestIsDeviceAllowedFuncs) 904 authz := iauthnzimpl.NewDefaultAuthorizer() 905 tokens := itokensjwt.ProvideITokens(itokensjwt.SecretKeyExample, time.Now) 906 appTokens := payloads.ProvideIAppTokensFactory(tokens).New(test.appQName) 907 queryProcessor := queryprocessor.ProvideServiceFactory()(serviceChannel, func(ctx context.Context, sender ibus.ISender) queryprocessor.IResultSenderClosable { 908 return out 909 }, appParts, maxPrepareQueries, imetrics.Provide(), "vvm", authn, authz, tokens, nil) 910 911 go queryProcessor.Run(context.Background()) 912 sysToken, err := payloads.GetSystemPrincipalTokenApp(appTokens) 913 require.NoError(err) 914 serviceChannel <- queryprocessor.NewQueryMessage(context.Background(), test.appQName, test.partition, test.workspace, nil, []byte(`{"args":{"After":5},"elements":[{"fields":["State"]}]}`), 915 qNameQueryState, "", sysToken) 916 <-out.done 917 918 out.requireNoError(require) 919 require.Len(out.resultRows, 1) // 1 row 920 require.Len(out.resultRows[0], 1) // 1 element in a row 921 require.Len(out.resultRows[0][0], 1) // 1 row element in an element 922 require.Len(out.resultRows[0][0][0], 1) // 1 cell in a row element 923 expected := ` 924 { 925 "test.article_price_exceptions":{ 926 "322685000131081":{ 927 "id_periods":3.22685000131076e+14, 928 "price":0.9, 929 "sys.ID":322685000131081, 930 "sys.IsActive":true, 931 "sys.ParentID":3.2268500013108e+14 932 }, 933 "322685000131089":{ 934 "id_periods":3.22685000131076e+14, 935 "price":1.8, 936 "sys.ID":322685000131089, 937 "sys.IsActive":true, 938 "sys.ParentID": 3.22685000131079e+14 939 } 940 } 941 }` 942 require.JSONEq(expected, out.resultRows[0][0][0][0].(string)) 943 } 944 945 func createEvent(require *require.Assertions, app istructs.IAppStructs, generator istructs.IIDGenerator, bld istructs.IRawEventBuilder) istructs.IPLogEvent { 946 rawEvent, buildErr := bld.BuildRawEvent() 947 var pLogEvent istructs.IPLogEvent 948 var err error 949 pLogEvent, err = app.Events().PutPlog(rawEvent, buildErr, generator) 950 require.NoError(err) 951 return pLogEvent 952 } 953 954 func saveEvent(require *require.Assertions, app istructs.IAppStructs, generator istructs.IIDGenerator, bld istructs.IRawEventBuilder) (pLogEvent istructs.IPLogEvent) { 955 pLogEvent = createEvent(require, app, generator, bld) 956 err := app.Records().Apply(pLogEvent) 957 require.NoError(err) 958 require.Equal("", pLogEvent.Error().ErrStr()) 959 return 960 } 961 962 func newPriceCUD(bld istructs.IRawEventBuilder, recordId istructs.RecordID, number int32, name string) { 963 rec := bld.CUDBuilder().Create(appdef.NewQName("test", "prices")) 964 rec.PutRecordID(appdef.SystemField_ID, recordId) 965 rec.PutString(test.priceNameIdent, name) 966 rec.PutInt32(test.priceNumberIdent, number) 967 rec.PutBool(appdef.SystemField_IsActive, true) 968 } 969 970 func newPeriodCUD(bld istructs.IRawEventBuilder, recordId istructs.RecordID, number int32, name string) { 971 rec := bld.CUDBuilder().Create(appdef.NewQName("test", "periods")) 972 rec.PutRecordID(appdef.SystemField_ID, recordId) 973 rec.PutString(test.periodNameIdent, name) 974 rec.PutInt32(test.periodNumberIdent, number) 975 rec.PutBool(appdef.SystemField_IsActive, true) 976 } 977 978 func newDepartmentCUD(bld istructs.IRawEventBuilder, recordId istructs.RecordID, number int32, name string) { 979 rec := bld.CUDBuilder().Create(appdef.NewQName("test", "departments")) 980 rec.PutRecordID(appdef.SystemField_ID, recordId) 981 rec.PutString(test.depNameIdent, name) 982 rec.PutInt32(test.depNumberIdent, number) 983 rec.PutBool(appdef.SystemField_IsActive, true) 984 } 985 986 func newArticleCUD(bld istructs.IRawEventBuilder, articleRecordId, department istructs.RecordID, number int32, name string) { 987 rec := bld.CUDBuilder().Create(appdef.NewQName("test", "articles")) 988 rec.PutRecordID(appdef.SystemField_ID, articleRecordId) 989 rec.PutString(test.articleNameIdent, name) 990 rec.PutInt32(test.articleNumberIdent, number) 991 rec.PutRecordID(test.articleDeptIdent, department) 992 rec.PutBool(appdef.SystemField_IsActive, true) 993 } 994 995 func updateArticleCUD(bld istructs.IRawEventBuilder, app istructs.IAppStructs, articleRecordId istructs.RecordID, number int32, name string) { 996 rec, err := app.Records().Get(test.workspace, false, articleRecordId) 997 if err != nil { 998 panic(err) 999 } 1000 if rec.QName() == appdef.NullQName { 1001 panic(fmt.Sprintf("Article %d not found", articleRecordId)) 1002 } 1003 writer := bld.CUDBuilder().Update(rec) 1004 writer.PutString(test.articleNameIdent, name) 1005 writer.PutInt32(test.articleNumberIdent, number) 1006 } 1007 1008 func newArPriceCUD(bld istructs.IRawEventBuilder, articleRecordId, articlePriceRecordId istructs.RecordID, idPrice istructs.RecordID, price float32) { 1009 rec := bld.CUDBuilder().Create(appdef.NewQName("test", "article_prices")) 1010 rec.PutRecordID(appdef.SystemField_ID, articlePriceRecordId) 1011 rec.PutRecordID(appdef.SystemField_ParentID, articleRecordId) 1012 rec.PutString(appdef.SystemField_Container, "article_prices") 1013 rec.PutRecordID(test.articlePricesPriceIdIdent, idPrice) 1014 rec.PutFloat32(test.articlePricesPriceIdent, price) 1015 rec.PutBool(appdef.SystemField_IsActive, true) 1016 } 1017 1018 func updateArPriceCUD(bld istructs.IRawEventBuilder, app istructs.IAppStructs, articlePriceRecordId istructs.RecordID, idPrice istructs.RecordID, price float32) { 1019 rec, err := app.Records().Get(test.workspace, true, articlePriceRecordId) 1020 if err != nil { 1021 panic(err) 1022 } 1023 if rec.QName() == appdef.NullQName { 1024 panic(fmt.Sprintf("Article price %d not found", articlePriceRecordId)) 1025 } 1026 writer := bld.CUDBuilder().Update(rec) 1027 writer.PutRecordID(test.articlePricesPriceIdIdent, idPrice) 1028 writer.PutFloat32(test.articlePricesPriceIdent, price) 1029 } 1030 1031 func newArPriceExceptionCUD(bld istructs.IRawEventBuilder, articlePriceRecordId, articlePriceExceptionRecordId, period istructs.RecordID, price float32) { 1032 rec := bld.CUDBuilder().Create(appdef.NewQName("test", "article_price_exceptions")) 1033 rec.PutRecordID(appdef.SystemField_ID, articlePriceExceptionRecordId) 1034 rec.PutRecordID(appdef.SystemField_ParentID, articlePriceRecordId) 1035 rec.PutString(appdef.SystemField_Container, "article_price_exceptions") 1036 rec.PutRecordID(test.articlePriceExceptionsPeriodIdIdent, period) 1037 rec.PutFloat32(test.articlePriceExceptionsPriceIdent, price) 1038 rec.PutBool(appdef.SystemField_IsActive, true) 1039 } 1040 1041 func updateArPriceExceptionCUD(bld istructs.IRawEventBuilder, app istructs.IAppStructs, articlePriceExceptionRecordId, idPeriod istructs.RecordID, price float32) { 1042 rec, err := app.Records().Get(test.workspace, true, articlePriceExceptionRecordId) 1043 if err != nil { 1044 panic(err) 1045 } 1046 if rec.QName() == appdef.NullQName { 1047 panic(fmt.Sprintf("Article price exception %d not found", articlePriceExceptionRecordId)) 1048 } 1049 1050 writer := bld.CUDBuilder().Update(rec) 1051 writer.PutRecordID(test.articlePriceExceptionsPeriodIdIdent, idPeriod) 1052 writer.PutFloat32(test.articlePriceExceptionsPriceIdent, price) 1053 } 1054 func insertPrices(require *require.Assertions, app istructs.IAppStructs, idGen *idsGeneratorType) (normalPrice, happyHourPrice istructs.RecordID, event istructs.IPLogEvent) { 1055 event = saveEvent(require, app, idGen, newModify(app, idGen, func(event istructs.IRawEventBuilder) { 1056 newPriceCUD(event, 51, 1, "Normal Price") 1057 newPriceCUD(event, 52, 2, "Happy Hour Price") 1058 })) 1059 return idGen.idmap[51], idGen.idmap[52], event 1060 } 1061 1062 func insertPeriods(require *require.Assertions, app istructs.IAppStructs, idGen *idsGeneratorType) (holiday, newYear istructs.RecordID, event istructs.IPLogEvent) { 1063 event = saveEvent(require, app, idGen, newModify(app, idGen, func(event istructs.IRawEventBuilder) { 1064 newPeriodCUD(event, 71, 1, "Holiday") 1065 newPeriodCUD(event, 72, 2, "New Year") 1066 })) 1067 return idGen.idmap[71], idGen.idmap[72], event 1068 } 1069 1070 func insertDepartments(require *require.Assertions, app istructs.IAppStructs, idGen *idsGeneratorType) (coldDrinks istructs.RecordID, event istructs.IPLogEvent) { 1071 event = saveEvent(require, app, idGen, newModify(app, idGen, func(event istructs.IRawEventBuilder) { 1072 newDepartmentCUD(event, 61, 1, "Cold Drinks") 1073 newDepartmentCUD(event, 62, 2, "Hot Drinks") 1074 })) 1075 coldDrinks = idGen.idmap[61] 1076 return 1077 } 1078 1079 type eventCallback func(event istructs.IRawEventBuilder) 1080 1081 func newModify(app istructs.IAppStructs, gen *idsGeneratorType, cb eventCallback) istructs.IRawEventBuilder { 1082 newOffset := gen.nextOffset() 1083 builder := app.Events().GetSyncRawEventBuilder( 1084 istructs.SyncRawEventBuilderParams{ 1085 GenericRawEventBuilderParams: istructs.GenericRawEventBuilderParams{ 1086 HandlingPartition: test.partition, 1087 Workspace: test.workspace, 1088 QName: appdef.NewQName("test", "modify"), 1089 PLogOffset: newOffset, 1090 WLogOffset: newOffset, 1091 }, 1092 }) 1093 cb(builder) 1094 return builder 1095 } 1096 1097 func Test_Idempotency(t *testing.T) { 1098 require := require.New(t) 1099 1100 appParts, appStructs, cleanup := deployTestApp(t) 1101 defer cleanup() 1102 1103 // create command processor 1104 processor := testProcessor(appParts) 1105 1106 // ID and Offset generators 1107 idGen := newIdsGenerator() 1108 1109 coldDrinks, _ := insertDepartments(require, appStructs, &idGen) 1110 1111 // CUDs: Insert coca-cola 1112 event1 := createEvent(require, appStructs, &idGen, newModify(appStructs, &idGen, func(event istructs.IRawEventBuilder) { 1113 newArticleCUD(event, 1, coldDrinks, test.cocaColaNumber, "Coca-cola") 1114 })) 1115 require.NoError(appStructs.Records().Apply(event1)) 1116 cocaColaDocID = idGen.idmap[1] 1117 require.NoError(processor.SendSync(event1)) 1118 1119 // CUDs: modify coca-cola number and normal price 1120 event2 := createEvent(require, appStructs, &idGen, newModify(appStructs, &idGen, func(event istructs.IRawEventBuilder) { 1121 updateArticleCUD(event, appStructs, cocaColaDocID, test.cocaColaNumber2, "Coca-cola") 1122 })) 1123 require.NoError(appStructs.Records().Apply(event2)) 1124 require.NoError(processor.SendSync(event2)) 1125 1126 // simulate sending event with the same offset 1127 idGen.decOffset() 1128 event2copy := createEvent(require, appStructs, &idGen, newModify(appStructs, &idGen, func(event istructs.IRawEventBuilder) { 1129 updateArticleCUD(event, appStructs, cocaColaDocID, test.cocaColaNumber, "Coca-cola") 1130 })) 1131 require.NoError(appStructs.Records().Apply(event2copy)) 1132 require.NoError(processor.SendSync(event2copy)) 1133 1134 // Check expected projection values 1135 { // coca-cola 1136 requireArticle(require, "Coca-cola", test.cocaColaNumber2, appStructs, cocaColaDocID) 1137 } 1138 1139 }