Compare commits

..

19 Commits

Author SHA1 Message Date
eea3be3a39
resolve failing test 2024-10-17 13:49:56 +03:00
849a950fbc Merge branch 'master' into menu-voucherlist 2024-10-17 13:44:31 +03:00
e986eaa538 Merge pull request 'menu-api-errors' (#112) from menu-api-errors into master
Reviewed-on: #112
2024-10-16 19:08:46 +02:00
0be570ae2d
add check for api call failure 2024-10-15 16:31:31 +03:00
6a36bc43b5
add check for api call failure 2024-10-15 16:31:15 +03:00
1d27a88908
add test on validate amount 2024-10-15 16:30:29 +03:00
4889e6d18b
Merge remote-tracking branch 'origin' into menu-api-errors 2024-10-15 14:04:45 +03:00
368c25125a
set flag count to 128 2024-10-15 13:59:49 +03:00
283793a2ae
add swahili menu option 2024-10-15 13:57:45 +03:00
bec7e5c69f
update: fetch balances in a sepate function,show profile information in swahili and english 2024-10-15 13:57:05 +03:00
d638aba85e
update tests 2024-10-15 13:50:28 +03:00
df7788dd0b
return actual reponses on the api calls 2024-10-15 13:48:14 +03:00
4a62773098
add handler fetching custodial balances 2024-10-15 13:44:50 +03:00
26d315b032
add check for api call failure 2024-10-15 13:42:20 +03:00
1927544533
reset authorized flag 2024-10-15 13:40:40 +03:00
c641a0c669
add api failure nodes 2024-10-14 23:19:09 +03:00
952da86931
update balance nodes 2024-10-14 23:18:42 +03:00
be6391686f
update return type 2024-10-14 23:17:57 +03:00
65794c1b20
add api calls flag 2024-10-14 23:17:17 +03:00
21 changed files with 379 additions and 183 deletions

View File

@ -87,7 +87,7 @@ func main() {
cfg := engine.Config{ cfg := engine.Config{
Root: "root", Root: "root",
OutputSize: uint32(size), OutputSize: uint32(size),
FlagCount: uint32(17), FlagCount: uint32(128),
} }
if engineDebug { if engineDebug {

View File

@ -60,7 +60,7 @@ func main() {
cfg := engine.Config{ cfg := engine.Config{
Root: "root", Root: "root",
OutputSize: uint32(size), OutputSize: uint32(size),
FlagCount: uint32(17), FlagCount: uint32(128),
} }
if engineDebug { if engineDebug {

View File

@ -48,7 +48,7 @@ func main() {
cfg := engine.Config{ cfg := engine.Config{
Root: "root", Root: "root",
OutputSize: uint32(size), OutputSize: uint32(size),
FlagCount: uint32(17), FlagCount: uint32(128),
} }
if engineDebug { if engineDebug {

View File

@ -40,7 +40,7 @@ func main() {
Root: "root", Root: "root",
SessionId: sessionId, SessionId: sessionId,
OutputSize: uint32(size), OutputSize: uint32(size),
FlagCount: uint32(17), FlagCount: uint32(128),
} }
resourceDir := scriptDir resourceDir := scriptDir

View File

@ -81,7 +81,6 @@ func (ls *LocalHandlerService) GetHandler() (*ussd.Handlers, error) {
ls.DbRs.AddLocalFunc("save_location", ussdHandlers.SaveLocation) ls.DbRs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
ls.DbRs.AddLocalFunc("save_yob", ussdHandlers.SaveYob) ls.DbRs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
ls.DbRs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings) ls.DbRs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
ls.DbRs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
ls.DbRs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized) ls.DbRs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
ls.DbRs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate) ls.DbRs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
ls.DbRs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo) ls.DbRs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
@ -91,6 +90,7 @@ func (ls *LocalHandlerService) GetHandler() (*ussd.Handlers, error) {
ls.DbRs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin) ls.DbRs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
ls.DbRs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange) ls.DbRs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
ls.DbRs.AddLocalFunc("quit_with_help", ussdHandlers.QuitWithHelp) ls.DbRs.AddLocalFunc("quit_with_help", ussdHandlers.QuitWithHelp)
ls.DbRs.AddLocalFunc("fetch_custodial_balances", ussdHandlers.FetchCustodialBalances)
ls.DbRs.AddLocalFunc("set_default_voucher", ussdHandlers.SetDefaultVoucher) ls.DbRs.AddLocalFunc("set_default_voucher", ussdHandlers.SetDefaultVoucher)
ls.DbRs.AddLocalFunc("check_vouchers", ussdHandlers.CheckVouchers) ls.DbRs.AddLocalFunc("check_vouchers", ussdHandlers.CheckVouchers)
ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList) ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList)

View File

@ -11,9 +11,9 @@ import (
) )
type AccountServiceInterface interface { type AccountServiceInterface interface {
CheckBalance(publicKey string) (string, error) CheckBalance(publicKey string) (*models.BalanceResponse, error)
CreateAccount() (*models.AccountResponse, error) CreateAccount() (*models.AccountResponse, error)
CheckAccountStatus(trackingId string) (string, error) CheckAccountStatus(trackingId string) (*models.TrackStatusResponse, error)
FetchVouchers(publicKey string) (*models.VoucherHoldingResponse, error) FetchVouchers(publicKey string) (*models.VoucherHoldingResponse, error)
} }
@ -31,53 +31,44 @@ type AccountService struct {
// - string: The status of the transaction as a string. If there is an error during the request or processing, this will be an empty string. // - string: The status of the transaction as a string. If there is an error during the request or processing, this will be an empty string.
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data. // - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
// If no error occurs, this will be nil. // If no error occurs, this will be nil.
func (as *AccountService) CheckAccountStatus(trackingId string) (string, error) { func (as *AccountService) CheckAccountStatus(trackingId string) (*models.TrackStatusResponse, error) {
resp, err := http.Get(config.TrackStatusURL + trackingId) resp, err := http.Get(config.TrackStatusURL + trackingId)
if err != nil { if err != nil {
return "", err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return "", err return nil, err
} }
var trackResp models.TrackStatusResponse var trackResp models.TrackStatusResponse
err = json.Unmarshal(body, &trackResp) err = json.Unmarshal(body, &trackResp)
if err != nil { if err != nil {
return "", err return nil, err
} }
return &trackResp, nil
status := trackResp.Result.Transaction.Status
return status, nil
} }
// CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint. // CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint.
// Parameters: // Parameters:
// - publicKey: The public key associated with the account whose balance needs to be checked. // - publicKey: The public key associated with the account whose balance needs to be checked.
func (as *AccountService) CheckBalance(publicKey string) (string, error) { func (as *AccountService) CheckBalance(publicKey string) (*models.BalanceResponse, error) {
resp, err := http.Get(config.BalanceURL + publicKey) resp, err := http.Get(config.BalanceURL + publicKey)
if err != nil { if err != nil {
return "0.0", err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return "0.0", err return nil, err
} }
var balanceResp models.BalanceResponse var balanceResp models.BalanceResponse
err = json.Unmarshal(body, &balanceResp) err = json.Unmarshal(body, &balanceResp)
if err != nil { if err != nil {
return "0.0", err return nil, err
} }
return &balanceResp, nil
balance := balanceResp.Result.Balance
return balance, nil
} }
// CreateAccount creates a new account in the custodial system. // CreateAccount creates a new account in the custodial system.
@ -92,18 +83,15 @@ func (as *AccountService) CreateAccount() (*models.AccountResponse, error) {
return nil, err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var accountResp models.AccountResponse var accountResp models.AccountResponse
err = json.Unmarshal(body, &accountResp) err = json.Unmarshal(body, &accountResp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &accountResp, nil return &accountResp, nil
} }
@ -123,4 +111,3 @@ func (as *AccountService) FetchVouchers(publicKey string) (*models.VoucherHoldin
} }
return &holdings, nil return &holdings, nil
} }

View File

@ -272,7 +272,6 @@ func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore store := h.userdataStore
temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN) temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN)
if err != nil { if err != nil {
@ -511,9 +510,7 @@ func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (res
// ResetIncorrectPin resets the incorrect pin flag after a new PIN attempt. // ResetIncorrectPin resets the incorrect pin flag after a new PIN attempt.
func (h *Handlers) ResetIncorrectPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) ResetIncorrectPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
res.FlagReset = append(res.FlagReset, flag_incorrect_pin) res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
return res, nil return res, nil
} }
@ -525,6 +522,7 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
flag_account_success, _ := h.flagManager.GetFlag("flag_account_success") flag_account_success, _ := h.flagManager.GetFlag("flag_account_success")
flag_account_pending, _ := h.flagManager.GetFlag("flag_account_pending") flag_account_pending, _ := h.flagManager.GetFlag("flag_account_pending")
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
sessionId, ok := ctx.Value("SessionId").(string) sessionId, ok := ctx.Value("SessionId").(string)
if !ok { if !ok {
@ -536,18 +534,23 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
return res, err return res, err
} }
status, err := h.accountService.CheckAccountStatus(string(trackingId)) accountStatus, err := h.accountService.CheckAccountStatus(string(trackingId))
if err != nil { if err != nil {
fmt.Println("Error checking account status:", err) fmt.Println("Error checking account status:", err)
return res, err return res, err
} }
if !accountStatus.Ok {
res.FlagSet = append(res.FlagSet, flag_api_error)
return res, err
}
res.FlagReset = append(res.FlagReset, flag_api_error)
status := accountStatus.Result.Transaction.Status
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(status)) err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(status))
if err != nil { if err != nil {
return res, nil return res, nil
} }
if accountStatus.Result.Transaction.Status == "SUCCESS" {
if status == "SUCCESS" {
res.FlagSet = append(res.FlagSet, flag_account_success) res.FlagSet = append(res.FlagSet, flag_account_success)
res.FlagReset = append(res.FlagReset, flag_account_pending) res.FlagReset = append(res.FlagReset, flag_account_pending)
} else { } else {
@ -650,6 +653,45 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
return res, nil return res, nil
} }
func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
symbol, _ := h.st.Where()
balanceType := strings.Split(symbol, "_")[0]
store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
if err != nil {
return res, err
}
balanceResponse, err := h.accountService.CheckBalance(string(publicKey))
if err != nil {
return res, nil
}
if !balanceResponse.Ok {
res.FlagSet = append(res.FlagSet, flag_api_error)
return res, nil
}
res.FlagReset = append(res.FlagReset, flag_api_error)
balance := balanceResponse.Result.Balance
switch balanceType {
case "my":
res.Content = fmt.Sprintf("Your balance is %s", balance)
case "community":
res.Content = fmt.Sprintf("Your community balance is %s", balance)
default:
break
}
return res, nil
}
// ValidateRecipient validates that the given input is a valid phone number. // ValidateRecipient validates that the given input is a valid phone number.
func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@ -830,36 +872,6 @@ func (h *Handlers) GetAmount(ctx context.Context, sym string, input []byte) (res
return res, nil return res, nil
} }
// QuickWithBalance retrieves the balance for a given public key from the custodial balance API endpoint before
// gracefully exiting the session.
func (h *Handlers) QuitWithBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var err error
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
code := codeFromCtx(ctx)
l := gotext.NewLocale(translationDir, code)
l.AddDomain("default")
store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
if err != nil {
return res, err
}
balance, err := h.accountService.CheckBalance(string(publicKey))
if err != nil {
return res, nil
}
res.Content = l.Get("Your account balance is %s", balance)
res.FlagReset = append(res.FlagReset, flag_account_authorized)
return res, nil
}
// InitiateTransaction returns a confirmation and resets the transaction data // InitiateTransaction returns a confirmation and resets the transaction data
// on the gdbm store. // on the gdbm store.
func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []byte) (resource.Result, error) {
@ -893,16 +905,23 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []
return res, nil return res, nil
} }
// GetProfileInfo retrieves and formats the profile information of a user from a Gdbm backed storage.
func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
var defaultValue string
sessionId, ok := ctx.Value("SessionId").(string) sessionId, ok := ctx.Value("SessionId").(string)
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
language, ok := ctx.Value("Language").(lang.Language)
// Default value when an entry is not found if !ok {
defaultValue := "Not Provided" 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"
}
// Helper function to handle nil byte slices and convert them to string // Helper function to handle nil byte slices and convert them to string
getEntryOrDefault := func(entry []byte, err error) string { getEntryOrDefault := func(entry []byte, err error) string {
@ -939,12 +958,23 @@ func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte)
return res, fmt.Errorf("invalid year of birth: %v", err) return res, fmt.Errorf("invalid year of birth: %v", err)
} }
} }
switch language.Code {
// Format the result case "eng":
res.Content = fmt.Sprintf( res.Content = fmt.Sprintf(
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n", "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n",
name, gender, age, location, offerings, name, gender, age, location, offerings,
) )
case "swa":
res.Content = fmt.Sprintf(
"Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\n",
name, gender, age, location, offerings,
)
default:
res.Content = fmt.Sprintf(
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n",
name, gender, age, location, offerings,
)
}
return res, nil return res, nil
} }

View File

@ -7,8 +7,11 @@ import (
"log" "log"
"path" "path"
"testing" "testing"
"time"
"git.defalsify.org/vise.git/asm"
"git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/lang"
"git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/state" "git.defalsify.org/vise.git/state"
@ -25,10 +28,53 @@ var (
flagsPath = path.Join(baseDir, "services", "registration", "pp.csv") flagsPath = path.Join(baseDir, "services", "registration", "pp.csv")
) )
func TestCreateAccount(t *testing.T) { type Transaction struct {
CreatedAt time.Time `json:"createdAt"`
Status string `json:"status"`
TransferValue json.Number `json:"transferValue"`
TxHash string `json:"txHash"`
TxType string `json:"txType"`
}
func TestNewHandlers(t *testing.T) {
fm, err := NewFlagManager(flagsPath) fm, err := NewFlagManager(flagsPath)
if err != nil {
t.Logf(err.Error())
}
t.Run("Valid UserDataStore", func(t *testing.T) {
mockStore := &mocks.MockUserDataStore{}
handlers, err := NewHandlers(fm.parser, mockStore)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if handlers == nil {
t.Fatal("expected handlers to be non-nil")
}
if handlers.userdataStore == nil {
t.Fatal("expected userdataStore to be set in handlers")
}
})
// Test case for nil userdataStore
t.Run("Nil UserDataStore", func(t *testing.T) {
appFlags := &asm.FlagParser{}
handlers, err := NewHandlers(appFlags, nil)
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)
}
})
}
func TestCreateAccount(t *testing.T) {
fm, err := NewFlagManager(flagsPath)
if err != nil { if err != nil {
t.Logf(err.Error()) t.Logf(err.Error())
} }
@ -509,12 +555,10 @@ func TestGetRecipient(t *testing.T) {
func TestGetFlag(t *testing.T) { func TestGetFlag(t *testing.T) {
fm, err := NewFlagManager(flagsPath) fm, err := NewFlagManager(flagsPath)
expectedFlag := uint32(9) expectedFlag := uint32(9)
if err != nil { if err != nil {
t.Logf(err.Error()) t.Logf(err.Error())
} }
flag, err := fm.GetFlag("flag_account_created") flag, err := fm.GetFlag("flag_account_created")
if err != nil { if err != nil {
t.Logf(err.Error()) t.Logf(err.Error())
} }
@ -987,53 +1031,109 @@ func TestVerifyCreatePin(t *testing.T) {
func TestCheckAccountStatus(t *testing.T) { func TestCheckAccountStatus(t *testing.T) {
fm, err := NewFlagManager(flagsPath) fm, err := NewFlagManager(flagsPath)
if err != nil { if err != nil {
t.Logf(err.Error()) t.Logf(err.Error())
} }
mockDataStore := new(mocks.MockUserDataStore)
mockCreateAccountService := new(mocks.MockAccountService)
sessionId := "session123" sessionId := "session123"
flag_account_success, _ := fm.GetFlag("flag_account_success") flag_account_success, _ := fm.GetFlag("flag_account_success")
flag_account_pending, _ := fm.GetFlag("flag_account_pending") flag_account_pending, _ := fm.GetFlag("flag_account_pending")
flag_api_error, _ := fm.GetFlag("flag_api_call_error")
ctx := context.WithValue(context.Background(), "SessionId", sessionId) ctx := context.WithValue(context.Background(), "SessionId", sessionId)
tests := []struct {
name string
input []byte
response *models.TrackStatusResponse
expectedResult resource.Result
}{
{
name: "Test when account status is Success",
input: []byte("TrackingId1234"),
response: &models.TrackStatusResponse{
Ok: true,
Result: struct {
Transaction struct {
CreatedAt time.Time "json:\"createdAt\""
Status string "json:\"status\""
TransferValue json.Number "json:\"transferValue\""
TxHash string "json:\"txHash\""
TxType string "json:\"txType\""
}
}{
Transaction: Transaction{
CreatedAt: time.Now(),
Status: "SUCCESS",
TransferValue: json.Number("0.5"),
TxHash: "0x123abc456def",
TxType: "transfer",
},
},
},
expectedResult: resource.Result{
FlagSet: []uint32{flag_account_success},
FlagReset: []uint32{flag_api_error, flag_account_pending},
},
},
{
name: "Test when fetching account status is not Success",
input: []byte("TrackingId1234"),
response: &models.TrackStatusResponse{
Ok: false,
},
expectedResult: resource.Result{
FlagSet: []uint32{flag_api_error},
},
},
{
name: "Test when checking account status api call is a SUCCESS but an account is not yet ready",
input: []byte("TrackingId1234"),
response: &models.TrackStatusResponse{
Ok: true,
Result: struct {
Transaction struct {
CreatedAt time.Time "json:\"createdAt\""
Status string "json:\"status\""
TransferValue json.Number "json:\"transferValue\""
TxHash string "json:\"txHash\""
TxType string "json:\"txType\""
}
}{
Transaction: Transaction{
CreatedAt: time.Now(),
Status: "IN_NETWORK",
TransferValue: json.Number("0.5"),
TxHash: "0x123abc456def",
TxType: "transfer",
},
},
},
expectedResult: resource.Result{
FlagSet: []uint32{flag_account_pending},
FlagReset: []uint32{flag_api_error, flag_account_success},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockDataStore := new(mocks.MockUserDataStore)
mockCreateAccountService := new(mocks.MockAccountService)
h := &Handlers{ h := &Handlers{
userdataStore: mockDataStore, userdataStore: mockDataStore,
accountService: mockCreateAccountService, accountService: mockCreateAccountService,
flagManager: fm.parser, flagManager: fm.parser,
} }
tests := []struct {
name string
input []byte
status string
expectedResult resource.Result
}{
{
name: "Test when account status is Success",
input: []byte("TrackingId1234"),
status: "SUCCESS",
expectedResult: resource.Result{
FlagSet: []uint32{flag_account_success},
FlagReset: []uint32{flag_account_pending},
},
},
}
typ := utils.DATA_TRACKING_ID
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status := tt.response.Result.Transaction.Status
// Define expected interactions with the mock // Define expected interactions with the mock
mockDataStore.On("ReadEntry", ctx, sessionId, typ).Return(tt.input, nil) mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TRACKING_ID).Return(tt.input, nil)
mockCreateAccountService.On("CheckAccountStatus", string(tt.input)).Return(tt.status, nil) mockCreateAccountService.On("CheckAccountStatus", string(tt.input)).Return(tt.response, nil)
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(tt.status)).Return(nil) mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(status)).Return(nil).Maybe()
// Call the method under test // Call the method under test
res, _ := h.CheckAccountStatus(ctx, "check_status", tt.input) res, _ := h.CheckAccountStatus(ctx, "check_account_status", tt.input)
// Assert that no errors occurred // Assert that no errors occurred
assert.NoError(t, err) assert.NoError(t, err)
@ -1333,66 +1433,6 @@ func TestIsValidPIN(t *testing.T) {
} }
} }
func TestQuitWithBalance(t *testing.T) {
fm, err := NewFlagManager(flagsPath)
if err != nil {
t.Logf(err.Error())
}
flag_account_authorized, _ := fm.parser.GetFlag("flag_account_authorized")
mockDataStore := new(mocks.MockUserDataStore)
mockCreateAccountService := new(mocks.MockAccountService)
sessionId := "session123"
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
h := &Handlers{
userdataStore: mockDataStore,
accountService: mockCreateAccountService,
flagManager: fm.parser,
}
tests := []struct {
name string
input []byte
publicKey []byte
balance string
expectedResult resource.Result
}{
{
name: "Test quit with balance",
balance: "0.02CELO",
publicKey: []byte("0xrqeqrequuq"),
expectedResult: resource.Result{
FlagReset: []uint32{flag_account_authorized},
Content: fmt.Sprintf("Your account balance is %s", "0.02CELO"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return(tt.publicKey, nil)
mockCreateAccountService.On("CheckBalance", string(tt.publicKey)).Return(tt.balance, nil)
// Call the method under test
res, _ := h.QuitWithBalance(ctx, "test_quit_with_balance", tt.input)
// Assert that no errors occurred
assert.NoError(t, err)
//Assert that the account created flag has been set to the result
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
// Assert that expectations were met
mockDataStore.AssertExpectations(t)
})
}
}
func TestValidateAmount(t *testing.T) { func TestValidateAmount(t *testing.T) {
fm, err := NewFlagManager(flagsPath) fm, err := NewFlagManager(flagsPath)
@ -1585,23 +1625,50 @@ func TestGetProfile(t *testing.T) {
mockDataStore := new(mocks.MockUserDataStore) mockDataStore := new(mocks.MockUserDataStore)
mockCreateAccountService := new(mocks.MockAccountService) mockCreateAccountService := new(mocks.MockAccountService)
mockState := state.NewState(16)
h := &Handlers{ h := &Handlers{
userdataStore: mockDataStore, userdataStore: mockDataStore,
accountService: mockCreateAccountService, accountService: mockCreateAccountService,
st: mockState,
} }
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
tests := []struct { tests := []struct {
name string name string
languageCode string
keys []utils.DataTyp keys []utils.DataTyp
profileInfo []string profileInfo []string
result resource.Result result resource.Result
}{ }{
{ {
name: "Test with full profile information", name: "Test with full profile information in eng",
keys: []utils.DataTyp{utils.DATA_FAMILY_NAME, utils.DATA_FIRST_NAME, utils.DATA_GENDER, utils.DATA_OFFERINGS, utils.DATA_LOCATION, utils.DATA_YOB}, keys: []utils.DataTyp{utils.DATA_FAMILY_NAME, utils.DATA_FIRST_NAME, utils.DATA_GENDER, utils.DATA_OFFERINGS, utils.DATA_LOCATION, utils.DATA_YOB},
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"}, profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"},
languageCode: "eng",
result: resource.Result{
Content: fmt.Sprintf(
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n",
"John Doee", "Male", "48", "Kilifi", "Bananas",
),
},
},
{
name: "Test with with profile information in swa ",
keys: []utils.DataTyp{utils.DATA_FAMILY_NAME, utils.DATA_FIRST_NAME, utils.DATA_GENDER, utils.DATA_OFFERINGS, utils.DATA_LOCATION, utils.DATA_YOB},
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"},
languageCode: "swa",
result: resource.Result{
Content: fmt.Sprintf(
"Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\n",
"John Doee", "Male", "48", "Kilifi", "Bananas",
),
},
},
{
name: "Test with with profile information with language that is not yet supported",
keys: []utils.DataTyp{utils.DATA_FAMILY_NAME, utils.DATA_FIRST_NAME, utils.DATA_GENDER, utils.DATA_OFFERINGS, utils.DATA_LOCATION, utils.DATA_YOB},
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"},
languageCode: "nor",
result: resource.Result{ result: resource.Result{
Content: fmt.Sprintf( Content: fmt.Sprintf(
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n", "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n",
@ -1612,9 +1679,14 @@ func TestGetProfile(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
ctx = context.WithValue(ctx, "Language", lang.Language{
Code: tt.languageCode,
})
for index, key := range tt.keys { for index, key := range tt.keys {
mockDataStore.On("ReadEntry", ctx, sessionId, key).Return([]byte(tt.profileInfo[index]), nil) mockDataStore.On("ReadEntry", ctx, sessionId, key).Return([]byte(tt.profileInfo[index]), nil).Maybe()
} }
res, _ := h.GetProfileInfo(ctx, "get_profile_info", []byte("")) res, _ := h.GetProfileInfo(ctx, "get_profile_info", []byte(""))
// Assert that expectations were met // Assert that expectations were met
@ -1768,3 +1840,85 @@ func TestSetVoucher(t *testing.T) {
// Assert that expectations were met // Assert that expectations were met
mockDataStore.AssertExpectations(t) mockDataStore.AssertExpectations(t)
} }
func TestFetchCustodialBalances(t *testing.T) {
fm, err := NewFlagManager(flagsPath)
if err != nil {
t.Logf(err.Error())
}
flag_api_error, _ := fm.GetFlag("flag_api_call_error")
// Define test data
sessionId := "session123"
publicKey := "0X13242618721"
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
tests := []struct {
name string
balanceResonse *models.BalanceResponse
expectedResult resource.Result
}{
{
name: "Test when fetch custodial balances is not a success",
balanceResonse: &models.BalanceResponse{
Ok: false,
Result: struct {
Balance string `json:"balance"`
Nonce json.Number `json:"nonce"`
}{
Balance: "0.003 CELO",
Nonce: json.Number("0"),
},
},
expectedResult: resource.Result{
FlagSet: []uint32{flag_api_error},
},
},
{
name: "Test when fetch custodial balances is a success",
balanceResonse: &models.BalanceResponse{
Ok: true,
Result: struct {
Balance string `json:"balance"`
Nonce json.Number `json:"nonce"`
}{
Balance: "0.003 CELO",
Nonce: json.Number("0"),
},
},
expectedResult: resource.Result{
FlagReset: []uint32{flag_api_error},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockDataStore := new(mocks.MockUserDataStore)
mockCreateAccountService := new(mocks.MockAccountService)
mockState := state.NewState(16)
// Create the Handlers instance with the mock store
h := &Handlers{
userdataStore: mockDataStore,
flagManager: fm.parser,
st: mockState,
accountService: mockCreateAccountService,
}
// Set up the expected behavior of the mock
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return([]byte(publicKey), nil)
mockCreateAccountService.On("CheckBalance", string(publicKey)).Return(tt.balanceResonse, nil)
// Call the method
res, _ := h.FetchCustodialBalances(ctx, "fetch_custodial_balances", []byte(""))
// Assert that expectations were met
mockDataStore.AssertExpectations(t)
//Assert that the result set to content is what was expected
assert.Equal(t, res, tt.expectedResult, "Result should contain flags set according to user input")
})
}
}

View File

@ -15,14 +15,14 @@ func (m *MockAccountService) CreateAccount() (*models.AccountResponse, error) {
return args.Get(0).(*models.AccountResponse), args.Error(1) return args.Get(0).(*models.AccountResponse), args.Error(1)
} }
func (m *MockAccountService) CheckBalance(publicKey string) (string, error) { func (m *MockAccountService) CheckBalance(publicKey string) (*models.BalanceResponse, error) {
args := m.Called(publicKey) args := m.Called(publicKey)
return args.String(0), args.Error(1) return args.Get(0).(*models.BalanceResponse), args.Error(1)
} }
func (m *MockAccountService) CheckAccountStatus(trackingId string) (string, error) { func (m *MockAccountService) CheckAccountStatus(trackingId string) (*models.TrackStatusResponse, error) {
args := m.Called(trackingId) args := m.Called(trackingId)
return args.String(0), args.Error(1) return args.Get(0).(*models.TrackStatusResponse), args.Error(1)
} }
func (m *MockAccountService) FetchVouchers(publicKey string) (*models.VoucherHoldingResponse, error) { func (m *MockAccountService) FetchVouchers(publicKey string) (*models.VoucherHoldingResponse, error) {

View File

@ -5,6 +5,7 @@ MOUT back 0
HALT HALT
LOAD validate_amount 64 LOAD validate_amount 64
RELOAD validate_amount RELOAD validate_amount
CATCH api_failure flag_api_call_error 1
CATCH invalid_amount flag_invalid_amount 1 CATCH invalid_amount flag_invalid_amount 1
INCMP _ 0 INCMP _ 0
LOAD get_recipient 12 LOAD get_recipient 12

View File

@ -0,0 +1 @@
Failed to connect to the custodial service.Please try again.

View File

@ -0,0 +1,5 @@
MOUT retry 0
MOUT quit 9
HALT
INCMP _ 0
INCMP quit 9

View File

@ -1,4 +1,5 @@
LOAD reset_account_authorized 0 LOAD reset_account_authorized 0
RELOAD reset_account_authorized
MOUT my_balance 1 MOUT my_balance 1
MOUT community_balance 2 MOUT community_balance 2
MOUT back 0 MOUT back 0

View File

@ -1 +1 @@
Your community balance is: 0.00SRF {{.fetch_custodial_balances}}

View File

@ -1,5 +1,11 @@
LOAD reset_incorrect 0 LOAD reset_incorrect 6
LOAD fetch_custodial_balances 0
CATCH api_failure flag_api_call_error 1
MAP fetch_custodial_balances
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH pin_entry flag_account_authorized 0 CATCH pin_entry flag_account_authorized 0
LOAD quit_with_balance 0 MOUT back 0
MOUT quit 9
HALT HALT
INCMP _ 0
INCMP quit 9

View File

@ -4,6 +4,7 @@ LOAD check_balance 64
RELOAD check_balance RELOAD check_balance
LOAD check_vouchers 10 LOAD check_vouchers 10
RELOAD check_vouchers RELOAD check_vouchers
CATCH api_failure flag_api_call_error 1
MAP check_balance MAP check_balance
MOUT send 1 MOUT send 1
MOUT vouchers 2 MOUT vouchers 2

View File

@ -1 +1 @@
Your balance is: 0.00 SRF {{.fetch_custodial_balances}}

View File

@ -1,5 +1,11 @@
LOAD reset_incorrect 0 LOAD reset_incorrect 6
LOAD fetch_custodial_balances 0
CATCH api_failure flag_api_call_error 1
MAP fetch_custodial_balances
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH pin_entry flag_account_authorized 0 CATCH pin_entry flag_account_authorized 0
LOAD quit_with_balance 0 MOUT back 0
MOUT quit 9
HALT HALT
INCMP _ 0
INCMP quit 9

View File

@ -15,3 +15,4 @@ flag,flag_allow_update,21,this is set to allow a user to update their profile da
flag,flag_single_edit,22,this is set to allow a user to edit a single profile item such as year of birth flag,flag_single_edit,22,this is set to allow a user to edit a single profile item such as year of birth
flag,flag_incorrect_date_format,23,this is set when the given year of birth is invalid flag,flag_incorrect_date_format,23,this is set when the given year of birth is invalid
flag,flag_incorrect_voucher,24,this is set when the selected voucher is invalid flag,flag_incorrect_voucher,24,this is set when the selected voucher is invalid
flag,flag_api_call_error,25,this is set when communication to an external service fails

1 flag flag_language_set 8 checks whether the user has set their prefered language
15 flag flag_single_edit 22 this is set to allow a user to edit a single profile item such as year of birth
16 flag flag_incorrect_date_format 23 this is set when the given year of birth is invalid
17 flag flag_incorrect_voucher 24 this is set when the selected voucher is invalid
18 flag flag_api_call_error 25 this is set when communication to an external service fails

View File

@ -1,6 +1,8 @@
CATCH select_language flag_language_set 0 CATCH select_language flag_language_set 0
CATCH terms flag_account_created 0 CATCH terms flag_account_created 0
LOAD check_account_status 0 LOAD check_account_status 0
RELOAD check_account_status
CATCH api_failure flag_api_call_error 1
CATCH account_pending flag_account_pending 1 CATCH account_pending flag_account_pending 1
CATCH create_pin flag_pin_set 0 CATCH create_pin flag_pin_set 0
CATCH main flag_account_success 1 CATCH main flag_account_success 1

View File

@ -0,0 +1 @@
Angalia Wasifu