diff --git a/ees/ees.go b/ees/ees.go index ed9facb01..2d7851206 100644 --- a/ees/ees.go +++ b/ees/ees.go @@ -156,13 +156,15 @@ func (eeS *EventExporterS) V1ProcessEvent(ctx *context.Context, cgrEv *engine.CG } } + clonedCgrEv := cgrEv.CGREvent.Clone() + clonedCgrEv.APIOpts[utils.MetaExporterID] = eeCfg.ID if eeCfg.Flags.GetBool(utils.MetaAttributes) { if err = eeS.attrSProcessEvent( - cgrEv.CGREvent, + clonedCgrEv, eeCfg.AttributeSIDs, utils.FirstNonEmpty( eeCfg.AttributeSCtx, - utils.IfaceAsString(cgrEv.APIOpts[utils.OptsContext]), + utils.IfaceAsString(clonedCgrEv.APIOpts[utils.OptsContext]), utils.MetaEEs)); err != nil { return } @@ -204,7 +206,7 @@ func (eeS *EventExporterS) V1ProcessEvent(ctx *context.Context, cgrEv *engine.CG utils.EEs, ee.Cfg().ID)) } go func(evict, sync bool, ee EventExporter) { - if err := exportEventWithExporter(ee, cgrEv.CGREvent, evict, eeS.cfg, eeS.filterS); err != nil { + if err := exportEventWithExporter(ee, clonedCgrEv, evict, eeS.cfg, eeS.filterS); err != nil { withErr = true } if sync { diff --git a/general_tests/ees_it_test.go b/general_tests/ees_it_test.go new file mode 100644 index 000000000..369f72be6 --- /dev/null +++ b/general_tests/ees_it_test.go @@ -0,0 +1,223 @@ +//go:build integration +// +build integration + +/* +Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments +Copyright (C) ITsysCOM GmbH + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ +package general_tests + +import ( + "os" + "testing" + "time" + + "github.com/cgrates/birpc/context" + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" +) + +// TestEEsExportEventChanges tests if the event that's about to be exported can be changed from one exporter to +// another through AttributeS. Additionally (unrelated to the case presented in the previous sentence), it also +// checks that *opts.exporterID field is being set correctly. +// +// The test steps are as follows: +// 1. Configure inside ees two event exporters. First one has the *attribute flag, while the second doesn't. +// 2. Set an Attribute without filter that changes the request type to *prepaid. +// 3. Send an event with CGRID and RequestType *rated in a request to EeSv1.ProcessEvent. +// 4. Verify that first export has RequestType *prepaid, while the second one has *rated. +func TestEEsExportEventChanges(t *testing.T) { + switch *dbType { + case utils.MetaInternal: + case utils.MetaMySQL, utils.MetaMongo, utils.MetaPostgres: + t.SkipNow() + default: + t.Fatal("unsupported dbtype value") + } + + content := `{ + +"data_db": { + "db_type": "*internal" +}, + +"stor_db": { + "db_type": "*internal" +}, + +"apiers": { + "enabled": true +}, + +"attributes": { + "enabled": true +}, + +"ees": { + "enabled": true, + "attributes_conns":["*localhost"], + "exporters": [ + { + "id": "exporter1", + "type": "*virt", + "export_path": "/tmp/exports", + "flags": ["*attributes"], + "attempts": 1, + "field_separator": ",", + "synchronous": true, + "fields":[ + {"tag": "CGRID", "path": "*uch.CGRID1", "type": "*variable", "value": "~*req.CGRID"}, + {"tag": "RequestType", "path": "*uch.RequestType1", "type": "*variable", "value": "~*req.RequestType"}, + {"tag": "ExporterID", "path": "*uch.ExporterID1", "type": "*variable", "value": "~*opts.*exporterID"} + ], + }, + { + "id": "exporter2", + "type": "*virt", + "export_path": "/tmp/exports", + "flags": [], + "attempts": 1, + "field_separator": ",", + "synchronous": true, + "fields":[ + {"tag": "CGRID", "path": "*uch.CGRID2", "type": "*variable", "value": "~*req.CGRID"}, + {"tag": "RequestType", "path": "*uch.RequestType2", "type": "*variable", "value": "~*req.RequestType"}, + {"tag": "ExporterID", "path": "*uch.ExporterID2", "type": "*variable", "value": "~*opts.*exporterID"} + ] + } + ] +} + +}` + + exportPath := "/tmp/exports" + err = os.MkdirAll(exportPath, 0755) + if err != nil { + t.Fatalf("could not create folder %s: %v", exportPath, err) + } + defer os.RemoveAll(exportPath) + + testEnv := TestEnvironment{ + Name: "TestEEsExportEventChanges", + ConfigJSON: content, + } + client, _, shutdown, err := testEnv.Setup(t, *waitRater) + if err != nil { + t.Fatal(err) + } + + defer shutdown() + + t.Run("SetAttributeProfile", func(t *testing.T) { + attrPrf := &engine.AttributeProfileWithAPIOpts{ + AttributeProfile: &engine.AttributeProfile{ + Tenant: "cgrates.org", + ID: "ATTR_TEST", + Attributes: []*engine.Attribute{ + { + Path: "*req.RequestType", + Value: config.RSRParsers{ + &config.RSRParser{ + Rules: utils.MetaPrepaid, + }, + }, + }, + }, + Blocker: false, + Weight: 10, + }, + } + attrPrf.Compile() + var result string + if err := client.Call(context.Background(), utils.APIerSv1SetAttributeProfile, attrPrf, &result); err != nil { + t.Error(err) + } + }) + + t.Run("ExportEvent", func(t *testing.T) { + eventToExport := &engine.CGREventWithEeIDs{ + CGREvent: &utils.CGREvent{ + Tenant: "cgrates.org", + ID: "voiceEvent", + Time: utils.TimePointer(time.Now()), + Event: map[string]any{ + utils.CGRID: "TEST", + utils.RequestType: utils.MetaRated, + }, + }, + } + + var reply map[string]map[string]any + if err := client.Call(context.Background(), utils.EeSv1ProcessEvent, eventToExport, &reply); err != nil { + t.Error(err) + } + + var requestTypeExport1 any + if err = client.Call(context.Background(), utils.CacheSv1GetItem, &utils.ArgsGetCacheItemWithAPIOpts{ + Tenant: "cgrates.org", + ArgsGetCacheItem: utils.ArgsGetCacheItem{ + CacheID: utils.CacheUCH, + ItemID: "RequestType1", + }, + }, &requestTypeExport1); err != nil { + t.Error(err) + } else if requestTypeExport1 != utils.MetaPrepaid { + t.Errorf("expected %v, received %v", utils.MetaPrepaid, requestTypeExport1) + } + + var requestTypeExport2 any + if err = client.Call(context.Background(), utils.CacheSv1GetItem, &utils.ArgsGetCacheItemWithAPIOpts{ + Tenant: "cgrates.org", + ArgsGetCacheItem: utils.ArgsGetCacheItem{ + CacheID: utils.CacheUCH, + ItemID: "RequestType2", + }, + }, &requestTypeExport2); err != nil { + t.Error(err) + } else if requestTypeExport2 != utils.MetaRated { + t.Errorf("expected %v, received %v", utils.MetaRated, requestTypeExport2) + } + }) + + t.Run("CheckExporterIDs", func(t *testing.T) { + var exporterID1 any + if err = client.Call(context.Background(), utils.CacheSv1GetItem, &utils.ArgsGetCacheItemWithAPIOpts{ + Tenant: "cgrates.org", + ArgsGetCacheItem: utils.ArgsGetCacheItem{ + CacheID: utils.CacheUCH, + ItemID: "ExporterID1", + }, + }, &exporterID1); err != nil { + t.Error(err) + } else if exporterID1 != "exporter1" { + t.Errorf("expected %v, received %v", "exporter1", exporterID1) + } + + var exporterID2 any + if err = client.Call(context.Background(), utils.CacheSv1GetItem, &utils.ArgsGetCacheItemWithAPIOpts{ + Tenant: "cgrates.org", + ArgsGetCacheItem: utils.ArgsGetCacheItem{ + CacheID: utils.CacheUCH, + ItemID: "ExporterID2", + }, + }, &exporterID2); err != nil { + t.Error(err) + } else if exporterID2 != "exporter2" { + t.Errorf("expected %v, received %v", "exporter2", exporterID2) + } + }) +} diff --git a/utils/consts.go b/utils/consts.go index fc45c6e64..d91547191 100644 --- a/utils/consts.go +++ b/utils/consts.go @@ -726,7 +726,7 @@ const ( MetaStore = "*store" MetaClear = "*clear" MetaExport = "*export" - MetaExportID = "*export_id" + MetaExporterID = "*exporterID" MetaTimeNow = "*time_now" MetaFirstEventATime = "*first_event_atime" MetaLastEventATime = "*last_event_atime"