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) }