Compare commits

..

No commits in common. "master" and "minor-bug-fixes" have entirely different histories.

49 changed files with 245 additions and 1378 deletions

View File

@ -7,6 +7,7 @@ import (
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
@ -31,8 +32,8 @@ import (
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
build = "dev"
menuSeparator = ": "
)
func init() {
@ -44,14 +45,14 @@ type atRequestParser struct{}
func (arp *atRequestParser) GetSessionId(rq any) (string, error) {
rqv, ok := rq.(*http.Request)
if !ok {
logg.Warnf("got an invalid request", "req", rq)
log.Printf("got an invalid request:", 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)
log.Printf("failed to read request body:", err)
return "", fmt.Errorf("failed to read request body: %v", err)
}
// Reset the body for further reading
@ -61,13 +62,13 @@ func (arp *atRequestParser) GetSessionId(rq any) (string, error) {
bodyLog := map[string]string{"body": string(body)}
logBytes, err := json.Marshal(bodyLog)
if err != nil {
logg.Warnf("failed to marshal request body", "err", err)
log.Printf("failed to marshal request body:", err)
} else {
logg.Debugf("received request", "bytes", logBytes)
log.Printf("Received request:", string(logBytes))
}
if err := rqv.ParseForm(); err != nil {
logg.Warnf("failed to parse form data", "err", err)
log.Printf("failed to parse form data: %v", err)
return "", fmt.Errorf("failed to parse form data: %v", err)
}
@ -78,7 +79,7 @@ func (arp *atRequestParser) GetSessionId(rq any) (string, error) {
formattedNumber, err := common.FormatPhoneNumber(phoneNumber)
if err != nil {
logg.Warnf("failed to format phone number", "err", err)
fmt.Printf("Error: %v\n", err)
return "", fmt.Errorf("failed to format number")
}
@ -133,7 +134,6 @@ func main() {
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(128),
MenuSeparator: menuSeparator,
}
if engineDebug {

View File

@ -23,7 +23,6 @@ import (
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
menuSeparator = ": "
)
func init() {
@ -74,7 +73,6 @@ func main() {
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(128),
MenuSeparator: menuSeparator,
}
if engineDebug {

View File

@ -26,7 +26,6 @@ import (
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
menuSeparator = ": "
)
func init() {
@ -62,7 +61,6 @@ func main() {
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(128),
MenuSeparator: menuSeparator,
}
if engineDebug {

View File

@ -20,7 +20,6 @@ import (
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
menuSeparator = ": "
)
func init() {
@ -54,7 +53,6 @@ func main() {
SessionId: sessionId,
OutputSize: uint32(size),
FlagCount: uint32(128),
MenuSeparator: menuSeparator,
}
resourceDir := scriptDir

View File

@ -7,84 +7,32 @@ import (
"git.defalsify.org/vise.git/logging"
)
// DataType is a subprefix value used in association with vise/db.DATATYPE_USERDATA.
//
// All keys are used only within the context of a single account. Unless otherwise specified, the user context is the session id.
//
// * The first byte is vise/db.DATATYPE_USERDATA
// * The last 2 bytes are the DataTyp value, big-endian.
// * The intermediate bytes are the id of the user context.
//
// All values are strings
type DataTyp uint16
const (
// API Tracking id to follow status of account creation
DATA_TRACKING_ID = iota
// EVM address returned from API on account creation
DATA_ACCOUNT DataTyp = iota
DATA_ACCOUNT_CREATED
DATA_TRACKING_ID
DATA_PUBLIC_KEY
// Currently active PIN used to authenticate ussd state change requests
DATA_CUSTODIAL_ID
DATA_ACCOUNT_PIN
// The first name of the user
DATA_ACCOUNT_STATUS
DATA_FIRST_NAME
// The last name of the user
DATA_FAMILY_NAME
// The year-of-birth of the user
DATA_YOB
// The location of the user
DATA_LOCATION
// The gender of the user
DATA_GENDER
// The offerings description of the user
DATA_OFFERINGS
// The ethereum address of the recipient of an ongoing send request
DATA_RECIPIENT
// The voucher value amount of an ongoing send request
DATA_AMOUNT
// A general swap field for temporary values
DATA_TEMPORARY_VALUE
// Currently active voucher symbol of user
DATA_ACTIVE_SYM
// Voucher balance of user's currently active voucher
DATA_ACTIVE_BAL
// String boolean indicating whether use of PIN is blocked
DATA_BLOCKED_NUMBER
// Reverse mapping of a user's evm address to a session id.
DATA_PUBLIC_KEY_REVERSE
// Decimal count of the currently active voucher
DATA_ACTIVE_DECIMAL
// EVM address of the currently active voucher
DATA_ACTIVE_ADDRESS
)
const (
// List of valid voucher symbols in the user context.
DATA_VOUCHER_SYMBOLS DataTyp = 256 + iota
// List of voucher balances for vouchers valid in the user context.
DATA_VOUCHER_BALANCES
// List of voucher decimal counts for vouchers valid in the user context.
DATA_VOUCHER_DECIMALS
// List of voucher EVM addresses for vouchers valid in the user context.
DATA_VOUCHER_ADDRESSES
// List of senders for valid transactions in the user context.
)
const (
DATA_TX_SENDERS = 512 + iota
// List of recipients for valid transactions in the user context.
DATA_TX_RECIPIENTS
// List of voucher values for valid transactions in the user context.
DATA_TX_VALUES
// List of voucher EVM addresses for valid transactions in the user context.
DATA_TX_ADDRESSES
// List of valid transaction hashes in the user context.
DATA_TX_HASHES
// List of transaction dates for valid transactions in the user context.
DATA_TX_DATES
// List of voucher symbols for valid transactions in the user context.
DATA_TX_SYMBOLS
// List of voucher decimal counts for valid transactions in the user context.
DATA_TX_DECIMALS
DATA_TRANSACTIONS
)
var (
@ -121,10 +69,3 @@ func StringToDataTyp(str string) (DataTyp, error) {
return 0, errors.New("invalid DataTyp string")
}
}
// ToBytes converts DataTyp or int to a byte slice
func ToBytes[T ~uint16 | int](value T) []byte {
bytes := make([]byte, 2)
binary.BigEndian.PutUint16(bytes, uint16(value))
return bytes
}

View File

@ -57,25 +57,25 @@ 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) {
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)
keys := []string{"txfrom", "txto", "txval", "txaddr", "txhash", "txdate", "txsym"}
data := make(map[string]string)
for _, key := range keys {
value, err := db.Get(ctx, ToBytes(key))
value, err := db.Get(ctx, []byte(key))
if err != nil {
return "", fmt.Errorf("failed to get %s: %v", ToBytes(key), err)
return "", fmt.Errorf("failed to get %s: %v", key, err)
}
data[key] = string(value)
}
// Split the data
senders := strings.Split(string(data[DATA_TX_SENDERS]), "\n")
recipients := strings.Split(string(data[DATA_TX_RECIPIENTS]), "\n")
values := strings.Split(string(data[DATA_TX_VALUES]), "\n")
addresses := strings.Split(string(data[DATA_TX_ADDRESSES]), "\n")
hashes := strings.Split(string(data[DATA_TX_HASHES]), "\n")
dates := strings.Split(string(data[DATA_TX_DATES]), "\n")
syms := strings.Split(string(data[DATA_TX_SYMBOLS]), "\n")
senders := strings.Split(string(data["txfrom"]), "\n")
recipients := strings.Split(string(data["txto"]), "\n")
values := strings.Split(string(data["txval"]), "\n")
addresses := strings.Split(string(data["txaddr"]), "\n")
hashes := strings.Split(string(data["txhash"]), "\n")
dates := strings.Split(string(data["txdate"]), "\n")
syms := strings.Split(string(data["txsym"]), "\n")
// Check if index is within range
if index < 1 || index > len(senders) {
@ -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]),

View File

@ -20,7 +20,7 @@ type UserDataStore struct {
func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error) {
store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(sessionId)
k := ToBytes(typ)
k := PackKey(typ, []byte(sessionId))
return store.Get(ctx, k)
}
@ -29,6 +29,6 @@ func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ
func (store *UserDataStore) WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error {
store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(sessionId)
k := ToBytes(typ)
k := PackKey(typ, []byte(sessionId))
return store.Put(ctx, k, value)
}

View File

@ -64,23 +64,22 @@ 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) {
keys := []DataTyp{DATA_VOUCHER_SYMBOLS, DATA_VOUCHER_BALANCES, DATA_VOUCHER_DECIMALS, DATA_VOUCHER_ADDRESSES}
data := make(map[DataTyp]string)
keys := []string{"sym", "bal", "deci", "addr"}
data := make(map[string]string)
for _, key := range keys {
value, err := db.Get(ctx, ToBytes(key))
value, err := db.Get(ctx, []byte(key))
if err != nil {
return nil, fmt.Errorf("failed to get %s: %v", ToBytes(key), err)
return nil, fmt.Errorf("failed to get %s: %v", key, err)
}
data[key] = string(value)
}
symbol, balance, decimal, address := MatchVoucher(input,
data[DATA_VOUCHER_SYMBOLS],
data[DATA_VOUCHER_BALANCES],
data[DATA_VOUCHER_DECIMALS],
data[DATA_VOUCHER_ADDRESSES],
)
data["sym"],
data["bal"],
data["deci"],
data["addr"])
if symbol == "" {
return nil, nil

View File

@ -8,9 +8,8 @@ import (
"github.com/alecthomas/assert/v2"
"github.com/stretchr/testify/require"
visedb "git.defalsify.org/vise.git/db"
memdb "git.defalsify.org/vise.git/db/mem"
"git.grassecon.net/urdt/ussd/internal/storage"
memdb "git.defalsify.org/vise.git/db/mem"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
)
@ -84,21 +83,19 @@ func TestGetVoucherData(t *testing.T) {
if err != nil {
t.Fatal(err)
}
prefix := ToBytes(visedb.DATATYPE_USERDATA)
spdb := storage.NewSubPrefixDb(db, prefix)
spdb := storage.NewSubPrefixDb(db, []byte("vouchers"))
// Test voucher data
mockData := map[DataTyp][]byte{
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"),
mockData := map[string][]byte{
"sym": []byte("1:SRF\n2:MILO"),
"bal": []byte("1:100\n2:200"),
"deci": []byte("1:6\n2:4"),
"addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
}
// Put the data
for key, value := range mockData {
err = spdb.Put(ctx, []byte(ToBytes(key)), []byte(value))
err = spdb.Put(ctx, []byte(key), []byte(value))
if err != nil {
t.Fatal(err)
}

View File

@ -1,5 +0,0 @@
package debug
var (
DebugCap uint32
)

View File

@ -1,84 +0,0 @@
package debug
import (
"fmt"
"encoding/binary"
"git.grassecon.net/urdt/ussd/common"
"git.defalsify.org/vise.git/db"
)
var (
dbTypStr map[common.DataTyp]string = make(map[common.DataTyp]string)
)
type KeyInfo struct {
SessionId string
Typ uint8
SubTyp common.DataTyp
Label string
Description string
}
func (k KeyInfo) String() string {
v := uint16(k.SubTyp)
s := subTypToString(k.SubTyp)
if s == "" {
v = uint16(k.Typ)
s = typToString(k.Typ)
}
return fmt.Sprintf("Session Id: %s\nTyp: %s (%d)\n", k.SessionId, s, v)
}
func ToKeyInfo(k []byte, sessionId string) (KeyInfo, error) {
o := KeyInfo{}
b := []byte(sessionId)
if len(k) <= len(b) {
return o, fmt.Errorf("storage key missing")
}
o.SessionId = sessionId
o.Typ = uint8(k[0])
k = k[1:]
o.SessionId = string(k[:len(b)])
k = k[len(b):]
if o.Typ == db.DATATYPE_USERDATA {
if len(k) == 0 {
return o, fmt.Errorf("missing subtype key")
}
v := binary.BigEndian.Uint16(k[:2])
o.SubTyp = common.DataTyp(v)
o.Label = subTypToString(o.SubTyp)
k = k[2:]
} else {
o.Label = typToString(o.Typ)
}
if len(k) != 0 {
return o, fmt.Errorf("excess key information")
}
return o, nil
}
func FromKey(k []byte) (KeyInfo, error) {
o := KeyInfo{}
if len(k) < 4 {
return o, fmt.Errorf("insufficient key length")
}
sessionIdBytes := k[1:len(k)-2]
return ToKeyInfo(k, string(sessionIdBytes))
}
func subTypToString(v common.DataTyp) string {
return dbTypStr[v + db.DATATYPE_USERDATA + 1]
}
func typToString(v uint8) string {
return dbTypStr[common.DataTyp(uint16(v))]
}

View File

@ -1,48 +0,0 @@
// +build debugdb
package debug
import (
"git.defalsify.org/vise.git/db"
"git.grassecon.net/urdt/ussd/common"
)
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"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_LOCATION] = "location"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_GENDER] = "gender"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_OFFERINGS] = "offerings"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_RECIPIENT] = "recipient"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_AMOUNT] = "amount"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TEMPORARY_VALUE] = "temporary value"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACTIVE_SYM] = "active sym"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACTIVE_BAL] = "active bal"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_BLOCKED_NUMBER] = "blocked number"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_PUBLIC_KEY_REVERSE] = "public_key_reverse"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACTIVE_DECIMAL] = "active decimal"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACTIVE_ADDRESS] = "active address"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_VOUCHER_SYMBOLS] = "voucher symbols"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_VOUCHER_BALANCES] = "voucher balances"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_VOUCHER_DECIMALS] = "voucher decimals"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_VOUCHER_ADDRESSES] = "voucher addresses"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_SENDERS] = "tx senders"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_RECIPIENTS] = "tx recipients"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_VALUES] = "tx values"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_ADDRESSES] = "tx addresses"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_HASHES] = "tx hashes"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_DATES] = "tx dates"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_SYMBOLS] = "tx symbols"
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_DECIMALS] = "tx decimals"
}

View File

@ -1,78 +0,0 @@
package debug
import (
"testing"
"git.grassecon.net/urdt/ussd/common"
"git.defalsify.org/vise.git/db"
)
func TestDebugDbSubKeyInfo(t *testing.T) {
s := "foo"
b := []byte{0x20}
b = append(b, []byte(s)...)
b = append(b, []byte{0x00, 0x02}...)
r, err := ToKeyInfo(b, s)
if err != nil {
t.Fatal(err)
}
if r.SessionId != s {
t.Fatalf("expected %s, got %s", s, r.SessionId)
}
if r.Typ != 32 {
t.Fatalf("expected 64, got %d", r.Typ)
}
if r.SubTyp != 2 {
t.Fatalf("expected 2, got %d", r.SubTyp)
}
if DebugCap & 1 > 0 {
if r.Label != "tracking id" {
t.Fatalf("expected 'tracking id', got '%s'", r.Label)
}
}
}
func TestDebugDbKeyInfo(t *testing.T) {
s := "bar"
b := []byte{0x10}
b = append(b, []byte(s)...)
r, err := ToKeyInfo(b, s)
if err != nil {
t.Fatal(err)
}
if r.SessionId != s {
t.Fatalf("expected %s, got %s", s, r.SessionId)
}
if r.Typ != 16 {
t.Fatalf("expected 16, got %d", r.Typ)
}
if DebugCap & 1 > 0 {
if r.Label != "internal state" {
t.Fatalf("expected 'internal_state', got '%s'", r.Label)
}
}
}
func TestDebugDbKeyInfoRestore(t *testing.T) {
s := "bar"
b := []byte{db.DATATYPE_USERDATA}
b = append(b, []byte(s)...)
k := common.ToBytes(common.DATA_ACTIVE_SYM)
b = append(b, k...)
r, err := ToKeyInfo(b, s)
if err != nil {
t.Fatal(err)
}
if r.SessionId != s {
t.Fatalf("expected %s, got %s", s, r.SessionId)
}
if r.Typ != 32 {
t.Fatalf("expected 32, got %d", r.Typ)
}
if DebugCap & 1 > 0 {
if r.Label != "active sym" {
t.Fatalf("expected 'active sym', got '%s'", r.Label)
}
}
}

View File

@ -1,79 +0,0 @@
package main
import (
"context"
"crypto/sha1"
"flag"
"fmt"
"os"
"path"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/common"
)
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
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)
resourceDir := scriptDir
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir)
store, err := menuStorageService.GetUserdataDb(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userStore := common.UserDataStore{store}
h := sha1.New()
h.Write([]byte(sessionId))
address := h.Sum(nil)
addressString := fmt.Sprintf("%x", address)
err = userStore.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(addressString))
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
err = userStore.WriteEntry(ctx, addressString, common.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId))
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
err = store.Close()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
}

View File

@ -1,78 +0,0 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"path"
"git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/debug"
"git.defalsify.org/vise.git/logging"
)
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
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)
resourceDir := scriptDir
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir)
store, err := menuStorageService.GetUserdataDb(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
d, err := store.Dump(ctx, []byte(sessionId))
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
for true {
k, v := d.Next(ctx)
if k == nil {
break
}
o, err := debug.FromKey(k)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
fmt.Printf("%vValue: %v\n\n", o, v)
}
err = store.Close()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
}

View File

@ -1,28 +0,0 @@
# Internals
## Version
This document describes component versions:
* `urdt-ussd` `v0.5.0-beta`
* `go-vise` `v0.2.2`
## User profile data
All user profile items are stored under keys matching the user's session id, prefixed with the 8-bit value `git.defalsify.org/vise.git/db.DATATYPE_USERDATA` (32), and followed with a 16-big big-endian value subprefix.
For example, given the sessionId `+254123` and the key `git.grassecon.net/urdt-ussd/common.DATA_PUBLIC_KEY` (2) will be stored under the key:
```
0x322b3235343132330002
prefix sessionid subprefix
32 2b323534313233 0002
```
### Sub-prefixes
All sub-prefixes are defined as constants in the `git.grassecon.net/urdt-ussd/common` package. The constant names have the prefix `DATA_`
Please refer to inline godoc documentation for the `git.grassecon.net/urdt-ussd/common` package for details on each data item.

2
go.mod
View File

@ -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.1-0.20241122120231-9e9ee5bdfa7a
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

4
go.sum
View File

@ -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.1-0.20241122120231-9e9ee5bdfa7a h1:LvGKktk0kUnuRN3nF9r15D8OoV0sFaMmQr52kGq2gtE=
git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a/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=

View File

@ -2,7 +2,6 @@ package handlers
import (
"context"
"strings"
"git.defalsify.org/vise.git/asm"
"git.defalsify.org/vise.git/db"
@ -65,11 +64,7 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) {
}
func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceInterface) (*ussd.Handlers, error) {
replaceSeparatorFunc := func(input string) string {
return strings.ReplaceAll(input, ":", ls.Cfg.MenuSeparator)
}
ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService, replaceSeparatorFunc)
ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService)
if err != nil {
return nil, err
}
@ -126,8 +121,6 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceIn
ls.DbRs.AddLocalFunc("check_transactions", ussdHandlers.CheckTransactions)
ls.DbRs.AddLocalFunc("get_transactions", ussdHandlers.GetTransactionsList)
ls.DbRs.AddLocalFunc("view_statement", ussdHandlers.ViewTransactionStatement)
ls.DbRs.AddLocalFunc("update_all_profile_items", ussdHandlers.UpdateAllProfileItems)
ls.DbRs.AddLocalFunc("set_back", ussdHandlers.SetBack)
return ussdHandlers, nil
}

View File

@ -20,7 +20,6 @@ import (
"git.defalsify.org/vise.git/state"
"git.grassecon.net/urdt/ussd/common"
"git.grassecon.net/urdt/ussd/internal/utils"
"git.grassecon.net/urdt/ussd/models"
"git.grassecon.net/urdt/ussd/remote"
"gopkg.in/leonelquinteros/gotext.v1"
@ -77,21 +76,17 @@ type Handlers struct {
flagManager *asm.FlagParser
accountService remote.AccountServiceInterface
prefixDb storage.PrefixDb
profile *models.Profile
ReplaceSeparatorFunc func(string) string
}
func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface, replaceSeparatorFunc func(string) string) (*Handlers, error) {
func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface) (*Handlers, error) {
if userdataStore == nil {
return nil, fmt.Errorf("cannot create handler with nil userdata store")
}
userDb := &common.UserDataStore{
Db: userdataStore,
}
// Instantiate the SubPrefixDb with "DATATYPE_USERDATA" prefix
prefix := common.ToBytes(db.DATATYPE_USERDATA)
prefixDb := storage.NewSubPrefixDb(userdataStore, prefix)
// Instantiate the SubPrefixDb with "vouchers" prefix
prefixDb := storage.NewSubPrefixDb(userdataStore, []byte("vouchers"))
h := &Handlers{
userdataStore: userDb,
@ -99,8 +94,6 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *util
adminstore: adminstore,
accountService: accountService,
prefixDb: prefixDb,
profile: &models.Profile{Max: 6},
ReplaceSeparatorFunc: replaceSeparatorFunc,
}
return h, nil
}
@ -218,7 +211,7 @@ func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte)
return res, fmt.Errorf("missing session")
}
store := h.userdataStore
_, err = store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
_, err = store.ReadEntry(ctx, sessionId, common.DATA_ACCOUNT_CREATED)
if err != nil {
if db.IsNotFound(err) {
logg.InfoCtxf(ctx, "Creating an account because it doesn't exist")
@ -418,10 +411,7 @@ func (h *Handlers) SaveFirstname(ctx context.Context, sym string, input []byte)
firstName := string(input)
store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set")
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
firstNameSet := h.st.MatchFlag(flag_firstname_set, true)
if allowUpdate {
temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, common.DATA_FIRST_NAME, []byte(temporaryFirstName))
@ -429,17 +419,12 @@ func (h *Handlers) SaveFirstname(ctx context.Context, sym string, input []byte)
logg.ErrorCtxf(ctx, "failed to write firstName entry with", "key", common.DATA_FIRST_NAME, "value", temporaryFirstName, "error", err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_firstname_set)
} else {
if firstNameSet {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(firstName))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryFirstName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", firstName, "error", err)
return res, err
}
} else {
h.profile.InsertOrShift(0, firstName)
}
}
return res, nil
@ -458,9 +443,7 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
familyName := string(input)
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set")
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
familyNameSet := h.st.MatchFlag(flag_familyname_set, true)
if allowUpdate {
temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@ -469,17 +452,12 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
logg.ErrorCtxf(ctx, "failed to write familyName entry with", "key", common.DATA_FAMILY_NAME, "value", temporaryFamilyName, "error", err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_familyname_set)
} else {
if familyNameSet {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(familyName))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryFamilyName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", familyName, "error", err)
return res, err
}
} else {
h.profile.InsertOrShift(1, familyName)
}
}
return res, nil
@ -496,10 +474,7 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
yob := string(input)
store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set")
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
yobSet := h.st.MatchFlag(flag_yob_set, true)
if allowUpdate {
temporaryYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@ -508,17 +483,12 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
logg.ErrorCtxf(ctx, "failed to write yob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", temporaryYob, "error", err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_yob_set)
} else {
if yobSet {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(yob))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryYob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", yob, "error", err)
return res, err
}
} else {
h.profile.InsertOrShift(3, yob)
}
}
return res, nil
@ -536,9 +506,7 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (
store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_location_set, _ := h.flagManager.GetFlag("flag_location_set")
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
locationSet := h.st.MatchFlag(flag_location_set, true)
if allowUpdate {
temporaryLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@ -547,18 +515,12 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (
logg.ErrorCtxf(ctx, "failed to write location entry with", "key", common.DATA_LOCATION, "value", temporaryLocation, "error", err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_location_set)
} else {
if locationSet {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(location))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryLocation entry with", "key", common.DATA_TEMPORARY_VALUE, "value", location, "error", err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_location_set)
} else {
h.profile.InsertOrShift(4, location)
}
}
return res, nil
@ -576,10 +538,7 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
gender := strings.Split(symbol, "_")[1]
store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set")
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
genderSet := h.st.MatchFlag(flag_gender_set, true)
if allowUpdate {
temporaryGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@ -588,17 +547,12 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
logg.ErrorCtxf(ctx, "failed to write gender entry with", "key", common.DATA_GENDER, "value", gender, "error", err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_gender_set)
} else {
if genderSet {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(gender))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryGender entry with", "key", common.DATA_TEMPORARY_VALUE, "value", gender, "error", err)
return res, err
}
} else {
h.profile.InsertOrShift(2, gender)
}
}
return res, nil
@ -617,10 +571,7 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set")
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
offeringsSet := h.st.MatchFlag(flag_offerings_set, true)
if allowUpdate {
temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@ -629,17 +580,12 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
logg.ErrorCtxf(ctx, "failed to write offerings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_offerings_set)
} else {
if offeringsSet {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(offerings))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryOfferings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
return res, err
}
} else {
h.profile.InsertOrShift(5, offerings)
}
}
return res, nil
@ -731,18 +677,6 @@ 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
func (h *Handlers) SetBack(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
//TODO:
//Add check if the navigation is lateral nav instead of checking the input.
if string(input) == "0" {
flag_back_set, _ := h.flagManager.GetFlag("flag_back_set")
res.FlagSet = append(res.FlagSet, flag_back_set)
}
return res, nil
}
// CheckAccountStatus queries the API using the TrackingId and sets flags
// based on the account status
func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
@ -1342,17 +1276,6 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
var res resource.Result
var profileInfo []byte
var err error
flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set")
flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set")
flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set")
flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set")
flag_location_set, _ := h.flagManager.GetFlag("flag_location_set")
flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set")
flag_back_set, _ := h.flagManager.GetFlag("flag_back_set")
res.FlagReset = append(res.FlagReset, flag_back_set)
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
@ -1379,7 +1302,6 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "Failed to read first name entry with", "key", "error", common.DATA_FIRST_NAME, err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_firstname_set)
res.Content = string(profileInfo)
case common.DATA_FAMILY_NAME:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME)
@ -1391,7 +1313,6 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "Failed to read family name entry with", "key", "error", common.DATA_FAMILY_NAME, err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_familyname_set)
res.Content = string(profileInfo)
case common.DATA_GENDER:
@ -1404,7 +1325,6 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "Failed to read gender entry with", "key", "error", common.DATA_GENDER, err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_gender_set)
res.Content = string(profileInfo)
case common.DATA_YOB:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_YOB)
@ -1416,8 +1336,8 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "Failed to read year of birth(yob) entry with", "key", "error", common.DATA_YOB, err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_yob_set)
res.Content = string(profileInfo)
case common.DATA_LOCATION:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_LOCATION)
if err != nil {
@ -1428,7 +1348,6 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "Failed to read location entry with", "key", "error", common.DATA_LOCATION, err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_location_set)
res.Content = string(profileInfo)
case common.DATA_OFFERINGS:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS)
@ -1440,7 +1359,6 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
logg.ErrorCtxf(ctx, "Failed to read offerings entry with", "key", "error", common.DATA_OFFERINGS, err)
return res, err
}
res.FlagSet = append(res.FlagSet, flag_offerings_set)
res.Content = string(profileInfo)
default:
break
@ -1658,15 +1576,15 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
data := common.ProcessVouchers(vouchersResp)
// Store all voucher data
dataMap := map[common.DataTyp]string{
common.DATA_VOUCHER_SYMBOLS: data.Symbols,
common.DATA_VOUCHER_BALANCES: data.Balances,
common.DATA_VOUCHER_DECIMALS: data.Decimals,
common.DATA_VOUCHER_ADDRESSES: data.Addresses,
dataMap := map[string]string{
"sym": data.Symbols,
"bal": data.Balances,
"deci": data.Decimals,
"addr": data.Addresses,
}
for key, value := range dataMap {
if err := h.prefixDb.Put(ctx, []byte(common.ToBytes(key)), []byte(value)); err != nil {
if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil {
return res, nil
}
}
@ -1679,15 +1597,13 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte)
var res resource.Result
// Read vouchers from the store
voucherData, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS))
voucherData, err := h.prefixDb.Get(ctx, []byte("sym"))
if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the voucherData from prefixDb", "error", err)
return res, err
}
formattedData := h.ReplaceSeparatorFunc(string(voucherData))
res.Content = string(formattedData)
res.Content = string(voucherData)
return res, nil
}
@ -1827,19 +1743,19 @@ func (h *Handlers) CheckTransactions(ctx context.Context, sym string, input []by
data := common.ProcessTransfers(transactionsResp)
// Store all transaction data
dataMap := map[common.DataTyp]string{
common.DATA_TX_SENDERS: data.Senders,
common.DATA_TX_RECIPIENTS: data.Recipients,
common.DATA_TX_VALUES: data.TransferValues,
common.DATA_TX_ADDRESSES: data.Addresses,
common.DATA_TX_HASHES: data.TxHashes,
common.DATA_TX_DATES: data.Dates,
common.DATA_TX_SYMBOLS: data.Symbols,
common.DATA_TX_DECIMALS: data.Decimals,
dataMap := map[string]string{
"txfrom": data.Senders,
"txto": data.Recipients,
"txval": data.TransferValues,
"txaddr": data.Addresses,
"txhash": data.TxHashes,
"txdate": data.Dates,
"txsym": data.Symbols,
"txdeci": data.Decimals,
}
for key, value := range dataMap {
if err := h.prefixDb.Put(ctx, []byte(common.ToBytes(key)), []byte(value)); err != nil {
if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil {
logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err)
return res, err
}
@ -1850,14 +1766,13 @@ 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)
if !ok {
return res, fmt.Errorf("missing session")
}
store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil {
@ -1866,22 +1781,22 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input []
}
// Read transactions from the store and format them
TransactionSenders, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_TX_SENDERS))
TransactionSenders, err := h.prefixDb.Get(ctx, []byte("txfrom"))
if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the TransactionSenders from prefixDb", "error", err)
return res, err
}
TransactionSyms, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_TX_SYMBOLS))
TransactionSyms, err := h.prefixDb.Get(ctx, []byte("txsym"))
if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the TransactionSyms from prefixDb", "error", err)
return res, err
}
TransactionValues, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_TX_VALUES))
TransactionValues, err := h.prefixDb.Get(ctx, []byte("txval"))
if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the TransactionValues from prefixDb", "error", err)
return res, err
}
TransactionDates, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_TX_DATES))
TransactionDates, err := h.prefixDb.Get(ctx, []byte("txdate"))
if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the TransactionDates from prefixDb", "error", err)
return res, err
@ -1900,14 +1815,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"
}
// Use the ReplaceSeparator function for the menu separator
transactionLine := fmt.Sprintf("%d%s%s %s %s %s", i+1, h.ReplaceSeparatorFunc(":"), status, value, sym, date)
formattedTransactions = append(formattedTransactions, transactionLine)
formattedTransactions = append(formattedTransactions, fmt.Sprintf("%d:%s %s %s %s", i+1, status, value, sym, date))
}
res.Content = strings.Join(formattedTransactions, "\n")
@ -1963,53 +1876,3 @@ func (h *Handlers) ViewTransactionStatement(ctx context.Context, sym string, inp
return res, nil
}
func (h *Handlers) insertProfileItems(ctx context.Context, sessionId string, res *resource.Result) error {
var err error
store := h.userdataStore
profileFlagNames := []string{
"flag_firstname_set",
"flag_familyname_set",
"flag_yob_set",
"flag_gender_set",
"flag_location_set",
"flag_offerings_set",
}
profileDataKeys := []common.DataTyp{
common.DATA_FIRST_NAME,
common.DATA_FAMILY_NAME,
common.DATA_GENDER,
common.DATA_YOB,
common.DATA_LOCATION,
common.DATA_OFFERINGS,
}
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)
}
}
return nil
}
// 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)
if !ok {
return res, fmt.Errorf("missing session")
}
err := h.insertProfileItems(ctx, sessionId, &res)
if err != nil {
return res, err
}
return res, nil
}

View File

@ -5,7 +5,6 @@ import (
"fmt"
"log"
"path"
"strings"
"testing"
"git.defalsify.org/vise.git/lang"
@ -23,7 +22,6 @@ import (
testdataloader "github.com/peteole/testdata-loader"
"github.com/stretchr/testify/require"
visedb "git.defalsify.org/vise.git/db"
memdb "git.defalsify.org/vise.git/db/mem"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
)
@ -33,11 +31,6 @@ 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()
@ -63,8 +56,7 @@ func InitializeTestSubPrefixDb(t *testing.T, ctx context.Context) *storage.SubPr
if err != nil {
t.Fatal(err)
}
prefix := common.ToBytes(visedb.DATATYPE_USERDATA)
spdb := storage.NewSubPrefixDb(db, prefix)
spdb := storage.NewSubPrefixDb(db, []byte("vouchers"))
return spdb
}
@ -73,15 +65,12 @@ func TestNewHandlers(t *testing.T) {
_, store := InitializeTestStore(t)
fm, err := NewFlagManager(flagsPath)
if err != nil {
log.Fatal(err)
}
accountService := testservice.TestAccountService{}
// Test case for valid UserDataStore
if err != nil {
t.Logf(err.Error())
}
t.Run("Valid UserDataStore", func(t *testing.T) {
handlers, err := NewHandlers(fm.parser, store, nil, &accountService, mockReplaceSeparator)
handlers, err := NewHandlers(fm.parser, store, nil, &accountService)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
@ -91,30 +80,19 @@ func TestNewHandlers(t *testing.T) {
if handlers.userdataStore == nil {
t.Fatal("expected userdataStore to be set in handlers")
}
if handlers.ReplaceSeparatorFunc == nil {
t.Fatal("expected ReplaceSeparatorFunc to be set in handlers")
}
// Test ReplaceSeparatorFunc functionality
input := "1:Menu item"
expectedOutput := "1: Menu item"
if handlers.ReplaceSeparatorFunc(input) != expectedOutput {
t.Fatalf("ReplaceSeparatorFunc function did not return expected output: got %v, want %v", handlers.ReplaceSeparatorFunc(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, mockReplaceSeparator)
handlers, err := NewHandlers(fm.parser, nil, nil, &accountService)
if err == nil {
t.Fatal("expected an error, got none")
}
if handlers != nil {
t.Fatal("expected handlers to be nil")
}
expectedError := "cannot create handler with nil userdata store"
if err.Error() != expectedError {
t.Fatalf("expected error '%s', got '%v'", expectedError, err)
if err.Error() != "cannot create handler with nil userdata store" {
t.Fatalf("expected specific error, got %v", err)
}
})
}
@ -201,14 +179,11 @@ func TestSaveFirstname(t *testing.T) {
fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_firstname_set, _ := fm.GetFlag("flag_firstname_set")
// Set the flag in the State
mockState := state.NewState(128)
mockState := state.NewState(16)
mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
// Define test data
firstName := "John"
@ -216,8 +191,6 @@ func TestSaveFirstname(t *testing.T) {
t.Fatal(err)
}
expectedResult.FlagSet = []uint32{flag_firstname_set}
// Create the Handlers instance with the mock store
h := &Handlers{
userdataStore: store,
@ -230,7 +203,7 @@ func TestSaveFirstname(t *testing.T) {
// Assert results
assert.NoError(t, err)
assert.Equal(t, expectedResult, res)
assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_FIRST_NAME entry has been updated with the temporary value
storedFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FIRST_NAME)
@ -245,16 +218,11 @@ func TestSaveFamilyname(t *testing.T) {
fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_firstname_set, _ := fm.GetFlag("flag_familyname_set")
// Set the flag in the State
mockState := state.NewState(128)
mockState := state.NewState(16)
mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
expectedResult.FlagSet = []uint32{flag_firstname_set}
// Define test data
familyName := "Doeee"
@ -274,7 +242,7 @@ func TestSaveFamilyname(t *testing.T) {
// Assert results
assert.NoError(t, err)
assert.Equal(t, expectedResult, res)
assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value
storedFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME)
@ -289,14 +257,11 @@ func TestSaveYoB(t *testing.T) {
fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_yob_set, _ := fm.GetFlag("flag_yob_set")
// Set the flag in the State
mockState := state.NewState(108)
mockState := state.NewState(16)
mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
// Define test data
yob := "1980"
@ -304,8 +269,6 @@ func TestSaveYoB(t *testing.T) {
t.Fatal(err)
}
expectedResult.FlagSet = []uint32{flag_yob_set}
// Create the Handlers instance with the mock store
h := &Handlers{
userdataStore: store,
@ -318,7 +281,7 @@ func TestSaveYoB(t *testing.T) {
// Assert results
assert.NoError(t, err)
assert.Equal(t, expectedResult, res)
assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_YOB entry has been updated with the temporary value
storedYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_YOB)
@ -333,14 +296,11 @@ func TestSaveLocation(t *testing.T) {
fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_location_set, _ := fm.GetFlag("flag_location_set")
// Set the flag in the State
mockState := state.NewState(108)
mockState := state.NewState(16)
mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
// Define test data
location := "Kilifi"
@ -348,8 +308,6 @@ func TestSaveLocation(t *testing.T) {
t.Fatal(err)
}
expectedResult.FlagSet = []uint32{flag_location_set}
// Create the Handlers instance with the mock store
h := &Handlers{
userdataStore: store,
@ -362,7 +320,7 @@ func TestSaveLocation(t *testing.T) {
// Assert results
assert.NoError(t, err)
assert.Equal(t, expectedResult, res)
assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_LOCATION entry has been updated with the temporary value
storedLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_LOCATION)
@ -377,14 +335,11 @@ func TestSaveOfferings(t *testing.T) {
fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_offerings_set, _ := fm.GetFlag("flag_offerings_set")
// Set the flag in the State
mockState := state.NewState(108)
mockState := state.NewState(16)
mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
// Define test data
offerings := "Bananas"
@ -392,8 +347,6 @@ func TestSaveOfferings(t *testing.T) {
t.Fatal(err)
}
expectedResult.FlagSet = []uint32{flag_offerings_set}
// Create the Handlers instance with the mock store
h := &Handlers{
userdataStore: store,
@ -406,7 +359,7 @@ func TestSaveOfferings(t *testing.T) {
// Assert results
assert.NoError(t, err)
assert.Equal(t, expectedResult, res)
assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_OFFERINGS entry has been updated with the temporary value
storedOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS)
@ -421,10 +374,9 @@ func TestSaveGender(t *testing.T) {
fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_gender_set, _ := fm.GetFlag("flag_gender_set")
// Set the flag in the State
mockState := state.NewState(108)
mockState := state.NewState(16)
mockState.SetFlag(flag_allow_update)
// Define test cases
@ -468,16 +420,12 @@ func TestSaveGender(t *testing.T) {
flagManager: fm.parser,
}
expectedResult := resource.Result{}
// Call the method
res, err := h.SaveGender(ctx, "save_gender", tt.input)
expectedResult.FlagSet = []uint32{flag_gender_set}
// Assert results
assert.NoError(t, err)
assert.Equal(t, expectedResult, res)
assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_GENDER entry has been updated with the temporary value
storedGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_GENDER)
@ -1989,7 +1937,7 @@ func TestCheckVouchers(t *testing.T) {
assert.NoError(t, err)
// Read voucher sym data from the store
voucherData, err := spdb.Get(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS))
voucherData, err := spdb.Get(ctx, []byte("sym"))
if err != nil {
t.Fatal(err)
}
@ -2002,31 +1950,26 @@ func TestCheckVouchers(t *testing.T) {
func TestGetVoucherList(t *testing.T) {
sessionId := "session123"
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
spdb := InitializeTestSubPrefixDb(t, ctx)
// Initialize Handlers
h := &Handlers{
prefixDb: spdb,
ReplaceSeparatorFunc: mockReplaceSeparator,
}
mockSyms := []byte("1:SRF\n2:MILO")
expectedSym := []byte("1:SRF\n2:MILO")
// Put voucher sym data from the store
err := spdb.Put(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS), mockSyms)
err := spdb.Put(ctx, []byte("sym"), expectedSym)
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(expectedSyms))
assert.Equal(t, res.Content, string(expectedSym))
}
func TestViewVoucher(t *testing.T) {
@ -2048,16 +1991,16 @@ 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_BALANCES: []byte("1:100\n2:200"),
common.DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"),
common.DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
mockData := map[string][]byte{
"sym": []byte("1:SRF\n2:MILO"),
"bal": []byte("1:100\n2:200"),
"deci": []byte("1:6\n2:4"),
"addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
}
// Put the data
for key, value := range mockData {
err = spdb.Put(ctx, []byte(common.ToBytes(key)), []byte(value))
err = spdb.Put(ctx, []byte(key), []byte(value))
if err != nil {
t.Fatal(err)
}

View File

@ -6,6 +6,10 @@ import (
"git.defalsify.org/vise.git/db"
)
const (
DATATYPE_USERSUB = 64
)
// PrefixDb interface abstracts the database operations.
type PrefixDb interface {
Get(ctx context.Context, key []byte) ([]byte, error)
@ -31,13 +35,13 @@ func (s *SubPrefixDb) toKey(k []byte) []byte {
}
func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
s.store.SetPrefix(db.DATATYPE_USERDATA)
s.store.SetPrefix(DATATYPE_USERSUB)
key = s.toKey(key)
return s.store.Get(ctx, key)
}
func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error {
s.store.SetPrefix(db.DATATYPE_USERDATA)
s.store.SetPrefix(DATATYPE_USERSUB)
key = s.toKey(key)
return s.store.Put(ctx, key, val)
}

View File

@ -115,8 +115,7 @@ func(tdb *ThreadGdbmDb) Close() error {
return err
}
func(tdb *ThreadGdbmDb) Dump(ctx context.Context, key []byte) (*db.Dumper, error) {
tdb.reserve()
defer tdb.release()
return tdb.db.Dump(ctx, key)
func(tdb *ThreadGdbmDb) Dump(_ context.Context, _ []byte) (*db.Dumper, error) {
logg.Warnf("method not implemented for thread gdbm db")
return nil, nil
}

View File

@ -167,7 +167,7 @@
]
},
{
"name": "menu_my_account_edit_all_account_details_starting_from_firstname",
"name": "menu_my_account_edit_firstname",
"steps": [
{
"input": "",
@ -187,26 +187,6 @@
},
{
"input": "foo",
"expectedContent": "Enter family name:\n0:Back"
},
{
"input": "bar",
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
},
{
"input": "1",
"expectedContent": "Enter your year of birth\n0:Back"
},
{
"input": "1940",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
@ -217,6 +197,10 @@
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@ -224,7 +208,7 @@
]
},
{
"name": "menu_my_account_edit_familyname_when_all_account__details_have_been_set",
"name": "menu_my_account_edit_familyname",
"steps": [
{
"input": "",
@ -254,6 +238,10 @@
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@ -262,7 +250,7 @@
]
},
{
"name": "menu_my_account_edit_gender_when_all_account__details_have_been_set",
"name": "menu_my_account_edit_gender",
"steps": [
{
"input": "",
@ -292,6 +280,10 @@
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@ -299,7 +291,7 @@
]
},
{
"name": "menu_my_account_edit_yob_when_all_account__details_have_been_set",
"name": "menu_my_account_edit_yob",
"steps": [
{
"input": "",
@ -329,6 +321,10 @@
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@ -336,7 +332,7 @@
]
},
{
"name": "menu_my_account_edit_location_when_all_account_details_have_been_set",
"name": "menu_my_account_edit_location",
"steps": [
{
"input": "",
@ -366,6 +362,10 @@
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@ -373,7 +373,7 @@
]
},
{
"name": "menu_my_account_edit_offerings_when_all_account__details_have_been_set",
"name": "menu_my_account_edit_offerings",
"steps": [
{
"input": "",
@ -403,6 +403,10 @@
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@ -430,12 +434,16 @@
},
{
"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",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"

View File

@ -3,7 +3,6 @@ package menutraversaltest
import (
"bytes"
"context"
"flag"
"log"
"math/rand"
"os"
@ -18,13 +17,12 @@ import (
var (
testData = driver.ReadData()
testStore = ".test_state"
groupTestFile = "group_test.json"
sessionID string
src = rand.NewSource(42)
g = rand.New(src)
)
var groupTestFile = flag.String("test-file", "group_test.json", "The test file to use for running the group tests")
func GenerateSessionId() string {
uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g))
v, err := uu.NewV4()
@ -339,7 +337,7 @@ func TestMainMenuSend(t *testing.T) {
}
func TestGroups(t *testing.T) {
groups, err := driver.LoadTestGroups(*groupTestFile)
groups, err := driver.LoadTestGroups(groupTestFile)
if err != nil {
log.Fatalf("Failed to load test groups: %v", err)
}

View File

@ -1,68 +0,0 @@
{
"groups": [
{
"name": "menu_my_account_edit_all_account_details_starting_from_family_name",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "2",
"expectedContent": "Enter family name:\n0:Back"
},
{
"input": "bar",
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
},
{
"input": "1",
"expectedContent": "Enter your year of birth\n0:Back"
},
{
"input": "1940",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@ -1,61 +0,0 @@
{
"groups": [
{
"name": "menu_my_account_edit_all_account_details_starting_from_firstname",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "1",
"expectedContent": "Enter your first names:\n0:Back"
},
{
"input": "foo",
"expectedContent": "Enter family name:\n0:Back"
},
{
"input": "bar",
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
},
{
"input": "1",
"expectedContent": "Enter your year of birth\n0:Back"
},
{
"input": "1940",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@ -1,55 +0,0 @@
{
"groups": [
{
"name": "menu_my_account_edit_all_account_details_starting_from_gender",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "3",
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
},
{
"input": "1",
"expectedContent": "Enter your year of birth\n0:Back"
},
{
"input": "1940",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@ -1,46 +0,0 @@
{
"groups": [
{
"name": "menu_my_account_edit_all_account_details_starting_from_location",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "5",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@ -1,42 +0,0 @@
{
"groups": [
{
"name": "menu_my_account_edit_all_account_details_starting_from_offerings",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "6",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@ -1,50 +0,0 @@
{
"groups": [
{
"name": "menu_my_account_edit_all_account_details_starting_from_yob",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "4",
"expectedContent": "Enter your year of birth\n0:Back"
},
{
"input": "1940",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@ -1,70 +0,0 @@
{
"groups": [
{
"name": "menu_my_account_edit_familyname_when_adjacent_profile_information_set",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "3",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "1",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "3",
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
},
{
"input": "1",
"expectedContent": "Enter your year of birth\n0:Back"
},
{
"input": "1940",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "2",
"expectedContent": "Enter family name:\n0:Back"
},
{
"input": "foo2",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@ -11,7 +11,7 @@
},
{
"input": "0",
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n0:Yes\n1:No"
"expectedContent": "Do you agree to terms and conditions?\n0:Yes\n1:No"
},
{
"input": "0",
@ -44,7 +44,7 @@
},
{
"input": "0",
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n0:Yes\n1:No"
"expectedContent": "Do you agree to terms and conditions?\n0:Yes\n1:No"
},
{
"input": "1",

View File

@ -1,18 +0,0 @@
package models
type Profile struct {
ProfileItems []string
Max int
}
func (p *Profile) InsertOrShift(index int, value string) {
if index < len(p.ProfileItems) {
p.ProfileItems = append(p.ProfileItems[:index], value)
} else {
for len(p.ProfileItems) < index {
p.ProfileItems = append(p.ProfileItems, "0")
}
p.ProfileItems = append(p.ProfileItems, "0")
p.ProfileItems[index] = value
}
}

View File

@ -2,17 +2,9 @@ CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_familyname flag_allow_update 1
LOAD get_current_profile_info 0
RELOAD get_current_profile_info
MAP get_current_profile_info
MOUT back 0
HALT
RELOAD set_back
CATCH _ flag_back_set 1
LOAD save_familyname 64
LOAD save_familyname 0
RELOAD save_familyname
CATCH pin_entry flag_familyname_set 1
CATCH select_gender flag_gender_set 0
CATCH edit_yob flag_yob_set 0
CATCH edit_location flag_location_set 0
CATCH edit_offerings flag_offerings_set 0
CATCH pin_entry flag_familyname_set 0
INCMP select_gender *
INCMP _ 0
INCMP pin_entry *

View File

@ -5,14 +5,7 @@ RELOAD get_current_profile_info
MAP get_current_profile_info
MOUT back 0
HALT
RELOAD set_back
CATCH _ flag_back_set 1
LOAD save_firstname 128
LOAD save_firstname 0
RELOAD save_firstname
CATCH pin_entry flag_firstname_set 1
CATCH edit_family_name flag_familyname_set 0
CATCH edit_gender flag_gender_set 0
CATCH edit_yob flag_yob_set 0
CATCH edit_location flag_location_set 0
CATCH edit_offerings flag_offerings_set 0
CATCH pin_entry flag_firstname_set 0
INCMP _ 0
INCMP pin_entry *

View File

@ -2,14 +2,9 @@ CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_location flag_allow_update 1
LOAD get_current_profile_info 0
RELOAD get_current_profile_info
LOAD save_location 16
MOUT back 0
HALT
RELOAD set_back
CATCH _ flag_back_set 1
LOAD save_location 0
RELOAD save_location
INCMP _ 0
CATCH pin_entry flag_location_set 1
CATCH edit_offerings flag_offerings_set 0
CATCH pin_entry flag_location_set 0
INCMP edit_offerings *
INCMP pin_entry *

View File

@ -2,13 +2,9 @@ CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_offerings flag_allow_update 1
LOAD get_current_profile_info 0
RELOAD get_current_profile_info
LOAD save_offerings 8
LOAD save_offerings 0
MOUT back 0
HALT
RELOAD set_back
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 *
INCMP pin_entry *

View File

@ -11,8 +11,7 @@ MOUT edit_offerings 6
MOUT view 7
MOUT back 0
HALT
LOAD set_back 6
INCMP ^ 0
INCMP my_account 0
INCMP edit_first_name 1
INCMP edit_family_name 2
INCMP select_gender 3

View File

@ -5,15 +5,10 @@ RELOAD get_current_profile_info
MAP get_current_profile_info
MOUT back 0
HALT
RELOAD set_back
CATCH _ flag_back_set 1
LOAD verify_yob 6
RELOAD verify_yob
CATCH incorrect_date_format flag_incorrect_date_format 1
LOAD save_yob 32
LOAD save_yob 0
RELOAD save_yob
CATCH pin_entry flag_yob_set 1
CATCH edit_location flag_location_set 0
CATCH edit_offerings flag_offerings_set 0
CATCH pin_entry flag_yob_set 0
INCMP edit_location *
INCMP _ 0
INCMP pin_entry *

View File

@ -21,10 +21,3 @@ flag,flag_admin_privilege,27,this is set when a user has admin privileges.
flag,flag_unregistered_number,28,this is set when an unregistered phonenumber tries to perform an action
flag,flag_no_transfers,29,this is set when a user does not have any transactions
flag,flag_incorrect_statement,30,this is set when the selected statement is invalid
flag,flag_firstname_set,31,this is set when the first name of the profile is set
flag,flag_familyname_set,32,this is set when the family name of the profile is set
flag,flag_yob_set,33,this is set when the yob of the profile is set
flag,flag_gender_set,34,this is set when the gender of the profile is set
flag,flag_location_set,35,this is set when the location of the profile is set
flag,flag_offerings_set,36,this is set when the offerings of the profile is set
flag,flag_back_set,37,this is set when it is a back navigation

1 flag flag_language_set 8 checks whether the user has set their prefered language
21 flag flag_unregistered_number 28 this is set when an unregistered phonenumber tries to perform an action
22 flag flag_no_transfers 29 this is set when a user does not have any transactions
23 flag flag_incorrect_statement 30 this is set when the selected statement is invalid
flag flag_firstname_set 31 this is set when the first name of the profile is set
flag flag_familyname_set 32 this is set when the family name of the profile is set
flag flag_yob_set 33 this is set when the yob of the profile is set
flag flag_gender_set 34 this is set when the gender of the profile is set
flag flag_location_set 35 this is set when the location of the profile is set
flag flag_offerings_set 36 this is set when the offerings of the profile is set
flag flag_back_set 37 this is set when it is a back navigation

View File

@ -1,5 +1,3 @@
LOAD update_all_profile_items 0
RELOAD update_all_profile_items
MOUT back 0
MOUT quit 9
HALT

View File

@ -1,10 +1,4 @@
LOAD save_gender 32
RELOAD save_gender
LOAD save_gender 0
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_gender flag_allow_update 1
CATCH pin_entry flag_gender_set 1
CATCH edit_yob flag_yob_set 0
CATCH edit_location flag_location_set 0
CATCH edit_offerings flag_offerings_set 0
CATCH pin_entry flag_gender_set 0
MOVE edit_yob
MOVE pin_entry

View File

@ -1,10 +1,4 @@
LOAD save_gender 16
RELOAD save_gender
LOAD save_gender 0
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_gender flag_allow_update 1
CATCH pin_entry flag_gender_set 1
CATCH edit_yob flag_yob_set 0
CATCH edit_location flag_location_set 0
CATCH edit_offerings flag_offerings_set 0
CATCH pin_entry flag_gender_set 0
MOVE edit_yob
MOVE pin_entry

View File

@ -1,10 +1,4 @@
LOAD save_gender 8
RELOAD save_gender
LOAD save_gender 0
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_gender flag_allow_update 1
CATCH pin_entry flag_gender_set 1
CATCH edit_yob flag_yob_set 0
CATCH edit_location flag_location_set 0
CATCH edit_offerings flag_offerings_set 0
CATCH pin_entry flag_gender_set 0
MOVE edit_yob
MOVE pin_entry

View File

@ -1,2 +1 @@
Do you agree to terms and conditions?
https://grassecon.org/pages/terms-and-conditions

View File

@ -1,2 +1 @@
Kwa kutumia hii huduma umekubali sheria na masharti?
https://grassecon.org/pages/terms-and-conditions

View File

@ -1,3 +0,0 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH profile_update_success flag_allow_update 1
MOVE pin_entry