forked from urdt/ussd
Merge branch 'master' into voucher-data
This commit is contained in:
@@ -13,6 +13,8 @@ import (
|
||||
"git.grassecon.net/urdt/ussd/internal/handlers"
|
||||
"git.grassecon.net/urdt/ussd/internal/handlers/server"
|
||||
"git.grassecon.net/urdt/ussd/internal/storage"
|
||||
"git.grassecon.net/urdt/ussd/internal/testutil/testservice"
|
||||
"git.grassecon.net/urdt/ussd/internal/testutil/testtag"
|
||||
testdataloader "github.com/peteole/testdata-loader"
|
||||
)
|
||||
|
||||
@@ -80,12 +82,12 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if AccountService == nil {
|
||||
AccountService = &server.AccountService{}
|
||||
if testtag.AccountService == nil {
|
||||
testtag.AccountService = &server.AccountService{}
|
||||
}
|
||||
|
||||
switch AccountService.(type) {
|
||||
case *server.TestAccountService:
|
||||
switch testtag.AccountService.(type) {
|
||||
case *testservice.TestAccountService:
|
||||
go func() {
|
||||
eventChannel <- false
|
||||
}()
|
||||
@@ -98,7 +100,7 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
|
||||
panic("Unknown account service type")
|
||||
}
|
||||
|
||||
hl, err := lhs.GetHandler(AccountService)
|
||||
hl, err := lhs.GetHandler(testtag.AccountService)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
|
||||
111
internal/testutil/driver/groupdriver.go
Normal file
111
internal/testutil/driver/groupdriver.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type Step struct {
|
||||
Input string `json:"input"`
|
||||
ExpectedContent string `json:"expectedContent"`
|
||||
}
|
||||
|
||||
func (s *Step) MatchesExpectedContent(content []byte) (bool, error) {
|
||||
pattern := regexp.QuoteMeta(s.ExpectedContent)
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if re.Match([]byte(content)) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Group represents a group of steps
|
||||
type Group struct {
|
||||
Name string `json:"name"`
|
||||
Steps []Step `json:"steps"`
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
Name string
|
||||
Input string
|
||||
ExpectedContent string
|
||||
}
|
||||
|
||||
func (s *TestCase) MatchesExpectedContent(content []byte) (bool, error) {
|
||||
pattern := regexp.QuoteMeta(s.ExpectedContent)
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Check if the content matches the regex pattern
|
||||
if re.Match(content) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// DataGroup represents the overall structure of the JSON.
|
||||
type DataGroup struct {
|
||||
Groups []Group `json:"groups"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
Name string `json:"name"`
|
||||
Groups []Group `json:"groups"`
|
||||
}
|
||||
|
||||
func ReadData() []Session {
|
||||
data, err := os.ReadFile("test_setup.json")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read file: %v", err)
|
||||
}
|
||||
// Unmarshal JSON data
|
||||
var sessions []Session
|
||||
err = json.Unmarshal(data, &sessions)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
return sessions
|
||||
}
|
||||
|
||||
func FilterGroupsByName(groups []Group, name string) []Group {
|
||||
var filteredGroups []Group
|
||||
for _, group := range groups {
|
||||
if group.Name == name {
|
||||
filteredGroups = append(filteredGroups, group)
|
||||
}
|
||||
}
|
||||
return filteredGroups
|
||||
}
|
||||
|
||||
func LoadTestGroups(filePath string) (DataGroup, error) {
|
||||
var sessionsData DataGroup
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return sessionsData, err
|
||||
}
|
||||
err = json.Unmarshal(data, &sessionsData)
|
||||
return sessionsData, err
|
||||
}
|
||||
|
||||
func CreateTestCases(group DataGroup) []TestCase {
|
||||
var tests []TestCase
|
||||
for _, group := range group.Groups {
|
||||
for _, step := range group.Steps {
|
||||
// Create a test case for each group
|
||||
tests = append(tests, TestCase{
|
||||
Name: group.Name,
|
||||
Input: step.Input,
|
||||
ExpectedContent: step.ExpectedContent,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return tests
|
||||
}
|
||||
59
internal/testutil/mocks/dbmock.go
Normal file
59
internal/testutil/mocks/dbmock.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.defalsify.org/vise.git/lang"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockDb struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockDb) SetPrefix(prefix uint8) {
|
||||
m.Called(prefix)
|
||||
}
|
||||
|
||||
func (m *MockDb) Prefix() uint8 {
|
||||
args := m.Called()
|
||||
return args.Get(0).(uint8)
|
||||
}
|
||||
|
||||
func (m *MockDb) Safe() bool {
|
||||
args := m.Called()
|
||||
return args.Get(0).(bool)
|
||||
}
|
||||
|
||||
func (m *MockDb) SetLanguage(language *lang.Language) {
|
||||
m.Called(language)
|
||||
}
|
||||
|
||||
func (m *MockDb) SetLock(uint8, bool) error {
|
||||
args := m.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockDb) Connect(ctx context.Context, connectionStr string) error {
|
||||
args := m.Called(ctx, connectionStr)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockDb) SetSession(sessionId string) {
|
||||
m.Called(sessionId)
|
||||
}
|
||||
|
||||
func (m *MockDb) Put(ctx context.Context, key, value []byte) error {
|
||||
args := m.Called(ctx, key, value)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockDb) Get(ctx context.Context, key []byte) ([]byte, error) {
|
||||
args := m.Called(ctx, key)
|
||||
return nil, args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockDb) Close() error {
|
||||
args := m.Called(nil)
|
||||
return args.Error(0)
|
||||
}
|
||||
30
internal/testutil/mocks/httpmocks/enginemock.go
Normal file
30
internal/testutil/mocks/httpmocks/enginemock.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package httpmocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
// MockEngine implements the engine.Engine interface for testing
|
||||
type MockEngine struct {
|
||||
InitFunc func(context.Context) (bool, error)
|
||||
ExecFunc func(context.Context, []byte) (bool, error)
|
||||
FlushFunc func(context.Context, io.Writer) (int, error)
|
||||
FinishFunc func() error
|
||||
}
|
||||
|
||||
func (m *MockEngine) Init(ctx context.Context) (bool, error) {
|
||||
return m.InitFunc(ctx)
|
||||
}
|
||||
|
||||
func (m *MockEngine) Exec(ctx context.Context, input []byte) (bool, error) {
|
||||
return m.ExecFunc(ctx, input)
|
||||
}
|
||||
|
||||
func (m *MockEngine) Flush(ctx context.Context, w io.Writer) (int, error) {
|
||||
return m.FlushFunc(ctx, w)
|
||||
}
|
||||
|
||||
func (m *MockEngine) Finish() error {
|
||||
return m.FinishFunc()
|
||||
}
|
||||
47
internal/testutil/mocks/httpmocks/requesthandlermock.go
Normal file
47
internal/testutil/mocks/httpmocks/requesthandlermock.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package httpmocks
|
||||
|
||||
import (
|
||||
"git.defalsify.org/vise.git/engine"
|
||||
"git.defalsify.org/vise.git/persist"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/urdt/ussd/internal/handlers"
|
||||
)
|
||||
|
||||
// MockRequestHandler implements handlers.RequestHandler interface for testing
|
||||
type MockRequestHandler struct {
|
||||
ProcessFunc func(handlers.RequestSession) (handlers.RequestSession, error)
|
||||
GetConfigFunc func() engine.Config
|
||||
GetEngineFunc func(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine
|
||||
OutputFunc func(rs handlers.RequestSession) (handlers.RequestSession, error)
|
||||
ResetFunc func(rs handlers.RequestSession) (handlers.RequestSession, error)
|
||||
ShutdownFunc func()
|
||||
GetRequestParserFunc func() handlers.RequestParser
|
||||
}
|
||||
|
||||
func (m *MockRequestHandler) Process(rqs handlers.RequestSession) (handlers.RequestSession, error) {
|
||||
return m.ProcessFunc(rqs)
|
||||
}
|
||||
|
||||
func (m *MockRequestHandler) GetConfig() engine.Config {
|
||||
return m.GetConfigFunc()
|
||||
}
|
||||
|
||||
func (m *MockRequestHandler) GetEngine(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine {
|
||||
return m.GetEngineFunc(cfg, rs, pe)
|
||||
}
|
||||
|
||||
func (m *MockRequestHandler) Output(rs handlers.RequestSession) (handlers.RequestSession, error) {
|
||||
return m.OutputFunc(rs)
|
||||
}
|
||||
|
||||
func (m *MockRequestHandler) Reset(rs handlers.RequestSession) (handlers.RequestSession, error) {
|
||||
return m.ResetFunc(rs)
|
||||
}
|
||||
|
||||
func (m *MockRequestHandler) Shutdown() {
|
||||
m.ShutdownFunc()
|
||||
}
|
||||
|
||||
func (m *MockRequestHandler) GetRequestParser() handlers.RequestParser {
|
||||
return m.GetRequestParserFunc()
|
||||
}
|
||||
15
internal/testutil/mocks/httpmocks/requestparsermock.go
Normal file
15
internal/testutil/mocks/httpmocks/requestparsermock.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package httpmocks
|
||||
|
||||
// MockRequestParser implements the handlers.RequestParser interface for testing
|
||||
type MockRequestParser struct {
|
||||
GetSessionIdFunc func(any) (string, error)
|
||||
GetInputFunc func(any) ([]byte, error)
|
||||
}
|
||||
|
||||
func (m *MockRequestParser) GetSessionId(rq any) (string, error) {
|
||||
return m.GetSessionIdFunc(rq)
|
||||
}
|
||||
|
||||
func (m *MockRequestParser) GetInput(rq any) ([]byte, error) {
|
||||
return m.GetInputFunc(rq)
|
||||
}
|
||||
25
internal/testutil/mocks/httpmocks/writermock.go
Normal file
25
internal/testutil/mocks/httpmocks/writermock.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package httpmocks
|
||||
|
||||
import "net/http"
|
||||
|
||||
// MockWriter implements a mock io.Writer for testing
|
||||
type MockWriter struct {
|
||||
WriteStringCalled bool
|
||||
WrittenString string
|
||||
}
|
||||
|
||||
func (m *MockWriter) Write(p []byte) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (m *MockWriter) WriteString(s string) (n int, err error) {
|
||||
m.WriteStringCalled = true
|
||||
m.WrittenString = s
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
func (m *MockWriter) Header() http.Header {
|
||||
return http.Header{}
|
||||
}
|
||||
|
||||
func (m *MockWriter) WriteHeader(statusCode int) {}
|
||||
40
internal/testutil/mocks/servicemock.go
Normal file
40
internal/testutil/mocks/servicemock.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.grassecon.net/urdt/ussd/internal/models"
|
||||
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockAccountService implements AccountServiceInterface for testing
|
||||
type MockAccountService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
|
||||
args := m.Called()
|
||||
return args.Get(0).(*api.OKResponse), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
|
||||
args := m.Called(publicKey)
|
||||
return args.Get(0).(*models.BalanceResponse), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
|
||||
args := m.Called(trackingId)
|
||||
return args.Get(0).(*models.TrackStatusResponse), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockAccountService) TrackAccountStatus(ctx context.Context,publicKey string) (*api.OKResponse, error) {
|
||||
args := m.Called(publicKey)
|
||||
return args.Get(0).(*api.OKResponse), args.Error(1)
|
||||
}
|
||||
|
||||
|
||||
func (m *MockAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
|
||||
args := m.Called(publicKey)
|
||||
return args.Get(0).(*models.VoucherHoldingResponse), args.Error(1)
|
||||
}
|
||||
21
internal/testutil/mocks/subprefixdbmock.go
Normal file
21
internal/testutil/mocks/subprefixdbmock.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockSubPrefixDb struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockSubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
|
||||
args := m.Called(ctx, key)
|
||||
return args.Get(0).([]byte), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockSubPrefixDb) Put(ctx context.Context, key, val []byte) error {
|
||||
args := m.Called(ctx, key, val)
|
||||
return args.Error(0)
|
||||
}
|
||||
24
internal/testutil/mocks/userdbmock.go
Normal file
24
internal/testutil/mocks/userdbmock.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.grassecon.net/urdt/ussd/internal/utils"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockUserDataStore struct {
|
||||
db.Db
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockUserDataStore) ReadEntry(ctx context.Context, sessionId string, typ utils.DataTyp) ([]byte, error) {
|
||||
args := m.Called(ctx, sessionId, typ)
|
||||
return args.Get(0).([]byte), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockUserDataStore) WriteEntry(ctx context.Context, sessionId string, typ utils.DataTyp, value []byte) error {
|
||||
args := m.Called(ctx, sessionId, typ, value)
|
||||
return args.Error(0)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// +build !online
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"git.grassecon.net/urdt/ussd/internal/handlers/server"
|
||||
)
|
||||
|
||||
var (
|
||||
AccountService server.AccountServiceInterface = &server.TestAccountService{}
|
||||
)
|
||||
101
internal/testutil/testservice/TestAccountService.go
Normal file
101
internal/testutil/testservice/TestAccountService.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package testservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"git.grassecon.net/urdt/ussd/internal/models"
|
||||
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||
)
|
||||
|
||||
type TestAccountService struct {
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
|
||||
return &api.OKResponse{
|
||||
Ok: true,
|
||||
Description: "Account creation succeeded",
|
||||
Result: map[string]any{
|
||||
"trackingId": "075ccc86-f6ef-4d33-97d5-e91cfb37aa0d",
|
||||
"publicKey": "0x623EFAFa8868df4B934dd12a8B26CB3Dd75A7AdD",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
|
||||
balanceResponse := &models.BalanceResponse{
|
||||
Ok: true,
|
||||
Result: struct {
|
||||
Balance string `json:"balance"`
|
||||
Nonce json.Number `json:"nonce"`
|
||||
}{
|
||||
Balance: "0.003 CELO",
|
||||
Nonce: json.Number("0"),
|
||||
},
|
||||
}
|
||||
|
||||
return balanceResponse, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
|
||||
trackResponse := &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: models.Transaction{
|
||||
CreatedAt: time.Now(),
|
||||
Status: "SUCCESS",
|
||||
TransferValue: json.Number("0.5"),
|
||||
TxHash: "0x123abc456def",
|
||||
TxType: "transfer",
|
||||
},
|
||||
},
|
||||
}
|
||||
return trackResponse, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error) {
|
||||
return &api.OKResponse{
|
||||
Ok: true,
|
||||
Description: "Account creation succeeded",
|
||||
Result: map[string]any{
|
||||
"active": true,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
|
||||
return &models.VoucherHoldingResponse{
|
||||
Ok: true,
|
||||
Result: struct {
|
||||
Holdings []struct {
|
||||
ContractAddress string `json:"contractAddress"`
|
||||
TokenSymbol string `json:"tokenSymbol"`
|
||||
TokenDecimals string `json:"tokenDecimals"`
|
||||
Balance string `json:"balance"`
|
||||
} `json:"holdings"`
|
||||
}{
|
||||
Holdings: []struct {
|
||||
ContractAddress string `json:"contractAddress"`
|
||||
TokenSymbol string `json:"tokenSymbol"`
|
||||
TokenDecimals string `json:"tokenDecimals"`
|
||||
Balance string `json:"balance"`
|
||||
}{
|
||||
{
|
||||
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
|
||||
TokenSymbol: "SRF",
|
||||
TokenDecimals: "6",
|
||||
Balance: "2745987",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
12
internal/testutil/testtag/offlinetest.go
Normal file
12
internal/testutil/testtag/offlinetest.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build !online
|
||||
|
||||
package testtag
|
||||
|
||||
import (
|
||||
"git.grassecon.net/urdt/ussd/internal/handlers/server"
|
||||
accountservice "git.grassecon.net/urdt/ussd/internal/testutil/testservice"
|
||||
)
|
||||
|
||||
var (
|
||||
AccountService server.AccountServiceInterface = &accountservice.TestAccountService{}
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build online
|
||||
|
||||
package testutil
|
||||
package testtag
|
||||
|
||||
import "git.grassecon.net/urdt/ussd/internal/handlers/server"
|
||||
|
||||
Reference in New Issue
Block a user