From 5d8de80a181971a61bb8de3cef894fafc2700b86 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 11 Dec 2024 18:58:50 +0300 Subject: [PATCH 01/71] Write the error in the response --- internal/http/server.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/internal/http/server.go b/internal/http/server.go index 3ea0159..a6239c4 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -17,8 +17,7 @@ var ( type DefaultRequestParser struct { } - -func(rp *DefaultRequestParser) GetSessionId(rq any) (string, error) { +func (rp *DefaultRequestParser) GetSessionId(rq any) (string, error) { rqv, ok := rq.(*http.Request) if !ok { return "", handlers.ErrInvalidRequest @@ -30,7 +29,7 @@ func(rp *DefaultRequestParser) GetSessionId(rq any) (string, error) { return v, nil } -func(rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) { +func (rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) { rqv, ok := rq.(*http.Request) if !ok { return nil, handlers.ErrInvalidRequest @@ -53,25 +52,24 @@ func ToSessionHandler(h handlers.RequestHandler) *SessionHandler { } } -func(f *SessionHandler) writeError(w http.ResponseWriter, code int, err error) { +func (f *SessionHandler) writeError(w http.ResponseWriter, code int, err error) { s := err.Error() w.Header().Set("Content-Length", strconv.Itoa(len(s))) w.WriteHeader(code) - _, err = w.Write([]byte{}) + _, err = w.Write([]byte(s)) if err != nil { logg.Errorf("error writing error!!", "err", err, "olderr", s) w.WriteHeader(500) } - return } -func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { +func (f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { var code int var err error var perr error rqs := handlers.RequestSession{ - Ctx: req.Context(), + Ctx: req.Context(), Writer: w, } From 3129e8210e848185701c328f1bf053e375d9b9ae Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 11 Dec 2024 19:25:38 +0300 Subject: [PATCH 02/71] Capitalize the transfer statement details --- common/transfer_statements.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/transfer_statements.go b/common/transfer_statements.go index 4e6f66b..243ef4c 100644 --- a/common/transfer_statements.go +++ b/common/transfer_statements.go @@ -84,18 +84,18 @@ func GetTransferData(ctx context.Context, db storage.PrefixDb, publicKey string, // Adjust for 0-based indexing i := index - 1 - transactionType := "received" - party := fmt.Sprintf("from: %s", strings.TrimSpace(senders[i])) + transactionType := "Received" + party := fmt.Sprintf("From: %s", strings.TrimSpace(senders[i])) if strings.TrimSpace(senders[i]) == publicKey { - transactionType = "sent" - party = fmt.Sprintf("to: %s", strings.TrimSpace(recipients[i])) + transactionType = "Sent" + party = fmt.Sprintf("To: %s", strings.TrimSpace(recipients[i])) } formattedDate := formatDate(strings.TrimSpace(dates[i])) // Build the full transaction detail detail := fmt.Sprintf( - "%s %s %s\n%s\ncontract address: %s\ntxhash: %s\ndate: %s", + "%s %s %s\n%s\nContract address: %s\nTxhash: %s\nDate: %s", transactionType, strings.TrimSpace(values[i]), strings.TrimSpace(syms[i]), From b1e4b63c6a3bd25998311aca149e08fa49ab7753 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 11 Dec 2024 19:28:53 +0300 Subject: [PATCH 03/71] Add a space after the colon for vouchers --- common/vouchers.go | 4 ++-- common/vouchers_test.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/vouchers.go b/common/vouchers.go index 6cff91d..790e3ca 100644 --- a/common/vouchers.go +++ b/common/vouchers.go @@ -24,7 +24,7 @@ func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata { var symbols, balances, decimals, addresses []string for i, h := range holdings { - symbols = append(symbols, fmt.Sprintf("%d:%s", i+1, h.TokenSymbol)) + symbols = append(symbols, fmt.Sprintf("%d: %s", i+1, h.TokenSymbol)) // Scale down the balance scaledBalance := ScaleDownBalance(h.Balance, h.TokenDecimals) @@ -103,7 +103,7 @@ func MatchVoucher(input, symbols, balances, decimals, addresses string) (symbol, logg.Tracef("found", "symlist", symList, "syms", symbols, "input", input) for i, sym := range symList { - parts := strings.SplitN(sym, ":", 2) + parts := strings.SplitN(sym, ": ", 2) if input == parts[0] || strings.EqualFold(input, parts[1]) { symbol = parts[1] diff --git a/common/vouchers_test.go b/common/vouchers_test.go index ba6cd60..2ebb4a0 100644 --- a/common/vouchers_test.go +++ b/common/vouchers_test.go @@ -34,7 +34,7 @@ func InitializeTestDb(t *testing.T) (context.Context, *UserDataStore) { } func TestMatchVoucher(t *testing.T) { - symbols := "1:SRF\n2:MILO" + symbols := "1: SRF\n2: MILO" balances := "1:100\n2:200" decimals := "1:6\n2:4" addresses := "1:0xd4c288865Ce\n2:0x41c188d63Qa" @@ -65,7 +65,7 @@ func TestProcessVouchers(t *testing.T) { } expectedResult := VoucherMetadata{ - Symbols: "1:SRF\n2:MILO", + Symbols: "1: SRF\n2: MILO", Balances: "1:100\n2:20000", Decimals: "1:6\n2:4", Addresses: "1:0xd4c288865Ce\n2:0x41c188d63Qa", @@ -90,7 +90,7 @@ func TestGetVoucherData(t *testing.T) { // Test voucher data mockData := map[DataTyp][]byte{ - DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"), + DATA_VOUCHER_SYMBOLS: []byte("1: SRF\n2: MILO"), DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"), DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"), DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"), From 6f3b30e2fe22ef20df038d5e0e99b0400a613bbb Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 11 Dec 2024 19:31:17 +0300 Subject: [PATCH 04/71] Capitalize statement details and add a space after the colon --- internal/handlers/ussd/menuhandler.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index c5356d0..f9d8728 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -1895,12 +1895,12 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input [] value := strings.TrimSpace(values[i]) date := strings.Split(strings.TrimSpace(dates[i]), " ")[0] - status := "received" + status := "Received" if sender == string(publicKey) { - status = "sent" + status = "Sent" } - formattedTransactions = append(formattedTransactions, fmt.Sprintf("%d:%s %s %s %s", i+1, status, value, sym, date)) + formattedTransactions = append(formattedTransactions, fmt.Sprintf("%d: %s %s %s %s", i+1, status, value, sym, date)) } res.Content = strings.Join(formattedTransactions, "\n") From 1a4ee0d3e1f322f296321f124b490ec14824a53c Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 11 Dec 2024 19:32:41 +0300 Subject: [PATCH 05/71] updated the description of the GetTransactionsList function --- internal/handlers/ussd/menuhandler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index f9d8728..5723bad 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -1846,7 +1846,7 @@ func (h *Handlers) CheckTransactions(ctx context.Context, sym string, input []by return res, nil } -// GetTransactionsList fetches the list of transactions and formats them +// GetTransactionsList reads the list of transactions from the db and formats them func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) From 2383e8ead3e9b97dc7d45f6667963a296b78edec Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 11 Dec 2024 19:35:04 +0300 Subject: [PATCH 06/71] updated failed menuhandler_test --- internal/handlers/ussd/menuhandler_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 25470e8..5cc3067 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -1961,7 +1961,7 @@ func TestCheckVouchers(t *testing.T) { {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, } - expectedSym := []byte("1:SRF\n2:MILO") + expectedSym := []byte("1: SRF\n2: MILO") mockAccountService.On("FetchVouchers", string(publicKey)).Return(mockVouchersResponse, nil) @@ -2024,7 +2024,7 @@ func TestViewVoucher(t *testing.T) { // Define mock voucher data mockData := map[common.DataTyp][]byte{ - common.DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"), + common.DATA_VOUCHER_SYMBOLS: []byte("1: SRF\n2: MILO"), common.DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"), common.DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"), common.DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"), From e05f8e72917fc013fab3494757d8b0777a71c7c2 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 11 Dec 2024 19:46:52 +0300 Subject: [PATCH 07/71] update to the latest go-vise changes --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d89f0f3..d6f620f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.grassecon.net/urdt/ussd go 1.23.0 require ( - git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a + git.defalsify.org/vise.git v0.2.1-0.20241209194740-26fc82a76ede github.com/alecthomas/assert/v2 v2.2.2 github.com/gofrs/uuid v4.4.0+incompatible github.com/grassrootseconomics/eth-custodial v1.3.0-beta diff --git a/go.sum b/go.sum index 5532c1c..e64d16f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a h1:LvGKktk0kUnuRN3nF9r15D8OoV0sFaMmQr52kGq2gtE= git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= +git.defalsify.org/vise.git v0.2.1-0.20241209194740-26fc82a76ede h1:BtooQZJDYEgeRKqv8RSxYSbW5jh0UIFJ6zRMEZZn0sc= +git.defalsify.org/vise.git v0.2.1-0.20241209194740-26fc82a76ede/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= From f8ea2daa736887e97733e24470ca9b4c7bdfe796 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 12 Dec 2024 19:55:01 +0300 Subject: [PATCH 08/71] initialize the restart state devtool --- devtools/restart_state/main.go | 110 +++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 devtools/restart_state/main.go diff --git a/devtools/restart_state/main.go b/devtools/restart_state/main.go new file mode 100644 index 0000000..136b38a --- /dev/null +++ b/devtools/restart_state/main.go @@ -0,0 +1,110 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "path" + + "git.defalsify.org/vise.git/engine" + "git.defalsify.org/vise.git/logging" + "git.defalsify.org/vise.git/resource" + + // "git.defalsify.org/vise.git/persist" + "git.grassecon.net/urdt/ussd/config" + "git.grassecon.net/urdt/ussd/initializers" + "git.grassecon.net/urdt/ussd/remote" + + "git.grassecon.net/urdt/ussd/internal/handlers" + "git.grassecon.net/urdt/ussd/internal/storage" +) + +var ( + logg = logging.NewVanilla() + scriptDir = path.Join("services", "registration") +) + +func init() { + initializers.LoadEnvVariables() +} + +func main() { + config.LoadConfig() + + var dbDir string + var sessionId string + var database string + var engineDebug bool + var size uint + + flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") + flag.StringVar(&database, "db", "gdbm", "database to be used") + flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") + flag.BoolVar(&engineDebug, "d", false, "use engine debug output") + flag.Parse() + + ctx := context.Background() + ctx = context.WithValue(ctx, "SessionId", sessionId) + ctx = context.WithValue(ctx, "Database", database) + + pfp := path.Join(scriptDir, "pp.csv") + + cfg := engine.Config{ + Root: "root", + SessionId: sessionId, + OutputSize: uint32(size), + FlagCount: uint32(128), + } + + resourceDir := scriptDir + menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir) + + err := menuStorageService.EnsureDbDir() + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + rs, err := menuStorageService.GetResource(ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + pe, err := menuStorageService.GetPersister(ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + userdatastore, err := menuStorageService.GetUserdataDb(ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + + // initialize the persister + + // get the state + + // restart the state + + // persist the state + + // exit + + st := pe.GetState() + + if st == nil { + logg.ErrorCtxf(ctx, "state fail in devtool", "state", st) + fmt.Errorf("cannot get state") + os.Exit(1) + } + + st.Restart() + + + os.Exit(1) +} From 0d7f7aaca139aed0a6d2cf0e19082bd847805870 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 12 Dec 2024 21:09:48 +0300 Subject: [PATCH 09/71] use latest commits from go-vise --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d6f620f..246c57d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.grassecon.net/urdt/ussd go 1.23.0 require ( - git.defalsify.org/vise.git v0.2.1-0.20241209194740-26fc82a76ede + git.defalsify.org/vise.git v0.2.1-0.20241212130612-18a95521a7cf github.com/alecthomas/assert/v2 v2.2.2 github.com/gofrs/uuid v4.4.0+incompatible github.com/grassrootseconomics/eth-custodial v1.3.0-beta diff --git a/go.sum b/go.sum index e64d16f..a21aa2f 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a h1:LvGKktk0kUnuR git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= git.defalsify.org/vise.git v0.2.1-0.20241209194740-26fc82a76ede h1:BtooQZJDYEgeRKqv8RSxYSbW5jh0UIFJ6zRMEZZn0sc= git.defalsify.org/vise.git v0.2.1-0.20241209194740-26fc82a76ede/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= +git.defalsify.org/vise.git v0.2.1-0.20241212130612-18a95521a7cf h1:K58RcCiBZSmfU7zwg4nCHzY5ikw+wyfFoDU+4SXkarU= +git.defalsify.org/vise.git v0.2.1-0.20241212130612-18a95521a7cf/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= From 6cc285d1e8e2184466cab608581cbc2be14be078 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 12 Dec 2024 21:12:25 +0300 Subject: [PATCH 10/71] add the custom separator to the menu --- cmd/africastalking/main.go | 9 ++++++--- cmd/async/main.go | 9 ++++++--- cmd/http/main.go | 9 ++++++--- cmd/main.go | 11 +++++++---- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index 7d1caa8..f57f8f4 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -115,6 +115,7 @@ func main() { var engineDebug bool var host string var port uint + var separator string flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") flag.StringVar(&database, "db", "gdbm", "database to be used") @@ -122,6 +123,7 @@ func main() { flag.UintVar(&size, "s", 160, "max size of output") flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host") flag.UintVar(&port, "p", initializers.GetEnvUint("PORT", 7123), "http port") + flag.StringVar(&separator, "sep", ": ", "custom separator for the menu") flag.Parse() logg.Infof("start command", "build", build, "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size) @@ -131,9 +133,10 @@ func main() { pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ - Root: "root", - OutputSize: uint32(size), - FlagCount: uint32(128), + Root: "root", + OutputSize: uint32(size), + FlagCount: uint32(128), + MenuSeparator: separator, } if engineDebug { diff --git a/cmd/async/main.go b/cmd/async/main.go index e4c94b0..1fdc532 100644 --- a/cmd/async/main.go +++ b/cmd/async/main.go @@ -53,6 +53,7 @@ func main() { var engineDebug bool var host string var port uint + var separator string flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") @@ -61,6 +62,7 @@ func main() { flag.UintVar(&size, "s", 160, "max size of output") flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host") flag.UintVar(&port, "p", initializers.GetEnvUint("PORT", 7123), "http port") + flag.StringVar(&separator, "sep", ": ", "custom separator for the menu") flag.Parse() logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size, "sessionId", sessionId) @@ -70,9 +72,10 @@ func main() { pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ - Root: "root", - OutputSize: uint32(size), - FlagCount: uint32(128), + Root: "root", + OutputSize: uint32(size), + FlagCount: uint32(128), + MenuSeparator: separator, } if engineDebug { diff --git a/cmd/http/main.go b/cmd/http/main.go index 96e2688..aa7d00a 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -42,6 +42,7 @@ func main() { var engineDebug bool var host string var port uint + var separator string flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") flag.StringVar(&database, "db", "gdbm", "database to be used") @@ -49,6 +50,7 @@ func main() { flag.UintVar(&size, "s", 160, "max size of output") flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host") flag.UintVar(&port, "p", initializers.GetEnvUint("PORT", 7123), "http port") + flag.StringVar(&separator, "sep", ": ", "custom separator for the menu") flag.Parse() logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size) @@ -58,9 +60,10 @@ func main() { pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ - Root: "root", - OutputSize: uint32(size), - FlagCount: uint32(128), + Root: "root", + OutputSize: uint32(size), + FlagCount: uint32(128), + MenuSeparator: separator, } if engineDebug { diff --git a/cmd/main.go b/cmd/main.go index 9599eb7..aec6a14 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -34,11 +34,13 @@ func main() { var sessionId string var database string var engineDebug bool + var separator string flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.StringVar(&database, "db", "gdbm", "database to be used") flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") flag.BoolVar(&engineDebug, "d", false, "use engine debug output") flag.UintVar(&size, "s", 160, "max size of output") + flag.StringVar(&separator, "sep", ": ", "custom separator for the menu") flag.Parse() logg.Infof("start command", "dbdir", dbDir, "outputsize", size) @@ -49,10 +51,11 @@ func main() { pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ - Root: "root", - SessionId: sessionId, - OutputSize: uint32(size), - FlagCount: uint32(128), + Root: "root", + SessionId: sessionId, + OutputSize: uint32(size), + FlagCount: uint32(128), + MenuSeparator: separator, } resourceDir := scriptDir From f38ea59569404c8fa967f6d78279a2227f421a16 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 13 Dec 2024 01:10:46 +0300 Subject: [PATCH 11/71] set the separator as a config and not an arg --- cmd/africastalking/main.go | 4 +--- cmd/async/main.go | 4 +--- cmd/http/main.go | 4 +--- cmd/main.go | 4 +--- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index f57f8f4..11d78b2 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -115,7 +115,6 @@ func main() { var engineDebug bool var host string var port uint - var separator string flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") flag.StringVar(&database, "db", "gdbm", "database to be used") @@ -123,7 +122,6 @@ func main() { flag.UintVar(&size, "s", 160, "max size of output") flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host") flag.UintVar(&port, "p", initializers.GetEnvUint("PORT", 7123), "http port") - flag.StringVar(&separator, "sep", ": ", "custom separator for the menu") flag.Parse() logg.Infof("start command", "build", build, "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size) @@ -136,7 +134,7 @@ func main() { Root: "root", OutputSize: uint32(size), FlagCount: uint32(128), - MenuSeparator: separator, + MenuSeparator: ": ", } if engineDebug { diff --git a/cmd/async/main.go b/cmd/async/main.go index 1fdc532..a5cac92 100644 --- a/cmd/async/main.go +++ b/cmd/async/main.go @@ -53,7 +53,6 @@ func main() { var engineDebug bool var host string var port uint - var separator string flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") @@ -62,7 +61,6 @@ func main() { flag.UintVar(&size, "s", 160, "max size of output") flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host") flag.UintVar(&port, "p", initializers.GetEnvUint("PORT", 7123), "http port") - flag.StringVar(&separator, "sep", ": ", "custom separator for the menu") flag.Parse() logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size, "sessionId", sessionId) @@ -75,7 +73,7 @@ func main() { Root: "root", OutputSize: uint32(size), FlagCount: uint32(128), - MenuSeparator: separator, + MenuSeparator: ": ", } if engineDebug { diff --git a/cmd/http/main.go b/cmd/http/main.go index aa7d00a..451d693 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -42,7 +42,6 @@ func main() { var engineDebug bool var host string var port uint - var separator string flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") flag.StringVar(&database, "db", "gdbm", "database to be used") @@ -50,7 +49,6 @@ func main() { flag.UintVar(&size, "s", 160, "max size of output") flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host") flag.UintVar(&port, "p", initializers.GetEnvUint("PORT", 7123), "http port") - flag.StringVar(&separator, "sep", ": ", "custom separator for the menu") flag.Parse() logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size) @@ -63,7 +61,7 @@ func main() { Root: "root", OutputSize: uint32(size), FlagCount: uint32(128), - MenuSeparator: separator, + MenuSeparator: ": ", } if engineDebug { diff --git a/cmd/main.go b/cmd/main.go index aec6a14..ddcf7a5 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -34,13 +34,11 @@ func main() { var sessionId string var database string var engineDebug bool - var separator string flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.StringVar(&database, "db", "gdbm", "database to be used") flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") flag.BoolVar(&engineDebug, "d", false, "use engine debug output") flag.UintVar(&size, "s", 160, "max size of output") - flag.StringVar(&separator, "sep", ": ", "custom separator for the menu") flag.Parse() logg.Infof("start command", "dbdir", dbDir, "outputsize", size) @@ -55,7 +53,7 @@ func main() { SessionId: sessionId, OutputSize: uint32(size), FlagCount: uint32(128), - MenuSeparator: separator, + MenuSeparator: ": ", } resourceDir := scriptDir From 64c1fe5276775e160bd5fe4e85cee3fed5d6329a Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 13 Dec 2024 11:38:10 +0300 Subject: [PATCH 12/71] set the separator as a var and add it to the context --- cmd/africastalking/main.go | 11 ++++++----- cmd/async/main.go | 4 +++- cmd/http/main.go | 4 +++- cmd/main.go | 8 +++++--- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index 11d78b2..58025e0 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -30,10 +30,10 @@ import ( ) var ( - logg = logging.NewVanilla() - scriptDir = path.Join("services", "registration") - - build = "dev" + logg = logging.NewVanilla() + scriptDir = path.Join("services", "registration") + build = "dev" + menuSeparator = ": " ) func init() { @@ -128,13 +128,14 @@ func main() { ctx := context.Background() ctx = context.WithValue(ctx, "Database", database) + ctx = context.WithValue(ctx, "MenuSeparator", menuSeparator) pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ Root: "root", OutputSize: uint32(size), FlagCount: uint32(128), - MenuSeparator: ": ", + MenuSeparator: menuSeparator, } if engineDebug { diff --git a/cmd/async/main.go b/cmd/async/main.go index a5cac92..e63f42a 100644 --- a/cmd/async/main.go +++ b/cmd/async/main.go @@ -23,6 +23,7 @@ import ( var ( logg = logging.NewVanilla() scriptDir = path.Join("services", "registration") + menuSeparator = ": " ) func init() { @@ -67,13 +68,14 @@ func main() { ctx := context.Background() ctx = context.WithValue(ctx, "Database", database) + ctx = context.WithValue(ctx, "MenuSeparator", menuSeparator) pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ Root: "root", OutputSize: uint32(size), FlagCount: uint32(128), - MenuSeparator: ": ", + MenuSeparator: menuSeparator, } if engineDebug { diff --git a/cmd/http/main.go b/cmd/http/main.go index 451d693..5196d5f 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -26,6 +26,7 @@ import ( var ( logg = logging.NewVanilla() scriptDir = path.Join("services", "registration") + menuSeparator = ": " ) func init() { @@ -55,13 +56,14 @@ func main() { ctx := context.Background() ctx = context.WithValue(ctx, "Database", database) + ctx = context.WithValue(ctx, "MenuSeparator", menuSeparator) pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ Root: "root", OutputSize: uint32(size), FlagCount: uint32(128), - MenuSeparator: ": ", + MenuSeparator: menuSeparator, } if engineDebug { diff --git a/cmd/main.go b/cmd/main.go index ddcf7a5..62d16a7 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -18,8 +18,9 @@ import ( ) var ( - logg = logging.NewVanilla() - scriptDir = path.Join("services", "registration") + logg = logging.NewVanilla() + scriptDir = path.Join("services", "registration") + menuSeparator = ": " ) func init() { @@ -46,6 +47,7 @@ func main() { ctx := context.Background() ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "Database", database) + ctx = context.WithValue(ctx, "MenuSeparator", menuSeparator) pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ @@ -53,7 +55,7 @@ func main() { SessionId: sessionId, OutputSize: uint32(size), FlagCount: uint32(128), - MenuSeparator: ": ", + MenuSeparator: menuSeparator, } resourceDir := scriptDir From df5e5f1a4bea91849564cf3963337800f5c473d1 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 13 Dec 2024 11:40:39 +0300 Subject: [PATCH 13/71] properly format the vouchers --- common/vouchers.go | 4 ++-- common/vouchers_test.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/vouchers.go b/common/vouchers.go index 790e3ca..6cff91d 100644 --- a/common/vouchers.go +++ b/common/vouchers.go @@ -24,7 +24,7 @@ func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata { var symbols, balances, decimals, addresses []string for i, h := range holdings { - symbols = append(symbols, fmt.Sprintf("%d: %s", i+1, h.TokenSymbol)) + symbols = append(symbols, fmt.Sprintf("%d:%s", i+1, h.TokenSymbol)) // Scale down the balance scaledBalance := ScaleDownBalance(h.Balance, h.TokenDecimals) @@ -103,7 +103,7 @@ func MatchVoucher(input, symbols, balances, decimals, addresses string) (symbol, logg.Tracef("found", "symlist", symList, "syms", symbols, "input", input) for i, sym := range symList { - parts := strings.SplitN(sym, ": ", 2) + parts := strings.SplitN(sym, ":", 2) if input == parts[0] || strings.EqualFold(input, parts[1]) { symbol = parts[1] diff --git a/common/vouchers_test.go b/common/vouchers_test.go index 2ebb4a0..ba6cd60 100644 --- a/common/vouchers_test.go +++ b/common/vouchers_test.go @@ -34,7 +34,7 @@ func InitializeTestDb(t *testing.T) (context.Context, *UserDataStore) { } func TestMatchVoucher(t *testing.T) { - symbols := "1: SRF\n2: MILO" + symbols := "1:SRF\n2:MILO" balances := "1:100\n2:200" decimals := "1:6\n2:4" addresses := "1:0xd4c288865Ce\n2:0x41c188d63Qa" @@ -65,7 +65,7 @@ func TestProcessVouchers(t *testing.T) { } expectedResult := VoucherMetadata{ - Symbols: "1: SRF\n2: MILO", + Symbols: "1:SRF\n2:MILO", Balances: "1:100\n2:20000", Decimals: "1:6\n2:4", Addresses: "1:0xd4c288865Ce\n2:0x41c188d63Qa", @@ -90,7 +90,7 @@ func TestGetVoucherData(t *testing.T) { // Test voucher data mockData := map[DataTyp][]byte{ - DATA_VOUCHER_SYMBOLS: []byte("1: SRF\n2: MILO"), + DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"), DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"), DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"), DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"), From 5cd791aae7f5900d046ce918b2cb61518720a363 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 13 Dec 2024 11:43:47 +0300 Subject: [PATCH 14/71] use the MenuSeparator --- internal/handlers/ussd/menuhandler.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 5723bad..d1d246a 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -1676,6 +1676,11 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result + menuSeparator, ok := ctx.Value("MenuSeparator").(string) + if !ok { + return res, fmt.Errorf("missing menu Separator") + } + // Read vouchers from the store voucherData, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS)) if err != nil { @@ -1683,7 +1688,9 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) return res, err } - res.Content = string(voucherData) + formattedData := strings.ReplaceAll(string(voucherData), ":", menuSeparator) + + res.Content = string(formattedData) return res, nil } @@ -1853,6 +1860,12 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input [] if !ok { return res, fmt.Errorf("missing session") } + + menuSeparator, ok := ctx.Value("MenuSeparator").(string) + if !ok { + return res, fmt.Errorf("missing menu Separator") + } + store := h.userdataStore publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY) if err != nil { @@ -1900,7 +1913,7 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input [] status = "Sent" } - formattedTransactions = append(formattedTransactions, fmt.Sprintf("%d: %s %s %s %s", i+1, status, value, sym, date)) + formattedTransactions = append(formattedTransactions, fmt.Sprintf("%d%s%s %s %s %s", i+1, menuSeparator, status, value, sym, date)) } res.Content = strings.Join(formattedTransactions, "\n") From 7aea2af9a1baca5ca5b9ef3ac38ef74f6b1b08d3 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 13 Dec 2024 11:44:04 +0300 Subject: [PATCH 15/71] updated tests --- internal/handlers/ussd/menuhandler_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 5cc3067..01beb89 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -1961,7 +1961,7 @@ func TestCheckVouchers(t *testing.T) { {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, } - expectedSym := []byte("1: SRF\n2: MILO") + expectedSym := []byte("1:SRF\n2:MILO") mockAccountService.On("FetchVouchers", string(publicKey)).Return(mockVouchersResponse, nil) @@ -1982,7 +1982,11 @@ func TestCheckVouchers(t *testing.T) { func TestGetVoucherList(t *testing.T) { sessionId := "session123" + menuSeparator := ":" + ctx := context.WithValue(context.Background(), "SessionId", sessionId) + ctx = context.WithValue(ctx, "MenuSeparator", menuSeparator) + spdb := InitializeTestSubPrefixDb(t, ctx) @@ -2024,7 +2028,7 @@ func TestViewVoucher(t *testing.T) { // Define mock voucher data mockData := map[common.DataTyp][]byte{ - common.DATA_VOUCHER_SYMBOLS: []byte("1: SRF\n2: MILO"), + common.DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"), common.DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"), common.DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"), common.DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"), From 5428626c3f6cef24196be7d391b20b794ec284b1 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 17 Dec 2024 17:56:56 +0300 Subject: [PATCH 16/71] cleaned up the restart_state --- devtools/restart_state/main.go | 35 +--------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/devtools/restart_state/main.go b/devtools/restart_state/main.go index 136b38a..3068a38 100644 --- a/devtools/restart_state/main.go +++ b/devtools/restart_state/main.go @@ -7,16 +7,9 @@ import ( "os" "path" - "git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/logging" - "git.defalsify.org/vise.git/resource" - - // "git.defalsify.org/vise.git/persist" "git.grassecon.net/urdt/ussd/config" "git.grassecon.net/urdt/ussd/initializers" - "git.grassecon.net/urdt/ussd/remote" - - "git.grassecon.net/urdt/ussd/internal/handlers" "git.grassecon.net/urdt/ussd/internal/storage" ) @@ -35,28 +28,16 @@ func main() { var dbDir string var sessionId string var database string - var engineDebug bool - var size uint - + flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.StringVar(&database, "db", "gdbm", "database to be used") flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") - flag.BoolVar(&engineDebug, "d", false, "use engine debug output") flag.Parse() ctx := context.Background() ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "Database", database) - pfp := path.Join(scriptDir, "pp.csv") - - cfg := engine.Config{ - Root: "root", - SessionId: sessionId, - OutputSize: uint32(size), - FlagCount: uint32(128), - } - resourceDir := scriptDir menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir) @@ -66,25 +47,12 @@ func main() { os.Exit(1) } - rs, err := menuStorageService.GetResource(ctx) - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - os.Exit(1) - } - pe, err := menuStorageService.GetPersister(ctx) if err != nil { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) } - userdatastore, err := menuStorageService.GetUserdataDb(ctx) - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - os.Exit(1) - } - - // initialize the persister // get the state @@ -105,6 +73,5 @@ func main() { st.Restart() - os.Exit(1) } From 553470618913fca61232761e3ffcfa8efe00cbab Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 17 Dec 2024 17:58:08 +0300 Subject: [PATCH 17/71] reset the state when input is nil --- internal/handlers/ussd/menuhandler.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index c5356d0..c2b41b2 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -124,6 +124,16 @@ func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource h.st = h.pe.GetState() h.ca = h.pe.GetMemory() + + fmt.Println("Pre restart state:", h.st) + + if len(input) == 0 { + h.st.Restart() + h.st = h.pe.GetState() + } + + fmt.Println("New state:", h.st) + sessionId, _ := ctx.Value("SessionId").(string) flag_admin_privilege, _ := h.flagManager.GetFlag("flag_admin_privilege") From 17ba6a06bacdc1398095fd9a2ea0e72094e097f9 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 18 Dec 2024 21:49:42 +0300 Subject: [PATCH 18/71] remove the MenuSeparator from the context --- cmd/africastalking/main.go | 1 - cmd/async/main.go | 1 - cmd/http/main.go | 1 - cmd/main.go | 1 - 4 files changed, 4 deletions(-) diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index 58025e0..3d11ba5 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -128,7 +128,6 @@ func main() { ctx := context.Background() ctx = context.WithValue(ctx, "Database", database) - ctx = context.WithValue(ctx, "MenuSeparator", menuSeparator) pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ diff --git a/cmd/async/main.go b/cmd/async/main.go index e63f42a..9cd04b3 100644 --- a/cmd/async/main.go +++ b/cmd/async/main.go @@ -68,7 +68,6 @@ func main() { ctx := context.Background() ctx = context.WithValue(ctx, "Database", database) - ctx = context.WithValue(ctx, "MenuSeparator", menuSeparator) pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ diff --git a/cmd/http/main.go b/cmd/http/main.go index 5196d5f..6ddfded 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -56,7 +56,6 @@ func main() { ctx := context.Background() ctx = context.WithValue(ctx, "Database", database) - ctx = context.WithValue(ctx, "MenuSeparator", menuSeparator) pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ diff --git a/cmd/main.go b/cmd/main.go index 62d16a7..4fd084f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -47,7 +47,6 @@ func main() { ctx := context.Background() ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "Database", database) - ctx = context.WithValue(ctx, "MenuSeparator", menuSeparator) pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ From d08afff44395133495873cec2ea07b55b550524c Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 18 Dec 2024 21:56:37 +0300 Subject: [PATCH 19/71] add the replaceSeparator func and pass it to the Handler struct --- internal/handlers/handlerservice.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/handlers/handlerservice.go b/internal/handlers/handlerservice.go index a14cf59..417fe18 100644 --- a/internal/handlers/handlerservice.go +++ b/internal/handlers/handlerservice.go @@ -2,6 +2,7 @@ package handlers import ( "context" + "strings" "git.defalsify.org/vise.git/asm" "git.defalsify.org/vise.git/db" @@ -64,7 +65,11 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) { } func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceInterface) (*ussd.Handlers, error) { - ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService) + replaceSeparator := func(input string) string { + return strings.ReplaceAll(input, ":", ls.Cfg.MenuSeparator) + } + + ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService, replaceSeparator) if err != nil { return nil, err } From fda68231eacefcdb20edab828f3903b1fc8642c3 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 18 Dec 2024 21:59:09 +0300 Subject: [PATCH 20/71] use the replaceSeparator func to format the generated menus --- internal/handlers/ussd/menuhandler.go | 50 ++++++++++++--------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index d1d246a..7b238e3 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -69,18 +69,19 @@ func (fm *FlagManager) GetFlag(label string) (uint32, error) { } type Handlers struct { - pe *persist.Persister - st *state.State - ca cache.Memory - userdataStore common.DataStore - adminstore *utils.AdminStore - flagManager *asm.FlagParser - accountService remote.AccountServiceInterface - prefixDb storage.PrefixDb - profile *models.Profile + pe *persist.Persister + st *state.State + ca cache.Memory + userdataStore common.DataStore + adminstore *utils.AdminStore + flagManager *asm.FlagParser + accountService remote.AccountServiceInterface + prefixDb storage.PrefixDb + profile *models.Profile + ReplaceSeparator func(string) string } -func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface) (*Handlers, error) { +func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface, replaceSeparator func(string) string) (*Handlers, error) { if userdataStore == nil { return nil, fmt.Errorf("cannot create handler with nil userdata store") } @@ -93,12 +94,13 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *util prefixDb := storage.NewSubPrefixDb(userdataStore, prefix) h := &Handlers{ - userdataStore: userDb, - flagManager: appFlags, - adminstore: adminstore, - accountService: accountService, - prefixDb: prefixDb, - profile: &models.Profile{Max: 6}, + userdataStore: userDb, + flagManager: appFlags, + adminstore: adminstore, + accountService: accountService, + prefixDb: prefixDb, + profile: &models.Profile{Max: 6}, + ReplaceSeparator: replaceSeparator, } return h, nil } @@ -1676,11 +1678,6 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result - menuSeparator, ok := ctx.Value("MenuSeparator").(string) - if !ok { - return res, fmt.Errorf("missing menu Separator") - } - // Read vouchers from the store voucherData, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS)) if err != nil { @@ -1688,7 +1685,7 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) return res, err } - formattedData := strings.ReplaceAll(string(voucherData), ":", menuSeparator) + formattedData := h.ReplaceSeparator(string(voucherData)) res.Content = string(formattedData) @@ -1861,11 +1858,6 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input [] return res, fmt.Errorf("missing session") } - menuSeparator, ok := ctx.Value("MenuSeparator").(string) - if !ok { - return res, fmt.Errorf("missing menu Separator") - } - store := h.userdataStore publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY) if err != nil { @@ -1913,7 +1905,9 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input [] status = "Sent" } - formattedTransactions = append(formattedTransactions, fmt.Sprintf("%d%s%s %s %s %s", i+1, menuSeparator, status, value, sym, date)) + // Use the ReplaceSeparator function for the menu separator + transactionLine := fmt.Sprintf("%d%s%s %s %s %s", i+1, h.ReplaceSeparator(":"), status, value, sym, date) + formattedTransactions = append(formattedTransactions, transactionLine) } res.Content = strings.Join(formattedTransactions, "\n") From ecfdab47a8cc0f12e0b4cea6889069421a1adf21 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 18 Dec 2024 22:21:52 +0300 Subject: [PATCH 21/71] updated test --- internal/handlers/ussd/menuhandler_test.go | 58 ++++++++++++++++------ 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 01beb89..22c02e9 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "path" + "strings" "testing" "git.defalsify.org/vise.git/lang" @@ -67,12 +68,20 @@ func TestNewHandlers(t *testing.T) { _, store := InitializeTestStore(t) fm, err := NewFlagManager(flagsPath) - accountService := testservice.TestAccountService{} if err != nil { - t.Logf(err.Error()) + log.Fatal(err) } + + accountService := testservice.TestAccountService{} + + // Mock function for replaceSeparator + mockReplaceSeparator := func(input string) string { + return strings.ReplaceAll(input, ":", ": ") + } + + // Test case for valid UserDataStore t.Run("Valid UserDataStore", func(t *testing.T) { - handlers, err := NewHandlers(fm.parser, store, nil, &accountService) + handlers, err := NewHandlers(fm.parser, store, nil, &accountService, mockReplaceSeparator) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -82,19 +91,30 @@ func TestNewHandlers(t *testing.T) { if handlers.userdataStore == nil { t.Fatal("expected userdataStore to be set in handlers") } + if handlers.ReplaceSeparator == nil { + t.Fatal("expected ReplaceSeparator to be set in handlers") + } + + // Test ReplaceSeparator functionality + input := "1:Menu item" + expectedOutput := "1: Menu item" + if handlers.ReplaceSeparator(input) != expectedOutput { + t.Fatalf("ReplaceSeparator function did not return expected output: got %v, want %v", handlers.ReplaceSeparator(input), expectedOutput) + } }) - // Test case for nil userdataStore + // Test case for nil UserDataStore t.Run("Nil UserDataStore", func(t *testing.T) { - handlers, err := NewHandlers(fm.parser, nil, nil, &accountService) + handlers, err := NewHandlers(fm.parser, nil, nil, &accountService, mockReplaceSeparator) if err == nil { t.Fatal("expected an error, got none") } if handlers != nil { t.Fatal("expected handlers to be nil") } - if err.Error() != "cannot create handler with nil userdata store" { - t.Fatalf("expected specific error, got %v", err) + expectedError := "cannot create handler with nil userdata store" + if err.Error() != expectedError { + t.Fatalf("expected error '%s', got '%v'", expectedError, err) } }) } @@ -1982,30 +2002,36 @@ func TestCheckVouchers(t *testing.T) { func TestGetVoucherList(t *testing.T) { sessionId := "session123" - menuSeparator := ":" - + ctx := context.WithValue(context.Background(), "SessionId", sessionId) - ctx = context.WithValue(ctx, "MenuSeparator", menuSeparator) - spdb := InitializeTestSubPrefixDb(t, ctx) - h := &Handlers{ - prefixDb: spdb, + // Mock replaceSeparator function + replaceSeparator := func(input string) string { + return strings.ReplaceAll(input, ":", ": ") } - expectedSym := []byte("1:SRF\n2:MILO") + // Initialize Handlers + h := &Handlers{ + prefixDb: spdb, + ReplaceSeparator: replaceSeparator, + } + + mockSyms := []byte("1:SRF\n2:MILO") // Put voucher sym data from the store - err := spdb.Put(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS), expectedSym) + err := spdb.Put(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS), mockSyms) if err != nil { t.Fatal(err) } + expectedSyms := []byte("1: SRF\n2: MILO") + res, err := h.GetVoucherList(ctx, "", []byte("")) assert.NoError(t, err) - assert.Equal(t, res.Content, string(expectedSym)) + assert.Equal(t, res.Content, string(expectedSyms)) } func TestViewVoucher(t *testing.T) { From 055c2db790281cfc9517f7aa878c32f2329b5670 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Wed, 18 Dec 2024 22:25:47 +0300 Subject: [PATCH 22/71] use a common mockReplaceSeparator func --- internal/handlers/ussd/menuhandler_test.go | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 22c02e9..3c32ee0 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -33,6 +33,11 @@ var ( flagsPath = path.Join(baseDir, "services", "registration", "pp.csv") ) +// mockReplaceSeparator function +var mockReplaceSeparator = func(input string) string { + return strings.ReplaceAll(input, ":", ": ") +} + // InitializeTestStore sets up and returns an in-memory database and store. func InitializeTestStore(t *testing.T) (context.Context, *common.UserDataStore) { ctx := context.Background() @@ -74,11 +79,6 @@ func TestNewHandlers(t *testing.T) { accountService := testservice.TestAccountService{} - // Mock function for replaceSeparator - mockReplaceSeparator := func(input string) string { - return strings.ReplaceAll(input, ":", ": ") - } - // Test case for valid UserDataStore t.Run("Valid UserDataStore", func(t *testing.T) { handlers, err := NewHandlers(fm.parser, store, nil, &accountService, mockReplaceSeparator) @@ -2007,15 +2007,10 @@ func TestGetVoucherList(t *testing.T) { spdb := InitializeTestSubPrefixDb(t, ctx) - // Mock replaceSeparator function - replaceSeparator := func(input string) string { - return strings.ReplaceAll(input, ":", ": ") - } - // Initialize Handlers h := &Handlers{ prefixDb: spdb, - ReplaceSeparator: replaceSeparator, + ReplaceSeparator: mockReplaceSeparator, } mockSyms := []byte("1:SRF\n2:MILO") From 12928512264e6144c20169bcde51fed7c704eece Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 19 Dec 2024 13:32:39 +0300 Subject: [PATCH 23/71] rename the function to ReplaceSeparatorFunc --- internal/handlers/handlerservice.go | 4 ++-- internal/handlers/ussd/menuhandler.go | 10 +++++----- internal/handlers/ussd/menuhandler_test.go | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/handlers/handlerservice.go b/internal/handlers/handlerservice.go index 417fe18..015098e 100644 --- a/internal/handlers/handlerservice.go +++ b/internal/handlers/handlerservice.go @@ -65,11 +65,11 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) { } func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceInterface) (*ussd.Handlers, error) { - replaceSeparator := func(input string) string { + replaceSeparatorFunc := func(input string) string { return strings.ReplaceAll(input, ":", ls.Cfg.MenuSeparator) } - ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService, replaceSeparator) + ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService, replaceSeparatorFunc) if err != nil { return nil, err } diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index f506793..986f8f0 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -78,10 +78,10 @@ type Handlers struct { accountService remote.AccountServiceInterface prefixDb storage.PrefixDb profile *models.Profile - ReplaceSeparator func(string) string + ReplaceSeparatorFunc func(string) string } -func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface, replaceSeparator func(string) string) (*Handlers, error) { +func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface, replaceSeparatorFunc func(string) string) (*Handlers, error) { if userdataStore == nil { return nil, fmt.Errorf("cannot create handler with nil userdata store") } @@ -100,7 +100,7 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *util accountService: accountService, prefixDb: prefixDb, profile: &models.Profile{Max: 6}, - ReplaceSeparator: replaceSeparator, + ReplaceSeparatorFunc: replaceSeparatorFunc, } return h, nil } @@ -1685,7 +1685,7 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) return res, err } - formattedData := h.ReplaceSeparator(string(voucherData)) + formattedData := h.ReplaceSeparatorFunc(string(voucherData)) res.Content = string(formattedData) @@ -1906,7 +1906,7 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input [] } // Use the ReplaceSeparator function for the menu separator - transactionLine := fmt.Sprintf("%d%s%s %s %s %s", i+1, h.ReplaceSeparator(":"), status, value, sym, date) + transactionLine := fmt.Sprintf("%d%s%s %s %s %s", i+1, h.ReplaceSeparatorFunc(":"), status, value, sym, date) formattedTransactions = append(formattedTransactions, transactionLine) } diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 3c32ee0..c01678d 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -91,15 +91,15 @@ func TestNewHandlers(t *testing.T) { if handlers.userdataStore == nil { t.Fatal("expected userdataStore to be set in handlers") } - if handlers.ReplaceSeparator == nil { - t.Fatal("expected ReplaceSeparator to be set in handlers") + if handlers.ReplaceSeparatorFunc == nil { + t.Fatal("expected ReplaceSeparatorFunc to be set in handlers") } - // Test ReplaceSeparator functionality + // Test ReplaceSeparatorFunc functionality input := "1:Menu item" expectedOutput := "1: Menu item" - if handlers.ReplaceSeparator(input) != expectedOutput { - t.Fatalf("ReplaceSeparator function did not return expected output: got %v, want %v", handlers.ReplaceSeparator(input), expectedOutput) + if handlers.ReplaceSeparatorFunc(input) != expectedOutput { + t.Fatalf("ReplaceSeparatorFunc function did not return expected output: got %v, want %v", handlers.ReplaceSeparatorFunc(input), expectedOutput) } }) @@ -2009,8 +2009,8 @@ func TestGetVoucherList(t *testing.T) { // Initialize Handlers h := &Handlers{ - prefixDb: spdb, - ReplaceSeparator: mockReplaceSeparator, + prefixDb: spdb, + ReplaceSeparatorFunc: mockReplaceSeparator, } mockSyms := []byte("1:SRF\n2:MILO") From be215d3f75ed64a1794e87497a3682b5050842b7 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 20 Dec 2024 12:26:07 +0300 Subject: [PATCH 24/71] set the code to an empty byte for it to move to the top node --- internal/handlers/ussd/menuhandler.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index b3c6f1d..f509ff4 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -124,16 +124,11 @@ func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource h.st = h.pe.GetState() h.ca = h.pe.GetMemory() - - fmt.Println("Pre restart state:", h.st) - if len(input) == 0 { - h.st.Restart() - h.st = h.pe.GetState() + // move to the top node + h.st.Code = []byte{} } - fmt.Println("New state:", h.st) - sessionId, _ := ctx.Value("SessionId").(string) flag_admin_privilege, _ := h.flagManager.GetFlag("flag_admin_privilege") From f4f4fdd3aceac3c170c71d5ef317afdee7fa7447 Mon Sep 17 00:00:00 2001 From: konstantinmds Date: Tue, 17 Dec 2024 16:32:10 +0100 Subject: [PATCH 25/71] issue-205: added comments for menu handlers methods and changed function name to better fit function workings. --- internal/handlers/handlerservice.go | 2 +- internal/handlers/ussd/menuhandler.go | 125 ++++++++++++++++---------- 2 files changed, 78 insertions(+), 49 deletions(-) diff --git a/internal/handlers/handlerservice.go b/internal/handlers/handlerservice.go index 015098e..1da28c3 100644 --- a/internal/handlers/handlerservice.go +++ b/internal/handlers/handlerservice.go @@ -116,7 +116,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceIn ls.DbRs.AddLocalFunc("set_voucher", ussdHandlers.SetVoucher) ls.DbRs.AddLocalFunc("get_voucher_details", ussdHandlers.GetVoucherDetails) ls.DbRs.AddLocalFunc("reset_valid_pin", ussdHandlers.ResetValidPin) - ls.DbRs.AddLocalFunc("check_pin_mismatch", ussdHandlers.CheckPinMisMatch) + ls.DbRs.AddLocalFunc("check_pin_mismatch", ussdHandlers.CheckBlockedNumPinMisMatch) ls.DbRs.AddLocalFunc("validate_blocked_number", ussdHandlers.ValidateBlockedNumber) ls.DbRs.AddLocalFunc("retrieve_blocked_number", ussdHandlers.RetrieveBlockedNumber) ls.DbRs.AddLocalFunc("reset_unregistered_number", ussdHandlers.ResetUnregisteredNumber) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 986f8f0..daf4b29 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -39,7 +39,7 @@ const ( pinPattern = `^\d{4}$` ) -// isValidPIN checks whether the given input is a 4 digit number +// checks whether the given input is a 4 digit number func isValidPIN(pin string) bool { match, _ := regexp.MatchString(pinPattern, pin) return match @@ -69,18 +69,19 @@ func (fm *FlagManager) GetFlag(label string) (uint32, error) { } type Handlers struct { - pe *persist.Persister - st *state.State - ca cache.Memory - userdataStore common.DataStore - adminstore *utils.AdminStore - flagManager *asm.FlagParser - accountService remote.AccountServiceInterface - prefixDb storage.PrefixDb - profile *models.Profile + pe *persist.Persister + st *state.State + ca cache.Memory + userdataStore common.DataStore + adminstore *utils.AdminStore + flagManager *asm.FlagParser + accountService remote.AccountServiceInterface + prefixDb storage.PrefixDb + profile *models.Profile ReplaceSeparatorFunc func(string) string } +// NewHandlers creates a new instance of the Handlers struct with the provided dependencies. func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface, replaceSeparatorFunc func(string) string) (*Handlers, error) { if userdataStore == nil { return nil, fmt.Errorf("cannot create handler with nil userdata store") @@ -94,17 +95,18 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *util prefixDb := storage.NewSubPrefixDb(userdataStore, prefix) h := &Handlers{ - userdataStore: userDb, - flagManager: appFlags, - adminstore: adminstore, - accountService: accountService, - prefixDb: prefixDb, - profile: &models.Profile{Max: 6}, + userdataStore: userDb, + flagManager: appFlags, + adminstore: adminstore, + accountService: accountService, + prefixDb: prefixDb, + profile: &models.Profile{Max: 6}, ReplaceSeparatorFunc: replaceSeparatorFunc, } return h, nil } +// WithPersister sets persister instance to the handlers. func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers { if h.pe != nil { panic("persister already set") @@ -113,6 +115,7 @@ func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers { return h } +// Init initializes the handler for a new session. func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) { var r resource.Result if h.pe == nil { @@ -151,7 +154,7 @@ func (h *Handlers) Exit() { h.pe = nil } -// SetLanguage sets the language across the menu +// SetLanguage sets the language across the menu. func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -175,6 +178,7 @@ func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (r return res, nil } +// handles the account creation when no existing account is present for the session and stores associated data in the user data store. func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error { flag_account_created, _ := h.flagManager.GetFlag("flag_account_created") r, err := h.accountService.CreateAccount(ctx) @@ -207,9 +211,9 @@ func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, r return nil } -// CreateAccount checks if any account exists on the JSON data file, and if not +// CreateAccount checks if any account exists on the JSON data file, and if not, // creates an account on the API, -// sets the default values and flags +// sets the default values and flags. func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -233,19 +237,22 @@ func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte) return res, nil } -func (h *Handlers) CheckPinMisMatch(ctx context.Context, sym string, input []byte) (resource.Result, error) { +// CheckBlockedNumPinMisMatch checks if the provided PIN matches a temporary PIN stored for a blocked number. +func (h *Handlers) CheckBlockedNumPinMisMatch(ctx context.Context, sym string, input []byte) (resource.Result, error) { res := resource.Result{} flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") sessionId, ok := ctx.Value("SessionId").(string) if !ok { return res, fmt.Errorf("missing session") } + // Get blocked number from storage. store := h.userdataStore blockedNumber, err := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER) if err != nil { logg.ErrorCtxf(ctx, "failed to read blockedNumber entry with", "key", common.DATA_BLOCKED_NUMBER, "error", err) return res, err } + // Get temporary PIN for the blocked number. temporaryPin, err := store.ReadEntry(ctx, string(blockedNumber), common.DATA_TEMPORARY_VALUE) if err != nil { logg.ErrorCtxf(ctx, "failed to read temporaryPin entry with", "key", common.DATA_TEMPORARY_VALUE, "error", err) @@ -259,6 +266,7 @@ func (h *Handlers) CheckPinMisMatch(ctx context.Context, sym string, input []byt return res, nil } +// VerifyNewPin checks if a new PIN meets the required format criteria. func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { res := resource.Result{} _, ok := ctx.Value("SessionId").(string) @@ -267,7 +275,7 @@ func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) ( } flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin") pinInput := string(input) - // Validate that the PIN is a 4-digit number + // Validate that the PIN is a 4-digit number. if isValidPIN(pinInput) { res.FlagSet = append(res.FlagSet, flag_valid_pin) } else { @@ -277,9 +285,9 @@ func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) ( return res, nil } -// SaveTemporaryPin saves the valid PIN input to the DATA_TEMPORARY_VALUE +// SaveTemporaryPin saves the valid PIN input to the DATA_TEMPORARY_VALUE, // during the account creation process -// and during the change PIN process +// and during the change PIN process. func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -292,7 +300,7 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") accountPIN := string(input) - // Validate that the PIN is a 4-digit number + // Validate that the PIN is a 4-digit number. if !isValidPIN(accountPIN) { res.FlagSet = append(res.FlagSet, flag_incorrect_pin) return res, nil @@ -308,6 +316,7 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt return res, nil } +// SaveOthersTemporaryPin allows authorized users to set temporary PINs for blocked numbers. func (h *Handlers) SaveOthersTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -318,12 +327,14 @@ func (h *Handlers) SaveOthersTemporaryPin(ctx context.Context, sym string, input return res, fmt.Errorf("missing session") } temporaryPin := string(input) + // First, we retrieve the blocked number associated with this session blockedNumber, err := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER) if err != nil { logg.ErrorCtxf(ctx, "failed to read blockedNumber entry with", "key", common.DATA_BLOCKED_NUMBER, "error", err) return res, err } + // Then we save the temporary PIN for that blocked number err = store.WriteEntry(ctx, string(blockedNumber), common.DATA_TEMPORARY_VALUE, []byte(temporaryPin)) if err != nil { logg.ErrorCtxf(ctx, "failed to write temporaryPin entry with", "key", common.DATA_TEMPORARY_VALUE, "value", temporaryPin, "error", err) @@ -333,6 +344,7 @@ func (h *Handlers) SaveOthersTemporaryPin(ctx context.Context, sym string, input return res, nil } +// ConfirmPinChange validates user's new PIN. If input matches the temporary PIN, saves it as the new account PIN. func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) @@ -352,6 +364,7 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt } else { res.FlagSet = append(res.FlagSet, flag_pin_mismatch) } + // If matched, save the confirmed PIN as the new account PIN err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(temporaryPin)) if err != nil { logg.ErrorCtxf(ctx, "failed to write temporaryPin entry with", "key", common.DATA_ACCOUNT_PIN, "value", temporaryPin, "error", err) @@ -362,7 +375,7 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt // VerifyCreatePin checks whether the confirmation PIN is similar to the temporary PIN // If similar, it sets the USERFLAG_PIN_SET flag and writes the account PIN allowing the user -// to access the main menu +// to access the main menu. func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -397,7 +410,7 @@ func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte return res, nil } -// codeFromCtx retrieves language codes from the context that can be used for handling translations +// retrieves language codes from the context that can be used for handling translations. func codeFromCtx(ctx context.Context) string { var code string if ctx.Value("Language") != nil { @@ -731,7 +744,7 @@ func (h *Handlers) ResetIncorrectPin(ctx context.Context, sym string, input []by return res, nil } -// Setback sets the flag_back_set flag when the navigation is back +// Setback sets the flag_back_set flag when the navigation is back. func (h *Handlers) SetBack(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result //TODO: @@ -744,7 +757,7 @@ func (h *Handlers) SetBack(ctx context.Context, sym string, input []byte) (resou } // CheckAccountStatus queries the API using the TrackingId and sets flags -// based on the account status +// based on the account status. func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -784,7 +797,7 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b return res, nil } -// Quit displays the Thank you message and exits the menu +// Quit displays the Thank you message and exits the menu. func (h *Handlers) Quit(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -799,7 +812,7 @@ func (h *Handlers) Quit(ctx context.Context, sym string, input []byte) (resource return res, nil } -// QuitWithHelp displays helpline information then exits the menu +// QuitWithHelp displays helpline information then exits the menu. func (h *Handlers) QuitWithHelp(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -814,7 +827,7 @@ func (h *Handlers) QuitWithHelp(ctx context.Context, sym string, input []byte) ( return res, nil } -// VerifyYob verifies the length of the given input +// VerifyYob verifies the length of the given input. func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -836,7 +849,7 @@ func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (res return res, nil } -// ResetIncorrectYob resets the incorrect date format flag after a new attempt +// ResetIncorrectYob resets the incorrect date format flag after a new attempt. func (h *Handlers) ResetIncorrectYob(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -846,7 +859,7 @@ func (h *Handlers) ResetIncorrectYob(ctx context.Context, sym string, input []by } // CheckBalance retrieves the balance of the active voucher and sets -// the balance as the result content +// the balance as the result content. func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -896,9 +909,12 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) ( return res, nil } +// FetchCommunityBalance retrieves and displays the balance for community accounts in user's preferred language. func (h *Handlers) FetchCommunityBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result + // retrieve the language code from the context code := codeFromCtx(ctx) + // Initialize the localization system with the appropriate translation directory l := gotext.NewLocale(translationDir, code) l.AddDomain("default") //TODO: @@ -907,6 +923,10 @@ func (h *Handlers) FetchCommunityBalance(ctx context.Context, sym string, input return res, nil } +// ResetOthersPin handles the PIN reset process for other users' accounts by: +// 1. Retrieving the blocked phone number from the session +// 2. Fetching the temporary PIN associated with that number +// 3. Updating the account PIN with the temporary PIN func (h *Handlers) ResetOthersPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result store := h.userdataStore @@ -932,6 +952,8 @@ func (h *Handlers) ResetOthersPin(ctx context.Context, sym string, input []byte) return res, nil } +// ResetUnregisteredNumber clears the unregistered number flag in the system, +// indicating that a number's registration status should no longer be marked as unregistered. func (h *Handlers) ResetUnregisteredNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number") @@ -939,6 +961,8 @@ func (h *Handlers) ResetUnregisteredNumber(ctx context.Context, sym string, inpu return res, nil } +// ValidateBlockedNumber performs validation of phone numbers, specifically for blocked numbers in the system. +// It checks phone number format and verifies registration status. func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -1067,7 +1091,7 @@ func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []by } // TransactionReset resets the previous transaction data (Recipient and Amount) -// as well as the invalid flags +// as well as the invalid flags. func (h *Handlers) TransactionReset(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -1120,7 +1144,7 @@ func (h *Handlers) InviteValidRecipient(ctx context.Context, sym string, input [ return res, nil } -// ResetTransactionAmount resets the transaction amount and invalid flag +// ResetTransactionAmount resets the transaction amount and invalid flag. func (h *Handlers) ResetTransactionAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -1250,7 +1274,7 @@ func (h *Handlers) RetrieveBlockedNumber(ctx context.Context, sym string, input return res, nil } -// GetSender returns the sessionId (phoneNumber) +// GetSender returns the sessionId (phoneNumber). func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -1264,7 +1288,7 @@ func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (res return res, nil } -// GetAmount retrieves the amount from teh Gdbm Db +// GetAmount retrieves the amount from teh Gdbm Db. func (h *Handlers) GetAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -1288,7 +1312,7 @@ func (h *Handlers) GetAmount(ctx context.Context, sym string, input []byte) (res return res, nil } -// InitiateTransaction calls the TokenTransfer and returns a confirmation based on the result +// InitiateTransaction calls the TokenTransfer and returns a confirmation based on the result. func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -1338,6 +1362,8 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input [] return res, nil } +// GetCurrentProfileInfo retrieves specific profile fields based on the current state of the USSD session. +// Uses flag management system to track profile field status and handle menu navigation. func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var profileInfo []byte @@ -1357,6 +1383,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input if !ok { return res, fmt.Errorf("missing session") } + // Extract the field name from the state machine position. sm, _ := h.st.Where() parts := strings.SplitN(sm, "_", 2) filename := parts[1] @@ -1449,6 +1476,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input return res, nil } +// GetProfileInfo provides a comprehensive view of a user's profile. func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var defaultValue string @@ -1517,7 +1545,7 @@ func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte) } // SetDefaultVoucher retrieves the current vouchers -// and sets the first as the default voucher, if no active voucher is set +// and sets the first as the default voucher, if no active voucher is set. func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var err error @@ -1602,7 +1630,7 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by } // CheckVouchers retrieves the token holdings from the API using the "PublicKey" and stores -// them to gdbm +// them to gdbm. func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) @@ -1674,7 +1702,7 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte) return res, nil } -// GetVoucherList fetches the list of vouchers and formats them +// GetVoucherList fetches the list of vouchers and formats them. func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -1693,7 +1721,7 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte) } // ViewVoucher retrieves the token holding and balance from the subprefixDB -// and displays it to the user for them to select it +// and displays it to the user for them to select it. func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) @@ -1734,7 +1762,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r return res, nil } -// SetVoucher retrieves the temp voucher data and sets it as the active data +// SetVoucher retrieves the temp voucher data and sets it as the active data. func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -1760,7 +1788,7 @@ func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (re return res, nil } -// GetVoucherDetails retrieves the voucher details +// GetVoucherDetails retrieves the voucher details. func (h *Handlers) GetVoucherDetails(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result store := h.userdataStore @@ -1792,7 +1820,7 @@ func (h *Handlers) GetVoucherDetails(ctx context.Context, sym string, input []by return res, nil } -// CheckTransactions retrieves the transactions from the API using the "PublicKey" and stores to prefixDb +// CheckTransactions retrieves the transactions from the API using the "PublicKey" and stores to prefixDb. func (h *Handlers) CheckTransactions(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) @@ -1850,7 +1878,7 @@ func (h *Handlers) CheckTransactions(ctx context.Context, sym string, input []by return res, nil } -// GetTransactionsList reads the list of transactions from the db and formats them +// GetTransactionsList fetches the list of transactions and formats them. func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) @@ -1916,7 +1944,7 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input [] } // ViewTransactionStatement retrieves the transaction statement -// and displays it to the user +// and displays it to the user. func (h *Handlers) ViewTransactionStatement(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) @@ -1964,6 +1992,7 @@ func (h *Handlers) ViewTransactionStatement(ctx context.Context, sym string, inp return res, nil } +// handles bulk updates of profile information. func (h *Handlers) insertProfileItems(ctx context.Context, sessionId string, res *resource.Result) error { var err error store := h.userdataStore @@ -2000,7 +2029,7 @@ func (h *Handlers) insertProfileItems(ctx context.Context, sessionId string, res return nil } -// UpdateAllProfileItems is used to persist all the new profile information and setup the required profile flags +// UpdateAllProfileItems is used to persist all the new profile information and setup the required profile flags. func (h *Handlers) UpdateAllProfileItems(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result sessionId, ok := ctx.Value("SessionId").(string) From 5579991d6644c10f7aae014f1c9ddf3127138c90 Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Fri, 27 Dec 2024 10:07:05 +0300 Subject: [PATCH 26/71] guard profile update after being set --- internal/handlers/ussd/menuhandler.go | 47 ++++++++++++++------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 986f8f0..901f9b3 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -69,15 +69,15 @@ func (fm *FlagManager) GetFlag(label string) (uint32, error) { } type Handlers struct { - pe *persist.Persister - st *state.State - ca cache.Memory - userdataStore common.DataStore - adminstore *utils.AdminStore - flagManager *asm.FlagParser - accountService remote.AccountServiceInterface - prefixDb storage.PrefixDb - profile *models.Profile + pe *persist.Persister + st *state.State + ca cache.Memory + userdataStore common.DataStore + adminstore *utils.AdminStore + flagManager *asm.FlagParser + accountService remote.AccountServiceInterface + prefixDb storage.PrefixDb + profile *models.Profile ReplaceSeparatorFunc func(string) string } @@ -94,12 +94,12 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *util prefixDb := storage.NewSubPrefixDb(userdataStore, prefix) h := &Handlers{ - userdataStore: userDb, - flagManager: appFlags, - adminstore: adminstore, - accountService: accountService, - prefixDb: prefixDb, - profile: &models.Profile{Max: 6}, + userdataStore: userDb, + flagManager: appFlags, + adminstore: adminstore, + accountService: accountService, + prefixDb: prefixDb, + profile: &models.Profile{Max: 6}, ReplaceSeparatorFunc: replaceSeparatorFunc, } return h, nil @@ -1986,15 +1986,16 @@ func (h *Handlers) insertProfileItems(ctx context.Context, sessionId string, res for index, profileItem := range h.profile.ProfileItems { // Ensure the profileItem is not "0"(is set) if profileItem != "0" { - err = store.WriteEntry(ctx, sessionId, profileDataKeys[index], []byte(profileItem)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write profile entry with", "key", profileDataKeys[index], "value", profileItem, "error", err) - return err - } - - // Get the flag for the current index flag, _ := h.flagManager.GetFlag(profileFlagNames[index]) - res.FlagSet = append(res.FlagSet, flag) + isProfileItemSet := h.st.MatchFlag(flag, true) + if !isProfileItemSet { + err = store.WriteEntry(ctx, sessionId, profileDataKeys[index], []byte(profileItem)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write profile entry with", "key", profileDataKeys[index], "value", profileItem, "error", err) + return err + } + res.FlagSet = append(res.FlagSet, flag) + } } } return nil From 11eb61ba3524d6f3ee25e2e962a274986c1e58ce Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Fri, 27 Dec 2024 16:11:09 +0300 Subject: [PATCH 27/71] repeat same node on invalid selection --- services/registration/select_gender.vis | 1 + 1 file changed, 1 insertion(+) diff --git a/services/registration/select_gender.vis b/services/registration/select_gender.vis index c1a00f5..e41da10 100644 --- a/services/registration/select_gender.vis +++ b/services/registration/select_gender.vis @@ -11,3 +11,4 @@ INCMP _ 0 INCMP set_male 1 INCMP set_female 2 INCMP set_unspecified 3 +INCMP . * From 46edf2b819a250043d2c7fa99deafd6c34bcbd3c Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Fri, 27 Dec 2024 16:13:36 +0300 Subject: [PATCH 28/71] remove redundant catch on pin entry --- services/registration/edit_offerings.vis | 1 - 1 file changed, 1 deletion(-) diff --git a/services/registration/edit_offerings.vis b/services/registration/edit_offerings.vis index ddbc9e0..9c1e747 100644 --- a/services/registration/edit_offerings.vis +++ b/services/registration/edit_offerings.vis @@ -10,5 +10,4 @@ CATCH _ flag_back_set 1 RELOAD save_offerings INCMP _ 0 CATCH pin_entry flag_offerings_set 1 -CATCH pin_entry flag_offerings_set 0 INCMP update_profile_items * From 58a60f2c81efa3c5971748ab81dd31519da5adce Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Sat, 28 Dec 2024 08:47:51 +0300 Subject: [PATCH 29/71] update expected age in test --- menutraversal_test/group_test.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/menutraversal_test/group_test.json b/menutraversal_test/group_test.json index 8f43ff5..5ffe615 100644 --- a/menutraversal_test/group_test.json +++ b/menutraversal_test/group_test.json @@ -430,7 +430,7 @@ }, { "input": "1234", - "expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 84\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back" + "expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 79\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back" }, { "input": "0", From 50c006546cc88138416f88b6fa0b041cf792d24c Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Sat, 28 Dec 2024 13:21:03 +0300 Subject: [PATCH 30/71] added code to reset the state and persist it --- devtools/restart_state/main.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/devtools/restart_state/main.go b/devtools/restart_state/main.go index 3068a38..5df34fd 100644 --- a/devtools/restart_state/main.go +++ b/devtools/restart_state/main.go @@ -53,25 +53,24 @@ func main() { os.Exit(1) } - // initialize the persister - - // get the state - - // restart the state - - // persist the state - - // exit - st := pe.GetState() if st == nil { logg.ErrorCtxf(ctx, "state fail in devtool", "state", st) - fmt.Errorf("cannot get state") + fmt.Println("cannot get state") os.Exit(1) } - st.Restart() + fmt.Println("The state:", st) - os.Exit(1) + // set empty Code to allow the menu to run from the top + st.Code = []byte{} + + err = pe.Save(sessionId) + if err != nil { + logg.ErrorCtxf(ctx, "failed to persist the state and cache", "error", err) + os.Exit(1) + } + + os.Exit(0) } From 0a332ec5012ba3b03027bbb72af5f470a560b83f Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Mon, 30 Dec 2024 08:05:36 +0300 Subject: [PATCH 31/71] chore: ensure swahili language translation. --- internal/handlers/ussd/menuhandler.go | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 7a188c6..6b6407b 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -1367,6 +1367,7 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input [] func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result var profileInfo []byte + var defaultValue string var err error flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set") @@ -1383,7 +1384,18 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input if !ok { return res, fmt.Errorf("missing session") } - // Extract the field name from the state machine position. + language, ok := ctx.Value("Language").(lang.Language) + if !ok { + return res, fmt.Errorf("value for 'Language' is not of type lang.Language") + } + code := language.Code + if code == "swa" { + defaultValue = "Haipo" + } else { + defaultValue = "Not Provided" + } + + sm, _ := h.st.Where() parts := strings.SplitN(sm, "_", 2) filename := parts[1] @@ -1400,7 +1412,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_FIRST_NAME) if err != nil { if db.IsNotFound(err) { - res.Content = "Not provided" + res.Content = defaultValue break } logg.ErrorCtxf(ctx, "Failed to read first name entry with", "key", "error", common.DATA_FIRST_NAME, err) @@ -1412,7 +1424,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME) if err != nil { if db.IsNotFound(err) { - res.Content = "Not provided" + res.Content = defaultValue break } logg.ErrorCtxf(ctx, "Failed to read family name entry with", "key", "error", common.DATA_FAMILY_NAME, err) @@ -1425,7 +1437,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_GENDER) if err != nil { if db.IsNotFound(err) { - res.Content = "Not provided" + res.Content = defaultValue break } logg.ErrorCtxf(ctx, "Failed to read gender entry with", "key", "error", common.DATA_GENDER, err) @@ -1437,7 +1449,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_YOB) if err != nil { if db.IsNotFound(err) { - res.Content = "Not provided" + res.Content = defaultValue break } logg.ErrorCtxf(ctx, "Failed to read year of birth(yob) entry with", "key", "error", common.DATA_YOB, err) @@ -1449,7 +1461,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_LOCATION) if err != nil { if db.IsNotFound(err) { - res.Content = "Not provided" + res.Content = defaultValue break } logg.ErrorCtxf(ctx, "Failed to read location entry with", "key", "error", common.DATA_LOCATION, err) @@ -1461,7 +1473,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS) if err != nil { if db.IsNotFound(err) { - res.Content = "Not provided" + res.Content = defaultValue break } logg.ErrorCtxf(ctx, "Failed to read offerings entry with", "key", "error", common.DATA_OFFERINGS, err) From db7c9bf56d4f7c173b30947d6c6ec2b511a39acc Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Mon, 30 Dec 2024 08:07:39 +0300 Subject: [PATCH 32/71] chore: add colon to enhance formatting. --- services/registration/edit_first_name_swa | 2 +- services/registration/edit_location_swa | 2 +- services/registration/edit_yob_swa | 2 +- services/registration/select_gender_swa | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/registration/edit_first_name_swa b/services/registration/edit_first_name_swa index 3fdd986..5776bf0 100644 --- a/services/registration/edit_first_name_swa +++ b/services/registration/edit_first_name_swa @@ -1,2 +1,2 @@ -Jina la kwanza la sasa {{.get_current_profile_info}} +Jina la kwanza la sasa: {{.get_current_profile_info}} Weka majina yako ya kwanza: \ No newline at end of file diff --git a/services/registration/edit_location_swa b/services/registration/edit_location_swa index 0a3476e..179c421 100644 --- a/services/registration/edit_location_swa +++ b/services/registration/edit_location_swa @@ -1,2 +1,2 @@ -Eneo la sasa {{.get_current_profile_info}} +Eneo la sasa: {{.get_current_profile_info}} Weka eneo: \ No newline at end of file diff --git a/services/registration/edit_yob_swa b/services/registration/edit_yob_swa index e0b5708..f923c86 100644 --- a/services/registration/edit_yob_swa +++ b/services/registration/edit_yob_swa @@ -1,2 +1,2 @@ -Mwaka wa sasa wa kuzaliwa {{.get_current_profile_info}} +Mwaka wa sasa wa kuzaliwa: {{.get_current_profile_info}} Weka mwaka wa kuzaliwa \ No newline at end of file diff --git a/services/registration/select_gender_swa b/services/registration/select_gender_swa index b077a0b..39d99d5 100644 --- a/services/registration/select_gender_swa +++ b/services/registration/select_gender_swa @@ -1,2 +1,2 @@ -Jinsia ya sasa {{.get_current_profile_info}} +Jinsia ya sasa: {{.get_current_profile_info}} Chagua jinsia \ No newline at end of file From 0e12c0ee4e7c8371133f39f5295b272609949f26 Mon Sep 17 00:00:00 2001 From: lash Date: Mon, 30 Dec 2024 19:35:45 +0000 Subject: [PATCH 33/71] Add prefix for dumper, format base dump key for pg --- devtools/store/main.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/devtools/store/main.go b/devtools/store/main.go index 9262703..908e321 100644 --- a/devtools/store/main.go +++ b/devtools/store/main.go @@ -11,6 +11,7 @@ import ( "git.grassecon.net/urdt/ussd/initializers" "git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/debug" + "git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/logging" ) @@ -47,13 +48,14 @@ func main() { store, err := menuStorageService.GetUserdataDb(ctx) if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) + fmt.Fprintf(os.Stderr, "get userdata db: %v\n", err.Error()) os.Exit(1) } + store.SetPrefix(db.DATATYPE_USERDATA) d, err := store.Dump(ctx, []byte(sessionId)) if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) + fmt.Fprintf(os.Stderr, "store dump fail: %v\n", err.Error()) os.Exit(1) } From 3bcd48e5a7849f58032251bd34ff15a0bbb1fd18 Mon Sep 17 00:00:00 2001 From: lash Date: Mon, 30 Dec 2024 19:58:34 +0000 Subject: [PATCH 34/71] Update govise --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e1b7ddb..7b7bff3 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.grassecon.net/urdt/ussd go 1.23.0 require ( - git.defalsify.org/vise.git v0.2.1-0.20241212145627-683015d4df80 + git.defalsify.org/vise.git v0.2.3-0.20241230195348-d746f4696a2d github.com/alecthomas/assert/v2 v2.2.2 github.com/gofrs/uuid v4.4.0+incompatible github.com/grassrootseconomics/eth-custodial v1.3.0-beta diff --git a/go.sum b/go.sum index ef7b782..d6dc755 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.defalsify.org/vise.git v0.2.1-0.20241212145627-683015d4df80 h1:GYUVXRUtMpA40T4COeAduoay6CIgXjD5cfDYZOTFIKw= -git.defalsify.org/vise.git v0.2.1-0.20241212145627-683015d4df80/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= +git.defalsify.org/vise.git v0.2.3-0.20241230195348-d746f4696a2d h1:suOILWHLMQCRc5K3BuzOF8B3wRr3kvJwZFqzGthT/lM= +git.defalsify.org/vise.git v0.2.3-0.20241230195348-d746f4696a2d/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= From 1311a0cab9980e32f3085a69da609596217ab0a5 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 31 Dec 2024 02:36:28 +0300 Subject: [PATCH 35/71] use the 'send_with_invite' test group in the menu traversal test --- menutraversal_test/menu_traversal_test.go | 5 +++-- menutraversal_test/test_setup.json | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/menutraversal_test/menu_traversal_test.go b/menutraversal_test/menu_traversal_test.go index 28d88db..6b6b3da 100644 --- a/menutraversal_test/menu_traversal_test.go +++ b/menutraversal_test/menu_traversal_test.go @@ -298,9 +298,10 @@ func TestMainMenuSend(t *testing.T) { ctx := context.Background() sessions := testData for _, session := range sessions { - groups := driver.FilterGroupsByName(session.Groups, "send_with_invalid_inputs") + groups := driver.FilterGroupsByName(session.Groups, "send_with_invite") for _, group := range groups { - for _, step := range group.Steps { + for index, step := range group.Steps { + t.Logf("step %v with input %v", index, step.Input) cont, err := en.Exec(ctx, []byte(step.Input)) if err != nil { t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err) diff --git a/menutraversal_test/test_setup.json b/menutraversal_test/test_setup.json index c5860b4..5115de9 100644 --- a/menutraversal_test/test_setup.json +++ b/menutraversal_test/test_setup.json @@ -64,8 +64,8 @@ "expectedContent": "Enter recipient's phone number/address/alias:\n0:Back" }, { - "input": "000", - "expectedContent": "000 is invalid, please try again:\n1:Retry\n9:Quit" + "input": "0@0", + "expectedContent": "0@0 is invalid, please try again:\n1:Retry\n9:Quit" }, { "input": "1", From 3c73fc718896a12f959a6496af287206bcf6deff Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 31 Dec 2024 05:05:39 +0300 Subject: [PATCH 36/71] added a test for the Init func with the different states --- internal/handlers/ussd/menuhandler_test.go | 101 +++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index c01678d..54b1581 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "git.defalsify.org/vise.git/cache" "git.defalsify.org/vise.git/lang" "git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/resource" @@ -15,6 +16,7 @@ import ( "git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/testutil/mocks" "git.grassecon.net/urdt/ussd/internal/testutil/testservice" + "git.grassecon.net/urdt/ussd/internal/utils" "git.grassecon.net/urdt/ussd/models" "git.grassecon.net/urdt/ussd/common" @@ -119,6 +121,105 @@ func TestNewHandlers(t *testing.T) { }) } +func TestInit(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestStore(t) + ctx = context.WithValue(ctx, "SessionId", sessionId) + + fm, err := NewFlagManager(flagsPath) + if err != nil { + t.Fatal(err.Error()) + } + + adminstore, err := utils.NewAdminStore(ctx, "admin_numbers") + if err != nil { + t.Fatal(err.Error()) + } + + st := state.NewState(128) + ca := cache.NewCache() + + flag_admin_privilege, _ := fm.GetFlag("flag_admin_privilege") + + nonAdminExpectedResult := resource.Result{} + nonAdminExpectedResult.FlagReset = []uint32{flag_admin_privilege} + + tests := []struct { + name string + setup func() (*Handlers, context.Context) + input []byte + expectedResult resource.Result + }{ + { + name: "Handler not ready", + setup: func() (*Handlers, context.Context) { + return &Handlers{}, ctx + }, + input: []byte("1"), + expectedResult: resource.Result{}, + }, + { + name: "State and memory initialization", + setup: func() (*Handlers, context.Context) { + pe := persist.NewPersister(store).WithSession(sessionId).WithContent(st, ca) + h := &Handlers{ + flagManager: fm.parser, + adminstore: adminstore, + pe: pe, + } + return h, context.WithValue(ctx, "SessionId", sessionId) + }, + input: []byte("1"), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_admin_privilege}, + }, + }, + { + name: "Non-admin session initialization", + setup: func() (*Handlers, context.Context) { + pe := persist.NewPersister(store).WithSession("0712345678").WithContent(st, ca) + h := &Handlers{ + flagManager: fm.parser, + adminstore: adminstore, + pe: pe, + } + return h, context.WithValue(context.Background(), "SessionId", "0712345678") + }, + input: []byte("1"), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_admin_privilege}, + }, + }, + { + name: "Move to top node on empty input", + setup: func() (*Handlers, context.Context) { + pe := persist.NewPersister(store).WithSession(sessionId).WithContent(st, ca) + h := &Handlers{ + flagManager: fm.parser, + adminstore: adminstore, + pe: pe, + } + st.Code = []byte("some pending bytecode") + return h, context.WithValue(ctx, "SessionId", sessionId) + }, + input: []byte(""), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_admin_privilege}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h, testCtx := tt.setup() + res, err := h.Init(testCtx, "", tt.input) + + assert.NoError(t, err, "Unexpected error occurred") + assert.Equal(t, res, tt.expectedResult, "Expected result should match actual result") + }) + } +} + func TestCreateAccount(t *testing.T) { sessionId := "session123" ctx, store := InitializeTestStore(t) From be2ea3a2f0fbdf79bfd9d28f2e2b4f09ca37ccb5 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 31 Dec 2024 10:51:29 +0300 Subject: [PATCH 37/71] removed the non-working restart_state devtool --- devtools/restart_state/main.go | 76 ---------------------------------- 1 file changed, 76 deletions(-) delete mode 100644 devtools/restart_state/main.go diff --git a/devtools/restart_state/main.go b/devtools/restart_state/main.go deleted file mode 100644 index 5df34fd..0000000 --- a/devtools/restart_state/main.go +++ /dev/null @@ -1,76 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "os" - "path" - - "git.defalsify.org/vise.git/logging" - "git.grassecon.net/urdt/ussd/config" - "git.grassecon.net/urdt/ussd/initializers" - "git.grassecon.net/urdt/ussd/internal/storage" -) - -var ( - logg = logging.NewVanilla() - scriptDir = path.Join("services", "registration") -) - -func init() { - initializers.LoadEnvVariables() -} - -func main() { - config.LoadConfig() - - var dbDir string - var sessionId string - var database string - - flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") - flag.StringVar(&database, "db", "gdbm", "database to be used") - flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") - flag.Parse() - - ctx := context.Background() - ctx = context.WithValue(ctx, "SessionId", sessionId) - ctx = context.WithValue(ctx, "Database", database) - - resourceDir := scriptDir - menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir) - - err := menuStorageService.EnsureDbDir() - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - os.Exit(1) - } - - pe, err := menuStorageService.GetPersister(ctx) - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - os.Exit(1) - } - - st := pe.GetState() - - if st == nil { - logg.ErrorCtxf(ctx, "state fail in devtool", "state", st) - fmt.Println("cannot get state") - os.Exit(1) - } - - fmt.Println("The state:", st) - - // set empty Code to allow the menu to run from the top - st.Code = []byte{} - - err = pe.Save(sessionId) - if err != nil { - logg.ErrorCtxf(ctx, "failed to persist the state and cache", "error", err) - os.Exit(1) - } - - os.Exit(0) -} From 4ea52bf3fbb0f605d1296c710fd6a823fa8c4bad Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 31 Dec 2024 11:16:43 +0300 Subject: [PATCH 38/71] removed unused code --- internal/handlers/ussd/menuhandler_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 54b1581..34c8e76 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -141,9 +141,6 @@ func TestInit(t *testing.T) { flag_admin_privilege, _ := fm.GetFlag("flag_admin_privilege") - nonAdminExpectedResult := resource.Result{} - nonAdminExpectedResult.FlagReset = []uint32{flag_admin_privilege} - tests := []struct { name string setup func() (*Handlers, context.Context) From c7dbe1d88f4174ec1b54d71f4b23980105a96668 Mon Sep 17 00:00:00 2001 From: lash Date: Tue, 31 Dec 2024 08:30:08 +0000 Subject: [PATCH 39/71] Remove obsolete subprefix strings --- debug/db_debug.go | 4 ---- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/debug/db_debug.go b/debug/db_debug.go index ed2dd66..05a238b 100644 --- a/debug/db_debug.go +++ b/debug/db_debug.go @@ -11,13 +11,9 @@ import ( func init() { DebugCap |= 1 dbTypStr[db.DATATYPE_STATE] = "internal state" - dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACCOUNT] = "account" - dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACCOUNT_CREATED] = "account created" dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TRACKING_ID] = "tracking id" dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_PUBLIC_KEY] = "public key" - dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_CUSTODIAL_ID] = "custodial id" dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACCOUNT_PIN] = "account pin" - dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACCOUNT_STATUS] = "account status" dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_FIRST_NAME] = "first name" dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_FAMILY_NAME] = "family name" dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_YOB] = "year of birth" diff --git a/go.mod b/go.mod index 7b7bff3..a423398 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.grassecon.net/urdt/ussd go 1.23.0 require ( - git.defalsify.org/vise.git v0.2.3-0.20241230195348-d746f4696a2d + git.defalsify.org/vise.git v0.2.3-0.20241231082805-35b41792f61b github.com/alecthomas/assert/v2 v2.2.2 github.com/gofrs/uuid v4.4.0+incompatible github.com/grassrootseconomics/eth-custodial v1.3.0-beta diff --git a/go.sum b/go.sum index d6dc755..95b4843 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.defalsify.org/vise.git v0.2.3-0.20241230195348-d746f4696a2d h1:suOILWHLMQCRc5K3BuzOF8B3wRr3kvJwZFqzGthT/lM= -git.defalsify.org/vise.git v0.2.3-0.20241230195348-d746f4696a2d/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= +git.defalsify.org/vise.git v0.2.3-0.20241231082805-35b41792f61b h1:s3tDS07BRCyCIxH8e9eTAsf5uqYIVGmvAmwpsT5IYT0= +git.defalsify.org/vise.git v0.2.3-0.20241231082805-35b41792f61b/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= From 7a535f796a79f39955f702ce2bfb2093f8f03d0e Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 31 Dec 2024 11:41:04 +0300 Subject: [PATCH 40/71] output the value as a string --- devtools/store/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/store/main.go b/devtools/store/main.go index 908e321..8bd4d16 100644 --- a/devtools/store/main.go +++ b/devtools/store/main.go @@ -69,7 +69,7 @@ func main() { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) } - fmt.Printf("%vValue: %v\n\n", o, v) + fmt.Printf("%vValue: %v\n\n", o, string(v)) } err = store.Close() From cd58f5ae33b4679d9c9a2a133ffdce36b72ead76 Mon Sep 17 00:00:00 2001 From: lash Date: Tue, 31 Dec 2024 08:55:25 +0000 Subject: [PATCH 41/71] Upgrade govise --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a423398..16ccdc3 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.grassecon.net/urdt/ussd go 1.23.0 require ( - git.defalsify.org/vise.git v0.2.3-0.20241231082805-35b41792f61b + git.defalsify.org/vise.git v0.2.3-0.20241231085136-8582c7e157d9 github.com/alecthomas/assert/v2 v2.2.2 github.com/gofrs/uuid v4.4.0+incompatible github.com/grassrootseconomics/eth-custodial v1.3.0-beta diff --git a/go.sum b/go.sum index 95b4843..9086cd8 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.defalsify.org/vise.git v0.2.3-0.20241231082805-35b41792f61b h1:s3tDS07BRCyCIxH8e9eTAsf5uqYIVGmvAmwpsT5IYT0= -git.defalsify.org/vise.git v0.2.3-0.20241231082805-35b41792f61b/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= +git.defalsify.org/vise.git v0.2.3-0.20241231085136-8582c7e157d9 h1:O3m+NgWDWtJm8OculT99c4bDMAO4xLe2c8hpCKpsd9g= +git.defalsify.org/vise.git v0.2.3-0.20241231085136-8582c7e157d9/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= From ffd5be1f1f37b3679ff4a453ffde5977489f7212 Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Thu, 2 Jan 2025 12:12:52 +0300 Subject: [PATCH 42/71] add quit option on view profile --- services/registration/view_profile.vis | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/registration/view_profile.vis b/services/registration/view_profile.vis index a7ffee4..4f4947c 100644 --- a/services/registration/view_profile.vis +++ b/services/registration/view_profile.vis @@ -4,5 +4,8 @@ LOAD reset_incorrect 6 CATCH incorrect_pin flag_incorrect_pin 1 CATCH pin_entry flag_account_authorized 0 MOUT back 0 +MOUT quit 9 HALT INCMP _ 0 +INCMP quit 9 +INCMP . * From e980586910861e4b316c7f8d1f1c09464403a7a1 Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Thu, 2 Jan 2025 12:15:57 +0300 Subject: [PATCH 43/71] chore: repeat same node on invalid menu choice --- services/registration/balances.vis | 1 + services/registration/community_balance.vis | 1 + services/registration/edit_profile.vis | 1 + services/registration/my_account.vis | 1 + services/registration/my_balance.vis | 1 + 5 files changed, 5 insertions(+) diff --git a/services/registration/balances.vis b/services/registration/balances.vis index aef397f..9a346d5 100644 --- a/services/registration/balances.vis +++ b/services/registration/balances.vis @@ -7,3 +7,4 @@ HALT INCMP _ 0 INCMP my_balance 1 INCMP community_balance 2 +INCMP . * diff --git a/services/registration/community_balance.vis b/services/registration/community_balance.vis index f3e0ae1..fad90cc 100644 --- a/services/registration/community_balance.vis +++ b/services/registration/community_balance.vis @@ -9,3 +9,4 @@ MOUT quit 9 HALT INCMP _ 0 INCMP quit 9 +INCMP . * diff --git a/services/registration/edit_profile.vis b/services/registration/edit_profile.vis index e5ee12b..0f0adb7 100644 --- a/services/registration/edit_profile.vis +++ b/services/registration/edit_profile.vis @@ -20,3 +20,4 @@ INCMP edit_yob 4 INCMP edit_location 5 INCMP edit_offerings 6 INCMP view_profile 7 +INCMP . * diff --git a/services/registration/my_account.vis b/services/registration/my_account.vis index 2b6289e..e3956d2 100644 --- a/services/registration/my_account.vis +++ b/services/registration/my_account.vis @@ -14,3 +14,4 @@ INCMP balances 3 INCMP check_statement 4 INCMP pin_management 5 INCMP address 6 +INCMP . * diff --git a/services/registration/my_balance.vis b/services/registration/my_balance.vis index 9144da9..b6094c0 100644 --- a/services/registration/my_balance.vis +++ b/services/registration/my_balance.vis @@ -9,3 +9,4 @@ MOUT quit 9 HALT INCMP _ 0 INCMP quit 9 +INCMP . * From e666c586444bd89cb298cee8bbb589e83ce0aa37 Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Thu, 2 Jan 2025 12:17:28 +0300 Subject: [PATCH 44/71] start primary selectors with 1 --- services/registration/change_language.vis | 8 ++++---- services/registration/select_language.vis | 8 ++++---- services/registration/terms.vis | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/services/registration/change_language.vis b/services/registration/change_language.vis index 05ca95b..8b1def9 100644 --- a/services/registration/change_language.vis +++ b/services/registration/change_language.vis @@ -2,9 +2,9 @@ LOAD reset_account_authorized 0 LOAD reset_incorrect 0 CATCH incorrect_pin flag_incorrect_pin 1 CATCH pin_entry flag_account_authorized 0 -MOUT english 0 -MOUT kiswahili 1 +MOUT english 1 +MOUT kiswahili 2 HALT -INCMP set_default 0 -INCMP set_swa 1 +INCMP set_default 1 +INCMP set_swa 2 INCMP . * diff --git a/services/registration/select_language.vis b/services/registration/select_language.vis index 54f08e9..0f7f298 100644 --- a/services/registration/select_language.vis +++ b/services/registration/select_language.vis @@ -1,6 +1,6 @@ -MOUT english 0 -MOUT kiswahili 1 +MOUT english 1 +MOUT kiswahili 2 HALT -INCMP set_eng 0 -INCMP set_swa 1 +INCMP set_eng 1 +INCMP set_swa 2 INCMP . * diff --git a/services/registration/terms.vis b/services/registration/terms.vis index f04bdf4..372b6ca 100644 --- a/services/registration/terms.vis +++ b/services/registration/terms.vis @@ -1,5 +1,5 @@ -MOUT yes 0 -MOUT no 1 +MOUT yes 1 +MOUT no 2 HALT -INCMP create_pin 0 +INCMP create_pin 1 INCMP quit * From 48d63fb43ff57ea7dc16c24f1ac3b7a715d49958 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 2 Jan 2025 13:16:38 +0300 Subject: [PATCH 45/71] added pin.go to contain all PIN related functionality --- common/pin.go | 14 ++++++++++++++ internal/handlers/ussd/menuhandler.go | 16 ++-------------- internal/handlers/ussd/menuhandler_test.go | 4 ++-- 3 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 common/pin.go diff --git a/common/pin.go b/common/pin.go new file mode 100644 index 0000000..4d46f12 --- /dev/null +++ b/common/pin.go @@ -0,0 +1,14 @@ +package common + +import "regexp" + +// Define the regex pattern as a constant +const ( + pinPattern = `^\d{4}$` +) + +// checks whether the given input is a 4 digit number +func IsValidPIN(pin string) bool { + match, _ := regexp.MatchString(pinPattern, pin) + return match +} diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 640517f..c20d75b 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "path" - "regexp" "strconv" "strings" @@ -34,17 +33,6 @@ var ( translationDir = path.Join(scriptDir, "locale") ) -// Define the regex patterns as constants -const ( - pinPattern = `^\d{4}$` -) - -// checks whether the given input is a 4 digit number -func isValidPIN(pin string) bool { - match, _ := regexp.MatchString(pinPattern, pin) - return match -} - // FlagManager handles centralized flag management type FlagManager struct { parser *asm.FlagParser @@ -281,7 +269,7 @@ func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) ( flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin") pinInput := string(input) // Validate that the PIN is a 4-digit number. - if isValidPIN(pinInput) { + if common.IsValidPIN(pinInput) { res.FlagSet = append(res.FlagSet, flag_valid_pin) } else { res.FlagReset = append(res.FlagReset, flag_valid_pin) @@ -306,7 +294,7 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt accountPIN := string(input) // Validate that the PIN is a 4-digit number. - if !isValidPIN(accountPIN) { + if !common.IsValidPIN(accountPIN) { res.FlagSet = append(res.FlagSet, flag_incorrect_pin) return res, nil } diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 34c8e76..0d66f72 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -1544,9 +1544,9 @@ func TestIsValidPIN(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - actual := isValidPIN(tt.pin) + actual := common.IsValidPIN(tt.pin) if actual != tt.expected { - t.Errorf("isValidPIN(%q) = %v; expected %v", tt.pin, actual, tt.expected) + t.Errorf("IsValidPIN(%q) = %v; expected %v", tt.pin, actual, tt.expected) } }) } From 5ca6a74274931fd49f8081a9fa3e68ee524a1eb3 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 2 Jan 2025 13:18:49 +0300 Subject: [PATCH 46/71] move PIN test to the common package --- common/pin_test.go | 56 ++++++++++++++++++++++ internal/handlers/ussd/menuhandler_test.go | 53 -------------------- 2 files changed, 56 insertions(+), 53 deletions(-) create mode 100644 common/pin_test.go diff --git a/common/pin_test.go b/common/pin_test.go new file mode 100644 index 0000000..9012024 --- /dev/null +++ b/common/pin_test.go @@ -0,0 +1,56 @@ +package common + +import "testing" + +func TestIsValidPIN(t *testing.T) { + tests := []struct { + name string + pin string + expected bool + }{ + { + name: "Valid PIN with 4 digits", + pin: "1234", + expected: true, + }, + { + name: "Valid PIN with leading zeros", + pin: "0001", + expected: true, + }, + { + name: "Invalid PIN with less than 4 digits", + pin: "123", + expected: false, + }, + { + name: "Invalid PIN with more than 4 digits", + pin: "12345", + expected: false, + }, + { + name: "Invalid PIN with letters", + pin: "abcd", + expected: false, + }, + { + name: "Invalid PIN with special characters", + pin: "12@#", + expected: false, + }, + { + name: "Empty PIN", + pin: "", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := IsValidPIN(tt.pin) + if actual != tt.expected { + t.Errorf("IsValidPIN(%q) = %v; expected %v", tt.pin, actual, tt.expected) + } + }) + } +} diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 0d66f72..9b3dc7b 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -1499,59 +1499,6 @@ func TestQuit(t *testing.T) { } } -func TestIsValidPIN(t *testing.T) { - tests := []struct { - name string - pin string - expected bool - }{ - { - name: "Valid PIN with 4 digits", - pin: "1234", - expected: true, - }, - { - name: "Valid PIN with leading zeros", - pin: "0001", - expected: true, - }, - { - name: "Invalid PIN with less than 4 digits", - pin: "123", - expected: false, - }, - { - name: "Invalid PIN with more than 4 digits", - pin: "12345", - expected: false, - }, - { - name: "Invalid PIN with letters", - pin: "abcd", - expected: false, - }, - { - name: "Invalid PIN with special characters", - pin: "12@#", - expected: false, - }, - { - name: "Empty PIN", - pin: "", - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - actual := common.IsValidPIN(tt.pin) - if actual != tt.expected { - t.Errorf("IsValidPIN(%q) = %v; expected %v", tt.pin, actual, tt.expected) - } - }) - } -} - func TestValidateAmount(t *testing.T) { fm, err := NewFlagManager(flagsPath) if err != nil { From c899c098f62994536f6952626c3feb7e5d041ab7 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 2 Jan 2025 13:20:01 +0300 Subject: [PATCH 47/71] updated the expected age --- internal/handlers/ussd/menuhandler_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 9b3dc7b..43f80a2 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -1745,7 +1745,7 @@ func TestGetProfile(t *testing.T) { result: resource.Result{ Content: fmt.Sprintf( "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n", - "John Doee", "Male", "48", "Kilifi", "Bananas", + "John Doee", "Male", "49", "Kilifi", "Bananas", ), }, }, @@ -1757,7 +1757,7 @@ func TestGetProfile(t *testing.T) { result: resource.Result{ Content: fmt.Sprintf( "Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\n", - "John Doee", "Male", "48", "Kilifi", "Bananas", + "John Doee", "Male", "49", "Kilifi", "Bananas", ), }, }, @@ -1769,7 +1769,7 @@ func TestGetProfile(t *testing.T) { result: resource.Result{ Content: fmt.Sprintf( "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n", - "John Doee", "Male", "48", "Kilifi", "Bananas", + "John Doee", "Male", "49", "Kilifi", "Bananas", ), }, }, From fd1ac85a1b2cbcdb233df8dc9b34cbfdf3e4de37 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 2 Jan 2025 13:43:38 +0300 Subject: [PATCH 48/71] add code to Hash and Verify the PIN --- common/pin.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/common/pin.go b/common/pin.go index 4d46f12..6db9d15 100644 --- a/common/pin.go +++ b/common/pin.go @@ -1,6 +1,10 @@ package common -import "regexp" +import ( + "regexp" + + "golang.org/x/crypto/bcrypt" +) // Define the regex pattern as a constant const ( @@ -12,3 +16,18 @@ func IsValidPIN(pin string) bool { match, _ := regexp.MatchString(pinPattern, pin) return match } + +// HashPIN uses bcrypt with 8 salt rounds to hash the PIN +func HashPIN(pin string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(pin), 8) + if err != nil { + return "", err + } + return string(hash), nil +} + +// VerifyPIN compareS the hashed PIN with the plaintext PIN +func VerifyPIN(hashedPIN, pin string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hashedPIN), []byte(pin)) + return err == nil +} From d95c7abea46dd84049262571ab9481922e089c33 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 2 Jan 2025 13:45:18 +0300 Subject: [PATCH 49/71] return if the PIN is not a match, and hash the PIN before saving it --- internal/handlers/ussd/menuhandler.go | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index c20d75b..645e74c 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -356,11 +356,19 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt res.FlagReset = append(res.FlagReset, flag_pin_mismatch) } else { res.FlagSet = append(res.FlagSet, flag_pin_mismatch) + return res, nil } - // If matched, save the confirmed PIN as the new account PIN - err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(temporaryPin)) + + // Hash the PIN + hashedPIN, err := common.HashPIN(string(temporaryPin)) if err != nil { - logg.ErrorCtxf(ctx, "failed to write temporaryPin entry with", "key", common.DATA_ACCOUNT_PIN, "value", temporaryPin, "error", err) + logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err) + } + + // save the hashed PIN as the new account PIN + err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(hashedPIN)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write DATA_ACCOUNT_PIN entry with", "key", common.DATA_ACCOUNT_PIN, "hashedPIN value", hashedPIN, "error", err) return res, err } return res, nil @@ -392,11 +400,18 @@ func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte res.FlagSet = append(res.FlagSet, flag_pin_set) } else { res.FlagSet = []uint32{flag_pin_mismatch} + return res, nil } - err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(temporaryPin)) + // Hash the PIN + hashedPIN, err := common.HashPIN(string(temporaryPin)) if err != nil { - logg.ErrorCtxf(ctx, "failed to write temporaryPin entry with", "key", common.DATA_ACCOUNT_PIN, "value", temporaryPin, "error", err) + logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err) + } + + err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(hashedPIN)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write DATA_ACCOUNT_PIN entry with", "key", common.DATA_ACCOUNT_PIN, "value", hashedPIN, "error", err) return res, err } From 99a4d3ff421f7da6c22e3bba7d155b6f0b82e982 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 2 Jan 2025 13:49:57 +0300 Subject: [PATCH 50/71] verify the PIN input against the hashed PIN --- internal/handlers/ussd/menuhandler.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 645e74c..0829011 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -725,7 +725,7 @@ func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (res return res, err } if len(input) == 4 { - if bytes.Equal(input, AccountPin) { + if common.VerifyPIN(string(AccountPin), string(input)) { if h.st.MatchFlag(flag_account_authorized, false) { res.FlagReset = append(res.FlagReset, flag_incorrect_pin) res.FlagSet = append(res.FlagSet, flag_allow_update, flag_account_authorized) @@ -1403,7 +1403,6 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input defaultValue = "Not Provided" } - sm, _ := h.st.Where() parts := strings.SplitN(sm, "_", 2) filename := parts[1] From 98db85511be78d3238f80fb74d77817ef8b799d2 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 2 Jan 2025 14:37:45 +0300 Subject: [PATCH 51/71] hash the PIN in the ResetOthersPin function --- internal/handlers/ussd/menuhandler.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 0829011..3919595 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -363,6 +363,7 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt hashedPIN, err := common.HashPIN(string(temporaryPin)) if err != nil { logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err) + return res, err } // save the hashed PIN as the new account PIN @@ -407,6 +408,7 @@ func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte hashedPIN, err := common.HashPIN(string(temporaryPin)) if err != nil { logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err) + return res, err } err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(hashedPIN)) @@ -952,7 +954,15 @@ func (h *Handlers) ResetOthersPin(ctx context.Context, sym string, input []byte) logg.ErrorCtxf(ctx, "failed to read temporaryPin entry with", "key", common.DATA_TEMPORARY_VALUE, "error", err) return res, err } - err = store.WriteEntry(ctx, string(blockedPhonenumber), common.DATA_ACCOUNT_PIN, []byte(temporaryPin)) + + // Hash the PIN + hashedPIN, err := common.HashPIN(string(temporaryPin)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err) + return res, err + } + + err = store.WriteEntry(ctx, string(blockedPhonenumber), common.DATA_ACCOUNT_PIN, []byte(hashedPIN)) if err != nil { return res, nil } From 82b4365d161ab9e16e0506f61439f4c124a6be96 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 2 Jan 2025 14:38:22 +0300 Subject: [PATCH 52/71] hash the PIN in TestAuthorize --- internal/handlers/ussd/menuhandler_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 43f80a2..12ed5c2 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -1047,7 +1047,14 @@ func TestAuthorize(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(accountPIN)) + // Hash the PIN + hashedPIN, err := common.HashPIN(accountPIN) + if err != nil { + logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err) + t.Fatal(err) + } + + err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(hashedPIN)) if err != nil { t.Fatal(err) } From ca8df5989a4eac8a723c8d2b1bf1c580d326c3dc Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 2 Jan 2025 15:15:52 +0300 Subject: [PATCH 53/71] updated expected age in test --- menutraversal_test/group_test.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/menutraversal_test/group_test.json b/menutraversal_test/group_test.json index 5ffe615..ac12de3 100644 --- a/menutraversal_test/group_test.json +++ b/menutraversal_test/group_test.json @@ -430,7 +430,7 @@ }, { "input": "1234", - "expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 79\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back" + "expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 80\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back" }, { "input": "0", From 29ce4b83bdff170d3e8bb6d24e9754da9dcb3dc4 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 2 Jan 2025 15:22:07 +0300 Subject: [PATCH 54/71] added tests for HashPIN and VerifyPIN --- common/pin_test.go | 98 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/common/pin_test.go b/common/pin_test.go index 9012024..1b11d2c 100644 --- a/common/pin_test.go +++ b/common/pin_test.go @@ -1,6 +1,10 @@ package common -import "testing" +import ( + "testing" + + "golang.org/x/crypto/bcrypt" +) func TestIsValidPIN(t *testing.T) { tests := []struct { @@ -54,3 +58,95 @@ func TestIsValidPIN(t *testing.T) { }) } } + +func TestHashPIN(t *testing.T) { + tests := []struct { + name string + pin string + }{ + { + name: "Valid PIN with 4 digits", + pin: "1234", + }, + { + name: "Valid PIN with leading zeros", + pin: "0001", + }, + { + name: "Empty PIN", + pin: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + hashedPIN, err := HashPIN(tt.pin) + if err != nil { + t.Errorf("HashPIN(%q) returned an error: %v", tt.pin, err) + return + } + + if hashedPIN == "" { + t.Errorf("HashPIN(%q) returned an empty hash", tt.pin) + } + + // Ensure the hash can be verified with bcrypt + err = bcrypt.CompareHashAndPassword([]byte(hashedPIN), []byte(tt.pin)) + if tt.pin != "" && err != nil { + t.Errorf("HashPIN(%q) produced a hash that does not match: %v", tt.pin, err) + } + }) + } +} + +func TestVerifyPIN(t *testing.T) { + tests := []struct { + name string + pin string + hashedPIN string + shouldPass bool + }{ + { + name: "Valid PIN verification", + pin: "1234", + hashedPIN: hashPINHelper("1234"), + shouldPass: true, + }, + { + name: "Invalid PIN verification with incorrect PIN", + pin: "5678", + hashedPIN: hashPINHelper("1234"), + shouldPass: false, + }, + { + name: "Invalid PIN verification with empty PIN", + pin: "", + hashedPIN: hashPINHelper("1234"), + shouldPass: false, + }, + { + name: "Invalid PIN verification with invalid hash", + pin: "1234", + hashedPIN: "invalidhash", + shouldPass: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := VerifyPIN(tt.hashedPIN, tt.pin) + if result != tt.shouldPass { + t.Errorf("VerifyPIN(%q, %q) = %v; expected %v", tt.hashedPIN, tt.pin, result, tt.shouldPass) + } + }) + } +} + +// Helper function to hash a PIN for testing purposes +func hashPINHelper(pin string) string { + hashedPIN, err := HashPIN(pin) + if err != nil { + panic("Failed to hash PIN for test setup: " + err.Error()) + } + return hashedPIN +} From 491b7424a9176e4d646dca82234e4e3b49968879 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Thu, 2 Jan 2025 16:01:19 +0300 Subject: [PATCH 55/71] point to the correct ./devtools/admin_numbers directory --- internal/handlers/handlerservice.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/handlers/handlerservice.go b/internal/handlers/handlerservice.go index 1da28c3..b2a6f2f 100644 --- a/internal/handlers/handlerservice.go +++ b/internal/handlers/handlerservice.go @@ -43,7 +43,7 @@ func NewLocalHandlerService(ctx context.Context, fp string, debug bool, dbResour if err != nil { return nil, err } - adminstore, err := utils.NewAdminStore(ctx, "admin_numbers") + adminstore, err := utils.NewAdminStore(ctx, "./devtools/admin_numbers") if err != nil { return nil, err } From f1fd690a7ba989f252a826d2e8463752018f4d14 Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Thu, 2 Jan 2025 17:37:26 +0300 Subject: [PATCH 56/71] update expected content --- menutraversal_test/group_test.json | 6 +++--- menutraversal_test/test_setup.json | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/menutraversal_test/group_test.json b/menutraversal_test/group_test.json index 5ffe615..f35beb9 100644 --- a/menutraversal_test/group_test.json +++ b/menutraversal_test/group_test.json @@ -62,10 +62,10 @@ }, { "input": "1234", - "expectedContent": "Select language:\n0:English\n1:Kiswahili" + "expectedContent": "Select language:\n1:English\n2:Kiswahili" }, { - "input": "0", + "input": "1", "expectedContent": "Your language change request was successful.\n0:Back\n9:Quit" }, { @@ -430,7 +430,7 @@ }, { "input": "1234", - "expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 79\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back" + "expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 80\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back\n9:Quit" }, { "input": "0", diff --git a/menutraversal_test/test_setup.json b/menutraversal_test/test_setup.json index 5115de9..8728640 100644 --- a/menutraversal_test/test_setup.json +++ b/menutraversal_test/test_setup.json @@ -7,14 +7,14 @@ "steps": [ { "input": "", - "expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:English\n1:Kiswahili" + "expectedContent": "Welcome to Sarafu Network\nPlease select a language\n1:English\n2:Kiswahili" }, { - "input": "0", - "expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n0:Yes\n1:No" + "input": "1", + "expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n1:Yes\n2:No" }, { - "input": "0", + "input": "1", "expectedContent": "Please enter a new four number PIN for your account:\n0:Exit" }, { @@ -40,14 +40,14 @@ "steps": [ { "input": "", - "expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:English\n1:Kiswahili" - }, - { - "input": "0", - "expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n0:Yes\n1:No" + "expectedContent": "Welcome to Sarafu Network\nPlease select a language\n1:English\n2:Kiswahili" }, { "input": "1", + "expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n1:Yes\n2:No" + }, + { + "input": "2", "expectedContent": "Thank you for using Sarafu. Goodbye!" } ] From 3830c12a57ecbb8c92bd6387b1acdd623b81fbbc Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Thu, 2 Jan 2025 17:42:03 +0300 Subject: [PATCH 57/71] update tests --- internal/handlers/ussd/menuhandler_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 34c8e76..2b168f2 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -1798,7 +1798,7 @@ func TestGetProfile(t *testing.T) { result: resource.Result{ Content: fmt.Sprintf( "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n", - "John Doee", "Male", "48", "Kilifi", "Bananas", + "John Doee", "Male", "49", "Kilifi", "Bananas", ), }, }, @@ -1810,7 +1810,7 @@ func TestGetProfile(t *testing.T) { result: resource.Result{ Content: fmt.Sprintf( "Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\n", - "John Doee", "Male", "48", "Kilifi", "Bananas", + "John Doee", "Male", "49", "Kilifi", "Bananas", ), }, }, @@ -1822,7 +1822,7 @@ func TestGetProfile(t *testing.T) { result: resource.Result{ Content: fmt.Sprintf( "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n", - "John Doee", "Male", "48", "Kilifi", "Bananas", + "John Doee", "Male", "49", "Kilifi", "Bananas", ), }, }, From 91dc9ce82f9cb5ca3e233013f8b0cccf3f0b4e5a Mon Sep 17 00:00:00 2001 From: Mohammed Sohail Date: Fri, 3 Jan 2025 11:10:07 +0300 Subject: [PATCH 58/71] tests: add sample pin/hash pair from migration dataset --- common/pin_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/common/pin_test.go b/common/pin_test.go index 1b11d2c..f10ad6b 100644 --- a/common/pin_test.go +++ b/common/pin_test.go @@ -99,6 +99,27 @@ func TestHashPIN(t *testing.T) { } } +func TestVerifyMigratedHashPin(t *testing.T) { + tests := []struct { + pin string + hash string + }{ + { + pin: "1234", + hash: "$2b$08$dTvIGxCCysJtdvrSnaLStuylPoOS/ZLYYkxvTeR5QmTFY3TSvPQC6", + }, + } + + for _, tt := range tests { + t.Run(tt.pin, func(t *testing.T) { + ok := VerifyPIN(tt.hash, tt.pin) + if !ok { + t.Errorf("VerifyPIN could not verify migrated PIN: %v", tt.pin, ok) + } + }) + } +} + func TestVerifyPIN(t *testing.T) { tests := []struct { name string From c26f5683f6a752b0f23fdbc9980a665d31f4555a Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 3 Jan 2025 11:17:09 +0300 Subject: [PATCH 59/71] removed second unused argument --- common/pin_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/pin_test.go b/common/pin_test.go index f10ad6b..154ab06 100644 --- a/common/pin_test.go +++ b/common/pin_test.go @@ -114,7 +114,7 @@ func TestVerifyMigratedHashPin(t *testing.T) { t.Run(tt.pin, func(t *testing.T) { ok := VerifyPIN(tt.hash, tt.pin) if !ok { - t.Errorf("VerifyPIN could not verify migrated PIN: %v", tt.pin, ok) + t.Errorf("VerifyPIN could not verify migrated PIN: %v", tt.pin) } }) } From 9d6e25e184e1647e97b9f9bfece69f39495f1c15 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Fri, 3 Jan 2025 11:24:24 +0300 Subject: [PATCH 60/71] revert to previous state for the adminstore --- internal/handlers/handlerservice.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/handlers/handlerservice.go b/internal/handlers/handlerservice.go index b2a6f2f..1da28c3 100644 --- a/internal/handlers/handlerservice.go +++ b/internal/handlers/handlerservice.go @@ -43,7 +43,7 @@ func NewLocalHandlerService(ctx context.Context, fp string, debug bool, dbResour if err != nil { return nil, err } - adminstore, err := utils.NewAdminStore(ctx, "./devtools/admin_numbers") + adminstore, err := utils.NewAdminStore(ctx, "admin_numbers") if err != nil { return nil, err } From 162e6c19343a4b12f2eb5230686da1684e7fb8ee Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Fri, 3 Jan 2025 11:26:56 +0300 Subject: [PATCH 61/71] fix: language change --- internal/utils/isocode.go | 6 +++--- services/registration/change_language.vis | 2 +- services/registration/set_default.vis | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 services/registration/set_default.vis diff --git a/internal/utils/isocode.go b/internal/utils/isocode.go index 3bdfbeb..692b7bb 100644 --- a/internal/utils/isocode.go +++ b/internal/utils/isocode.go @@ -1,9 +1,9 @@ package utils var isoCodes = map[string]bool{ - "eng": true, // English - "swa": true, // Swahili - + "eng": true, // English + "swa": true, // Swahili + "default": true, // Default language: English } func IsValidISO639(code string) bool { diff --git a/services/registration/change_language.vis b/services/registration/change_language.vis index 8b1def9..f20bcfb 100644 --- a/services/registration/change_language.vis +++ b/services/registration/change_language.vis @@ -5,6 +5,6 @@ CATCH pin_entry flag_account_authorized 0 MOUT english 1 MOUT kiswahili 2 HALT -INCMP set_default 1 +INCMP set_eng 1 INCMP set_swa 2 INCMP . * diff --git a/services/registration/set_default.vis b/services/registration/set_default.vis new file mode 100644 index 0000000..b66a1b7 --- /dev/null +++ b/services/registration/set_default.vis @@ -0,0 +1,4 @@ +LOAD set_language 6 +RELOAD set_language +CATCH terms flag_account_created 0 +MOVE language_changed From f1b258fa6ddecdc455776d9a4dcd2dac80fd5200 Mon Sep 17 00:00:00 2001 From: lash Date: Sat, 4 Jan 2025 07:29:22 +0000 Subject: [PATCH 62/71] Factor out at code --- cmd/africastalking/main.go | 2 +- .../{at_session_handler.go => at/server.go} | 22 +- .../http/{http_test.go => at/server_test.go} | 215 ---------------- internal/http/server.go | 12 +- internal/http/server_test.go | 229 ++++++++++++++++++ 5 files changed, 250 insertions(+), 230 deletions(-) rename internal/http/{at_session_handler.go => at/server.go} (80%) rename internal/http/{http_test.go => at/server_test.go} (55%) create mode 100644 internal/http/server_test.go diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index ca88978..1f142da 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -23,7 +23,7 @@ import ( "git.grassecon.net/urdt/ussd/config" "git.grassecon.net/urdt/ussd/initializers" "git.grassecon.net/urdt/ussd/internal/handlers" - httpserver "git.grassecon.net/urdt/ussd/internal/http" + httpserver "git.grassecon.net/urdt/ussd/internal/http/at" "git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/remote" ) diff --git a/internal/http/at_session_handler.go b/internal/http/at/server.go similarity index 80% rename from internal/http/at_session_handler.go rename to internal/http/at/server.go index 25da954..9cade3d 100644 --- a/internal/http/at_session_handler.go +++ b/internal/http/at/server.go @@ -4,16 +4,22 @@ import ( "io" "net/http" + "git.defalsify.org/vise.git/logging" "git.grassecon.net/urdt/ussd/internal/handlers" + httpserver "git.grassecon.net/urdt/ussd/internal/http" +) + +var ( + logg = logging.NewVanilla().WithDomain("atserver") ) type ATSessionHandler struct { - *SessionHandler + *httpserver.SessionHandler } func NewATSessionHandler(h handlers.RequestHandler) *ATSessionHandler { return &ATSessionHandler{ - SessionHandler: ToSessionHandler(h), + SessionHandler: httpserver.ToSessionHandler(h), } } @@ -31,14 +37,14 @@ func (ash *ATSessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) cfg.SessionId, err = rp.GetSessionId(req) if err != nil { logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err) - ash.writeError(w, 400, err) + ash.WriteError(w, 400, err) return } rqs.Config = cfg rqs.Input, err = rp.GetInput(req) if err != nil { logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err) - ash.writeError(w, 400, err) + ash.WriteError(w, 400, err) return } @@ -53,7 +59,7 @@ func (ash *ATSessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) } if code != 200 { - ash.writeError(w, 500, err) + ash.WriteError(w, 500, err) return } @@ -61,13 +67,13 @@ func (ash *ATSessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) w.Header().Set("Content-Type", "text/plain") rqs, err = ash.Output(rqs) if err != nil { - ash.writeError(w, 500, err) + ash.WriteError(w, 500, err) return } rqs, err = ash.Reset(rqs) if err != nil { - ash.writeError(w, 500, err) + ash.WriteError(w, 500, err) return } } @@ -89,4 +95,4 @@ func (ash *ATSessionHandler) Output(rqs handlers.RequestSession) (handlers.Reque _, err = rqs.Engine.Flush(rqs.Ctx, rqs.Writer) return rqs, err -} \ No newline at end of file +} diff --git a/internal/http/http_test.go b/internal/http/at/server_test.go similarity index 55% rename from internal/http/http_test.go rename to internal/http/at/server_test.go index 14bb90a..d49f9ce 100644 --- a/internal/http/http_test.go +++ b/internal/http/at/server_test.go @@ -1,7 +1,6 @@ package http import ( - "bytes" "context" "errors" "io" @@ -16,16 +15,6 @@ import ( "git.grassecon.net/urdt/ussd/internal/testutil/mocks/httpmocks" ) -// invalidRequestType is a custom type to test invalid request scenarios -type invalidRequestType struct{} - -// errorReader is a helper type that always returns an error when Read is called -type errorReader struct{} - -func (e *errorReader) Read(p []byte) (n int, err error) { - return 0, errors.New("read error") -} - func TestNewATSessionHandler(t *testing.T) { mockHandler := &httpmocks.MockRequestHandler{} ash := NewATSessionHandler(mockHandler) @@ -242,208 +231,4 @@ func TestATSessionHandler_Output(t *testing.T) { } } -func TestSessionHandler_ServeHTTP(t *testing.T) { - tests := []struct { - name string - sessionID string - input []byte - parserErr error - processErr error - outputErr error - resetErr error - expectedStatus int - }{ - { - name: "Success", - sessionID: "123", - input: []byte("test input"), - expectedStatus: http.StatusOK, - }, - { - name: "Missing Session ID", - sessionID: "", - parserErr: handlers.ErrSessionMissing, - expectedStatus: http.StatusBadRequest, - }, - { - name: "Process Error", - sessionID: "123", - input: []byte("test input"), - processErr: handlers.ErrStorage, - expectedStatus: http.StatusInternalServerError, - }, - { - name: "Output Error", - sessionID: "123", - input: []byte("test input"), - outputErr: errors.New("output error"), - expectedStatus: http.StatusOK, - }, - { - name: "Reset Error", - sessionID: "123", - input: []byte("test input"), - resetErr: errors.New("reset error"), - expectedStatus: http.StatusOK, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockRequestParser := &httpmocks.MockRequestParser{ - GetSessionIdFunc: func(any) (string, error) { - return tt.sessionID, tt.parserErr - }, - GetInputFunc: func(any) ([]byte, error) { - return tt.input, nil - }, - } - - mockRequestHandler := &httpmocks.MockRequestHandler{ - ProcessFunc: func(rs handlers.RequestSession) (handlers.RequestSession, error) { - return rs, tt.processErr - }, - OutputFunc: func(rs handlers.RequestSession) (handlers.RequestSession, error) { - return rs, tt.outputErr - }, - ResetFunc: func(rs handlers.RequestSession) (handlers.RequestSession, error) { - return rs, tt.resetErr - }, - GetRequestParserFunc: func() handlers.RequestParser { - return mockRequestParser - }, - GetConfigFunc: func() engine.Config { - return engine.Config{} - }, - } - - sessionHandler := ToSessionHandler(mockRequestHandler) - - req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(tt.input)) - req.Header.Set("X-Vise-Session", tt.sessionID) - - rr := httptest.NewRecorder() - - sessionHandler.ServeHTTP(rr, req) - - if status := rr.Code; status != tt.expectedStatus { - t.Errorf("handler returned wrong status code: got %v want %v", - status, tt.expectedStatus) - } - }) - } -} - -func TestSessionHandler_writeError(t *testing.T) { - handler := &SessionHandler{} - mockWriter := &httpmocks.MockWriter{} - err := errors.New("test error") - - handler.writeError(mockWriter, http.StatusBadRequest, err) - - if mockWriter.WrittenString != "" { - t.Errorf("Expected empty body, got %s", mockWriter.WrittenString) - } -} - -func TestDefaultRequestParser_GetSessionId(t *testing.T) { - tests := []struct { - name string - request any - expectedID string - expectedError error - }{ - { - name: "Valid Session ID", - request: func() *http.Request { - req := httptest.NewRequest(http.MethodPost, "/", nil) - req.Header.Set("X-Vise-Session", "123456") - return req - }(), - expectedID: "123456", - expectedError: nil, - }, - { - name: "Missing Session ID", - request: httptest.NewRequest(http.MethodPost, "/", nil), - expectedID: "", - expectedError: handlers.ErrSessionMissing, - }, - { - name: "Invalid Request Type", - request: invalidRequestType{}, - expectedID: "", - expectedError: handlers.ErrInvalidRequest, - }, - } - - parser := &DefaultRequestParser{} - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - id, err := parser.GetSessionId(tt.request) - - if id != tt.expectedID { - t.Errorf("Expected session ID %s, got %s", tt.expectedID, id) - } - - if err != tt.expectedError { - t.Errorf("Expected error %v, got %v", tt.expectedError, err) - } - }) - } -} - -func TestDefaultRequestParser_GetInput(t *testing.T) { - tests := []struct { - name string - request any - expectedInput []byte - expectedError error - }{ - { - name: "Valid Input", - request: func() *http.Request { - return httptest.NewRequest(http.MethodPost, "/", bytes.NewBufferString("test input")) - }(), - expectedInput: []byte("test input"), - expectedError: nil, - }, - { - name: "Empty Input", - request: httptest.NewRequest(http.MethodPost, "/", nil), - expectedInput: []byte{}, - expectedError: nil, - }, - { - name: "Invalid Request Type", - request: invalidRequestType{}, - expectedInput: nil, - expectedError: handlers.ErrInvalidRequest, - }, - { - name: "Read Error", - request: func() *http.Request { - return httptest.NewRequest(http.MethodPost, "/", &errorReader{}) - }(), - expectedInput: nil, - expectedError: errors.New("read error"), - }, - } - - parser := &DefaultRequestParser{} - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - input, err := parser.GetInput(tt.request) - - if !bytes.Equal(input, tt.expectedInput) { - t.Errorf("Expected input %s, got %s", tt.expectedInput, input) - } - - if err != tt.expectedError && (err == nil || err.Error() != tt.expectedError.Error()) { - t.Errorf("Expected error %v, got %v", tt.expectedError, err) - } - }) - } -} diff --git a/internal/http/server.go b/internal/http/server.go index a6239c4..df15407 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -52,7 +52,7 @@ func ToSessionHandler(h handlers.RequestHandler) *SessionHandler { } } -func (f *SessionHandler) writeError(w http.ResponseWriter, code int, err error) { +func (f *SessionHandler) WriteError(w http.ResponseWriter, code int, err error) { s := err.Error() w.Header().Set("Content-Length", strconv.Itoa(len(s))) w.WriteHeader(code) @@ -78,13 +78,13 @@ func (f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { cfg.SessionId, err = rp.GetSessionId(req) if err != nil { logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err) - f.writeError(w, 400, err) + f.WriteError(w, 400, err) } rqs.Config = cfg rqs.Input, err = rp.GetInput(req) if err != nil { logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err) - f.writeError(w, 400, err) + f.WriteError(w, 400, err) return } @@ -101,7 +101,7 @@ func (f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { } if code != 200 { - f.writeError(w, 500, err) + f.WriteError(w, 500, err) return } @@ -110,11 +110,11 @@ func (f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { rqs, err = f.Output(rqs) rqs, perr = f.Reset(rqs) if err != nil { - f.writeError(w, 500, err) + f.WriteError(w, 500, err) return } if perr != nil { - f.writeError(w, 500, perr) + f.WriteError(w, 500, perr) return } } diff --git a/internal/http/server_test.go b/internal/http/server_test.go new file mode 100644 index 0000000..a46f98e --- /dev/null +++ b/internal/http/server_test.go @@ -0,0 +1,229 @@ +package http + +import ( + "bytes" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "git.defalsify.org/vise.git/engine" + "git.grassecon.net/urdt/ussd/internal/handlers" + "git.grassecon.net/urdt/ussd/internal/testutil/mocks/httpmocks" +) + +// invalidRequestType is a custom type to test invalid request scenarios +type invalidRequestType struct{} + +// errorReader is a helper type that always returns an error when Read is called +type errorReader struct{} + +func (e *errorReader) Read(p []byte) (n int, err error) { + return 0, errors.New("read error") +} + +func TestSessionHandler_ServeHTTP(t *testing.T) { + tests := []struct { + name string + sessionID string + input []byte + parserErr error + processErr error + outputErr error + resetErr error + expectedStatus int + }{ + { + name: "Success", + sessionID: "123", + input: []byte("test input"), + expectedStatus: http.StatusOK, + }, + { + name: "Missing Session ID", + sessionID: "", + parserErr: handlers.ErrSessionMissing, + expectedStatus: http.StatusBadRequest, + }, + { + name: "Process Error", + sessionID: "123", + input: []byte("test input"), + processErr: handlers.ErrStorage, + expectedStatus: http.StatusInternalServerError, + }, + { + name: "Output Error", + sessionID: "123", + input: []byte("test input"), + outputErr: errors.New("output error"), + expectedStatus: http.StatusOK, + }, + { + name: "Reset Error", + sessionID: "123", + input: []byte("test input"), + resetErr: errors.New("reset error"), + expectedStatus: http.StatusOK, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockRequestParser := &httpmocks.MockRequestParser{ + GetSessionIdFunc: func(any) (string, error) { + return tt.sessionID, tt.parserErr + }, + GetInputFunc: func(any) ([]byte, error) { + return tt.input, nil + }, + } + + mockRequestHandler := &httpmocks.MockRequestHandler{ + ProcessFunc: func(rs handlers.RequestSession) (handlers.RequestSession, error) { + return rs, tt.processErr + }, + OutputFunc: func(rs handlers.RequestSession) (handlers.RequestSession, error) { + return rs, tt.outputErr + }, + ResetFunc: func(rs handlers.RequestSession) (handlers.RequestSession, error) { + return rs, tt.resetErr + }, + GetRequestParserFunc: func() handlers.RequestParser { + return mockRequestParser + }, + GetConfigFunc: func() engine.Config { + return engine.Config{} + }, + } + + sessionHandler := ToSessionHandler(mockRequestHandler) + + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(tt.input)) + req.Header.Set("X-Vise-Session", tt.sessionID) + + rr := httptest.NewRecorder() + + sessionHandler.ServeHTTP(rr, req) + + if status := rr.Code; status != tt.expectedStatus { + t.Errorf("handler returned wrong status code: got %v want %v", + status, tt.expectedStatus) + } + }) + } +} + +func TestSessionHandler_WriteError(t *testing.T) { + handler := &SessionHandler{} + mockWriter := &httpmocks.MockWriter{} + err := errors.New("test error") + + handler.WriteError(mockWriter, http.StatusBadRequest, err) + + if mockWriter.WrittenString != "" { + t.Errorf("Expected empty body, got %s", mockWriter.WrittenString) + } +} + +func TestDefaultRequestParser_GetSessionId(t *testing.T) { + tests := []struct { + name string + request any + expectedID string + expectedError error + }{ + { + name: "Valid Session ID", + request: func() *http.Request { + req := httptest.NewRequest(http.MethodPost, "/", nil) + req.Header.Set("X-Vise-Session", "123456") + return req + }(), + expectedID: "123456", + expectedError: nil, + }, + { + name: "Missing Session ID", + request: httptest.NewRequest(http.MethodPost, "/", nil), + expectedID: "", + expectedError: handlers.ErrSessionMissing, + }, + { + name: "Invalid Request Type", + request: invalidRequestType{}, + expectedID: "", + expectedError: handlers.ErrInvalidRequest, + }, + } + + parser := &DefaultRequestParser{} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + id, err := parser.GetSessionId(tt.request) + + if id != tt.expectedID { + t.Errorf("Expected session ID %s, got %s", tt.expectedID, id) + } + + if err != tt.expectedError { + t.Errorf("Expected error %v, got %v", tt.expectedError, err) + } + }) + } +} + +func TestDefaultRequestParser_GetInput(t *testing.T) { + tests := []struct { + name string + request any + expectedInput []byte + expectedError error + }{ + { + name: "Valid Input", + request: func() *http.Request { + return httptest.NewRequest(http.MethodPost, "/", bytes.NewBufferString("test input")) + }(), + expectedInput: []byte("test input"), + expectedError: nil, + }, + { + name: "Empty Input", + request: httptest.NewRequest(http.MethodPost, "/", nil), + expectedInput: []byte{}, + expectedError: nil, + }, + { + name: "Invalid Request Type", + request: invalidRequestType{}, + expectedInput: nil, + expectedError: handlers.ErrInvalidRequest, + }, + { + name: "Read Error", + request: func() *http.Request { + return httptest.NewRequest(http.MethodPost, "/", &errorReader{}) + }(), + expectedInput: nil, + expectedError: errors.New("read error"), + }, + } + + parser := &DefaultRequestParser{} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + input, err := parser.GetInput(tt.request) + + if !bytes.Equal(input, tt.expectedInput) { + t.Errorf("Expected input %s, got %s", tt.expectedInput, input) + } + + if err != tt.expectedError && (err == nil || err.Error() != tt.expectedError.Error()) { + t.Errorf("Expected error %v, got %v", tt.expectedError, err) + } + }) + } +} From 67007fcd488994819339422cbc701b7b578b0ac9 Mon Sep 17 00:00:00 2001 From: lash Date: Sat, 4 Jan 2025 07:35:28 +0000 Subject: [PATCH 63/71] Factor out gdbm package --- internal/storage/{ => gdbm}/gdbm.go | 5 +++++ internal/storage/storageservice.go | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) rename internal/storage/{ => gdbm}/gdbm.go (95%) diff --git a/internal/storage/gdbm.go b/internal/storage/gdbm/gdbm.go similarity index 95% rename from internal/storage/gdbm.go rename to internal/storage/gdbm/gdbm.go index 31ebf47..dab767a 100644 --- a/internal/storage/gdbm.go +++ b/internal/storage/gdbm/gdbm.go @@ -6,6 +6,11 @@ import ( "git.defalsify.org/vise.git/db" gdbmdb "git.defalsify.org/vise.git/db/gdbm" "git.defalsify.org/vise.git/lang" + "git.defalsify.org/vise.git/logging" +) + +var ( + logg = logging.NewVanilla().WithDomain("gdbmstorage") ) var ( diff --git a/internal/storage/storageservice.go b/internal/storage/storageservice.go index ca28bbb..7856095 100644 --- a/internal/storage/storageservice.go +++ b/internal/storage/storageservice.go @@ -13,6 +13,7 @@ import ( "git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/resource" "git.grassecon.net/urdt/ussd/initializers" + gdbmstorage "git.grassecon.net/urdt/ussd/internal/storage/gdbm" ) var ( @@ -75,7 +76,7 @@ func (ms *MenuStorageService) getOrCreateDb(ctx context.Context, existingDb db.D connStr := buildConnStr() err = newDb.Connect(ctx, connStr) } else { - newDb = NewThreadGdbmDb() + newDb = gdbmstorage.NewThreadGdbmDb() storeFile := path.Join(ms.dbDir, fileName) err = newDb.Connect(ctx, storeFile) } From f65c458daa6315b18ee8ac7dec7a8a96a4881bae Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Sat, 4 Jan 2025 10:35:59 +0300 Subject: [PATCH 64/71] update go-vise. --- go.mod | 4 ++-- go.sum | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 16ccdc3..41c6700 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.grassecon.net/urdt/ussd go 1.23.0 require ( - git.defalsify.org/vise.git v0.2.3-0.20241231085136-8582c7e157d9 + git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d github.com/alecthomas/assert/v2 v2.2.2 github.com/gofrs/uuid v4.4.0+incompatible github.com/grassrootseconomics/eth-custodial v1.3.0-beta @@ -11,6 +11,7 @@ require ( github.com/joho/godotenv v1.5.1 github.com/peteole/testdata-loader v0.3.0 github.com/stretchr/testify v1.9.0 + golang.org/x/crypto v0.27.0 gopkg.in/leonelquinteros/gotext.v1 v1.3.1 ) @@ -32,7 +33,6 @@ require ( github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/crypto v0.27.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/text v0.18.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 9086cd8..6bef621 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.defalsify.org/vise.git v0.2.3-0.20241231085136-8582c7e157d9 h1:O3m+NgWDWtJm8OculT99c4bDMAO4xLe2c8hpCKpsd9g= -git.defalsify.org/vise.git v0.2.3-0.20241231085136-8582c7e157d9/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= +git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d h1:bPAOVZOX4frSGhfOdcj7kc555f8dc9DmMd2YAyC2AMw= +git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= From 3ce14355911d12cde021f51e07b30cd986f3a6bb Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Sat, 4 Jan 2025 10:38:25 +0300 Subject: [PATCH 65/71] extract session id from africastalking request --- cmd/africastalking/main.go | 52 +++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index ca88978..e25813a 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net/http" + "net/url" "os" "os/signal" "path" @@ -29,7 +30,7 @@ import ( ) var ( - logg = logging.NewVanilla() + logg = logging.NewVanilla().WithDomain("AfricasTalking").WithContextKey("at-session-id") scriptDir = path.Join("services", "registration") build = "dev" menuSeparator = ": " @@ -39,7 +40,43 @@ func init() { initializers.LoadEnvVariables() } -type atRequestParser struct{} +type atRequestParser struct { + context context.Context +} + +func parseQueryParams(query string) map[string]string { + params := make(map[string]string) + + queryParams := strings.Split(query, "&") + for _, param := range queryParams { + // Split each key-value pair by '=' + parts := strings.SplitN(param, "=", 2) + if len(parts) == 2 { + params[parts[0]] = parts[1] + } + } + return params +} + +func extractATSessionId(decodedStr string) (string, error) { + var data map[string]string + err := json.Unmarshal([]byte(decodedStr), &data) + + if err != nil { + logg.Errorf("Error unmarshalling JSON: %v", err) + return "", nil + } + decodedBody, err := url.QueryUnescape(data["body"]) + if err != nil { + logg.Errorf("Error URL-decoding body: %v", err) + return "", nil + } + params := parseQueryParams(decodedBody) + + sessionId := params["sessionId"] + return sessionId, nil + +} func (arp *atRequestParser) GetSessionId(rq any) (string, error) { rqv, ok := rq.(*http.Request) @@ -63,7 +100,12 @@ func (arp *atRequestParser) GetSessionId(rq any) (string, error) { if err != nil { logg.Warnf("failed to marshal request body", "err", err) } else { - logg.Debugf("received request", "bytes", logBytes) + decodedStr := string(logBytes) + sessionId, err := extractATSessionId(decodedStr) + if err != nil { + context.WithValue(arp.context, "at-session-id", sessionId) + } + logg.Debugf("Received request:", decodedStr) } if err := rqv.ParseForm(); err != nil { @@ -191,7 +233,9 @@ func main() { } defer stateStore.Close() - rp := &atRequestParser{} + rp := &atRequestParser{ + context: ctx, + } bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) sh := httpserver.NewATSessionHandler(bsh) From 62f3681b9e725ad10521c1f4957782a7de650159 Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Sat, 4 Jan 2025 10:40:26 +0300 Subject: [PATCH 66/71] define context keysessionid using go-vise --withcontext --- internal/handlers/ussd/menuhandler.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 3919595..1cebba3 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -28,7 +28,7 @@ import ( ) var ( - logg = logging.NewVanilla().WithDomain("ussdmenuhandler") + logg = logging.NewVanilla().WithDomain("ussdmenuhandler").WithContextKey("session-id") scriptDir = path.Join("services", "registration") translationDir = path.Join(scriptDir, "locale") ) @@ -122,9 +122,12 @@ func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource h.st.Code = []byte{} } - sessionId, _ := ctx.Value("SessionId").(string) - flag_admin_privilege, _ := h.flagManager.GetFlag("flag_admin_privilege") + sessionId, ok := ctx.Value("SessionId").(string) + if ok { + context.WithValue(ctx, "session-id", sessionId) + } + flag_admin_privilege, _ := h.flagManager.GetFlag("flag_admin_privilege") isAdmin, _ := h.adminstore.IsAdmin(sessionId) if isAdmin { From 3ed9caf16d3e903b72de8e967a34cd5013be6104 Mon Sep 17 00:00:00 2001 From: lash Date: Sat, 4 Jan 2025 08:02:44 +0000 Subject: [PATCH 67/71] Factor out request parsers --- cmd/africastalking/main.go | 109 +--------------------------- internal/http/at/parse.go | 121 ++++++++++++++++++++++++++++++++ internal/http/at/server.go | 2 +- internal/http/at/server_test.go | 2 +- internal/http/parse.go | 38 ++++++++++ internal/http/server.go | 29 -------- 6 files changed, 162 insertions(+), 139 deletions(-) create mode 100644 internal/http/at/parse.go create mode 100644 internal/http/parse.go diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index a7fa74b..3ac1591 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -39,113 +39,6 @@ var ( func init() { initializers.LoadEnvVariables() } - -type atRequestParser struct { - context context.Context -} - -func parseQueryParams(query string) map[string]string { - params := make(map[string]string) - - queryParams := strings.Split(query, "&") - for _, param := range queryParams { - // Split each key-value pair by '=' - parts := strings.SplitN(param, "=", 2) - if len(parts) == 2 { - params[parts[0]] = parts[1] - } - } - return params -} - -func extractATSessionId(decodedStr string) (string, error) { - var data map[string]string - err := json.Unmarshal([]byte(decodedStr), &data) - - if err != nil { - logg.Errorf("Error unmarshalling JSON: %v", err) - return "", nil - } - decodedBody, err := url.QueryUnescape(data["body"]) - if err != nil { - logg.Errorf("Error URL-decoding body: %v", err) - return "", nil - } - params := parseQueryParams(decodedBody) - - sessionId := params["sessionId"] - return sessionId, nil - -} - -func (arp *atRequestParser) GetSessionId(rq any) (string, error) { - rqv, ok := rq.(*http.Request) - if !ok { - logg.Warnf("got an invalid request", "req", rq) - return "", handlers.ErrInvalidRequest - } - - // Capture body (if any) for logging - body, err := io.ReadAll(rqv.Body) - if err != nil { - logg.Warnf("failed to read request body", "err", err) - return "", fmt.Errorf("failed to read request body: %v", err) - } - // Reset the body for further reading - rqv.Body = io.NopCloser(bytes.NewReader(body)) - - // Log the body as JSON - bodyLog := map[string]string{"body": string(body)} - logBytes, err := json.Marshal(bodyLog) - if err != nil { - logg.Warnf("failed to marshal request body", "err", err) - } else { - decodedStr := string(logBytes) - sessionId, err := extractATSessionId(decodedStr) - if err != nil { - context.WithValue(arp.context, "at-session-id", sessionId) - } - logg.Debugf("Received request:", decodedStr) - } - - if err := rqv.ParseForm(); err != nil { - logg.Warnf("failed to parse form data", "err", err) - return "", fmt.Errorf("failed to parse form data: %v", err) - } - - phoneNumber := rqv.FormValue("phoneNumber") - if phoneNumber == "" { - return "", fmt.Errorf("no phone number found") - } - - formattedNumber, err := common.FormatPhoneNumber(phoneNumber) - if err != nil { - logg.Warnf("failed to format phone number", "err", err) - return "", fmt.Errorf("failed to format number") - } - - return formattedNumber, nil -} - -func (arp *atRequestParser) GetInput(rq any) ([]byte, error) { - rqv, ok := rq.(*http.Request) - if !ok { - return nil, handlers.ErrInvalidRequest - } - if err := rqv.ParseForm(); err != nil { - return nil, fmt.Errorf("failed to parse form data: %v", err) - } - - text := rqv.FormValue("text") - - parts := strings.Split(text, "*") - if len(parts) == 0 { - return nil, fmt.Errorf("no input found") - } - - return []byte(parts[len(parts)-1]), nil -} - func main() { config.LoadConfig() @@ -233,7 +126,7 @@ func main() { } defer stateStore.Close() - rp := &atRequestParser{ + rp := &at.ATRequestParser{ context: ctx, } bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) diff --git a/internal/http/at/parse.go b/internal/http/at/parse.go new file mode 100644 index 0000000..a40cf0f --- /dev/null +++ b/internal/http/at/parse.go @@ -0,0 +1,121 @@ +package at + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "git.grassecon.net/urdt/ussd/common" + "git.grassecon.net/urdt/ussd/internal/handlers" +) + +type ATRequestParser struct { + context context.Context +} + +func (arp *ATRequestParser) GetSessionId(rq any) (string, error) { + rqv, ok := rq.(*http.Request) + if !ok { + logg.Warnf("got an invalid request", "req", rq) + return "", handlers.ErrInvalidRequest + } + + // Capture body (if any) for logging + body, err := io.ReadAll(rqv.Body) + if err != nil { + logg.Warnf("failed to read request body", "err", err) + return "", fmt.Errorf("failed to read request body: %v", err) + } + // Reset the body for further reading + rqv.Body = io.NopCloser(bytes.NewReader(body)) + + // Log the body as JSON + bodyLog := map[string]string{"body": string(body)} + logBytes, err := json.Marshal(bodyLog) + if err != nil { + logg.Warnf("failed to marshal request body", "err", err) + } else { + decodedStr := string(logBytes) + sessionId, err := extractATSessionId(decodedStr) + if err != nil { + context.WithValue(arp.context, "at-session-id", sessionId) + } + logg.Debugf("Received request:", decodedStr) + } + + if err := rqv.ParseForm(); err != nil { + logg.Warnf("failed to parse form data", "err", err) + return "", fmt.Errorf("failed to parse form data: %v", err) + } + + phoneNumber := rqv.FormValue("phoneNumber") + if phoneNumber == "" { + return "", fmt.Errorf("no phone number found") + } + + formattedNumber, err := common.FormatPhoneNumber(phoneNumber) + if err != nil { + logg.Warnf("failed to format phone number", "err", err) + return "", fmt.Errorf("failed to format number") + } + + return formattedNumber, nil +} + +func (arp *ATRequestParser) GetInput(rq any) ([]byte, error) { + rqv, ok := rq.(*http.Request) + if !ok { + return nil, handlers.ErrInvalidRequest + } + if err := rqv.ParseForm(); err != nil { + return nil, fmt.Errorf("failed to parse form data: %v", err) + } + + text := rqv.FormValue("text") + + parts := strings.Split(text, "*") + if len(parts) == 0 { + return nil, fmt.Errorf("no input found") + } + + return []byte(parts[len(parts)-1]), nil +} + +func parseQueryParams(query string) map[string]string { + params := make(map[string]string) + + queryParams := strings.Split(query, "&") + for _, param := range queryParams { + // Split each key-value pair by '=' + parts := strings.SplitN(param, "=", 2) + if len(parts) == 2 { + params[parts[0]] = parts[1] + } + } + return params +} + +func extractATSessionId(decodedStr string) (string, error) { + var data map[string]string + err := json.Unmarshal([]byte(decodedStr), &data) + + if err != nil { + logg.Errorf("Error unmarshalling JSON: %v", err) + return "", nil + } + decodedBody, err := url.QueryUnescape(data["body"]) + if err != nil { + logg.Errorf("Error URL-decoding body: %v", err) + return "", nil + } + params := parseQueryParams(decodedBody) + + sessionId := params["sessionId"] + return sessionId, nil + +} diff --git a/internal/http/at/server.go b/internal/http/at/server.go index 9cade3d..705ff76 100644 --- a/internal/http/at/server.go +++ b/internal/http/at/server.go @@ -1,4 +1,4 @@ -package http +package at import ( "io" diff --git a/internal/http/at/server_test.go b/internal/http/at/server_test.go index d49f9ce..dd45c25 100644 --- a/internal/http/at/server_test.go +++ b/internal/http/at/server_test.go @@ -1,4 +1,4 @@ -package http +package at import ( "context" diff --git a/internal/http/parse.go b/internal/http/parse.go new file mode 100644 index 0000000..ec8e00b --- /dev/null +++ b/internal/http/parse.go @@ -0,0 +1,38 @@ +package http + +import ( + "io/ioutil" + "net/http" + + "git.grassecon.net/urdt/ussd/internal/handlers" +) + +type DefaultRequestParser struct { +} + +func (rp *DefaultRequestParser) GetSessionId(rq any) (string, error) { + rqv, ok := rq.(*http.Request) + if !ok { + return "", handlers.ErrInvalidRequest + } + v := rqv.Header.Get("X-Vise-Session") + if v == "" { + return "", handlers.ErrSessionMissing + } + return v, nil +} + +func (rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) { + rqv, ok := rq.(*http.Request) + if !ok { + return nil, handlers.ErrInvalidRequest + } + defer rqv.Body.Close() + v, err := ioutil.ReadAll(rqv.Body) + if err != nil { + return nil, err + } + return v, nil +} + + diff --git a/internal/http/server.go b/internal/http/server.go index df15407..9cadfa3 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -1,7 +1,6 @@ package http import ( - "io/ioutil" "net/http" "strconv" @@ -14,34 +13,6 @@ var ( logg = logging.NewVanilla().WithDomain("httpserver") ) -type DefaultRequestParser struct { -} - -func (rp *DefaultRequestParser) GetSessionId(rq any) (string, error) { - rqv, ok := rq.(*http.Request) - if !ok { - return "", handlers.ErrInvalidRequest - } - v := rqv.Header.Get("X-Vise-Session") - if v == "" { - return "", handlers.ErrSessionMissing - } - return v, nil -} - -func (rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) { - rqv, ok := rq.(*http.Request) - if !ok { - return nil, handlers.ErrInvalidRequest - } - defer rqv.Body.Close() - v, err := ioutil.ReadAll(rqv.Body) - if err != nil { - return nil, err - } - return v, nil -} - type SessionHandler struct { handlers.RequestHandler } From bcb3ab905e789b9213e492bd8de21c3f36aaf71b Mon Sep 17 00:00:00 2001 From: lash Date: Sat, 4 Jan 2025 08:09:18 +0000 Subject: [PATCH 68/71] Move db related to own package --- internal/storage/{ => db}/gdbm/gdbm.go | 0 internal/storage/{ => db}/sub_prefix_db.go | 0 internal/storage/{ => db}/sub_prefix_db_test.go | 0 internal/storage/storageservice.go | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) rename internal/storage/{ => db}/gdbm/gdbm.go (100%) rename internal/storage/{ => db}/sub_prefix_db.go (100%) rename internal/storage/{ => db}/sub_prefix_db_test.go (100%) diff --git a/internal/storage/gdbm/gdbm.go b/internal/storage/db/gdbm/gdbm.go similarity index 100% rename from internal/storage/gdbm/gdbm.go rename to internal/storage/db/gdbm/gdbm.go diff --git a/internal/storage/sub_prefix_db.go b/internal/storage/db/sub_prefix_db.go similarity index 100% rename from internal/storage/sub_prefix_db.go rename to internal/storage/db/sub_prefix_db.go diff --git a/internal/storage/sub_prefix_db_test.go b/internal/storage/db/sub_prefix_db_test.go similarity index 100% rename from internal/storage/sub_prefix_db_test.go rename to internal/storage/db/sub_prefix_db_test.go diff --git a/internal/storage/storageservice.go b/internal/storage/storageservice.go index 7856095..04e75ce 100644 --- a/internal/storage/storageservice.go +++ b/internal/storage/storageservice.go @@ -13,7 +13,7 @@ import ( "git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/resource" "git.grassecon.net/urdt/ussd/initializers" - gdbmstorage "git.grassecon.net/urdt/ussd/internal/storage/gdbm" + gdbmstorage "git.grassecon.net/urdt/ussd/internal/storage/db/gdbm" ) var ( From d950b10b50f59c91732f4ad2f1f7ca42f0f69ef7 Mon Sep 17 00:00:00 2001 From: lash Date: Sat, 4 Jan 2025 08:37:28 +0000 Subject: [PATCH 69/71] Move prefix db spec to separate package --- cmd/africastalking/main.go | 5 ----- common/storage.go | 5 +++-- common/transfer_statements.go | 4 ++-- common/vouchers.go | 4 ++-- internal/handlers/ussd/menuhandler.go | 6 +++--- internal/handlers/ussd/menuhandler_test.go | 6 +++--- 6 files changed, 13 insertions(+), 17 deletions(-) diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index 3ac1591..2696b51 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -1,19 +1,14 @@ package main import ( - "bytes" "context" - "encoding/json" "flag" "fmt" - "io" "net/http" - "net/url" "os" "os/signal" "path" "strconv" - "strings" "syscall" "git.defalsify.org/vise.git/engine" diff --git a/common/storage.go b/common/storage.go index dff4774..d37bce3 100644 --- a/common/storage.go +++ b/common/storage.go @@ -8,14 +8,15 @@ import ( "git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/persist" "git.grassecon.net/urdt/ussd/internal/storage" + dbstorage "git.grassecon.net/urdt/ussd/internal/storage/db" ) func StoreToDb(store *UserDataStore) db.Db { return store.Db } -func StoreToPrefixDb(store *UserDataStore, pfx []byte) storage.PrefixDb { - return storage.NewSubPrefixDb(store.Db, pfx) +func StoreToPrefixDb(store *UserDataStore, pfx []byte) dbstorage.PrefixDb { + return dbstorage.NewSubPrefixDb(store.Db, pfx) } type StorageServices interface { diff --git a/common/transfer_statements.go b/common/transfer_statements.go index 243ef4c..e97437f 100644 --- a/common/transfer_statements.go +++ b/common/transfer_statements.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "git.grassecon.net/urdt/ussd/internal/storage" + dbstorage "git.grassecon.net/urdt/ussd/internal/storage/db" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" ) @@ -56,7 +56,7 @@ func ProcessTransfers(transfers []dataserviceapi.Last10TxResponse) TransferMetad // GetTransferData retrieves and matches transfer data // returns a formatted string of the full transaction/statement -func GetTransferData(ctx context.Context, db storage.PrefixDb, publicKey string, index int) (string, error) { +func GetTransferData(ctx context.Context, db dbstorage.PrefixDb, publicKey string, index int) (string, error) { keys := []DataTyp{DATA_TX_SENDERS, DATA_TX_RECIPIENTS, DATA_TX_VALUES, DATA_TX_ADDRESSES, DATA_TX_HASHES, DATA_TX_DATES, DATA_TX_SYMBOLS} data := make(map[DataTyp]string) diff --git a/common/vouchers.go b/common/vouchers.go index 6cff91d..5dbdb71 100644 --- a/common/vouchers.go +++ b/common/vouchers.go @@ -6,7 +6,7 @@ import ( "math/big" "strings" - "git.grassecon.net/urdt/ussd/internal/storage" + dbstorage "git.grassecon.net/urdt/ussd/internal/storage/db" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" ) @@ -63,7 +63,7 @@ func ScaleDownBalance(balance, decimals string) string { } // GetVoucherData retrieves and matches voucher data -func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) { +func GetVoucherData(ctx context.Context, db dbstorage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) { keys := []DataTyp{DATA_VOUCHER_SYMBOLS, DATA_VOUCHER_BALANCES, DATA_VOUCHER_DECIMALS, DATA_VOUCHER_ADDRESSES} data := make(map[DataTyp]string) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 1cebba3..095d77b 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -23,7 +23,7 @@ import ( "git.grassecon.net/urdt/ussd/remote" "gopkg.in/leonelquinteros/gotext.v1" - "git.grassecon.net/urdt/ussd/internal/storage" + dbstorage "git.grassecon.net/urdt/ussd/internal/storage/db" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" ) @@ -64,7 +64,7 @@ type Handlers struct { adminstore *utils.AdminStore flagManager *asm.FlagParser accountService remote.AccountServiceInterface - prefixDb storage.PrefixDb + prefixDb dbstorage.PrefixDb profile *models.Profile ReplaceSeparatorFunc func(string) string } @@ -80,7 +80,7 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *util // Instantiate the SubPrefixDb with "DATATYPE_USERDATA" prefix prefix := common.ToBytes(db.DATATYPE_USERDATA) - prefixDb := storage.NewSubPrefixDb(userdataStore, prefix) + prefixDb := dbstorage.NewSubPrefixDb(userdataStore, prefix) h := &Handlers{ userdataStore: userDb, diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 12ed5c2..914dffc 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -13,7 +13,7 @@ import ( "git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/state" - "git.grassecon.net/urdt/ussd/internal/storage" + dbstorage "git.grassecon.net/urdt/ussd/internal/storage/db" "git.grassecon.net/urdt/ussd/internal/testutil/mocks" "git.grassecon.net/urdt/ussd/internal/testutil/testservice" "git.grassecon.net/urdt/ussd/internal/utils" @@ -59,14 +59,14 @@ func InitializeTestStore(t *testing.T) (context.Context, *common.UserDataStore) return ctx, store } -func InitializeTestSubPrefixDb(t *testing.T, ctx context.Context) *storage.SubPrefixDb { +func InitializeTestSubPrefixDb(t *testing.T, ctx context.Context) *dbstorage.SubPrefixDb { db := memdb.NewMemDb() err := db.Connect(ctx, "") if err != nil { t.Fatal(err) } prefix := common.ToBytes(visedb.DATATYPE_USERDATA) - spdb := storage.NewSubPrefixDb(db, prefix) + spdb := dbstorage.NewSubPrefixDb(db, prefix) return spdb } From d5a2680500cabfc2533db48ab7289efbeb491315 Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Sat, 4 Jan 2025 12:02:45 +0300 Subject: [PATCH 70/71] make context accessible --- cmd/africastalking/main.go | 4 ++-- internal/http/at/parse.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index 2696b51..0019239 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -15,10 +15,10 @@ import ( "git.defalsify.org/vise.git/logging" "git.defalsify.org/vise.git/resource" - "git.grassecon.net/urdt/ussd/common" "git.grassecon.net/urdt/ussd/config" "git.grassecon.net/urdt/ussd/initializers" "git.grassecon.net/urdt/ussd/internal/handlers" + "git.grassecon.net/urdt/ussd/internal/http/at" httpserver "git.grassecon.net/urdt/ussd/internal/http/at" "git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/remote" @@ -122,7 +122,7 @@ func main() { defer stateStore.Close() rp := &at.ATRequestParser{ - context: ctx, + Context: ctx, } bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) sh := httpserver.NewATSessionHandler(bsh) diff --git a/internal/http/at/parse.go b/internal/http/at/parse.go index a40cf0f..d2696ed 100644 --- a/internal/http/at/parse.go +++ b/internal/http/at/parse.go @@ -15,7 +15,7 @@ import ( ) type ATRequestParser struct { - context context.Context + Context context.Context } func (arp *ATRequestParser) GetSessionId(rq any) (string, error) { @@ -43,7 +43,7 @@ func (arp *ATRequestParser) GetSessionId(rq any) (string, error) { decodedStr := string(logBytes) sessionId, err := extractATSessionId(decodedStr) if err != nil { - context.WithValue(arp.context, "at-session-id", sessionId) + context.WithValue(arp.Context, "at-session-id", sessionId) } logg.Debugf("Received request:", decodedStr) } From 25867cf05ea1ba3da5ebbf8e8b7deacb5ca3208d Mon Sep 17 00:00:00 2001 From: lash Date: Sat, 4 Jan 2025 09:42:36 +0000 Subject: [PATCH 71/71] Rehabilitate voucher test --- common/vouchers_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/vouchers_test.go b/common/vouchers_test.go index ba6cd60..8b04e4a 100644 --- a/common/vouchers_test.go +++ b/common/vouchers_test.go @@ -10,7 +10,7 @@ import ( visedb "git.defalsify.org/vise.git/db" memdb "git.defalsify.org/vise.git/db/mem" - "git.grassecon.net/urdt/ussd/internal/storage" + dbstorage "git.grassecon.net/urdt/ussd/internal/storage/db" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" ) @@ -86,7 +86,7 @@ func TestGetVoucherData(t *testing.T) { } prefix := ToBytes(visedb.DATATYPE_USERDATA) - spdb := storage.NewSubPrefixDb(db, prefix) + spdb := dbstorage.NewSubPrefixDb(db, prefix) // Test voucher data mockData := map[DataTyp][]byte{