diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index 42acd90..a70a033 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -29,9 +29,21 @@ type FSData struct { St *state.State } +type AccountCreator interface { + CreateAccount() (*models.AccountResponse, error) +} + +// ServerAccountCreator implements AccountCreator using the server package +type ServerAccountCreator struct{} + +func (s *ServerAccountCreator) CreateAccount() (*models.AccountResponse, error) { + return server.CreateAccount() +} + type Handlers struct { fs *FSData //accountFileHandler *utils.AccountFileHandler + accountCreator AccountCreator accountFileHandler utils.AccountFileHandlerInterface accountService server.AccountServiceInterface } @@ -43,6 +55,7 @@ func NewHandlers(path string, st *state.State) *Handlers { St: st, }, accountFileHandler: utils.NewAccountFileHandler(path + "_data"), + accountCreator: &ServerAccountCreator{}, accountService: &server.AccountService{}, } } diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go index 98b8dc4..1b22411 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/ussd/menuhandler_test.go @@ -1,53 +1,281 @@ package ussd import ( + "context" "encoding/json" + "os" + "path/filepath" "testing" - "git.grassecon.net/urdt/ussd/internal/handlers/ussd/mocks" + "git.defalsify.org/vise.git/resource" "git.grassecon.net/urdt/ussd/internal/models" - "github.com/alecthomas/assert/v2" - "github.com/stretchr/testify/mock" + "git.grassecon.net/urdt/ussd/internal/utils" ) -func TestCreateAccount_Success(t *testing.T) { - mockAccountFileHandler := new(mocks.MockAccountFileHandler) - mockCreateAccountService := new(mocks.MockAccountService) +// MockAccountCreator implements AccountCreator for testing +type MockAccountCreator struct { + mockResponse *models.AccountResponse + mockError error +} - mockAccountFileHandler.On("EnsureFileExists").Return(nil) +func (m *MockAccountCreator) CreateAccount() (*models.AccountResponse, error) { + return m.mockResponse, m.mockError +} - // Mock that no account data exists - mockAccountFileHandler.On("ReadAccountData").Return(nil, nil) +func TestCreateAccount(t *testing.T) { + // Setup + tempDir, err := os.MkdirTemp("", "test_create_account") + if err != nil { + t.Fatalf("Failed to create temp directory: %v", err) + } + defer os.RemoveAll(tempDir) // Clean up after the test run - // Define expected account response after api call - expectedAccountResp := &models.AccountResponse{ - Ok: true, - Result: struct { - CustodialId json.Number `json:"custodialId"` - PublicKey string `json:"publicKey"` - TrackingId string `json:"trackingId"` - }{ - CustodialId: "12", - PublicKey: "some-public-key", - TrackingId: "some-tracking-id", + sessionID := "07xxxxxxxx" + + // Set up the data file path using the session ID + accountFilePath := filepath.Join(tempDir, sessionID+"_data") + + // Initialize account file handler + accountFileHandler := utils.NewAccountFileHandler(accountFilePath) + + // Create a mock account creator + mockAccountCreator := &MockAccountCreator{ + mockResponse: &models.AccountResponse{ + Ok: true, + Result: struct { + CustodialId json.Number `json:"custodialId"` + PublicKey string `json:"publicKey"` + TrackingId string `json:"trackingId"` + }{ + CustodialId: "test-custodial-id", + PublicKey: "test-public-key", + TrackingId: "test-tracking-id", + }, }, } - mockCreateAccountService.On("CreateAccount").Return(expectedAccountResp, nil) - // Mock WriteAccountData to not error - mockAccountFileHandler.On("WriteAccountData", mock.Anything).Return(nil) - - handlers := &Handlers{ - accountService: mockCreateAccountService, + // Initialize Handlers with mock account creator + h := &Handlers{ + accountFileHandler: accountFileHandler, + accountCreator: mockAccountCreator, } - - actualResponse, err := handlers.accountService.CreateAccount() + tests := []struct { + name string + existingData map[string]string + expectedResult resource.Result + expectedData map[string]string + }{ + { + name: "New account creation", + existingData: nil, + expectedResult: resource.Result{ + FlagSet: []uint32{models.USERFLAG_ACCOUNT_CREATED}, + }, + expectedData: map[string]string{ + "TrackingId": "test-tracking-id", + "PublicKey": "test-public-key", + "CustodialId": "test-custodial-id", + "Status": "PENDING", + "Gender": "Not provided", + "YOB": "Not provided", + "Location": "Not provided", + "Offerings": "Not provided", + "FirstName": "Not provided", + "FamilyName": "Not provided", + }, + }, + { + name: "Existing account", + existingData: map[string]string{ + "TrackingId": "test-tracking-id", + "PublicKey": "test-public-key", + "CustodialId": "test-custodial-id", + "Status": "PENDING", + "Gender": "Not provided", + "YOB": "Not provided", + "Location": "Not provided", + "Offerings": "Not provided", + "FirstName": "Not provided", + "FamilyName": "Not provided", + }, + expectedResult: resource.Result{ + FlagSet: []uint32{models.USERFLAG_ACCOUNT_CREATED}, + }, + expectedData: map[string]string{ + "TrackingId": "test-tracking-id", + "PublicKey": "test-public-key", + "CustodialId": "test-custodial-id", + "Status": "PENDING", + "Gender": "Not provided", + "YOB": "Not provided", + "Location": "Not provided", + "Offerings": "Not provided", + "FirstName": "Not provided", + "FamilyName": "Not provided", + }, + }, + } - // Assert results - assert.NoError(t, err) - assert.Equal(t,expectedAccountResp.Ok,true) - assert.Equal(t,expectedAccountResp,actualResponse) - + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set up the data file path using the session ID + accountFilePath := filepath.Join(tempDir, sessionID+"_data") + // Setup existing data if any + if tt.existingData != nil { + data, _ := json.Marshal(tt.existingData) + err := os.WriteFile(accountFilePath, data, 0644) + if err != nil { + t.Fatalf("Failed to write existing data: %v", err) + } + } + + // Call the function + result, err := h.CreateAccount(context.Background(), "", nil) + + // Check for errors + if err != nil { + t.Fatalf("CreateAccount returned an error: %v", err) + } + + // Check the result + if len(result.FlagSet) != len(tt.expectedResult.FlagSet) { + t.Errorf("Expected %d flags, got %d", len(tt.expectedResult.FlagSet), len(result.FlagSet)) + } + for i, flag := range tt.expectedResult.FlagSet { + if result.FlagSet[i] != flag { + t.Errorf("Expected flag %d, got %d", flag, result.FlagSet[i]) + } + } + + // Check the stored data + data, err := os.ReadFile(accountFilePath) + if err != nil { + t.Fatalf("Failed to read account data file: %v", err) + } + + var storedData map[string]string + err = json.Unmarshal(data, &storedData) + if err != nil { + t.Fatalf("Failed to unmarshal stored data: %v", err) + } + + for key, expectedValue := range tt.expectedData { + if storedValue, ok := storedData[key]; !ok || storedValue != expectedValue { + t.Errorf("Expected %s to be %s, got %s", key, expectedValue, storedValue) + } + } + }) + } +} + +func TestSavePin(t *testing.T) { + // Setup + tempDir, err := os.MkdirTemp("", "test_save_pin") + if err != nil { + t.Fatalf("Failed to create temp directory: %v", err) + } + defer os.RemoveAll(tempDir) + + sessionID := "07xxxxxxxx" + + // Set up the data file path using the session ID + accountFilePath := filepath.Join(tempDir, sessionID+"_data") + initialAccountData := map[string]string{ + "TrackingId": "test-tracking-id", + "PublicKey": "test-public-key", + } + data, _ := json.Marshal(initialAccountData) + err = os.WriteFile(accountFilePath, data, 0644) + if err != nil { + t.Fatalf("Failed to write initial account data: %v", err) + } + + // Create a new AccountFileHandler and set it in the Handlers struct + accountFileHandler := utils.NewAccountFileHandler(accountFilePath) + h := &Handlers{ + accountFileHandler: accountFileHandler, + } + + tests := []struct { + name string + input []byte + expectedFlags []uint32 + expectedData map[string]string + expectedErrors bool + }{ + { + name: "Valid PIN", + input: []byte("1234"), + expectedFlags: []uint32{}, + expectedData: map[string]string{ + "TrackingId": "test-tracking-id", + "PublicKey": "test-public-key", + "AccountPIN": "1234", + }, + }, + { + name: "Invalid PIN - non-numeric", + input: []byte("12ab"), + expectedFlags: []uint32{models.USERFLAG_INCORRECTPIN}, + expectedData: initialAccountData, // No changes expected + expectedErrors: false, + }, + { + name: "Invalid PIN - less than 4 digits", + input: []byte("123"), + expectedFlags: []uint32{models.USERFLAG_INCORRECTPIN}, + expectedData: initialAccountData, // No changes expected + expectedErrors: false, + }, + { + name: "Invalid PIN - more than 4 digits", + input: []byte("12345"), + expectedFlags: []uint32{models.USERFLAG_INCORRECTPIN}, + expectedData: initialAccountData, // No changes expected + expectedErrors: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Ensure the file exists before running the test + err := accountFileHandler.EnsureFileExists() + if err != nil { + t.Fatalf("Failed to ensure account file exists: %v", err) + } + + result, err := h.SavePin(context.Background(), "", tt.input) + if err != nil && !tt.expectedErrors { + t.Fatalf("SavePin returned an unexpected error: %v", err) + } + + if len(result.FlagSet) != len(tt.expectedFlags) { + t.Errorf("Expected %d flags, got %d", len(tt.expectedFlags), len(result.FlagSet)) + } + for i, flag := range tt.expectedFlags { + if result.FlagSet[i] != flag { + t.Errorf("Expected flag %d, got %d", flag, result.FlagSet[i]) + } + } + + data, err := os.ReadFile(accountFilePath) + if err != nil { + t.Fatalf("Failed to read account data file: %v", err) + } + + var storedData map[string]string + err = json.Unmarshal(data, &storedData) + if err != nil { + t.Fatalf("Failed to unmarshal stored data: %v", err) + } + + for key, expectedValue := range tt.expectedData { + if storedValue, ok := storedData[key]; !ok || storedValue != expectedValue { + t.Errorf("Expected %s to be %s, got %s", key, expectedValue, storedValue) + } + } + }) + } }