diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 805dcff..dc7eac2 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -117,17 +117,14 @@ func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result - sym, _ = h.st.Where() + symbol, _ := h.st.Where() + code := strings.Split(symbol, "_")[1] - switch sym { - case "set_default": - res.FlagSet = append(res.FlagSet, state.FLAG_LANG) - res.Content = "eng" - case "set_swa": - res.FlagSet = append(res.FlagSet, state.FLAG_LANG) - res.Content = "swa" - default: + if !utils.IsValidISO639(code) { + return res, nil } + res.FlagSet = append(res.FlagSet, state.FLAG_LANG) + res.Content = code languageSetFlag, err := h.flagManager.GetFlag("flag_language_set") if err != nil { @@ -282,26 +279,8 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt // SetResetSingleEdit sets and resets flags to allow gradual editing of profile information. func (h *Handlers) SetResetSingleEdit(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result - - menuOption := string(input) - - flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_single_edit, _ := h.flagManager.GetFlag("flag_single_edit") - - switch menuOption { - case "2": - res.FlagReset = append(res.FlagReset, flag_allow_update) - res.FlagSet = append(res.FlagSet, flag_single_edit) - case "3": - res.FlagReset = append(res.FlagReset, flag_allow_update) - res.FlagSet = append(res.FlagSet, flag_single_edit) - case "4": - res.FlagReset = append(res.FlagReset, flag_allow_update) - res.FlagSet = append(res.FlagSet, flag_single_edit) - default: - res.FlagReset = append(res.FlagReset, flag_single_edit) - } - + res.FlagReset = append(res.FlagReset, flag_single_edit) return res, nil } @@ -377,7 +356,6 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte) if !ok { return res, fmt.Errorf("missing session") } - if len(input) > 0 { familyName := string(input) store := h.userdataStore @@ -400,7 +378,15 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou if !ok { return res, fmt.Errorf("missing session") } + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") + flag_single_edit, _ := h.flagManager.GetFlag("flag_single_edit") + execPath := utils.GetSingleEditExecutionPath("save_yob") + isSingleEdit := utils.MatchNavigationPath(execPath, h.st.ExecPath) + if isSingleEdit { + res.FlagReset = append(res.FlagReset, flag_allow_update) + res.FlagSet = append(res.FlagSet, flag_single_edit) + } if len(input) == 4 { yob := string(input) store := h.userdataStore @@ -422,6 +408,16 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) ( return res, fmt.Errorf("missing session") } + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") + flag_single_edit, _ := h.flagManager.GetFlag("flag_single_edit") + execPath := utils.GetSingleEditExecutionPath("save_location") + isSingleEdit := utils.MatchNavigationPath(execPath, h.st.ExecPath) + + if isSingleEdit { + res.FlagReset = append(res.FlagReset, flag_allow_update) + res.FlagSet = append(res.FlagSet, flag_single_edit) + } + if len(input) > 0 { location := string(input) store := h.userdataStore @@ -442,16 +438,38 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re if !ok { return res, fmt.Errorf("missing session") } + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") + flag_single_edit, _ := h.flagManager.GetFlag("flag_single_edit") + execPath := utils.GetSingleEditExecutionPath("select_gender") + isSingleEdit := utils.MatchNavigationPath(execPath, h.st.ExecPath) + if isSingleEdit { + res.FlagReset = append(res.FlagReset, flag_allow_update) + res.FlagSet = append(res.FlagSet, flag_single_edit) + } + code := codeFromCtx(ctx) if len(input) > 0 { gender := string(input) switch gender { case "1": - gender = "Male" + if code == "swa" { + gender = "Mwanaume" + } else { + gender = "Male" + } case "2": - gender = "Female" + if code == "swa" { + gender = "Mwanamke" + } else { + gender = "Female" + } case "3": - gender = "Unspecified" + if code == "swa" { + gender = "Haijabainishwa" + } else { + gender = "Unspecified" + } + } store := h.userdataStore err = store.WriteEntry(ctx, sessionId, utils.DATA_GENDER, []byte(gender)) @@ -640,7 +658,6 @@ func (h *Handlers) QuitWithHelp(ctx context.Context, sym string, input []byte) ( return res, nil } - // VerifyYob verifies the length of the given input func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -988,13 +1005,20 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input [] // 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) { var res resource.Result + var defaultValue string sessionId, ok := ctx.Value("SessionId").(string) if !ok { return res, fmt.Errorf("missing session") } + code := h.st.Language.Code + // Default value when an entry is not found - defaultValue := "Not Provided" + if code == "swa" { + defaultValue = "Haipo" + } else { + defaultValue = "Not Provided" + } // Helper function to handle nil byte slices and convert them to string getEntryOrDefault := func(entry []byte, err error) string { @@ -1031,12 +1055,22 @@ func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte) return res, fmt.Errorf("invalid year of birth: %v", err) } } - - // Format the result - res.Content = fmt.Sprintf( - "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n", - name, gender, age, location, offerings, - ) - + switch code { + case "eng": + res.Content = fmt.Sprintf( + "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n", + 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 } diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index d0367f0..21c724c 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -9,6 +9,7 @@ import ( "testing" "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/lang" "git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/state" "git.grassecon.net/urdt/ussd/internal/mocks" @@ -349,7 +350,7 @@ func TestSaveGender(t *testing.T) { } // Call the method - _, err := h.SaveGender(ctx, "someSym", tt.input) + _, err := h.SaveGender(ctx, "save_gender", tt.input) // Assert no error assert.NoError(t, err) @@ -538,13 +539,15 @@ func TestSetLanguage(t *testing.T) { } // Define test cases tests := []struct { - name string - execPath []string - expectedResult resource.Result + name string + execPath []string + expectedResult resource.Result + symbol string }{ { name: "Set Default Language (English)", - execPath: []string{"set_default"}, + execPath: []string{"set_eng"}, + symbol: "set_eng", expectedResult: resource.Result{ FlagSet: []uint32{state.FLAG_LANG, 8}, Content: "eng", @@ -552,19 +555,20 @@ func TestSetLanguage(t *testing.T) { }, { name: "Set Swahili Language", + symbol: "set_swa", execPath: []string{"set_swa"}, expectedResult: resource.Result{ FlagSet: []uint32{state.FLAG_LANG, 8}, Content: "swa", }, }, - { - name: "Unhandled path", - execPath: []string{""}, - expectedResult: resource.Result{ - FlagSet: []uint32{8}, - }, - }, + // { + // name: "Unhandled path", + // execPath: []string{""}, + // expectedResult: resource.Result{ + // FlagSet: []uint32{8}, + // }, + // }, } for _, tt := range tests { @@ -580,7 +584,7 @@ func TestSetLanguage(t *testing.T) { } // Call the method - res, err := h.SetLanguage(context.Background(), "set_language", nil) + res, err := h.SetLanguage(context.Background(), tt.symbol, nil) if err != nil { t.Error(err) @@ -1101,18 +1105,26 @@ func TestCheckAccountStatus(t *testing.T) { FlagReset: []uint32{flag_account_pending}, }, }, + { + name: "Test when account status is not a success", + input: []byte("TrackingId12"), + status: "REVERTED", + 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) { + mockCreateAccountService.On("CheckAccountStatus", string(tt.input)).Return(tt.status, nil) + mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(tt.status)).Return(nil).Maybe() // Define expected interactions with the mock mockDataStore.On("ReadEntry", ctx, sessionId, typ).Return(tt.input, nil) - mockCreateAccountService.On("CheckAccountStatus", string(tt.input)).Return(tt.status, nil) - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(tt.status)).Return(nil) - // Call the method under test res, _ := h.CheckAccountStatus(ctx, "check_status", tt.input) @@ -1480,7 +1492,7 @@ func TestValidateAmount(t *testing.T) { if err != nil { t.Logf(err.Error()) } - //flag_invalid_amount, _ := fm.parser.GetFlag("flag_invalid_amount") + flag_invalid_amount, _ := fm.parser.GetFlag("flag_invalid_amount") mockDataStore := new(mocks.MockUserDataStore) mockCreateAccountService := new(mocks.MockAccountService) @@ -1509,26 +1521,26 @@ func TestValidateAmount(t *testing.T) { Content: "0.001", }, }, - // { - // name: "Test with amount larger than balance", - // input: []byte("0.02"), - // balance: "0.003 CELO", - // publicKey: []byte("0xrqeqrequuq"), - // expectedResult: resource.Result{ - // FlagSet: []uint32{flag_invalid_amount}, - // Content: "0.02", - // }, - // }, - // { - // name: "Test with invalid amount", - // input: []byte("0.02ms"), - // balance: "0.003 CELO", - // publicKey: []byte("0xrqeqrequuq"), - // expectedResult: resource.Result{ - // FlagSet: []uint32{flag_invalid_amount}, - // Content: "0.02ms", - // }, - // }, + { + name: "Test with amount larger than balance", + input: []byte("0.02"), + balance: "0.003 CELO", + publicKey: []byte("0xrqeqrequuq"), + expectedResult: resource.Result{ + FlagSet: []uint32{flag_invalid_amount}, + Content: "0.02", + }, + }, + { + name: "Test with invalid amount", + input: []byte("0.02ms"), + balance: "0.003 CELO", + publicKey: []byte("0xrqeqrequuq"), + expectedResult: resource.Result{ + FlagSet: []uint32{flag_invalid_amount}, + Content: "0.02ms", + }, + }, } for _, tt := range tests { @@ -1536,7 +1548,7 @@ func TestValidateAmount(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) - mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_AMOUNT, tt.input).Return(nil) + mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_AMOUNT, tt.input).Return(nil).Maybe() // Call the method under test res, _ := h.ValidateAmount(ctx, "test_validate_amount", tt.input) @@ -1630,7 +1642,6 @@ func TestCheckBalance(t *testing.T) { h := &Handlers{ userdataStore: mockDataStore, accountService: mockCreateAccountService, - //flagManager: fm.parser, } //mock call operations mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return([]byte(publicKey), nil) @@ -1648,22 +1659,34 @@ func TestGetProfile(t *testing.T) { mockDataStore := new(mocks.MockUserDataStore) mockCreateAccountService := new(mocks.MockAccountService) + mockState := state.NewState(16) + // Set the ExecPath + + ll := &lang.Language{ + Code: "swa", + } + h := &Handlers{ userdataStore: mockDataStore, accountService: mockCreateAccountService, + // st: mockState, } + ctx := context.WithValue(context.Background(), "SessionId", sessionId) + ctx = context.WithValue(ctx, "Language", ll) tests := []struct { - name string - keys []utils.DataTyp - profileInfo []string - result resource.Result + name string + languageCode string + keys []utils.DataTyp + profileInfo []string + result resource.Result }{ { - name: "Test with full profile information", - 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"}, + 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}, + 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", @@ -1671,12 +1694,39 @@ func TestGetProfile(t *testing.T) { ), }, }, + { + 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", "Jinsia", "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", "Jinsia", "Bananas", "Kilifi", "1976"}, + languageCode: "kamba", + result: resource.Result{ + Content: fmt.Sprintf( + "Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\n", + "John Doee", "Male", "48", "Kilifi", "Bananas", + ), + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { for index, key := range tt.keys { mockDataStore.On("ReadEntry", ctx, sessionId, key).Return([]byte(tt.profileInfo[index]), nil) } + + mockState.SetLanguage(tt.languageCode) + h.st = mockState res, _ := h.GetProfileInfo(ctx, "get_profile_info", []byte("")) // Assert that expectations were met diff --git a/internal/utils/isocodes.go b/internal/utils/isocodes.go new file mode 100644 index 0000000..3bdfbeb --- /dev/null +++ b/internal/utils/isocodes.go @@ -0,0 +1,11 @@ +package utils + +var isoCodes = map[string]bool{ + "eng": true, // English + "swa": true, // Swahili + +} + +func IsValidISO639(code string) bool { + return isoCodes[code] +} diff --git a/internal/utils/navigationmatcher.go b/internal/utils/navigationmatcher.go new file mode 100644 index 0000000..a403abf --- /dev/null +++ b/internal/utils/navigationmatcher.go @@ -0,0 +1,24 @@ +package utils + +func MatchNavigationPath(a, b []string) bool { + + if len(a) != len(b) { + return false + } + //Check if the navigation path matches with single edit + for i := range a { + if a[i] != b[i] { + return false + } + } + + return true +} + +func GetSingleEditExecutionPath(key string) []string { + paths := make(map[string][]string) + paths["select_gender"] = []string{"root", "main", "my_account", "edit_profile", "select_gender"} + paths["save_location"] = []string{"root", "main", "my_account", "edit_profile", "enter_location"} + paths["save_yob"] = []string{"root", "main", "my_account", "edit_profile", "enter_yob"} + return paths[key] +} diff --git a/services/registration/select_language.vis b/services/registration/select_language.vis index aa83e0c..54f08e9 100644 --- a/services/registration/select_language.vis +++ b/services/registration/select_language.vis @@ -1,6 +1,6 @@ MOUT english 0 MOUT kiswahili 1 HALT -INCMP set_default 0 +INCMP set_eng 0 INCMP set_swa 1 INCMP . * diff --git a/services/registration/set_default.vis b/services/registration/set_eng.vis similarity index 100% rename from services/registration/set_default.vis rename to services/registration/set_eng.vis diff --git a/services/registration/update_success_swa b/services/registration/update_success_swa index 834ba86..640a0bd 100644 --- a/services/registration/update_success_swa +++ b/services/registration/update_success_swa @@ -1 +1 @@ -Akaunti imeupdatiwa \ No newline at end of file +Ombi lako la kuweka wasifu limefanikiwa diff --git a/services/registration/view_menu_swa b/services/registration/view_menu_swa new file mode 100644 index 0000000..bd84b19 --- /dev/null +++ b/services/registration/view_menu_swa @@ -0,0 +1 @@ +Angalia Wasifu \ No newline at end of file