api-structs #117
@ -1,10 +1,7 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
    CreateAccountURL = "https://custodial.sarafu.africa/api/account/create"
 | 
			
		||||
    TrackStatusURL   = "https://custodial.sarafu.africa/api/track/"
 | 
			
		||||
    BalanceURL       = "https://custodial.sarafu.africa/api/account/status/"
 | 
			
		||||
	CreateAccountURL = "http://localhost:5003/api/v2/account/create"
 | 
			
		||||
	BalanceURL       = "https://custodial.sarafu.africa/api/account/status/"
 | 
			
		||||
	TrackURL         = "http://localhost:5003/api/v2/account/status"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
@ -10,10 +11,16 @@ import (
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/internal/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var apiResponse struct {
 | 
			
		||||
	Ok          bool   `json:"ok"`
 | 
			
		||||
	Description string `json:"description"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AccountServiceInterface interface {
 | 
			
		||||
	CheckBalance(publicKey string) (*models.BalanceResponse, error)
 | 
			
		||||
	CreateAccount() (*models.AccountResponse, error)
 | 
			
		||||
	CreateAccount() (*OKResponse, *ErrResponse)
 | 
			
		||||
	CheckAccountStatus(trackingId string) (*models.TrackStatusResponse, error)
 | 
			
		||||
	TrackAccountStatus(publicKey string) (*OKResponse, *ErrResponse)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AccountService struct {
 | 
			
		||||
@ -22,8 +29,6 @@ type AccountService struct {
 | 
			
		||||
type TestAccountService struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckAccountStatus retrieves the status of an account transaction based on the provided tracking ID.
 | 
			
		||||
//
 | 
			
		||||
// Parameters:
 | 
			
		||||
//   - trackingId: A unique identifier for the account.This should be obtained from a previous call to
 | 
			
		||||
//     CreateAccount or a similar function that returns an AccountResponse. The `trackingId` field in the
 | 
			
		||||
@ -32,9 +37,9 @@ type TestAccountService struct {
 | 
			
		||||
// Returns:
 | 
			
		||||
//   - 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.
 | 
			
		||||
//     If no error occurs, this will be nil.
 | 
			
		||||
//     If no error occurs, this will be nil
 | 
			
		||||
func (as *AccountService) CheckAccountStatus(trackingId string) (*models.TrackStatusResponse, error) {
 | 
			
		||||
	resp, err := http.Get(config.TrackStatusURL + trackingId)
 | 
			
		||||
	resp, err := http.Get(config.BalanceURL + trackingId)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@ -44,12 +49,67 @@ func (as *AccountService) CheckAccountStatus(trackingId string) (*models.TrackSt
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var trackResp models.TrackStatusResponse
 | 
			
		||||
	err = json.Unmarshal(body, &trackResp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &trackResp, nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (as *AccountService) TrackAccountStatus(publicKey string) (*OKResponse, *ErrResponse) {
 | 
			
		||||
	var errResponse ErrResponse
 | 
			
		||||
	var okResponse OKResponse
 | 
			
		||||
	var err error
 | 
			
		||||
	// Construct the URL with the path parameter
 | 
			
		||||
	url := fmt.Sprintf("%s/%s", config.TrackURL, publicKey)
 | 
			
		||||
	req, err := http.NewRequest("GET", url, nil)
 | 
			
		||||
| 
					
	
	
	
	
	
	
	
	 
					
					lash marked this conversation as resolved
					
				 
				 | 
			||||
	if err != nil {
 | 
			
		||||
		errResponse.Description = err.Error()
 | 
			
		||||
		return nil, &errResponse
 | 
			
		||||
	}
 | 
			
		||||
	// Set headers
 | 
			
		||||
	req.Header.Set("Content-Type", "application/json")
 | 
			
		||||
	req.Header.Set("X-GE-KEY", "xd")
 | 
			
		||||
 | 
			
		||||
	// Send the request
 | 
			
		||||
	resp, err := http.DefaultClient.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errResponse.Description = err.Error()
 | 
			
		||||
		return nil, &errResponse
 | 
			
		||||
	}
 | 
			
		||||
| 
					
	
	
	
	
	
	
	
	 
				
					
						kamikazechaser
						commented  
			
		Check the HTTP response code first. If it is >= 400 (Bad request you can then unmarshal to an api.ErrResp. Check the HTTP response code first. If it is >= 400 (Bad request you can then unmarshal to an api.ErrResp. 
			
			
		 | 
			||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	// Read the response body
 | 
			
		||||
	body, err := io.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errResponse.Description = err.Error()
 | 
			
		||||
		return nil, &errResponse
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Step 2: Unmarshal into the generic struct
 | 
			
		||||
	err = json.Unmarshal([]byte(body), &apiResponse)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errResponse.Description = err.Error()
 | 
			
		||||
		return nil, &errResponse
 | 
			
		||||
	}
 | 
			
		||||
	if apiResponse.Ok {
 | 
			
		||||
		err = json.Unmarshal([]byte(body), &okResponse)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errResponse.Description = err.Error()
 | 
			
		||||
			return nil, &errResponse
 | 
			
		||||
		}
 | 
			
		||||
		return &okResponse, nil
 | 
			
		||||
	} else {
 | 
			
		||||
		err := json.Unmarshal([]byte(body), &errResponse)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errResponse.Description = err.Error()
 | 
			
		||||
			return nil, &errResponse
 | 
			
		||||
		}
 | 
			
		||||
		return nil, &errResponse
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint.
 | 
			
		||||
@ -79,22 +139,52 @@ func (as *AccountService) CheckBalance(publicKey string) (*models.BalanceRespons
 | 
			
		||||
//     If there is an error during the request or processing, this will be nil.
 | 
			
		||||
//   - 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.
 | 
			
		||||
func (as *AccountService) CreateAccount() (*models.AccountResponse, error) {
 | 
			
		||||
	resp, err := http.Post(config.CreateAccountURL, "application/json", nil)
 | 
			
		||||
func (as *AccountService) CreateAccount() (*OKResponse, *ErrResponse) {
 | 
			
		||||
 | 
			
		||||
	var errResponse ErrResponse
 | 
			
		||||
	var okResponse OKResponse
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	// Create a new request
 | 
			
		||||
	req, err := http.NewRequest("POST", config.CreateAccountURL, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
		errResponse.Description = err.Error()
 | 
			
		||||
		return nil, &errResponse
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Set("Content-Type", "application/json")
 | 
			
		||||
	req.Header.Set("X-GE-KEY", "xd")
 | 
			
		||||
 | 
			
		||||
	resp, err := http.DefaultClient.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errResponse.Description = err.Error()
 | 
			
		||||
		return nil, &errResponse
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	body, err := io.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
		errResponse.Description = err.Error()
 | 
			
		||||
		return nil, &errResponse
 | 
			
		||||
	}
 | 
			
		||||
	var accountResp models.AccountResponse
 | 
			
		||||
	err = json.Unmarshal(body, &accountResp)
 | 
			
		||||
	err = json.Unmarshal([]byte(body), &apiResponse)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
		return nil, &errResponse
 | 
			
		||||
	}
 | 
			
		||||
	if apiResponse.Ok {
 | 
			
		||||
		err = json.Unmarshal([]byte(body), &okResponse)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errResponse.Description = err.Error()
 | 
			
		||||
			return nil, &errResponse
 | 
			
		||||
		}
 | 
			
		||||
		return &okResponse, nil
 | 
			
		||||
	} else {
 | 
			
		||||
		err := json.Unmarshal([]byte(body), &errResponse)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errResponse.Description = err.Error()
 | 
			
		||||
			return nil, &errResponse
 | 
			
		||||
		}
 | 
			
		||||
		return nil, &errResponse
 | 
			
		||||
	}
 | 
			
		||||
	return &accountResp, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (tas *TestAccountService) CreateAccount() (*models.AccountResponse, error) {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										60
									
								
								internal/handlers/server/api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								internal/handlers/server/api.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
type (
 | 
			
		||||
	OKResponse struct {
 | 
			
		||||
		Ok          bool           `json:"ok"`
 | 
			
		||||
		Description string         `json:"description"`
 | 
			
		||||
		Result      map[string]any `json:"result"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ErrResponse struct {
 | 
			
		||||
		Ok          bool   `json:"ok"`
 | 
			
		||||
		Description string `json:"description"`
 | 
			
		||||
		ErrCode     string `json:"errorCode"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TransferRequest struct {
 | 
			
		||||
		From         string `json:"from" validate:"required,eth_addr_checksum"`
 | 
			
		||||
		To           string `json:"to" validate:"required,eth_addr_checksum"`
 | 
			
		||||
		TokenAddress string `json:"tokenAddress" validate:"required,eth_addr_checksum"`
 | 
			
		||||
		Amount       string `json:"amount" validate:"required,number,gt=0"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	PoolSwapRequest struct {
 | 
			
		||||
		From             string `json:"from" validate:"required,eth_addr_checksum"`
 | 
			
		||||
		FromTokenAddress string `json:"fromTokenAddress" validate:"required,eth_addr_checksum"`
 | 
			
		||||
		ToTokenAddress   string `json:"toTokenAddress" validate:"required,eth_addr_checksum"`
 | 
			
		||||
		PoolAddress      string `json:"poolAddress" validate:"required,eth_addr_checksum"`
 | 
			
		||||
		Amount           string `json:"amount" validate:"required,number,gt=0"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	PoolDepositRequest struct {
 | 
			
		||||
		From         string `json:"from" validate:"required,eth_addr_checksum"`
 | 
			
		||||
		TokenAddress string `json:"tokenAddress" validate:"required,eth_addr_checksum"`
 | 
			
		||||
		PoolAddress  string `json:"poolAddress" validate:"required,eth_addr_checksum"`
 | 
			
		||||
		Amount       string `json:"amount" validate:"required,number,gt=0"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	AccountAddressParam struct {
 | 
			
		||||
		Address string `param:"address"  validate:"required,eth_addr_checksum"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TrackingIDParam struct {
 | 
			
		||||
		TrackingID string `param:"trackingId"  validate:"required,uuid"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	OTXByAccountRequest struct {
 | 
			
		||||
		Address string `param:"address" validate:"required,eth_addr_checksum"`
 | 
			
		||||
		PerPage int    `query:"perPage" validate:"required,number,gt=0"`
 | 
			
		||||
		Cursor  int    `query:"cursor" validate:"number"`
 | 
			
		||||
		Next    bool   `query:"next"`
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ErrCodeInternalServerError = "E01"
 | 
			
		||||
	ErrCodeInvalidJSON         = "E02"
 | 
			
		||||
	ErrCodeInvalidAPIKey       = "E03"
 | 
			
		||||
	ErrCodeValidationFailed    = "E04"
 | 
			
		||||
	ErrCodeAccountNotExists    = "E05"
 | 
			
		||||
)
 | 
			
		||||
@ -27,6 +27,8 @@ var (
 | 
			
		||||
	logg           = logging.NewVanilla().WithDomain("ussdmenuhandler")
 | 
			
		||||
	scriptDir      = path.Join("services", "registration")
 | 
			
		||||
	translationDir = path.Join(scriptDir, "locale")
 | 
			
		||||
	okResponse     *server.OKResponse
 | 
			
		||||
	errResponse    *server.ErrResponse
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FlagManager handles centralized flag management
 | 
			
		||||
@ -136,11 +138,16 @@ func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error {
 | 
			
		||||
	accountResp, err := h.accountService.CreateAccount()
 | 
			
		||||
	okResponse, errResponse := h.accountService.CreateAccount()
 | 
			
		||||
	if errResponse != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	trackingId := okResponse.Result["trackingId"].(string)
 | 
			
		||||
	publicKey := okResponse.Result["publicKey"].(string)
 | 
			
		||||
 | 
			
		||||
	data := map[utils.DataTyp]string{
 | 
			
		||||
		utils.DATA_TRACKING_ID:  accountResp.Result.TrackingId,
 | 
			
		||||
		utils.DATA_PUBLIC_KEY:   accountResp.Result.PublicKey,
 | 
			
		||||
		utils.DATA_CUSTODIAL_ID: accountResp.Result.CustodialId.String(),
 | 
			
		||||
		utils.DATA_TRACKING_ID: trackingId,
 | 
			
		||||
		utils.DATA_PUBLIC_KEY:  publicKey,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for key, value := range data {
 | 
			
		||||
@ -152,7 +159,7 @@ func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, r
 | 
			
		||||
	}
 | 
			
		||||
	flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
 | 
			
		||||
	res.FlagSet = append(res.FlagSet, flag_account_created)
 | 
			
		||||
	return err
 | 
			
		||||
	return nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -191,7 +198,6 @@ func (h *Handlers) SavePin(ctx context.Context, sym string, input []byte) (resou
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
 | 
			
		||||
 | 
			
		||||
	accountPIN := string(input)
 | 
			
		||||
	// Validate that the PIN is a 4-digit number
 | 
			
		||||
	if !isValidPIN(accountPIN) {
 | 
			
		||||
@ -368,7 +374,6 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(input) == 4 {
 | 
			
		||||
		yob := string(input)
 | 
			
		||||
		store := h.userdataStore
 | 
			
		||||
@ -411,7 +416,6 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gender := strings.Split(symbol, "_")[1]
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
	err = store.WriteEntry(ctx, sessionId, utils.DATA_GENDER, []byte(gender))
 | 
			
		||||
@ -430,7 +434,6 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(input) > 0 {
 | 
			
		||||
		offerings := string(input)
 | 
			
		||||
		store := h.userdataStore
 | 
			
		||||
@ -456,7 +459,6 @@ func (h *Handlers) ResetAllowUpdate(ctx context.Context, sym string, input []byt
 | 
			
		||||
// ResetAccountAuthorized resets the account authorization flag after a successful PIN entry.
 | 
			
		||||
func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
			
		||||
	var res resource.Result
 | 
			
		||||
 | 
			
		||||
	flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
 | 
			
		||||
 | 
			
		||||
	res.FlagReset = append(res.FlagReset, flag_account_authorized)
 | 
			
		||||
@ -466,12 +468,10 @@ func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input
 | 
			
		||||
// CheckIdentifier retrieves the PublicKey from the JSON data file.
 | 
			
		||||
func (h *Handlers) CheckIdentifier(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, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
			
		||||
 | 
			
		||||
@ -485,12 +485,10 @@ func (h *Handlers) CheckIdentifier(ctx context.Context, sym string, input []byte
 | 
			
		||||
func (h *Handlers) Authorize(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_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
 | 
			
		||||
	flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
 | 
			
		||||
	flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
 | 
			
		||||
@ -542,28 +540,20 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
 | 
			
		||||
		return res, fmt.Errorf("missing session")
 | 
			
		||||
	}
 | 
			
		||||
	store := h.userdataStore
 | 
			
		||||
	trackingId, err := store.ReadEntry(ctx, sessionId, utils.DATA_TRACKING_ID)
 | 
			
		||||
	publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	accountStatus, err := h.accountService.CheckAccountStatus(string(trackingId))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("Error checking account status:", err)
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
	if !accountStatus.Ok {
 | 
			
		||||
		res.FlagSet = append(res.FlagSet, flag_api_error)
 | 
			
		||||
	okResponse, errResponse = h.accountService.TrackAccountStatus(string(publicKey))
 | 
			
		||||
	if errResponse != nil {
 | 
			
		||||
		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))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return res, nil
 | 
			
		||||
	isActive := okResponse.Result["active"].(bool)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return res, err
 | 
			
		||||
	}
 | 
			
		||||
	if accountStatus.Result.Transaction.Status == "SUCCESS" {
 | 
			
		||||
	if isActive {
 | 
			
		||||
		res.FlagSet = append(res.FlagSet, flag_account_success)
 | 
			
		||||
		res.FlagReset = append(res.FlagReset, flag_account_pending)
 | 
			
		||||
	} else {
 | 
			
		||||
 | 
			
		||||
@ -72,68 +72,75 @@ func TestCreateAccount(t *testing.T) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Logf(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create required mocks
 | 
			
		||||
	mockDataStore := new(mocks.MockUserDataStore)
 | 
			
		||||
	mockCreateAccountService := new(mocks.MockAccountService)
 | 
			
		||||
	expectedResult := resource.Result{}
 | 
			
		||||
	accountCreatedFlag, err := fm.GetFlag("flag_account_created")
 | 
			
		||||
 | 
			
		||||
	flag_account_created, err := fm.GetFlag("flag_account_created")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Logf(err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	expectedResult.FlagSet = append(expectedResult.FlagSet, accountCreatedFlag)
 | 
			
		||||
 | 
			
		||||
	// Define session ID and mock data
 | 
			
		||||
	sessionId := "session123"
 | 
			
		||||
	typ := utils.DATA_ACCOUNT_CREATED
 | 
			
		||||
	fakeError := db.ErrNotFound{}
 | 
			
		||||
	// Create context with session ID
 | 
			
		||||
	notFoundErr := db.ErrNotFound{}
 | 
			
		||||
	ctx := context.WithValue(context.Background(), "SessionId", sessionId)
 | 
			
		||||
 | 
			
		||||
	// Define expected interactions with the mock
 | 
			
		||||
	mockDataStore.On("ReadEntry", ctx, sessionId, typ).Return([]byte("123"), fakeError)
 | 
			
		||||
	expectedAccountResp := &models.AccountResponse{
 | 
			
		||||
		Ok: true,
 | 
			
		||||
		Result: struct {
 | 
			
		||||
			CustodialId json.Number `json:"custodialId"`
 | 
			
		||||
			PublicKey   string      `json:"publicKey"`
 | 
			
		||||
			TrackingId  string      `json:"trackingId"`
 | 
			
		||||
		}{
 | 
			
		||||
			CustodialId: "12",
 | 
			
		||||
			PublicKey:   "0x8E0XSCSVA",
 | 
			
		||||
			TrackingId:  "d95a7e83-196c-4fd0-866fSGAGA",
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name           string
 | 
			
		||||
		serverResponse *server.OKResponse
 | 
			
		||||
		expectedResult resource.Result
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "Test account creation success",
 | 
			
		||||
			serverResponse: &server.OKResponse{
 | 
			
		||||
				Ok:          true,
 | 
			
		||||
				Description: "Account creation successed",
 | 
			
		||||
				Result: map[string]any{
 | 
			
		||||
					"trackingId": "1234567890",
 | 
			
		||||
					"publicKey":  "1235QERYU",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectedResult: resource.Result{
 | 
			
		||||
				FlagSet: []uint32{flag_account_created},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	mockCreateAccountService.On("CreateAccount").Return(expectedAccountResp, nil)
 | 
			
		||||
	data := map[utils.DataTyp]string{
 | 
			
		||||
		utils.DATA_TRACKING_ID:  expectedAccountResp.Result.TrackingId,
 | 
			
		||||
		utils.DATA_PUBLIC_KEY:   expectedAccountResp.Result.PublicKey,
 | 
			
		||||
		utils.DATA_CUSTODIAL_ID: expectedAccountResp.Result.CustodialId.String(),
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
			mockDataStore := new(mocks.MockUserDataStore)
 | 
			
		||||
			mockCreateAccountService := new(mocks.MockAccountService)
 | 
			
		||||
 | 
			
		||||
			// Create a Handlers instance with the mock data store
 | 
			
		||||
			h := &Handlers{
 | 
			
		||||
				userdataStore:  mockDataStore,
 | 
			
		||||
				accountService: mockCreateAccountService,
 | 
			
		||||
				flagManager:    fm.parser,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			data := map[utils.DataTyp]string{
 | 
			
		||||
				utils.DATA_TRACKING_ID: tt.serverResponse.Result["trackingId"].(string),
 | 
			
		||||
				utils.DATA_PUBLIC_KEY:  tt.serverResponse.Result["publicKey"].(string),
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_ACCOUNT_CREATED).Return([]byte(""), notFoundErr)
 | 
			
		||||
			mockCreateAccountService.On("CreateAccount").Return(tt.serverResponse, nil)
 | 
			
		||||
 | 
			
		||||
			for key, value := range data {
 | 
			
		||||
				mockDataStore.On("WriteEntry", ctx, sessionId, key, []byte(value)).Return(nil)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Call the method you want to test
 | 
			
		||||
			res, err := h.CreateAccount(ctx, "create_account", []byte("some-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)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for key, value := range data {
 | 
			
		||||
		mockDataStore.On("WriteEntry", ctx, sessionId, key, []byte(value)).Return(nil)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create a Handlers instance with the mock data store
 | 
			
		||||
	h := &Handlers{
 | 
			
		||||
		userdataStore:  mockDataStore,
 | 
			
		||||
		accountService: mockCreateAccountService,
 | 
			
		||||
		flagManager:    fm.parser,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Call the method you want to test
 | 
			
		||||
	res, err := h.CreateAccount(ctx, "create_account", []byte("some-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, expectedResult, "Expected result should be equal to the actual result")
 | 
			
		||||
 | 
			
		||||
	// Assert that expectations were met
 | 
			
		||||
	mockDataStore.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWithPersister(t *testing.T) {
 | 
			
		||||
@ -1066,12 +1073,20 @@ func TestCheckAccountStatus(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name           string
 | 
			
		||||
		input          []byte
 | 
			
		||||
		serverResponse *server.OKResponse
 | 
			
		||||
		response       *models.TrackStatusResponse
 | 
			
		||||
		expectedResult resource.Result
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:  "Test when account status is Success",
 | 
			
		||||
			name:  "Test when account is on the Sarafu network",
 | 
			
		||||
			input: []byte("TrackingId1234"),
 | 
			
		||||
			serverResponse: &server.OKResponse{
 | 
			
		||||
				Ok:          true,
 | 
			
		||||
				Description: "Account creation successed",
 | 
			
		||||
				Result: map[string]any{
 | 
			
		||||
					"active": true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			response: &models.TrackStatusResponse{
 | 
			
		||||
				Ok: true,
 | 
			
		||||
				Result: struct {
 | 
			
		||||
@ -1098,17 +1113,7 @@ func TestCheckAccountStatus(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			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",
 | 
			
		||||
			name:  "Test when the account is not  yet on the sarafu network",
 | 
			
		||||
			input: []byte("TrackingId1234"),
 | 
			
		||||
			response: &models.TrackStatusResponse{
 | 
			
		||||
				Ok: true,
 | 
			
		||||
@ -1123,13 +1128,20 @@ func TestCheckAccountStatus(t *testing.T) {
 | 
			
		||||
				}{
 | 
			
		||||
					Transaction: models.Transaction{
 | 
			
		||||
						CreatedAt:     time.Now(),
 | 
			
		||||
						Status:        "IN_NETWORK",
 | 
			
		||||
						Status:        "SUCCESS",
 | 
			
		||||
						TransferValue: json.Number("0.5"),
 | 
			
		||||
						TxHash:        "0x123abc456def",
 | 
			
		||||
						TxType:        "transfer",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			serverResponse: &server.OKResponse{
 | 
			
		||||
				Ok:          true,
 | 
			
		||||
				Description: "Account creation successed",
 | 
			
		||||
				Result: map[string]any{
 | 
			
		||||
					"active": false,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectedResult: resource.Result{
 | 
			
		||||
				FlagSet:   []uint32{flag_account_pending},
 | 
			
		||||
				FlagReset: []uint32{flag_api_error, flag_account_success},
 | 
			
		||||
@ -1149,9 +1161,10 @@ func TestCheckAccountStatus(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
			status := tt.response.Result.Transaction.Status
 | 
			
		||||
			// Define expected interactions with the mock
 | 
			
		||||
			mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TRACKING_ID).Return(tt.input, nil)
 | 
			
		||||
			mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return(tt.input, nil)
 | 
			
		||||
 | 
			
		||||
			mockCreateAccountService.On("CheckAccountStatus", string(tt.input)).Return(tt.response, nil)
 | 
			
		||||
			mockCreateAccountService.On("TrackAccountStatus", string(tt.input)).Return(tt.serverResponse, nil)
 | 
			
		||||
			mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(status)).Return(nil).Maybe()
 | 
			
		||||
 | 
			
		||||
			// Call the method under test
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
package mocks
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/internal/handlers/server"
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/internal/models"
 | 
			
		||||
	"github.com/stretchr/testify/mock"
 | 
			
		||||
)
 | 
			
		||||
@ -10,9 +11,19 @@ type MockAccountService struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccountService) CreateAccount() (*models.AccountResponse, error) {
 | 
			
		||||
func (m *MockAccountService) CreateAccount() (*server.OKResponse, *server.ErrResponse) {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	return args.Get(0).(*models.AccountResponse), args.Error(1)
 | 
			
		||||
	okResponse, ok := args.Get(0).(*server.OKResponse)
 | 
			
		||||
	errResponse, err := args.Get(1).(*server.ErrResponse)
 | 
			
		||||
 | 
			
		||||
	if ok {
 | 
			
		||||
		return okResponse, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err {
 | 
			
		||||
		return nil, errResponse
 | 
			
		||||
	}
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccountService) CheckBalance(publicKey string) (*models.BalanceResponse, error) {
 | 
			
		||||
@ -23,4 +34,17 @@ func (m *MockAccountService) CheckBalance(publicKey string) (*models.BalanceResp
 | 
			
		||||
func (m *MockAccountService) CheckAccountStatus(trackingId string) (*models.TrackStatusResponse, error) {
 | 
			
		||||
	args := m.Called(trackingId)
 | 
			
		||||
	return args.Get(0).(*models.TrackStatusResponse), args.Error(1)
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockAccountService) TrackAccountStatus(publicKey string) (*server.OKResponse, *server.ErrResponse) {
 | 
			
		||||
	args := m.Called(publicKey)
 | 
			
		||||
	okResponse, ok := args.Get(0).(*server.OKResponse)
 | 
			
		||||
	errResponse, err := args.Get(1).(*server.ErrResponse)
 | 
			
		||||
	if ok {
 | 
			
		||||
		return okResponse, nil
 | 
			
		||||
	}
 | 
			
		||||
	if err {
 | 
			
		||||
		return nil, errResponse
 | 
			
		||||
	}
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,10 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AccountResponse struct {
 | 
			
		||||
	Ok     bool `json:"ok"`
 | 
			
		||||
	Result struct {
 | 
			
		||||
		CustodialId json.Number `json:"custodialId"`
 | 
			
		||||
		PublicKey   string      `json:"publicKey"`
 | 
			
		||||
		TrackingId  string      `json:"trackingId"`
 | 
			
		||||
	Ok          bool   `json:"ok"`
 | 
			
		||||
	Description string `json:"description"` // Include the description field
 | 
			
		||||
	Result      struct {
 | 
			
		||||
		PublicKey  string `json:"publicKey"`
 | 
			
		||||
		TrackingId string `json:"trackingId"`
 | 
			
		||||
	} `json:"result"`
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@ import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Transaction struct {
 | 
			
		||||
	CreatedAt     time.Time   `json:"createdAt"`
 | 
			
		||||
	Status        string      `json:"status"`
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	
I think it is better and safer to use the
net/urlpackage for handling urls?I will add it as a separate issue.
#125