Merge pull request 'wip-flag-migration' (#28) from wip-flag-migration into master

Reviewed-on: urdt/ussd#28
This commit is contained in:
Alfred Kamanda 2024-09-04 11:25:33 +02:00
commit c6c66f956a
22 changed files with 577 additions and 381 deletions

View File

@ -2,10 +2,14 @@ package main
import ( import (
"context" "context"
"encoding/csv"
"flag" "flag"
"fmt" "fmt"
"io"
"log"
"os" "os"
"path" "path"
"strconv"
"git.defalsify.org/vise.git/cache" "git.defalsify.org/vise.git/cache"
"git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/engine"
@ -13,7 +17,6 @@ import (
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/state" "git.defalsify.org/vise.git/state"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd" "git.grassecon.net/urdt/ussd/internal/handlers/ussd"
"git.grassecon.net/urdt/ussd/internal/models"
) )
var ( var (
@ -35,22 +38,43 @@ func main() {
ctx := context.Background() ctx := context.Background()
st := state.NewState(16) st := state.NewState(16)
st.UseDebug() st.UseDebug()
state.FlagDebugger.Register(models.USERFLAG_LANGUAGE_SET, "LANGUAGE_CHANGE")
state.FlagDebugger.Register(models.USERFLAG_ACCOUNT_CREATED, "ACCOUNT_CREATED") pfp := path.Join(scriptDir, "pp.csv")
state.FlagDebugger.Register(models.USERFLAG_ACCOUNT_SUCCESS, "ACCOUNT_SUCCESS") file, err := os.Open(pfp)
state.FlagDebugger.Register(models.USERFLAG_ACCOUNT_PENDING, "ACCOUNT_PENDING") if err != nil {
state.FlagDebugger.Register(models.USERFLAG_INCORRECTPIN, "INCORRECTPIN") fmt.Fprintf(os.Stderr, "Failed to open CSV file: %v\n", err)
state.FlagDebugger.Register(models.USERFLAG_INCORRECTDATEFORMAT, "INVALIDDATEFORMAT") os.Exit(1)
state.FlagDebugger.Register(models.USERFLAG_INVALID_RECIPIENT, "INVALIDRECIPIENT") }
state.FlagDebugger.Register(models.USERFLAG_PINMISMATCH, "PINMISMATCH") defer file.Close()
state.FlagDebugger.Register(models.USERFLAG_PIN_SET, "PIN_SET") reader := csv.NewReader(file)
state.FlagDebugger.Register(models.USERFLAG_INVALID_RECIPIENT_WITH_INVITE, "INVALIDRECIPIENT_WITH_INVITE")
state.FlagDebugger.Register(models.USERFLAG_INVALID_AMOUNT, "INVALIDAMOUNT") // Iterate through the CSV records and register the flags
state.FlagDebugger.Register(models.USERFLAG_ALLOW_UPDATE, "UNLOCKFORUPDATE") for {
state.FlagDebugger.Register(models.USERFLAG_VALIDPIN, "VALIDPIN") record, err := reader.Read()
state.FlagDebugger.Register(models.USERFLAG_VALIDPIN, "ACCOUNTUNLOCKED") if err != nil {
state.FlagDebugger.Register(models.USERFLAG_ACCOUNT_CREATION_FAILED, "ACCOUNT_CREATION_FAILED") if err == io.EOF {
state.FlagDebugger.Register(models.USERFLAG_SINGLE_EDIT, "SINGLEEDIT") break
}
fmt.Fprintf(os.Stderr, "Error reading CSV file: %v\n", err)
os.Exit(1)
}
// Ensure the record starts with "flag" and has at least 3 columns
if len(record) < 3 || record[0] != "flag" {
continue
}
flagName := record[1]
flagValue, err := strconv.Atoi(record[2])
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to convert flag value %s to integer: %v\n", record[2], err)
continue
}
// Register the flag
log.Printf("Registering flagName:%s; flagValue:%v", flagName, flagValue)
state.FlagDebugger.Register(uint32(flagValue), flagName)
}
rfs := resource.NewFsResource(scriptDir) rfs := resource.NewFsResource(scriptDir)
ca := cache.NewCache() ca := cache.NewCache()
@ -60,7 +84,7 @@ func main() {
} }
dp := path.Join(scriptDir, ".state") dp := path.Join(scriptDir, ".state")
err := os.MkdirAll(dp, 0700) err = os.MkdirAll(dp, 0700)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "state dir create exited with error: %v\n", err) fmt.Fprintf(os.Stderr, "state dir create exited with error: %v\n", err)
os.Exit(1) os.Exit(1)
@ -83,7 +107,11 @@ func main() {
fp := path.Join(dp, sessionId) fp := path.Join(dp, sessionId)
ussdHandlers := ussd.NewHandlers(fp, &st) ussdHandlers, err := ussd.NewHandlers(fp, &st, sessionId)
if err != nil {
fmt.Fprintf(os.Stderr, "handler setup failed with error: %v\n", err)
}
rfs.AddLocalFunc("select_language", ussdHandlers.SetLanguage) rfs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
rfs.AddLocalFunc("create_account", ussdHandlers.CreateAccount) rfs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ package ussd
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -20,6 +21,15 @@ type MockAccountService struct {
mock.Mock mock.Mock
} }
type MockFlagParser struct {
mock.Mock
}
func (m *MockFlagParser) GetFlag(key string) (uint32, error) {
args := m.Called(key)
return args.Get(0).(uint32), args.Error(1)
}
func (m *MockAccountService) CreateAccount() (*models.AccountResponse, error) { func (m *MockAccountService) CreateAccount() (*models.AccountResponse, error) {
args := m.Called() args := m.Called()
return args.Get(0).(*models.AccountResponse), args.Error(1) return args.Get(0).(*models.AccountResponse), args.Error(1)
@ -69,11 +79,20 @@ func TestCreateAccount(t *testing.T) {
// Set up expectations for the mock account service // Set up expectations for the mock account service
mockAccountService.On("CreateAccount").Return(mockAccountResponse, nil) mockAccountService.On("CreateAccount").Return(mockAccountResponse, nil)
mockParser := new(MockFlagParser)
flag_account_created := uint32(1)
flag_account_creation_failed := uint32(2)
mockParser.On("GetFlag", "flag_account_created").Return(flag_account_created, nil)
mockParser.On("GetFlag", "flag_account_creation_failed").Return(flag_account_creation_failed, nil)
// Initialize Handlers with mock account service // Initialize Handlers with mock account service
h := &Handlers{ h := &Handlers{
fs: &FSData{Path: accountFilePath}, fs: &FSData{Path: accountFilePath},
accountFileHandler: accountFileHandler, accountFileHandler: accountFileHandler,
accountService: mockAccountService, accountService: mockAccountService,
parser: mockParser,
} }
tests := []struct { tests := []struct {
@ -86,7 +105,7 @@ func TestCreateAccount(t *testing.T) {
name: "New account creation", name: "New account creation",
existingData: nil, existingData: nil,
expectedResult: resource.Result{ expectedResult: resource.Result{
FlagSet: []uint32{models.USERFLAG_ACCOUNT_CREATED}, FlagSet: []uint32{flag_account_created},
}, },
expectedData: map[string]string{ expectedData: map[string]string{
"TrackingId": "test-tracking-id", "TrackingId": "test-tracking-id",
@ -247,10 +266,16 @@ func TestSavePin(t *testing.T) {
// Create a new AccountFileHandler and set it in the Handlers struct // Create a new AccountFileHandler and set it in the Handlers struct
accountFileHandler := utils.NewAccountFileHandler(accountFilePath) accountFileHandler := utils.NewAccountFileHandler(accountFilePath)
mockParser := new(MockFlagParser)
h := &Handlers{ h := &Handlers{
accountFileHandler: accountFileHandler, accountFileHandler: accountFileHandler,
parser: mockParser,
} }
flag_incorrect_pin := uint32(1)
mockParser.On("GetFlag", "flag_incorrect_pin").Return(flag_incorrect_pin, nil)
tests := []struct { tests := []struct {
name string name string
input []byte input []byte
@ -271,21 +296,21 @@ func TestSavePin(t *testing.T) {
{ {
name: "Invalid PIN - non-numeric", name: "Invalid PIN - non-numeric",
input: []byte("12ab"), input: []byte("12ab"),
expectedFlags: []uint32{models.USERFLAG_INCORRECTPIN}, expectedFlags: []uint32{flag_incorrect_pin},
expectedData: initialAccountData, // No changes expected expectedData: initialAccountData, // No changes expected
expectedErrors: false, expectedErrors: false,
}, },
{ {
name: "Invalid PIN - less than 4 digits", name: "Invalid PIN - less than 4 digits",
input: []byte("123"), input: []byte("123"),
expectedFlags: []uint32{models.USERFLAG_INCORRECTPIN}, expectedFlags: []uint32{flag_incorrect_pin},
expectedData: initialAccountData, // No changes expected expectedData: initialAccountData, // No changes expected
expectedErrors: false, expectedErrors: false,
}, },
{ {
name: "Invalid PIN - more than 4 digits", name: "Invalid PIN - more than 4 digits",
input: []byte("12345"), input: []byte("12345"),
expectedFlags: []uint32{models.USERFLAG_INCORRECTPIN}, expectedFlags: []uint32{flag_incorrect_pin},
expectedData: initialAccountData, // No changes expected expectedData: initialAccountData, // No changes expected
expectedErrors: false, expectedErrors: false,
}, },
@ -293,7 +318,6 @@ func TestSavePin(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) {
// Ensure the file exists before running the test
err := accountFileHandler.EnsureFileExists() err := accountFileHandler.EnsureFileExists()
if err != nil { if err != nil {
t.Fatalf("Failed to ensure account file exists: %v", err) t.Fatalf("Failed to ensure account file exists: %v", err)
@ -690,7 +714,6 @@ func TestSaveOfferings(t *testing.T) {
} }
} }
func TestSaveGender(t *testing.T) { func TestSaveGender(t *testing.T) {
// Create a new instance of MockAccountFileHandler // Create a new instance of MockAccountFileHandler
mockFileHandler := new(mocks.MockAccountFileHandler) mockFileHandler := new(mocks.MockAccountFileHandler)
@ -876,3 +899,95 @@ func TestGetAmount(t *testing.T) {
} }
} }
func TestGetProfileInfo(t *testing.T) {
tests := []struct {
name string
accountData map[string]string
readError error
expectedResult resource.Result
expectedError error
}{
{
name: "Complete Profile",
accountData: map[string]string{
"FirstName": "John",
"FamilyName": "Doe",
"Gender": "Male",
"YOB": "1980",
"Location": "Mombasa",
"Offerings": "Product A",
},
readError: nil,
expectedResult: resource.Result{
Content: fmt.Sprintf(
"Name: %s %s\nGender: %s\nAge: %d\nLocation: %s\nYou provide: %s\n",
"John", "Doe", "Male", 44, "Mombasa", "Product A",
),
},
expectedError: nil,
},
{
name: "Profile with Not Provided Fields",
accountData: map[string]string{
"FirstName": "Not provided",
"FamilyName": "Doe",
"Gender": "Female",
"YOB": "1995",
"Location": "Not provided",
"Offerings": "Service B",
},
readError: nil,
expectedResult: resource.Result{
Content: fmt.Sprintf(
"Name: %s\nGender: %s\nAge: %d\nLocation: %s\nYou provide: %s\n",
"Not provided", "Female", 29, "Not provided", "Service B",
),
},
expectedError: nil,
},
{
name: "Profile with YOB as Not provided",
accountData: map[string]string{
"FirstName": "Not provided",
"FamilyName": "Doe",
"Gender": "Female",
"YOB": "Not provided",
"Location": "Not provided",
"Offerings": "Service B",
},
readError: nil,
expectedResult: resource.Result{
Content: fmt.Sprintf(
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n",
"Not provided", "Female", "Not provided", "Not provided", "Service B",
),
},
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a new instance of MockAccountFileHandler
mockFileHandler := new(mocks.MockAccountFileHandler)
// Set up the mock expectations
mockFileHandler.On("ReadAccountData").Return(tt.accountData, tt.readError)
// Create the Handlers instance with the mock file handler
h := &Handlers{
accountFileHandler: mockFileHandler,
}
// Call the method
result, err := h.GetProfileInfo(context.Background(), "get_profile_info", nil)
// Assert the results
assert.Equal(t, tt.expectedResult, result)
assert.Equal(t, tt.expectedError, err)
// Assert all expectations were met
mockFileHandler.AssertExpectations(t)
})
}
}

View File

@ -1,22 +0,0 @@
package models
import "git.defalsify.org/vise.git/state"
const (
USERFLAG_LANGUAGE_SET = iota + state.FLAG_USERSTART
USERFLAG_ACCOUNT_CREATED
USERFLAG_ACCOUNT_PENDING
USERFLAG_ACCOUNT_SUCCESS
USERFLAG_ACCOUNT_AUTHORIZED
USERFLAG_INVALID_RECIPIENT
USERFLAG_INVALID_RECIPIENT_WITH_INVITE
USERFLAG_INCORRECTPIN
USERFLAG_ALLOW_UPDATE
USERFLAG_INVALID_AMOUNT
USERFLAG_PIN_SET
USERFLAG_VALIDPIN
USERFLAG_PINMISMATCH
USERFLAG_INCORRECTDATEFORMAT
USERFLAG_ACCOUNT_CREATION_FAILED
USERFLAG_SINGLE_EDIT
)

View File

@ -4,7 +4,7 @@ TXTS = $(wildcard ./*.txt.orig)
# Rule to build .bin files from .vis files # Rule to build .bin files from .vis files
%.vis: %.vis:
go run ../../go-vise/dev/asm $(basename $@).vis > $(basename $@).bin go run ../../go-vise/dev/asm -f pp.csv $(basename $@).vis > $(basename $@).bin
@echo "Built $(basename $@).bin from $(basename $@).vis" @echo "Built $(basename $@).bin from $(basename $@).vis"
# Rule to copy .orig files to .txt # Rule to copy .orig files to .txt

View File

@ -1,4 +1,4 @@
RELOAD verify_pin RELOAD verify_pin
CATCH create_pin_mismatch 20 1 CATCH create_pin_mismatch flag_pin_mismatch 1
LOAD quit 0 LOAD quit 0
HALT HALT

View File

@ -1,3 +1,3 @@
RELOAD check_account_status RELOAD check_account_status
CATCH main 11 1 CATCH main flag_account_success 1
HALT HALT

View File

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

View File

@ -1,4 +1,4 @@
LOAD reset_unlocked 0 LOAD reset_account_authorized 0
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,5 +1,5 @@
LOAD reset_incorrect 0 LOAD reset_incorrect 0
CATCH incorrect_pin 15 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH pin_entry 12 0 CATCH pin_entry flag_account_authorized 0
LOAD quit_with_balance 0 LOAD quit_with_balance 0
HALT HALT

View File

@ -1,9 +1,9 @@
LOAD create_account 0 LOAD create_account 0
CATCH account_creation_failed 22 1 CATCH account_creation_failed flag_account_creation_failed 1
MOUT exit 0 MOUT exit 0
HALT HALT
LOAD save_pin 0 LOAD save_pin 0
RELOAD save_pin RELOAD save_pin
CATCH . 15 1 CATCH . flag_incorrect_pin 1
INCMP quit 0 INCMP quit 0
INCMP confirm_create_pin * INCMP confirm_create_pin *

View File

@ -1,12 +1,9 @@
CATCH incorrect_date_format 21 1 CATCH incorrect_date_format flag_incorrect_date_format 1
LOAD save_yob 0 LOAD save_yob 0
CATCH update_success 16 1 CATCH update_success flag_allow_update 1
MOUT back 0 MOUT back 0
HALT HALT
INCMP _ 0 INCMP _ 0
LOAD save_location 0 LOAD save_location 0
CATCH pin_entry 23 1 CATCH pin_entry flag_single_edit 1
INCMP enter_offerings * INCMP enter_offerings *

View File

@ -1,6 +1,6 @@
LOAD save_location 0 LOAD save_location 0
CATCH incorrect_pin 15 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_success 16 1 CATCH update_success flag_allow_update 1
MOUT back 0 MOUT back 0
HALT HALT
LOAD save_offerings 0 LOAD save_offerings 0

View File

@ -1,9 +1,9 @@
LOAD save_gender 0 LOAD save_gender 0
CATCH update_success 16 1 CATCH update_success flag_allow_update 1
MOUT back 0 MOUT back 0
HALT HALT
INCMP _ 0 INCMP _ 0
LOAD verify_yob 8 LOAD verify_yob 8
LOAD save_yob 0 LOAD save_yob 0
CATCH pin_entry 23 1 CATCH pin_entry flag_single_edit 1
INCMP enter_location * INCMP enter_location *

View File

@ -1,5 +1,5 @@
LOAD reset_incorrect 0 LOAD reset_incorrect 0
CATCH incorrect_pin 15 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH pin_entry 12 0 CATCH pin_entry flag_account_authorized 0
LOAD quit_with_balance 0 LOAD quit_with_balance 0
HALT HALT

View File

@ -0,0 +1,16 @@
flag,flag_language_set,8,checks whether the user has set their prefered language
flag,flag_account_created,9,this is set when an account has been created on the API
flag,flag_account_creation_failed,10,this is set when there's an error from the API during account creation
flag,flag_account_pending,11,this is set when an account does not have a status of SUCCESS
flag,flag_account_success,12,this is set when an account has a status of SUCCESS
flag,flag_pin_mismatch,13,this is set when the confirmation PIN matches the initial PIN during registration
flag,flag_pin_set,14,this is set when a newly registered user sets a PIN. This must be present for an account to access the main menu
flag,flag_account_authorized,15,this is set to allow a user access guarded nodes after providing a correct PIN
flag,flag_invalid_recipient,16,this is set when the transaction recipient is invalid
flag,flag_invalid_recipient_with_invite,17,this is set when the transaction recipient is valid but not on the platform
flag,flag_invalid_amount,18,this is set when the given transaction amount is invalid
flag,flag_incorrect_pin,19,this is set when the provided PIN is invalid or does not match the current account's PIN
flag,flag_valid_pin,20,this is set when the given PIN is valid
flag,flag_allow_update,21,this is set to allow a user to update their profile data
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
1 flag flag_language_set 8 checks whether the user has set their prefered language
2 flag flag_account_created 9 this is set when an account has been created on the API
3 flag flag_account_creation_failed 10 this is set when there's an error from the API during account creation
4 flag flag_account_pending 11 this is set when an account does not have a status of SUCCESS
5 flag flag_account_success 12 this is set when an account has a status of SUCCESS
6 flag flag_pin_mismatch 13 this is set when the confirmation PIN matches the initial PIN during registration
7 flag flag_pin_set 14 this is set when a newly registered user sets a PIN. This must be present for an account to access the main menu
8 flag flag_account_authorized 15 this is set to allow a user access guarded nodes after providing a correct PIN
9 flag flag_invalid_recipient 16 this is set when the transaction recipient is invalid
10 flag flag_invalid_recipient_with_invite 17 this is set when the transaction recipient is valid but not on the platform
11 flag flag_invalid_amount 18 this is set when the given transaction amount is invalid
12 flag flag_incorrect_pin 19 this is set when the provided PIN is invalid or does not match the current account's PIN
13 flag flag_valid_pin 20 this is set when the given PIN is valid
14 flag flag_allow_update 21 this is set to allow a user to update their profile data
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

View File

@ -1,7 +1,7 @@
CATCH select_language 8 0 CATCH select_language flag_language_set 0
CATCH terms 9 0 CATCH terms flag_account_created 0
LOAD check_account_status 0 LOAD check_account_status 0
CATCH account_pending 10 1 CATCH account_pending flag_account_pending 1
CATCH create_pin 18 0 CATCH create_pin flag_pin_set 0
CATCH main 11 1 CATCH main flag_account_success 1
HALT HALT

View File

@ -1,12 +1,12 @@
LOAD save_familyname 0 LOAD save_familyname 0
CATCH update_success 16 1 CATCH update_success flag_allow_update 1
MOUT male 1 MOUT male 1
MOUT female 2 MOUT female 2
MOUT unspecified 3 MOUT unspecified 3
MOUT back 0 MOUT back 0
HALT HALT
LOAD save_gender 0 LOAD save_gender 0
CATCH pin_entry 23 1 CATCH pin_entry flag_single_edit 1
INCMP _ 0 INCMP _ 0
INCMP enter_yob 1 INCMP enter_yob 1
INCMP enter_yob 2 INCMP enter_yob 2

View File

@ -3,6 +3,6 @@ MOUT back 0
HALT HALT
LOAD validate_recipient 20 LOAD validate_recipient 20
RELOAD validate_recipient RELOAD validate_recipient
CATCH invalid_recipient 13 1 CATCH invalid_recipient flag_invalid_recipient 1
INCMP _ 0 INCMP _ 0
INCMP amount * INCMP amount *

View File

@ -1,4 +1,6 @@
LOAD reset_incorrect 0 LOAD reset_incorrect 6
CATCH incorrect_pin flag_incorrect_pin 1
CATCH _ flag_account_authorized 0
LOAD get_amount 10 LOAD get_amount 10
MAP get_amount MAP get_amount
RELOAD get_recipient RELOAD get_recipient

View File

@ -6,10 +6,9 @@ MAP get_sender
MOUT back 0 MOUT back 0
MOUT quit 9 MOUT quit 9
HALT HALT
LOAD authorize_account 1 LOAD authorize_account 6
RELOAD authorize_account RELOAD authorize_account
CATCH incorrect_pin 15 1 CATCH incorrect_pin flag_incorrect_pin 1
INCMP _ 0 INCMP _ 0
INCMP quit 9 INCMP quit 9
MOVE transaction_initiated INCMP transaction_initiated *

View File

@ -1,8 +1,8 @@
LOAD get_profile_info 0 LOAD get_profile_info 0
MAP get_profile_info MAP get_profile_info
LOAD reset_incorrect 0 LOAD reset_incorrect 6
CATCH incorrect_pin 15 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH pin_entry 12 0 CATCH pin_entry flag_account_authorized 0
MOUT back 0 MOUT back 0
HALT HALT
INCMP _ 0 INCMP _ 0