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  }