diff --git a/apier/v1/tpaccountprofiles.go b/apier/v1/tpaccountprofiles.go
new file mode 100644
index 000000000..b9d487843
--- /dev/null
+++ b/apier/v1/tpaccountprofiles.go
@@ -0,0 +1,95 @@
+/*
+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 v1
+
+import (
+ "github.com/cgrates/cgrates/utils"
+)
+
+// SetTPAccountProfile creates a new TPAccountProfile within a tariff plan
+func (apierSv1 *APIerSv1) SetTPAccountProfile(attrs *utils.TPAccountProfile, reply *string) error {
+ if missing := utils.MissingStructFields(attrs, []string{utils.TPid, utils.ID}); len(missing) != 0 {
+ return utils.NewErrMandatoryIeMissing(missing...)
+ }
+ if attrs.Tenant == utils.EmptyString {
+ attrs.Tenant = apierSv1.Config.GeneralCfg().DefaultTenant
+ }
+ if err := apierSv1.StorDb.SetTPAccountProfiles([]*utils.TPAccountProfile{attrs}); err != nil {
+ return utils.NewErrServerError(err)
+ }
+ *reply = utils.OK
+ return nil
+}
+
+// GetTPAccountProfile queries specific TPAccountProfile on tariff plan
+func (apierSv1 *APIerSv1) GetTPAccountProfile(attr *utils.TPTntID, reply *utils.TPAccountProfile) error {
+ if missing := utils.MissingStructFields(attr, []string{utils.TPid, utils.ID}); len(missing) != 0 { //Params missing
+ return utils.NewErrMandatoryIeMissing(missing...)
+ }
+ if attr.Tenant == utils.EmptyString {
+ attr.Tenant = apierSv1.Config.GeneralCfg().DefaultTenant
+ }
+ spp, err := apierSv1.StorDb.GetTPAccountProfiles(attr.TPid, attr.Tenant, attr.ID)
+ if err != nil {
+ if err.Error() != utils.ErrNotFound.Error() {
+ err = utils.NewErrServerError(err)
+ }
+ return err
+ }
+ *reply = *spp[0]
+ return nil
+}
+
+type AttrGetTPAccountProfileIDs struct {
+ TPid string // Tariff plan id
+ utils.PaginatorWithSearch
+}
+
+// GetTPRouteProfileIDs queries TPAccountProfiles identities on specific tariff plan.
+func (apierSv1 *APIerSv1) GetTPAccountProfileIDs(attrs *AttrGetTPAccountProfileIDs, reply *[]string) error {
+ if missing := utils.MissingStructFields(attrs, []string{utils.TPid}); len(missing) != 0 { //Params missing
+ return utils.NewErrMandatoryIeMissing(missing...)
+ }
+ ids, err := apierSv1.StorDb.GetTpTableIds(attrs.TPid, utils.TBLTPAccountProfiles,
+ utils.TPDistinctIds{"tenant", "id"}, nil, &attrs.PaginatorWithSearch)
+ if err != nil {
+ if err.Error() != utils.ErrNotFound.Error() {
+ err = utils.NewErrServerError(err)
+ }
+ return err
+ }
+ *reply = ids
+ return nil
+}
+
+// RemoveTPAccountProfile removes specific TPAccountProfile on Tariff plan
+func (apierSv1 *APIerSv1) RemoveTPAccountProfile(attrs *utils.TPTntID, reply *string) error {
+ if missing := utils.MissingStructFields(attrs, []string{utils.TPid, utils.ID}); len(missing) != 0 { //Params missing
+ return utils.NewErrMandatoryIeMissing(missing...)
+ }
+ if attrs.Tenant == utils.EmptyString {
+ attrs.Tenant = apierSv1.Config.GeneralCfg().DefaultTenant
+ }
+ if err := apierSv1.StorDb.RemTpData(utils.TBLTPAccountProfiles, attrs.TPid,
+ map[string]string{utils.TenantCfg: attrs.Tenant, utils.IDCfg: attrs.ID}); err != nil {
+ return utils.NewErrServerError(err)
+ }
+ *reply = utils.OK
+ return nil
+}
diff --git a/apier/v1/tpaccountprofiles_it_test.go b/apier/v1/tpaccountprofiles_it_test.go
new file mode 100644
index 000000000..99f84b9c5
--- /dev/null
+++ b/apier/v1/tpaccountprofiles_it_test.go
@@ -0,0 +1,249 @@
+// +build offline
+
+/*
+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 v1
+
+import (
+ "net/rpc"
+ "net/rpc/jsonrpc"
+ "path"
+ "reflect"
+ "sort"
+ "strings"
+ "testing"
+
+ "github.com/cgrates/cgrates/config"
+ "github.com/cgrates/cgrates/engine"
+ "github.com/cgrates/cgrates/utils"
+)
+
+var (
+ tpAcctPrfCfgPath string
+ tpAcctPrfCfg *config.CGRConfig
+ tpAcctPrfRPC *rpc.Client
+ tpAcctPrfDataDir = "/usr/share/cgrates"
+ tpAcctPrf *utils.TPAccountProfile
+ tpAcctPrfDelay int
+ tpAcctPrfConfigDIR string //run tests for specific configuration
+)
+
+var sTestsTPAcctPrf = []func(t *testing.T){
+ testTPAcctPrfInitCfg,
+ testTPAcctPrfResetStorDb,
+ testTPAcctPrfStartEngine,
+ testTPAcctPrfRPCConn,
+ testTPAcctPrfGetTPAcctPrfBeforeSet,
+ testTPAcctPrfSetTPAcctPrf,
+ testTPAcctPrfGetTPAcctPrfAfterSet,
+ testTPAcctPrfGetTPAcctPrfIDs,
+ testTPAcctPrfUpdateTPAcctBal,
+ testTPAcctPrfGetTPAcctBalAfterUpdate,
+ testTPAcctPrfRemTPAcctPrf,
+ testTPAcctPrfGetTPAcctPrfAfterRemove,
+ testTPAcctPrfKillEngine,
+}
+
+//Test start here
+func TestTPAcctPrfIT(t *testing.T) {
+ switch *dbType {
+ case utils.MetaInternal:
+ tpAcctPrfConfigDIR = "tutinternal"
+ case utils.MetaMySQL:
+ tpAcctPrfConfigDIR = "tutmysql"
+ case utils.MetaMongo:
+ tpAcctPrfConfigDIR = "tutmongo"
+ case utils.MetaPostgres:
+ t.SkipNow()
+ default:
+ t.Fatal("Unknown Database type")
+ }
+ for _, stest := range sTestsTPAcctPrf {
+ t.Run(tpAcctPrfConfigDIR, stest)
+ }
+}
+
+func testTPAcctPrfInitCfg(t *testing.T) {
+ var err error
+ tpAcctPrfCfgPath = path.Join(tpAcctPrfDataDir, "conf", "samples", tpAcctPrfConfigDIR)
+ tpAcctPrfCfg, err = config.NewCGRConfigFromPath(tpAcctPrfCfgPath)
+ if err != nil {
+ t.Error(err)
+ }
+ tpAcctPrfDelay = 1000
+}
+
+// Wipe out the cdr database
+func testTPAcctPrfResetStorDb(t *testing.T) {
+ if err := engine.InitStorDb(tpAcctPrfCfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Start CGR Engine
+func testTPAcctPrfStartEngine(t *testing.T) {
+ if _, err := engine.StopStartEngine(tpAcctPrfCfgPath, tpAcctPrfDelay); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Connect rpc client to rater
+func testTPAcctPrfRPCConn(t *testing.T) {
+ var err error
+ tpAcctPrfRPC, err = jsonrpc.Dial(utils.TCP, tpAcctPrfCfg.ListenCfg().RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func testTPAcctPrfGetTPAcctPrfBeforeSet(t *testing.T) {
+ var reply *utils.TPAccountProfile
+ if err := tpAcctPrfRPC.Call(utils.APIerSv1GetTPAccountProfile,
+ &utils.TPTntID{TPid: "TP1", Tenant: "cgrates.org", ID: "Attr1"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ t.Error(err)
+ }
+}
+
+func testTPAcctPrfSetTPAcctPrf(t *testing.T) {
+ tpAcctPrf = &utils.TPAccountProfile{
+ TPid: "TP1",
+ Tenant: "cgrates.org",
+ ID: "1001",
+ Weight: 20,
+ Balances: []*utils.TPAccountBalance{
+ &utils.TPAccountBalance{
+ ID: "MonetaryBalance",
+ FilterIDs: []string{},
+ Weight: 10,
+ Type: utils.MONETARY,
+ Value: 14,
+ },
+ },
+ ThresholdIDs: []string{utils.META_NONE},
+ }
+ sort.Strings(tpAcctPrf.FilterIDs)
+ var result string
+ if err := tpAcctPrfRPC.Call(utils.APIerSv1SetTPAccountProfile, tpAcctPrf, &result); err != nil {
+ t.Error(err)
+ } else if result != utils.OK {
+ t.Error("Unexpected reply returned", result)
+ }
+}
+
+func testTPAcctPrfGetTPAcctPrfAfterSet(t *testing.T) {
+ var reply *utils.TPAccountProfile
+ if err := tpAcctPrfRPC.Call(utils.APIerSv1GetTPAccountProfile,
+ &utils.TPTntID{TPid: "TP1", Tenant: "cgrates.org", ID: "1001"}, &reply); err != nil {
+ t.Fatal(err)
+ }
+ sort.Strings(reply.FilterIDs)
+ if !reflect.DeepEqual(tpAcctPrf, reply) {
+ t.Errorf("Expecting : %+v, received: %+v", utils.ToJSON(tpAcctPrf), utils.ToJSON(reply))
+ }
+}
+
+func testTPAcctPrfGetTPAcctPrfIDs(t *testing.T) {
+ var result []string
+ expectedTPID := []string{"cgrates.org:1001"}
+ if err := tpAcctPrfRPC.Call(utils.APIerSv1GetTPAccountProfileIDs,
+ &AttrGetTPAccountProfileIDs{TPid: "TP1"}, &result); err != nil {
+ t.Error(err)
+ } else if !reflect.DeepEqual(expectedTPID, result) {
+ t.Errorf("Expecting: %+v, received: %+v", expectedTPID, result)
+ }
+}
+
+func testTPAcctPrfUpdateTPAcctBal(t *testing.T) {
+ tpAcctPrf.Balances = []*utils.TPAccountBalance{
+ {
+ ID: "MonetaryBalance2",
+ FilterIDs: []string{},
+ Weight: 12,
+ Type: utils.MONETARY,
+ Value: 16,
+ },
+ }
+ var result string
+ if err := tpAcctPrfRPC.Call(utils.APIerSv1SetTPAccountProfile, tpAcctPrf, &result); err != nil {
+ t.Error(err)
+ } else if result != utils.OK {
+ t.Error("Unexpected reply returned", result)
+ }
+}
+
+func testTPAcctPrfGetTPAcctBalAfterUpdate(t *testing.T) {
+ var reply *utils.TPAccountProfile
+ revTPAcctPrf := &utils.TPAccountProfile{
+ TPid: "TP1",
+ Tenant: "cgrates.org",
+ ID: "1001",
+ Weight: 20,
+ Balances: []*utils.TPAccountBalance{
+ &utils.TPAccountBalance{
+ ID: "MonetaryBalance2",
+ FilterIDs: []string{},
+ Weight: 12,
+ Type: utils.MONETARY,
+ Value: 16,
+ },
+ },
+ ThresholdIDs: []string{utils.META_NONE},
+ }
+ sort.Strings(revTPAcctPrf.FilterIDs)
+ sort.Slice(revTPAcctPrf.Balances, func(i, j int) bool {
+ return strings.Compare(revTPAcctPrf.Balances[i].Type, revTPAcctPrf.Balances[j].Type) == -1
+ })
+ if err := tpAcctPrfRPC.Call(utils.APIerSv1GetTPAccountProfile,
+ &utils.TPTntID{TPid: "TP1", Tenant: "cgrates.org", ID: "1001"}, &reply); err != nil {
+ t.Fatal(err)
+ }
+ sort.Strings(reply.FilterIDs)
+ sort.Slice(reply.Balances, func(i, j int) bool {
+ return strings.Compare(reply.Balances[i].Type, reply.Balances[j].Type) == -1
+ })
+ if !reflect.DeepEqual(tpAcctPrf, reply) && !reflect.DeepEqual(revTPAcctPrf, reply) {
+ t.Errorf("Expecting : %+v, \n received: %+v", utils.ToJSON(tpAcctPrf), utils.ToJSON(reply))
+ }
+}
+
+func testTPAcctPrfRemTPAcctPrf(t *testing.T) {
+ var resp string
+ if err := tpAcctPrfRPC.Call(utils.APIerSv1RemoveTPAccountProfile,
+ &utils.TPTntID{TPid: "TP1", Tenant: "cgrates.org", ID: "1001"},
+ &resp); err != nil {
+ t.Error(err)
+ } else if resp != utils.OK {
+ t.Error("Unexpected reply returned", resp)
+ }
+}
+
+func testTPAcctPrfGetTPAcctPrfAfterRemove(t *testing.T) {
+ var reply *utils.TPAccountProfile
+ if err := tpAcctPrfRPC.Call(utils.APIerSv1GetTPAccountProfile,
+ &utils.TPTntID{TPid: "TP1", Tenant: "cgrates.org", ID: "1001"},
+ &reply); err == nil || err.Error() != utils.ErrNotFound.Error() {
+ t.Error(err)
+ }
+}
+
+func testTPAcctPrfKillEngine(t *testing.T) {
+ if err := engine.KillEngine(tpAcctPrfDelay); err != nil {
+ t.Error(err)
+ }
+}
diff --git a/utils/consts.go b/utils/consts.go
index f65ba6233..6b8dbec40 100755
--- a/utils/consts.go
+++ b/utils/consts.go
@@ -1470,6 +1470,10 @@ const (
APIerSv1SetTPRateProfile = "APIerSv1.SetTPRateProfile"
APIerSv1GetTPRateProfileIds = "APIerSv1.GetTPRateProfileIds"
APIerSv1RemoveTPRateProfile = "APIerSv1.RemoveTPRateProfile"
+ APIerSv1GetTPAccountProfile = "APIerSv1.GetTPAccountProfile"
+ APIerSv1SetTPAccountProfile = "APIerSv1.SetTPAccountProfile"
+ APIerSv1RemoveTPAccountProfile = "APIerSv1.RemoveTPAccountProfile"
+ APIerSv1GetTPAccountProfileIDs = "APIerSv1.GetTPAccountProfileIDs"
)
// APIerSv1 TP APIs