Compare commits

...

7 Commits

10 changed files with 91 additions and 82 deletions

View File

@ -96,13 +96,7 @@ func main() {
en = en.WithDebug(nil) en = en.WithDebug(nil)
} }
_, err = en.Init(ctx) err = engine.Loop(ctx, en, os.Stdin, os.Stdout, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "engine init exited with error: %v\n", err)
os.Exit(1)
}
err = engine.Loop(ctx, en, os.Stdin, os.Stdout)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err) fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err)
os.Exit(1) os.Exit(1)

2
go.mod
View File

@ -3,7 +3,7 @@ module git.grassecon.net/urdt/ussd
go 1.22.6 go 1.22.6
require ( require (
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f git.defalsify.org/vise.git v0.1.0-rc.3.0.20240920144308-b2d2c5f18f38
github.com/alecthomas/assert/v2 v2.2.2 github.com/alecthomas/assert/v2 v2.2.2
github.com/peteole/testdata-loader v0.3.0 github.com/peteole/testdata-loader v0.3.0
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 gopkg.in/leonelquinteros/gotext.v1 v1.3.1

4
go.sum
View File

@ -1,5 +1,9 @@
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f h1:CuJvG3NyMoRtHUim4aZdrfjjJBg2AId7z0yp7Q97bRM= git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f h1:CuJvG3NyMoRtHUim4aZdrfjjJBg2AId7z0yp7Q97bRM=
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M=
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240914163514-577f56f43bea h1:6ZYT+dIjd/f5vn9y5AJDZ7SQQckA6w5ZfUoKygyI11o=
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240914163514-577f56f43bea/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M=
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240920144308-b2d2c5f18f38 h1:4aAZijIcq33ixnZ+U48ckDIkwSfZL3St/CqoXZcC5K8=
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240920144308-b2d2c5f18f38/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M=
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=
github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g=

View File

@ -71,19 +71,7 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error)
} }
rqs.Engine = en rqs.Engine = en
r, err = rqs.Engine.Init(rqs.Ctx)
if err != nil {
perr := f.provider.Put(rqs.Config.SessionId, rqs.Storage)
rqs.Storage = nil
if perr != nil {
logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr)
}
return rqs, err
}
if r && len(rqs.Input) > 0 {
r, err = rqs.Engine.Exec(rqs.Ctx, rqs.Input) r, err = rqs.Engine.Exec(rqs.Ctx, rqs.Input)
}
if err != nil { if err != nil {
perr := f.provider.Put(rqs.Config.SessionId, rqs.Storage) perr := f.provider.Put(rqs.Config.SessionId, rqs.Storage)
rqs.Storage = nil rqs.Storage = nil
@ -99,7 +87,7 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error)
func(f *BaseSessionHandler) Output(rqs RequestSession) (RequestSession, error) { func(f *BaseSessionHandler) Output(rqs RequestSession) (RequestSession, error) {
var err error var err error
_, err = rqs.Engine.WriteResult(rqs.Ctx, rqs.Writer) _, err = rqs.Engine.Flush(rqs.Ctx, rqs.Writer)
return rqs, err return rqs, err
} }

View File

@ -37,8 +37,6 @@ type RequestSession struct {
Continue bool Continue bool
} }
type engineMaker func(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine
// TODO: seems like can remove this. // TODO: seems like can remove this.
type RequestParser interface { type RequestParser interface {
GetSessionId(rq any) (string, error) GetSessionId(rq any) (string, error)

View File

@ -279,7 +279,10 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt
// SetResetSingleEdit sets and resets flags to allow gradual editing of profile information. // 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) { func (h *Handlers) SetResetSingleEdit(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
flag_single_edit, _ := h.flagManager.GetFlag("flag_single_edit") flag_single_edit, err := h.flagManager.GetFlag("flag_single_edit")
if err != nil {
return res, err
}
res.FlagReset = append(res.FlagReset, flag_single_edit) res.FlagReset = append(res.FlagReset, flag_single_edit)
return res, nil return res, nil
} }

View File

@ -9,7 +9,7 @@ import (
"testing" "testing"
"git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/lang" "git.defalsify.org/vise.git/persist"
"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/mocks" "git.grassecon.net/urdt/ussd/internal/mocks"
@ -17,6 +17,7 @@ import (
"git.grassecon.net/urdt/ussd/internal/utils" "git.grassecon.net/urdt/ussd/internal/utils"
"github.com/alecthomas/assert/v2" "github.com/alecthomas/assert/v2"
testdataloader "github.com/peteole/testdata-loader" testdataloader "github.com/peteole/testdata-loader"
"github.com/stretchr/testify/require"
) )
var ( var (
@ -96,6 +97,11 @@ func TestCreateAccount(t *testing.T) {
} }
func TestSaveFirstname(t *testing.T) { func TestSaveFirstname(t *testing.T) {
fm, err := NewFlagManager(flagsPath)
if err != nil {
t.Fatal(err)
}
// Create a new instance of MockMyDataStore // Create a new instance of MockMyDataStore
mockStore := new(mocks.MockUserDataStore) mockStore := new(mocks.MockUserDataStore)
@ -104,12 +110,16 @@ func TestSaveFirstname(t *testing.T) {
firstName := "John" firstName := "John"
ctx := context.WithValue(context.Background(), "SessionId", sessionId) ctx := context.WithValue(context.Background(), "SessionId", sessionId)
flag_allow_update, _ := fm.parser.GetFlag("flag_allow_update")
flag_single_edit, _ := fm.parser.GetFlag("flag_single_edit")
// Set up the expected behavior of the mock // Set up the expected behavior of the mock
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_FIRST_NAME, []byte(firstName)).Return(nil) mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_FIRST_NAME, []byte(firstName)).Return(nil)
// Create the Handlers instance with the mock store // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: mockStore, userdataStore: mockStore,
flagManager: fm.parser,
} }
// Call the method // Call the method
@ -117,7 +127,10 @@ func TestSaveFirstname(t *testing.T) {
// Assert results // Assert results
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, resource.Result{}, res) assert.Equal(t, resource.Result{
FlagSet: []uint32{flag_single_edit},
FlagReset: []uint32{flag_allow_update},
}, res)
// Assert all expectations were met // Assert all expectations were met
mockStore.AssertExpectations(t) mockStore.AssertExpectations(t)
@ -562,13 +575,6 @@ func TestSetLanguage(t *testing.T) {
Content: "swa", Content: "swa",
}, },
}, },
// {
// name: "Unhandled path",
// execPath: []string{""},
// expectedResult: resource.Result{
// FlagSet: []uint32{8},
// },
// },
} }
for _, tt := range tests { for _, tt := range tests {
@ -600,7 +606,7 @@ func TestSetLanguage(t *testing.T) {
func TestSetResetSingleEdit(t *testing.T) { func TestSetResetSingleEdit(t *testing.T) {
fm, err := NewFlagManager(flagsPath) fm, err := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.parser.GetFlag("flag_allow_update") //flag_allow_update, _ := fm.parser.GetFlag("flag_allow_update")
flag_single_edit, _ := fm.parser.GetFlag("flag_single_edit") flag_single_edit, _ := fm.parser.GetFlag("flag_single_edit")
if err != nil { if err != nil {
@ -612,30 +618,30 @@ func TestSetResetSingleEdit(t *testing.T) {
input []byte input []byte
expectedResult resource.Result expectedResult resource.Result
}{ }{
{ // {
name: "Set single Edit", // name: "Set single Edit",
input: []byte("2"), // input: []byte("2"),
expectedResult: resource.Result{ // expectedResult: resource.Result{
FlagSet: []uint32{flag_single_edit}, // FlagSet: []uint32{flag_single_edit},
FlagReset: []uint32{flag_allow_update}, // FlagReset: []uint32{flag_allow_update},
}, // },
}, // },
{ // {
name: "Set single Edit", // name: "Set single Edit",
input: []byte("3"), // input: []byte("3"),
expectedResult: resource.Result{ // expectedResult: resource.Result{
FlagSet: []uint32{flag_single_edit}, // FlagSet: []uint32{flag_single_edit},
FlagReset: []uint32{flag_allow_update}, // FlagReset: []uint32{flag_allow_update},
}, // },
}, // },
{ // {
name: "Set single edit", // name: "Set single edit",
input: []byte("4"), // input: []byte("4"),
expectedResult: resource.Result{ // expectedResult: resource.Result{
FlagReset: []uint32{flag_allow_update}, // FlagReset: []uint32{flag_allow_update},
FlagSet: []uint32{flag_single_edit}, // FlagSet: []uint32{flag_single_edit},
}, // },
}, // },
{ {
name: "No single edit set", name: "No single edit set",
input: []byte("1"), input: []byte("1"),
@ -1660,20 +1666,13 @@ func TestGetProfile(t *testing.T) {
mockDataStore := new(mocks.MockUserDataStore) mockDataStore := new(mocks.MockUserDataStore)
mockCreateAccountService := new(mocks.MockAccountService) mockCreateAccountService := new(mocks.MockAccountService)
mockState := state.NewState(16) mockState := state.NewState(16)
// Set the ExecPath
ll := &lang.Language{
Code: "swa",
}
h := &Handlers{ h := &Handlers{
userdataStore: mockDataStore, userdataStore: mockDataStore,
accountService: mockCreateAccountService, accountService: mockCreateAccountService,
// st: mockState, st: mockState,
} }
ctx := context.WithValue(context.Background(), "SessionId", sessionId) ctx := context.WithValue(context.Background(), "SessionId", sessionId)
ctx = context.WithValue(ctx, "Language", ll)
tests := []struct { tests := []struct {
name string name string
@ -1710,10 +1709,10 @@ func TestGetProfile(t *testing.T) {
name: "Test with with profile information with language that is not yet supported", 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}, 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"}, profileInfo: []string{"Doee", "John", "Jinsia", "Bananas", "Kilifi", "1976"},
languageCode: "kamba", languageCode: "nor",
result: resource.Result{ result: resource.Result{
Content: fmt.Sprintf( Content: fmt.Sprintf(
"Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\n", "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n",
"John Doee", "Male", "48", "Kilifi", "Bananas", "John Doee", "Male", "48", "Kilifi", "Bananas",
), ),
}, },
@ -1725,8 +1724,12 @@ func TestGetProfile(t *testing.T) {
mockDataStore.On("ReadEntry", ctx, sessionId, key).Return([]byte(tt.profileInfo[index]), nil) mockDataStore.On("ReadEntry", ctx, sessionId, key).Return([]byte(tt.profileInfo[index]), nil)
} }
mockState.SetLanguage(tt.languageCode) err := mockState.SetLanguage(tt.languageCode)
if err != nil {
t.Fail()
}
h.st = mockState h.st = mockState
res, _ := h.GetProfileInfo(ctx, "get_profile_info", []byte("")) res, _ := h.GetProfileInfo(ctx, "get_profile_info", []byte(""))
// Assert that expectations were met // Assert that expectations were met
@ -1834,6 +1837,25 @@ func TestSaveTemporaryPIn(t *testing.T) {
mockStore.AssertExpectations(t) mockStore.AssertExpectations(t)
} }
func TestWithPersister(t *testing.T) {
// Test case: Setting a persister
h := &Handlers{}
p := &persist.Persister{}
result := h.WithPersister(p)
assert.Equal(t, p, h.pe, "The persister should be set correctly.")
assert.Equal(t, h, result, "The returned handler should be the same instance.")
}
func TestWithPersister_PanicWhenAlreadySet(t *testing.T) {
// Test case: Panic on multiple calls
h := &Handlers{pe: &persist.Persister{}}
require.Panics(t, func() {
h.WithPersister(&persist.Persister{})
}, "Should panic when trying to set a persister again.")
}
func TestConfirmPin(t *testing.T) { func TestConfirmPin(t *testing.T) {
sessionId := "session123" sessionId := "session123"

View File

@ -87,6 +87,6 @@ func (ash *ATSessionHandler) Output(rqs handlers.RequestSession) (handlers.Reque
return rqs, err return rqs, err
} }
_, err = rqs.Engine.WriteResult(rqs.Ctx, rqs.Writer) _, err = rqs.Engine.Flush(rqs.Ctx, rqs.Writer)
return rqs, err return rqs, err
} }

View File

@ -69,7 +69,7 @@ func TestATSessionHandler_ServeHTTP(t *testing.T) {
mh.GetRequestParserFunc = func() handlers.RequestParser { return mrp } mh.GetRequestParserFunc = func() handlers.RequestParser { return mrp }
mh.OutputFunc = func(rs handlers.RequestSession) (handlers.RequestSession, error) { return rs, nil } mh.OutputFunc = func(rs handlers.RequestSession) (handlers.RequestSession, error) { return rs, nil }
mh.ResetFunc = func(rs handlers.RequestSession) (handlers.RequestSession, error) { return rs, nil } mh.ResetFunc = func(rs handlers.RequestSession) (handlers.RequestSession, error) { return rs, nil }
me.WriteResultFunc = func(context.Context, io.Writer) (int, error) { return 0, nil } me.FlushFunc = func(context.Context, io.Writer) (int, error) { return 0, nil }
}, },
formData: url.Values{ formData: url.Values{
"phoneNumber": []string{"+1234567890"}, "phoneNumber": []string{"+1234567890"},
@ -178,7 +178,7 @@ func TestATSessionHandler_Output(t *testing.T) {
input: handlers.RequestSession{ input: handlers.RequestSession{
Continue: true, Continue: true,
Engine: &httpmocks.MockEngine{ Engine: &httpmocks.MockEngine{
WriteResultFunc: func(context.Context, io.Writer) (int, error) { FlushFunc: func(context.Context, io.Writer) (int, error) {
return 0, nil return 0, nil
}, },
}, },
@ -192,7 +192,7 @@ func TestATSessionHandler_Output(t *testing.T) {
input: handlers.RequestSession{ input: handlers.RequestSession{
Continue: false, Continue: false,
Engine: &httpmocks.MockEngine{ Engine: &httpmocks.MockEngine{
WriteResultFunc: func(context.Context, io.Writer) (int, error) { FlushFunc: func(context.Context, io.Writer) (int, error) {
return 0, nil return 0, nil
}, },
}, },
@ -202,11 +202,11 @@ func TestATSessionHandler_Output(t *testing.T) {
expectedError: false, expectedError: false,
}, },
{ {
name: "WriteResult error", name: "Flush error",
input: handlers.RequestSession{ input: handlers.RequestSession{
Continue: true, Continue: true,
Engine: &httpmocks.MockEngine{ Engine: &httpmocks.MockEngine{
WriteResultFunc: func(context.Context, io.Writer) (int, error) { FlushFunc: func(context.Context, io.Writer) (int, error) {
return 0, errors.New("write error") return 0, errors.New("write error")
}, },
}, },

View File

@ -9,7 +9,7 @@ import (
type MockEngine struct { type MockEngine struct {
InitFunc func(context.Context) (bool, error) InitFunc func(context.Context) (bool, error)
ExecFunc func(context.Context, []byte) (bool, error) ExecFunc func(context.Context, []byte) (bool, error)
WriteResultFunc func(context.Context, io.Writer) (int, error) FlushFunc func(context.Context, io.Writer) (int, error)
FinishFunc func() error FinishFunc func() error
} }
@ -21,8 +21,8 @@ func (m *MockEngine) Exec(ctx context.Context, input []byte) (bool, error) {
return m.ExecFunc(ctx, input) return m.ExecFunc(ctx, input)
} }
func (m *MockEngine) WriteResult(ctx context.Context, w io.Writer) (int, error) { func (m *MockEngine) Flush(ctx context.Context, w io.Writer) (int, error) {
return m.WriteResultFunc(ctx, w) return m.FlushFunc(ctx, w)
} }
func (m *MockEngine) Finish() error { func (m *MockEngine) Finish() error {