Compare commits

...

31 Commits

Author SHA1 Message Date
lash
e09e324a50 Merge branch 'lash/purify-more' into lash/purify-max 2025-01-06 09:03:30 +00:00
lash
599815c343 Fix remaining conflict in cmd cli 2025-01-06 09:00:41 +00:00
lash
462c0d7677 Merge branch 'master' into lash/purify-more 2025-01-06 08:59:42 +00:00
lash
02823fd64e Merge branch 'lash/ssh-fixes' into lash/purify-more 2025-01-06 08:47:49 +00:00
lash
cd575c2edb Merge remote-tracking branch 'urdt' into lash/purify-more 2025-01-06 08:46:01 +00:00
lash
f8d8f265f1 Merge upstream 2025-01-06 08:40:50 +00:00
lash
9371b52f3e Merge branch 'lash/ssh-fixes' into lash/purify-max 2025-01-06 08:39:40 +00:00
lash
563000ec15 Merge branch 'lash/purify-more' into lash/purify-max 2025-01-06 08:38:05 +00:00
lash
52fd1eced2 Enable env, config, db in ssh 2025-01-06 08:11:37 +00:00
lash
5c7a535288 Merge branch 'lash/purify-more' into lash/ssh-fixes 2025-01-06 07:55:40 +00:00
lash
cc2f7b41df Merge branch 'master' into lash/purify-more 2025-01-06 07:50:55 +00:00
lash
d39740a09a Edit ssh cli help text 2025-01-06 07:41:24 +00:00
lash
739fd90dfd Expose httpmocks 2025-01-05 21:04:21 +00:00
lash
6789c4f550 Export handlers 2025-01-05 11:17:58 +00:00
lash
437f73827d Rehabilitate all tets 2025-01-05 09:54:19 +00:00
lash
f0a4a0df61 Correct package name in errors, add state store getter 2025-01-05 07:19:25 +00:00
lash
bd604219b8 WIP Factor out request, errors 2025-01-04 22:27:46 +00:00
lash
51b6fc0dde Remove unused methods in storage interfaces, improve logs 2025-01-04 21:13:39 +00:00
lash
cc9760125a Remove unnecessary connection chain step 2025-01-04 21:02:08 +00:00
lash
3a9f3fa373 Update env example 2025-01-04 20:54:51 +00:00
lash
89c21847b9 Improve error message 2025-01-04 20:52:49 +00:00
lash
450dfa02cc Refactor to use conndata as menustorageservice conn arg 2025-01-04 20:36:18 +00:00
lash
f61e65f4fe Rename accountservice testservice source file 2025-01-04 13:24:14 +00:00
lash
a4d6cef9c0 Remove commented code 2025-01-04 13:21:31 +00:00
lash
2992f7ae8e Update executables with new conn str 2025-01-04 13:19:30 +00:00
lash
dc61d05584 WIP revert connstr gdbm to dir only, add file as table spec 2025-01-04 12:16:28 +00:00
lash
e92e498726 Merge branch 'lash/purify' into lash/purify-more 2025-01-04 09:46:18 +00:00
lash
c3cbe1cd92 Add connstr to last executable 2025-01-04 09:41:24 +00:00
lash
418080d093 Merge branch 'lash/purify' into lash/purify-more 2025-01-04 09:38:23 +00:00
lash
2e30739ec9 Implement connstr 2025-01-04 09:37:12 +00:00
lash
dc1674ec55 WIP add connection string parser 2025-01-04 08:40:43 +00:00
32 changed files with 504 additions and 848 deletions

View File

@@ -6,13 +6,9 @@ HOST=127.0.0.1
AT_ENDPOINT=/ussd/africastalking AT_ENDPOINT=/ussd/africastalking
#PostgreSQL #PostgreSQL
DB_HOST=localhost DB_CONN=postgres://postgres:strongpass@localhost:5432/urdt_ussd
DB_USER=postgres #DB_TIMEZONE=Africa/Nairobi
DB_PASSWORD=strongpass #DB_SCHEMA=vise
DB_NAME=urdt_ussd
DB_PORT=5432
DB_SSLMODE=disable
DB_TIMEZONE=Africa/Nairobi
#External API Calls #External API Calls
CUSTODIAL_URL_BASE=http://localhost:5003 CUSTODIAL_URL_BASE=http://localhost:5003

View File

@@ -1,165 +0,0 @@
package main
import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"path"
"strconv"
"syscall"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/lang"
"git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/http/at"
httpserver "git.grassecon.net/urdt/ussd/internal/http/at"
"git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/remote"
"git.grassecon.net/urdt/ussd/internal/args"
)
var (
logg = logging.NewVanilla().WithDomain("AfricasTalking").WithContextKey("at-session-id")
scriptDir = path.Join("services", "registration")
build = "dev"
menuSeparator = ": "
)
func init() {
initializers.LoadEnvVariables()
}
func main() {
config.LoadConfig()
var dbDir string
var resourceDir string
var size uint
var database string
var engineDebug bool
var host string
var port uint
var gettextDir string
var langs args.LangVar
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
flag.StringVar(&database, "db", "gdbm", "database to be used")
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host")
flag.UintVar(&port, "p", initializers.GetEnvUint("PORT", 7123), "http port")
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
flag.Var(&langs, "language", "add symbol resolution for language")
flag.Parse()
logg.Infof("start command", "build", build, "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size)
ctx := context.Background()
ctx = context.WithValue(ctx, "Database", database)
ln, err := lang.LanguageFromCode(config.DefaultLanguage)
if err != nil {
fmt.Fprintf(os.Stderr, "default language set error: %v", err)
os.Exit(1)
}
ctx = context.WithValue(ctx, "Language", ln)
pfp := path.Join(scriptDir, "pp.csv")
cfg := engine.Config{
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(128),
MenuSeparator: menuSeparator,
}
if engineDebug {
cfg.EngineDebug = true
}
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir)
rs, err := menuStorageService.GetResource(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
err = menuStorageService.EnsureDbDir()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdataStore, err := menuStorageService.GetUserdataDb(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer userdataStore.Close()
dbResource, ok := rs.(*resource.DbResource)
if !ok {
os.Exit(1)
}
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
lhs.SetDataStore(&userdataStore)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
accountService := remote.AccountService{}
hl, err := lhs.GetHandler(&accountService)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
stateStore, err := menuStorageService.GetStateStore(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer stateStore.Close()
rp := &at.ATRequestParser{}
bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
sh := httpserver.NewATSessionHandler(bsh)
mux := http.NewServeMux()
mux.Handle(initializers.GetEnv("AT_ENDPOINT", "/"), sh)
s := &http.Server{
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
Handler: mux,
}
s.RegisterOnShutdown(sh.Shutdown)
cint := make(chan os.Signal)
cterm := make(chan os.Signal)
signal.Notify(cint, os.Interrupt, syscall.SIGINT)
signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
go func() {
select {
case _ = <-cint:
case _ = <-cterm:
}
s.Shutdown(ctx)
}()
err = s.ListenAndServe()
if err != nil {
logg.Infof("Server closed with error", "err", err)
}
}

View File

@@ -16,9 +16,10 @@ import (
"git.grassecon.net/urdt/ussd/config" "git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers" "git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/handlers" "git.grassecon.net/urdt/ussd/handlers"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/remote" "git.grassecon.net/urdt/ussd/remote"
"git.grassecon.net/urdt/ussd/request"
"git.grassecon.net/urdt/ussd/internal/args" "git.grassecon.net/urdt/ussd/internal/args"
) )
@@ -48,20 +49,21 @@ func (p *asyncRequestParser) GetInput(r any) ([]byte, error) {
func main() { func main() {
config.LoadConfig() config.LoadConfig()
var connStr string
var sessionId string var sessionId string
var dbDir string
var resourceDir string var resourceDir string
var size uint var size uint
var database string var database string
var engineDebug bool var engineDebug bool
var host string var host string
var port uint var port uint
var err error
var gettextDir string var gettextDir string
var langs args.LangVar var langs args.LangVar
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
flag.StringVar(&database, "db", "gdbm", "database to be used") flag.StringVar(&connStr, "c", "", "connection string")
flag.BoolVar(&engineDebug, "d", false, "use engine debug output") flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output") flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host") flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host")
@@ -70,7 +72,16 @@ func main() {
flag.Var(&langs, "language", "add symbol resolution for language") flag.Var(&langs, "language", "add symbol resolution for language")
flag.Parse() flag.Parse()
logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size, "sessionId", sessionId) if connStr != "" {
connStr = config.DbConn
}
connData, err := storage.ToConnData(config.DbConn)
if err != nil {
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
os.Exit(1)
}
logg.Infof("start command", "conn", connData, "resourcedir", resourceDir, "outputsize", size, "sessionId", sessionId)
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, "Database", database) ctx = context.WithValue(ctx, "Database", database)
@@ -95,18 +106,18 @@ func main() {
cfg.EngineDebug = true cfg.EngineDebug = true
} }
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir) menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
rs, err := menuStorageService.GetResource(ctx) rs, err := menuStorageService.GetResource(ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
err = menuStorageService.EnsureDbDir()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdataStore, err := menuStorageService.GetUserdataDb(ctx) userdataStore, err := menuStorageService.GetUserdataDb(ctx)
if err != nil { if err != nil {
@@ -142,7 +153,7 @@ func main() {
} }
sh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) sh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
cfg.SessionId = sessionId cfg.SessionId = sessionId
rqs := handlers.RequestSession{ rqs := request.RequestSession{
Ctx: ctx, Ctx: ctx,
Writer: os.Stdout, Writer: os.Stdout,
Config: cfg, Config: cfg,

View File

@@ -18,7 +18,7 @@ import (
"git.grassecon.net/urdt/ussd/config" "git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers" "git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/handlers" "git.grassecon.net/urdt/ussd/handlers"
httpserver "git.grassecon.net/urdt/ussd/internal/http" httpserver "git.grassecon.net/urdt/ussd/internal/http"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/remote" "git.grassecon.net/urdt/ussd/remote"
@@ -38,18 +38,19 @@ func init() {
func main() { func main() {
config.LoadConfig() config.LoadConfig()
var dbDir string var connStr string
var resourceDir string var resourceDir string
var size uint var size uint
var database string var database string
var engineDebug bool var engineDebug bool
var host string var host string
var port uint var port uint
var err error
var gettextDir string var gettextDir string
var langs args.LangVar var langs args.LangVar
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
flag.StringVar(&database, "db", "gdbm", "database to be used") flag.StringVar(&connStr, "c", "", "connection string")
flag.BoolVar(&engineDebug, "d", false, "use engine debug output") flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output") flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host") flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host")
@@ -58,7 +59,16 @@ func main() {
flag.Var(&langs, "language", "add symbol resolution for language") flag.Var(&langs, "language", "add symbol resolution for language")
flag.Parse() flag.Parse()
logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size) if connStr != "" {
connStr = config.DbConn
}
connData, err := storage.ToConnData(config.DbConn)
if err != nil {
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
os.Exit(1)
}
logg.Infof("start command", "conn", connData, "resourcedir", resourceDir, "outputsize", size)
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, "Database", database) ctx = context.WithValue(ctx, "Database", database)
@@ -83,19 +93,14 @@ func main() {
cfg.EngineDebug = true cfg.EngineDebug = true
} }
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir) menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
rs, err := menuStorageService.GetResource(ctx) rs, err := menuStorageService.GetResource(ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
err = menuStorageService.EnsureDbDir()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdataStore, err := menuStorageService.GetUserdataDb(ctx) userdataStore, err := menuStorageService.GetUserdataDb(ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
@@ -132,7 +137,11 @@ func main() {
rp := &httpserver.DefaultRequestParser{} rp := &httpserver.DefaultRequestParser{}
bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
sh := httpserver.ToSessionHandler(bsh) // TODO: less hacky way of making session handler
//sh := request.ToSessionHandler(bsh)
sh := &httpserver.SessionHandler{
RequestHandler: bsh,
}
s := &http.Server{ s := &http.Server{
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))), Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
Handler: sh, Handler: sh,

View File

@@ -12,8 +12,8 @@ import (
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/lang" "git.defalsify.org/vise.git/lang"
"git.grassecon.net/urdt/ussd/config" "git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/handlers"
"git.grassecon.net/urdt/ussd/initializers" "git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/internal/args" "git.grassecon.net/urdt/ussd/internal/args"
"git.grassecon.net/urdt/ussd/remote" "git.grassecon.net/urdt/ussd/remote"
@@ -33,23 +33,35 @@ func init() {
func main() { func main() {
config.LoadConfig() config.LoadConfig()
var dbDir string var connStr string
var size uint var size uint
var sessionId string var sessionId string
var database string var database string
var engineDebug bool var engineDebug bool
var resourceDir string
var err error
var gettextDir string var gettextDir string
var langs args.LangVar var langs args.LangVar
flag.StringVar(&resourceDir, "resourcedir", scriptDir, "resource dir")
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
flag.StringVar(&database, "db", "gdbm", "database to be used") flag.StringVar(&connStr, "c", "", "connection string")
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.BoolVar(&engineDebug, "d", false, "use engine debug output") flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output") flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory") flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
flag.Var(&langs, "language", "add symbol resolution for language") flag.Var(&langs, "language", "add symbol resolution for language")
flag.Parse() flag.Parse()
logg.Infof("start command", "dbdir", dbDir, "outputsize", size) if connStr != "" {
connStr = config.DbConn
}
connData, err := storage.ToConnData(config.DbConn)
if err != nil {
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
os.Exit(1)
}
logg.Infof("start command", "conn", connData, "outputsize", size)
if len(langs.Langs()) == 0 { if len(langs.Langs()) == 0 {
langs.Set(config.DefaultLanguage) langs.Set(config.DefaultLanguage)
@@ -76,18 +88,12 @@ func main() {
MenuSeparator: menuSeparator, MenuSeparator: menuSeparator,
} }
resourceDir := scriptDir menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir)
if gettextDir != "" { if gettextDir != "" {
menuStorageService = menuStorageService.WithGettext(gettextDir, langs.Langs()) menuStorageService = menuStorageService.WithGettext(gettextDir, langs.Langs())
} }
err = menuStorageService.EnsureDbDir()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
rs, err := menuStorageService.GetResource(ctx) rs, err := menuStorageService.GetResource(ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())

View File

@@ -14,7 +14,10 @@ import (
"git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging" "git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/ssh" "git.grassecon.net/urdt/ussd/internal/ssh"
"git.grassecon.net/urdt/ussd/internal/storage"
) )
var ( var (
@@ -26,7 +29,14 @@ var (
build = "dev" build = "dev"
) )
func init() {
initializers.LoadEnvVariables()
}
func main() { func main() {
config.LoadConfig()
var connStr string
var dbDir string var dbDir string
var resourceDir string var resourceDir string
var size uint var size uint
@@ -34,17 +44,25 @@ func main() {
var stateDebug bool var stateDebug bool
var host string var host string
var port uint var port uint
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") flag.StringVar(&connStr, "c", "", "connection string")
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
flag.BoolVar(&engineDebug, "engine-debug", false, "use engine debug output") flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
flag.BoolVar(&stateDebug, "state-debug", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output") flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&host, "h", "127.0.0.1", "http host") flag.StringVar(&host, "h", "127.0.0.1", "socket host")
flag.UintVar(&port, "p", 7122, "http port") flag.UintVar(&port, "p", 7122, "socket port")
flag.Parse() flag.Parse()
if connStr != "" {
connStr = config.DbConn
}
connData, err := storage.ToConnData(config.DbConn)
if err != nil {
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
os.Exit(1)
}
sshKeyFile := flag.Arg(0) sshKeyFile := flag.Arg(0)
_, err := os.Stat(sshKeyFile) _, err = os.Stat(sshKeyFile)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "cannot open ssh server private key file: %v\n", err) fmt.Fprintf(os.Stderr, "cannot open ssh server private key file: %v\n", err)
os.Exit(1) os.Exit(1)
@@ -92,10 +110,10 @@ func main() {
signal.Notify(cterm, os.Interrupt, syscall.SIGTERM) signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
runner := &ssh.SshRunner{ runner := &ssh.SshRunner{
Cfg: cfg, Cfg: cfg,
Debug: engineDebug, Debug: engineDebug,
FlagFile: pfp, FlagFile: pfp,
DbDir: dbDir, Conn: connData,
ResourceDir: resourceDir, ResourceDir: resourceDir,
SrvKeyFile: sshKeyFile, SrvKeyFile: sshKeyFile,
Host: host, Host: host,

View File

@@ -11,6 +11,10 @@ import (
dbstorage "git.grassecon.net/urdt/ussd/internal/storage/db" dbstorage "git.grassecon.net/urdt/ussd/internal/storage/db"
) )
var (
ToConnData = storage.ToConnData
)
func StoreToDb(store *UserDataStore) db.Db { func StoreToDb(store *UserDataStore) db.Db {
return store.Db return store.Db
} }
@@ -23,17 +27,23 @@ type StorageServices interface {
GetPersister(ctx context.Context) (*persist.Persister, error) GetPersister(ctx context.Context) (*persist.Persister, error)
GetUserdataDb(ctx context.Context) (db.Db, error) GetUserdataDb(ctx context.Context) (db.Db, error)
GetResource(ctx context.Context) (resource.Resource, error) GetResource(ctx context.Context) (resource.Resource, error)
EnsureDbDir() error
} }
type StorageService struct { type StorageService struct {
svc *storage.MenuStorageService svc *storage.MenuStorageService
} }
func NewStorageService(dbDir string) *StorageService { func NewStorageService(conn storage.ConnData) (*StorageService, error) {
return &StorageService{ svc := &StorageService{
svc: storage.NewMenuStorageService(dbDir, ""), svc: storage.NewMenuStorageService(conn, ""),
} }
return svc, nil
}
// TODO: simplify enable poresource, conndata instead
func(ss *StorageService) SetResourceDir(resourceDir string) error {
ss.svc = ss.svc.WithResourceDir(resourceDir)
return nil
} }
func(ss *StorageService) GetPersister(ctx context.Context) (*persist.Persister, error) { func(ss *StorageService) GetPersister(ctx context.Context) (*persist.Persister, error) {
@@ -48,6 +58,6 @@ func(ss *StorageService) GetResource(ctx context.Context) (resource.Resource, er
return nil, errors.New("not implemented") return nil, errors.New("not implemented")
} }
func(ss *StorageService) EnsureDbDir() error { func(ss *StorageService) GetStateStore(ctx context.Context) (db.Db, error) {
return ss.svc.EnsureDbDir() return ss.svc.GetStateStore(ctx)
} }

View File

@@ -40,6 +40,7 @@ var (
VoucherTransfersURL string VoucherTransfersURL string
VoucherDataURL string VoucherDataURL string
CheckAliasURL string CheckAliasURL string
DbConn string
DefaultLanguage string DefaultLanguage string
Languages []string Languages []string
) )
@@ -69,14 +70,20 @@ func setBase() error {
dataURLBase = initializers.GetEnv("DATA_URL_BASE", "http://localhost:5006") dataURLBase = initializers.GetEnv("DATA_URL_BASE", "http://localhost:5006")
BearerToken = initializers.GetEnv("BEARER_TOKEN", "") BearerToken = initializers.GetEnv("BEARER_TOKEN", "")
_, err = url.JoinPath(custodialURLBase, "/foo") _, err = url.Parse(custodialURLBase)
if err != nil { if err != nil {
return err return err
} }
_, err = url.JoinPath(dataURLBase, "/bar") _, err = url.Parse(dataURLBase)
if err != nil { if err != nil {
return err return err
} }
return nil
}
func setConn() error {
DbConn = initializers.GetEnv("DB_CONN", "")
return nil return nil
} }
@@ -86,6 +93,10 @@ func LoadConfig() error {
if err != nil { if err != nil {
return err return err
} }
err = setConn()
if err != nil {
return err
}
err = setLanguage() err = setLanguage()
if err != nil { if err != nil {
return err return err

View File

@@ -37,23 +37,34 @@ func formatItem(k []byte, v []byte) (string, error) {
func main() { func main() {
config.LoadConfig() config.LoadConfig()
var dbDir string var connStr string
var sessionId string var sessionId string
var database string var database string
var engineDebug bool var engineDebug bool
var err error
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
flag.StringVar(&database, "db", "gdbm", "database to be used") flag.StringVar(&connStr, "c", ".state", "connection string")
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.BoolVar(&engineDebug, "d", false, "use engine debug output") flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
flag.Parse() flag.Parse()
if connStr != "" {
connStr = config.DbConn
}
connData, err := storage.ToConnData(config.DbConn)
if err != nil {
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
os.Exit(1)
}
logg.Infof("start command", "conn", connData)
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
ctx = context.WithValue(ctx, "Database", database) ctx = context.WithValue(ctx, "Database", database)
resourceDir := scriptDir resourceDir := scriptDir
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir) menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
store, err := menuStorageService.GetUserdataDb(ctx) store, err := menuStorageService.GetUserdataDb(ctx)
if err != nil { if err != nil {

View File

@@ -28,24 +28,35 @@ func init() {
func main() { func main() {
config.LoadConfig() config.LoadConfig()
var dbDir string var connStr string
var sessionId string var sessionId string
var database string var database string
var engineDebug bool var engineDebug bool
var err error
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
flag.StringVar(&database, "db", "gdbm", "database to be used") flag.StringVar(&connStr, "c", "", "connection string")
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.BoolVar(&engineDebug, "d", false, "use engine debug output") flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
flag.Parse() flag.Parse()
if connStr != "" {
connStr = config.DbConn
}
connData, err := storage.ToConnData(config.DbConn)
if err != nil {
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
os.Exit(1)
}
logg.Infof("start command", "conn", connData)
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
ctx = context.WithValue(ctx, "Database", database) ctx = context.WithValue(ctx, "Database", database)
resourceDir := scriptDir resourceDir := scriptDir
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir) menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
store, err := menuStorageService.GetUserdataDb(ctx) store, err := menuStorageService.GetUserdataDb(ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())

15
errors/errors.go Normal file
View File

@@ -0,0 +1,15 @@
package errors
import (
"git.grassecon.net/urdt/ussd/internal/handlers"
)
var (
ErrInvalidRequest = handlers.ErrInvalidRequest
ErrSessionMissing = handlers.ErrSessionMissing
ErrInvalidInput = handlers.ErrInvalidInput
ErrStorage = handlers.ErrStorage
ErrEngineType = handlers.ErrEngineType
ErrEngineInit = handlers.ErrEngineInit
ErrEngineExec = handlers.ErrEngineExec
)

View File

@@ -5,20 +5,27 @@ import (
"git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/request"
"git.grassecon.net/urdt/ussd/errors"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd" "git.grassecon.net/urdt/ussd/internal/handlers/ussd"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
) )
var (
logg = logging.NewVanilla().WithDomain("handlers")
)
type BaseSessionHandler struct { type BaseSessionHandler struct {
cfgTemplate engine.Config cfgTemplate engine.Config
rp RequestParser rp request.RequestParser
rs resource.Resource rs resource.Resource
hn *ussd.Handlers hn *ussd.Handlers
provider storage.StorageProvider provider storage.StorageProvider
} }
func NewBaseSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn *ussd.Handlers) *BaseSessionHandler { func NewBaseSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp request.RequestParser, hn *ussd.Handlers) *BaseSessionHandler {
return &BaseSessionHandler{ return &BaseSessionHandler{
cfgTemplate: cfg, cfgTemplate: cfg,
rs: rs, rs: rs,
@@ -41,7 +48,7 @@ func(f *BaseSessionHandler) GetEngine(cfg engine.Config, rs resource.Resource, p
return en return en
} }
func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error) { func(f *BaseSessionHandler) Process(rqs request.RequestSession) (request.RequestSession, error) {
var r bool var r bool
var err error var err error
var ok bool var ok bool
@@ -51,7 +58,7 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error)
rqs.Storage, err = f.provider.Get(rqs.Config.SessionId) rqs.Storage, err = f.provider.Get(rqs.Config.SessionId)
if err != nil { if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "storage get error", err) logg.ErrorCtxf(rqs.Ctx, "", "storage get error", err)
return rqs, ErrStorage return rqs, errors.ErrStorage
} }
f.hn = f.hn.WithPersister(rqs.Storage.Persister) f.hn = f.hn.WithPersister(rqs.Storage.Persister)
@@ -66,7 +73,7 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error)
if perr != nil { if perr != nil {
logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr) logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr)
} }
return rqs, ErrEngineType return rqs, errors.ErrEngineType
} }
en = en.WithFirst(f.hn.Init) en = en.WithFirst(f.hn.Init)
if rqs.Config.EngineDebug { if rqs.Config.EngineDebug {
@@ -88,13 +95,13 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error)
return rqs, nil return rqs, nil
} }
func(f *BaseSessionHandler) Output(rqs RequestSession) (RequestSession, error) { func(f *BaseSessionHandler) Output(rqs request.RequestSession) (request.RequestSession, error) {
var err error var err error
_, err = rqs.Engine.Flush(rqs.Ctx, rqs.Writer) _, err = rqs.Engine.Flush(rqs.Ctx, rqs.Writer)
return rqs, err return rqs, err
} }
func(f *BaseSessionHandler) Reset(rqs RequestSession) (RequestSession, error) { func(f *BaseSessionHandler) Reset(rqs request.RequestSession) (request.RequestSession, error) {
defer f.provider.Put(rqs.Config.SessionId, rqs.Storage) defer f.provider.Put(rqs.Config.SessionId, rqs.Storage)
return rqs, rqs.Engine.Finish() return rqs, rqs.Engine.Finish()
} }
@@ -103,6 +110,6 @@ func(f *BaseSessionHandler) GetConfig() engine.Config {
return f.cfgTemplate return f.cfgTemplate
} }
func(f *BaseSessionHandler) GetRequestParser() RequestParser { func(f *BaseSessionHandler) GetRequestParser() request.RequestParser {
return f.rp return f.rp
} }

View File

@@ -1,119 +0,0 @@
package at
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"git.grassecon.net/urdt/ussd/common"
"git.grassecon.net/urdt/ussd/internal/handlers"
)
type ATRequestParser struct {
}
func (arp *ATRequestParser) GetSessionId(ctx context.Context, rq any) (string, error) {
rqv, ok := rq.(*http.Request)
if !ok {
logg.Warnf("got an invalid request", "req", rq)
return "", handlers.ErrInvalidRequest
}
// Capture body (if any) for logging
body, err := io.ReadAll(rqv.Body)
if err != nil {
logg.Warnf("failed to read request body", "err", err)
return "", fmt.Errorf("failed to read request body: %v", err)
}
// Reset the body for further reading
rqv.Body = io.NopCloser(bytes.NewReader(body))
// Log the body as JSON
bodyLog := map[string]string{"body": string(body)}
logBytes, err := json.Marshal(bodyLog)
if err != nil {
logg.Warnf("failed to marshal request body", "err", err)
} else {
decodedStr := string(logBytes)
sessionId, err := extractATSessionId(decodedStr)
if err != nil {
ctx = context.WithValue(ctx, "AT-SessionId", sessionId)
}
logg.DebugCtxf(ctx, "Received request:", decodedStr)
}
if err := rqv.ParseForm(); err != nil {
logg.Warnf("failed to parse form data", "err", err)
return "", fmt.Errorf("failed to parse form data: %v", err)
}
phoneNumber := rqv.FormValue("phoneNumber")
if phoneNumber == "" {
return "", fmt.Errorf("no phone number found")
}
formattedNumber, err := common.FormatPhoneNumber(phoneNumber)
if err != nil {
logg.Warnf("failed to format phone number", "err", err)
return "", fmt.Errorf("failed to format number")
}
return formattedNumber, nil
}
func (arp *ATRequestParser) GetInput(rq any) ([]byte, error) {
rqv, ok := rq.(*http.Request)
if !ok {
return nil, handlers.ErrInvalidRequest
}
if err := rqv.ParseForm(); err != nil {
return nil, fmt.Errorf("failed to parse form data: %v", err)
}
text := rqv.FormValue("text")
parts := strings.Split(text, "*")
if len(parts) == 0 {
return nil, fmt.Errorf("no input found")
}
return []byte(parts[len(parts)-1]), nil
}
func parseQueryParams(query string) map[string]string {
params := make(map[string]string)
queryParams := strings.Split(query, "&")
for _, param := range queryParams {
// Split each key-value pair by '='
parts := strings.SplitN(param, "=", 2)
if len(parts) == 2 {
params[parts[0]] = parts[1]
}
}
return params
}
func extractATSessionId(decodedStr string) (string, error) {
var data map[string]string
err := json.Unmarshal([]byte(decodedStr), &data)
if err != nil {
logg.Errorf("Error unmarshalling JSON: %v", err)
return "", nil
}
decodedBody, err := url.QueryUnescape(data["body"])
if err != nil {
logg.Errorf("Error URL-decoding body: %v", err)
return "", nil
}
params := parseQueryParams(decodedBody)
sessionId := params["sessionId"]
return sessionId, nil
}

View File

@@ -1,98 +0,0 @@
package at
import (
"io"
"net/http"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/handlers"
httpserver "git.grassecon.net/urdt/ussd/internal/http"
)
var (
logg = logging.NewVanilla().WithDomain("atserver").WithContextKey("SessionId").WithContextKey("AT-SessionId")
)
type ATSessionHandler struct {
*httpserver.SessionHandler
}
func NewATSessionHandler(h handlers.RequestHandler) *ATSessionHandler {
return &ATSessionHandler{
SessionHandler: httpserver.ToSessionHandler(h),
}
}
func (ash *ATSessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var code int
var err error
rqs := handlers.RequestSession{
Ctx: req.Context(),
Writer: w,
}
rp := ash.GetRequestParser()
cfg := ash.GetConfig()
cfg.SessionId, err = rp.GetSessionId(req.Context(), req)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
ash.WriteError(w, 400, err)
return
}
rqs.Config = cfg
rqs.Input, err = rp.GetInput(req)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
ash.WriteError(w, 400, err)
return
}
rqs, err = ash.Process(rqs)
switch err {
case nil: // set code to 200 if no err
code = 200
case handlers.ErrStorage, handlers.ErrEngineInit, handlers.ErrEngineExec, handlers.ErrEngineType:
code = 500
default:
code = 500
}
if code != 200 {
ash.WriteError(w, 500, err)
return
}
w.WriteHeader(200)
w.Header().Set("Content-Type", "text/plain")
rqs, err = ash.Output(rqs)
if err != nil {
ash.WriteError(w, 500, err)
return
}
rqs, err = ash.Reset(rqs)
if err != nil {
ash.WriteError(w, 500, err)
return
}
}
func (ash *ATSessionHandler) Output(rqs handlers.RequestSession) (handlers.RequestSession, error) {
var err error
var prefix string
if rqs.Continue {
prefix = "CON "
} else {
prefix = "END "
}
_, err = io.WriteString(rqs.Writer, prefix)
if err != nil {
return rqs, err
}
_, err = rqs.Engine.Flush(rqs.Ctx, rqs.Writer)
return rqs, err
}

View File

@@ -1,234 +0,0 @@
package at
import (
"context"
"errors"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"git.defalsify.org/vise.git/engine"
"git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/testutil/mocks/httpmocks"
)
func TestNewATSessionHandler(t *testing.T) {
mockHandler := &httpmocks.MockRequestHandler{}
ash := NewATSessionHandler(mockHandler)
if ash == nil {
t.Fatal("NewATSessionHandler returned nil")
}
if ash.SessionHandler == nil {
t.Fatal("SessionHandler is nil")
}
}
func TestATSessionHandler_ServeHTTP(t *testing.T) {
tests := []struct {
name string
setupMocks func(*httpmocks.MockRequestHandler, *httpmocks.MockRequestParser, *httpmocks.MockEngine)
formData url.Values
expectedStatus int
expectedBody string
}{
{
name: "Successful request",
setupMocks: func(mh *httpmocks.MockRequestHandler, mrp *httpmocks.MockRequestParser, me *httpmocks.MockEngine) {
mrp.GetSessionIdFunc = func(rq any) (string, error) {
req := rq.(*http.Request)
return req.FormValue("phoneNumber"), nil
}
mrp.GetInputFunc = func(rq any) ([]byte, error) {
req := rq.(*http.Request)
text := req.FormValue("text")
parts := strings.Split(text, "*")
return []byte(parts[len(parts)-1]), nil
}
mh.ProcessFunc = func(rqs handlers.RequestSession) (handlers.RequestSession, error) {
rqs.Continue = true
rqs.Engine = me
return rqs, nil
}
mh.GetConfigFunc = func() engine.Config { return engine.Config{} }
mh.GetRequestParserFunc = func() handlers.RequestParser { return mrp }
mh.OutputFunc = func(rs handlers.RequestSession) (handlers.RequestSession, error) { return rs, nil }
mh.ResetFunc = func(rs handlers.RequestSession) (handlers.RequestSession, error) { return rs, nil }
me.FlushFunc = func(context.Context, io.Writer) (int, error) { return 0, nil }
},
formData: url.Values{
"phoneNumber": []string{"+1234567890"},
"text": []string{"1*2*3"},
},
expectedStatus: http.StatusOK,
expectedBody: "CON ",
},
{
name: "GetSessionId error",
setupMocks: func(mh *httpmocks.MockRequestHandler, mrp *httpmocks.MockRequestParser, me *httpmocks.MockEngine) {
mrp.GetSessionIdFunc = func(rq any) (string, error) {
return "", errors.New("no phone number found")
}
mh.GetConfigFunc = func() engine.Config { return engine.Config{} }
mh.GetRequestParserFunc = func() handlers.RequestParser { return mrp }
},
formData: url.Values{
"text": []string{"1*2*3"},
},
expectedStatus: http.StatusBadRequest,
expectedBody: "",
},
{
name: "GetInput error",
setupMocks: func(mh *httpmocks.MockRequestHandler, mrp *httpmocks.MockRequestParser, me *httpmocks.MockEngine) {
mrp.GetSessionIdFunc = func(rq any) (string, error) {
req := rq.(*http.Request)
return req.FormValue("phoneNumber"), nil
}
mrp.GetInputFunc = func(rq any) ([]byte, error) {
return nil, errors.New("no input found")
}
mh.GetConfigFunc = func() engine.Config { return engine.Config{} }
mh.GetRequestParserFunc = func() handlers.RequestParser { return mrp }
},
formData: url.Values{
"phoneNumber": []string{"+1234567890"},
},
expectedStatus: http.StatusBadRequest,
expectedBody: "",
},
{
name: "Process error",
setupMocks: func(mh *httpmocks.MockRequestHandler, mrp *httpmocks.MockRequestParser, me *httpmocks.MockEngine) {
mrp.GetSessionIdFunc = func(rq any) (string, error) {
req := rq.(*http.Request)
return req.FormValue("phoneNumber"), nil
}
mrp.GetInputFunc = func(rq any) ([]byte, error) {
req := rq.(*http.Request)
text := req.FormValue("text")
parts := strings.Split(text, "*")
return []byte(parts[len(parts)-1]), nil
}
mh.ProcessFunc = func(rqs handlers.RequestSession) (handlers.RequestSession, error) {
return rqs, handlers.ErrStorage
}
mh.GetConfigFunc = func() engine.Config { return engine.Config{} }
mh.GetRequestParserFunc = func() handlers.RequestParser { return mrp }
},
formData: url.Values{
"phoneNumber": []string{"+1234567890"},
"text": []string{"1*2*3"},
},
expectedStatus: http.StatusInternalServerError,
expectedBody: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockHandler := &httpmocks.MockRequestHandler{}
mockRequestParser := &httpmocks.MockRequestParser{}
mockEngine := &httpmocks.MockEngine{}
tt.setupMocks(mockHandler, mockRequestParser, mockEngine)
ash := NewATSessionHandler(mockHandler)
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tt.formData.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
ash.ServeHTTP(w, req)
if w.Code != tt.expectedStatus {
t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
}
if tt.expectedBody != "" && w.Body.String() != tt.expectedBody {
t.Errorf("Expected body %q, got %q", tt.expectedBody, w.Body.String())
}
})
}
}
func TestATSessionHandler_Output(t *testing.T) {
tests := []struct {
name string
input handlers.RequestSession
expectedPrefix string
expectedError bool
}{
{
name: "Continue true",
input: handlers.RequestSession{
Continue: true,
Engine: &httpmocks.MockEngine{
FlushFunc: func(context.Context, io.Writer) (int, error) {
return 0, nil
},
},
Writer: &httpmocks.MockWriter{},
},
expectedPrefix: "CON ",
expectedError: false,
},
{
name: "Continue false",
input: handlers.RequestSession{
Continue: false,
Engine: &httpmocks.MockEngine{
FlushFunc: func(context.Context, io.Writer) (int, error) {
return 0, nil
},
},
Writer: &httpmocks.MockWriter{},
},
expectedPrefix: "END ",
expectedError: false,
},
{
name: "Flush error",
input: handlers.RequestSession{
Continue: true,
Engine: &httpmocks.MockEngine{
FlushFunc: func(context.Context, io.Writer) (int, error) {
return 0, errors.New("write error")
},
},
Writer: &httpmocks.MockWriter{},
},
expectedPrefix: "CON ",
expectedError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ash := &ATSessionHandler{}
_, err := ash.Output(tt.input)
if tt.expectedError && err == nil {
t.Error("Expected an error, but got nil")
}
if !tt.expectedError && err != nil {
t.Errorf("Unexpected error: %v", err)
}
mw := tt.input.Writer.(*httpmocks.MockWriter)
if !mw.WriteStringCalled {
t.Error("WriteString was not called")
}
if mw.WrittenString != tt.expectedPrefix {
t.Errorf("Expected prefix %q, got %q", tt.expectedPrefix, mw.WrittenString)
}
})
}
}

View File

@@ -7,22 +7,16 @@ import (
"git.defalsify.org/vise.git/logging" "git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/handlers" "git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/request"
) )
var ( var (
logg = logging.NewVanilla().WithDomain("httpserver") logg = logging.NewVanilla().WithDomain("httpserver")
) )
type SessionHandler struct { type SessionHandler request.SessionHandler
handlers.RequestHandler
}
func ToSessionHandler(h handlers.RequestHandler) *SessionHandler {
return &SessionHandler{
RequestHandler: h,
}
}
// TODO: duplicated
func (f *SessionHandler) WriteError(w http.ResponseWriter, code int, err error) { func (f *SessionHandler) WriteError(w http.ResponseWriter, code int, err error) {
s := err.Error() s := err.Error()
w.Header().Set("Content-Length", strconv.Itoa(len(s))) w.Header().Set("Content-Length", strconv.Itoa(len(s)))
@@ -39,7 +33,7 @@ func (f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var err error var err error
var perr error var perr error
rqs := handlers.RequestSession{ rqs := request.RequestSession{
Ctx: req.Context(), Ctx: req.Context(),
Writer: w, Writer: w,
} }

View File

@@ -10,7 +10,8 @@ import (
"git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/engine"
"git.grassecon.net/urdt/ussd/internal/handlers" "git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/testutil/mocks/httpmocks" "git.grassecon.net/urdt/ussd/testutil/mocks/httpmocks"
"git.grassecon.net/urdt/ussd/request"
) )
// invalidRequestType is a custom type to test invalid request scenarios // invalidRequestType is a custom type to test invalid request scenarios
@@ -81,16 +82,16 @@ func TestSessionHandler_ServeHTTP(t *testing.T) {
} }
mockRequestHandler := &httpmocks.MockRequestHandler{ mockRequestHandler := &httpmocks.MockRequestHandler{
ProcessFunc: func(rs handlers.RequestSession) (handlers.RequestSession, error) { ProcessFunc: func(rs request.RequestSession) (request.RequestSession, error) {
return rs, tt.processErr return rs, tt.processErr
}, },
OutputFunc: func(rs handlers.RequestSession) (handlers.RequestSession, error) { OutputFunc: func(rs request.RequestSession) (request.RequestSession, error) {
return rs, tt.outputErr return rs, tt.outputErr
}, },
ResetFunc: func(rs handlers.RequestSession) (handlers.RequestSession, error) { ResetFunc: func(rs request.RequestSession) (request.RequestSession, error) {
return rs, tt.resetErr return rs, tt.resetErr
}, },
GetRequestParserFunc: func() handlers.RequestParser { GetRequestParserFunc: func() request.RequestParser {
return mockRequestParser return mockRequestParser
}, },
GetConfigFunc: func() engine.Config { GetConfigFunc: func() engine.Config {
@@ -98,7 +99,10 @@ func TestSessionHandler_ServeHTTP(t *testing.T) {
}, },
} }
sessionHandler := ToSessionHandler(mockRequestHandler) sessionHandler := &SessionHandler{
RequestHandler: mockRequestHandler,
}
//sessionHandler := request.ToSessionHandler(mockRequestHandler)
req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(tt.input)) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(tt.input))
req.Header.Set("X-Vise-Session", tt.sessionID) req.Header.Set("X-Vise-Session", tt.sessionID)

View File

@@ -17,7 +17,7 @@ import (
"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/handlers" "git.grassecon.net/urdt/ussd/handlers"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/remote" "git.grassecon.net/urdt/ussd/remote"
) )
@@ -71,6 +71,20 @@ func(a *auther) Get(k []byte) (string, error) {
return v, nil return v, nil
} }
type SshRunner struct {
Ctx context.Context
Cfg engine.Config
FlagFile string
Conn storage.ConnData
ResourceDir string
Debug bool
SrvKeyFile string
Host string
Port uint
wg sync.WaitGroup
lst net.Listener
}
func(s *SshRunner) serve(ctx context.Context, sessionId string, ch ssh.NewChannel, en engine.Engine) error { func(s *SshRunner) serve(ctx context.Context, sessionId string, ch ssh.NewChannel, en engine.Engine) error {
if ch == nil { if ch == nil {
return errors.New("nil channel") return errors.New("nil channel")
@@ -128,32 +142,13 @@ func(s *SshRunner) serve(ctx context.Context, sessionId string, ch ssh.NewChanne
return nil return nil
} }
type SshRunner struct {
Ctx context.Context
Cfg engine.Config
FlagFile string
DbDir string
ResourceDir string
Debug bool
SrvKeyFile string
Host string
Port uint
wg sync.WaitGroup
lst net.Listener
}
func(s *SshRunner) Stop() error { func(s *SshRunner) Stop() error {
return s.lst.Close() return s.lst.Close()
} }
func(s *SshRunner) GetEngine(sessionId string) (engine.Engine, func(), error) { func(s *SshRunner) GetEngine(sessionId string) (engine.Engine, func(), error) {
ctx := s.Ctx ctx := s.Ctx
menuStorageService := storage.NewMenuStorageService(s.DbDir, s.ResourceDir) menuStorageService := storage.NewMenuStorageService(s.Conn, s.ResourceDir)
err := menuStorageService.EnsureDbDir()
if err != nil {
return nil, nil, err
}
rs, err := menuStorageService.GetResource(ctx) rs, err := menuStorageService.GetResource(ctx)
if err != nil { if err != nil {

69
internal/storage/parse.go Normal file
View File

@@ -0,0 +1,69 @@
package storage
import (
"fmt"
"net/url"
"path"
)
const (
DBTYPE_MEM = iota
DBTYPE_GDBM
DBTYPE_POSTGRES
)
type ConnData struct {
typ int
str string
}
func (cd *ConnData) DbType() int {
return cd.typ
}
func (cd *ConnData) String() string {
return cd.str
}
func probePostgres(s string) (string, bool) {
v, err := url.Parse(s)
if err != nil {
return "", false
}
if v.Scheme != "postgres" {
return "", false
}
return s, true
}
func probeGdbm(s string) (string, bool) {
if !path.IsAbs(s) {
return "", false
}
s = path.Clean(s)
return s, true
}
func ToConnData(connStr string) (ConnData, error) {
var o ConnData
if connStr == "" {
return o, nil
}
v, ok := probePostgres(connStr)
if ok {
o.typ = DBTYPE_POSTGRES
o.str = v
return o, nil
}
v, ok = probeGdbm(connStr)
if ok {
o.typ = DBTYPE_GDBM
o.str = v
return o, nil
}
return o, fmt.Errorf("invalid connection string: %s", connStr)
}

View File

@@ -0,0 +1,28 @@
package storage
import (
"testing"
)
func TestParseConnStr(t *testing.T) {
_, err := ToConnData("postgres://foo:bar@localhost:5432/baz")
if err != nil {
t.Fatal(err)
}
_, err = ToConnData("/foo/bar")
if err != nil {
t.Fatal(err)
}
_, err = ToConnData("/foo/bar/")
if err != nil {
t.Fatal(err)
}
_, err = ToConnData("foo/bar")
if err == nil {
t.Fatalf("expected error")
}
_, err = ToConnData("http://foo/bar")
if err == nil {
t.Fatalf("expected error")
}
}

View File

@@ -13,7 +13,6 @@ import (
"git.defalsify.org/vise.git/logging" "git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/initializers"
gdbmstorage "git.grassecon.net/urdt/ussd/internal/storage/db/gdbm" gdbmstorage "git.grassecon.net/urdt/ussd/internal/storage/db/gdbm"
) )
@@ -25,11 +24,10 @@ type StorageService interface {
GetPersister(ctx context.Context) (*persist.Persister, error) GetPersister(ctx context.Context) (*persist.Persister, error)
GetUserdataDb(ctx context.Context) db.Db GetUserdataDb(ctx context.Context) db.Db
GetResource(ctx context.Context) (resource.Resource, error) GetResource(ctx context.Context) (resource.Resource, error)
EnsureDbDir() error
} }
type MenuStorageService struct { type MenuStorageService struct {
dbDir string conn ConnData
resourceDir string resourceDir string
poResource resource.Resource poResource resource.Resource
resourceStore db.Db resourceStore db.Db
@@ -37,29 +35,50 @@ type MenuStorageService struct {
userDataStore db.Db userDataStore db.Db
} }
func buildConnStr() string { func NewMenuStorageService(conn ConnData, resourceDir string) *MenuStorageService {
host := initializers.GetEnv("DB_HOST", "localhost")
user := initializers.GetEnv("DB_USER", "postgres")
password := initializers.GetEnv("DB_PASSWORD", "")
dbName := initializers.GetEnv("DB_NAME", "")
port := initializers.GetEnv("DB_PORT", "5432")
connString := fmt.Sprintf(
"postgres://%s:%s@%s:%s/%s",
user, password, host, port, dbName,
)
logg.Debugf("pg conn string", "conn", connString)
return connString
}
func NewMenuStorageService(dbDir string, resourceDir string) *MenuStorageService {
return &MenuStorageService{ return &MenuStorageService{
dbDir: dbDir, conn: conn,
resourceDir: resourceDir, resourceDir: resourceDir,
} }
} }
func (ms *MenuStorageService) WithResourceDir(resourceDir string) *MenuStorageService {
ms.resourceDir = resourceDir
return ms
}
func (ms *MenuStorageService) getOrCreateDb(ctx context.Context, existingDb db.Db, section string) (db.Db, error) {
var newDb db.Db
var err error
if existingDb != nil {
return existingDb, nil
}
connStr := ms.conn.String()
dbTyp := ms.conn.DbType()
if dbTyp == DBTYPE_POSTGRES {
newDb = postgres.NewPgDb()
} else if dbTyp == DBTYPE_GDBM {
err = ms.ensureDbDir()
if err != nil {
return nil, err
}
connStr = path.Join(connStr, section)
newDb = gdbmstorage.NewThreadGdbmDb()
} else {
return nil, fmt.Errorf("unsupported connection string: '%s'\n", ms.conn.String())
}
logg.DebugCtxf(ctx, "connecting to db", "conn", connStr)
err = newDb.Connect(ctx, connStr)
if err != nil {
return nil, err
}
return newDb, nil
}
// WithGettext triggers use of gettext for translation of templates and menus. // WithGettext triggers use of gettext for translation of templates and menus.
// //
// The first language in `lns` will be used as default language, to resolve node keys to // The first language in `lns` will be used as default language, to resolve node keys to
@@ -82,36 +101,6 @@ func (ms *MenuStorageService) WithGettext(path string, lns []lang.Language) *Men
return ms return ms
} }
func (ms *MenuStorageService) getOrCreateDb(ctx context.Context, existingDb db.Db, fileName string) (db.Db, error) {
database, ok := ctx.Value("Database").(string)
if !ok {
return nil, fmt.Errorf("failed to select the database")
}
if existingDb != nil {
return existingDb, nil
}
var newDb db.Db
var err error
if database == "postgres" {
newDb = postgres.NewPgDb()
connStr := buildConnStr()
err = newDb.Connect(ctx, connStr)
} else {
newDb = gdbmstorage.NewThreadGdbmDb()
storeFile := path.Join(ms.dbDir, fileName)
err = newDb.Connect(ctx, storeFile)
}
if err != nil {
return nil, err
}
return newDb, nil
}
func (ms *MenuStorageService) GetPersister(ctx context.Context) (*persist.Persister, error) { func (ms *MenuStorageService) GetPersister(ctx context.Context) (*persist.Persister, error) {
stateStore, err := ms.GetStateStore(ctx) stateStore, err := ms.GetStateStore(ctx)
if err != nil { if err != nil {
@@ -166,8 +155,8 @@ func (ms *MenuStorageService) GetStateStore(ctx context.Context) (db.Db, error)
return ms.stateStore, nil return ms.stateStore, nil
} }
func (ms *MenuStorageService) EnsureDbDir() error { func (ms *MenuStorageService) ensureDbDir() error {
err := os.MkdirAll(ms.dbDir, 0700) err := os.MkdirAll(ms.conn.String(), 0700)
if err != nil { if err != nil {
return fmt.Errorf("state dir create exited with error: %v\n", err) return fmt.Errorf("state dir create exited with error: %v\n", err)
} }

View File

@@ -5,12 +5,13 @@ import (
"fmt" "fmt"
"os" "os"
"path" "path"
"path/filepath"
"time" "time"
"git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging" "git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers" "git.grassecon.net/urdt/ussd/handlers"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/internal/testutil/testservice" "git.grassecon.net/urdt/ussd/internal/testutil/testservice"
"git.grassecon.net/urdt/ussd/internal/testutil/testtag" "git.grassecon.net/urdt/ussd/internal/testutil/testtag"
@@ -27,7 +28,6 @@ var (
func TestEngine(sessionId string) (engine.Engine, func(), chan bool) { func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
ctx = context.WithValue(ctx, "Database", "gdbm")
pfp := path.Join(scriptDir, "pp.csv") pfp := path.Join(scriptDir, "pp.csv")
var eventChannel = make(chan bool) var eventChannel = make(chan bool)
@@ -39,37 +39,40 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
FlagCount: uint32(128), FlagCount: uint32(128),
} }
dbDir := ".test_state" connStr, err := filepath.Abs(".test_state/state.gdbm")
resourceDir := scriptDir
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir)
err := menuStorageService.EnsureDbDir()
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, "connstr err: %v", err)
os.Exit(1) os.Exit(1)
} }
conn, err := storage.ToConnData(connStr)
if err != nil {
fmt.Fprintf(os.Stderr, "connstr parse err: %v", err)
os.Exit(1)
}
resourceDir := scriptDir
menuStorageService := storage.NewMenuStorageService(conn, resourceDir)
rs, err := menuStorageService.GetResource(ctx) rs, err := menuStorageService.GetResource(ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, "resource error: %v", err)
os.Exit(1) os.Exit(1)
} }
pe, err := menuStorageService.GetPersister(ctx) pe, err := menuStorageService.GetPersister(ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, "persister error: %v", err)
os.Exit(1) os.Exit(1)
} }
userDataStore, err := menuStorageService.GetUserdataDb(ctx) userDataStore, err := menuStorageService.GetUserdataDb(ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, "userdb error: %v", err)
os.Exit(1) os.Exit(1)
} }
dbResource, ok := rs.(*resource.DbResource) dbResource, ok := rs.(*resource.DbResource)
if !ok { if !ok {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, "dbresource cast error")
os.Exit(1) os.Exit(1)
} }

View File

@@ -0,0 +1,15 @@
package testutil
import (
"testing"
)
func TestCreateEngine(t *testing.T) {
o, clean, eventC := TestEngine("foo")
defer clean()
defer func() {
<-eventC
close(eventC)
}()
_ = o
}

View File

@@ -1,47 +0,0 @@
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()
}

View File

@@ -7,6 +7,7 @@ import (
"log" "log"
"math/rand" "math/rand"
"os" "os"
"path/filepath"
"regexp" "regexp"
"testing" "testing"
@@ -17,7 +18,6 @@ import (
var ( var (
testData = driver.ReadData() testData = driver.ReadData()
testStore = ".test_state"
sessionID string sessionID string
src = rand.NewSource(42) src = rand.NewSource(42)
g = rand.New(src) g = rand.New(src)
@@ -25,6 +25,11 @@ var (
var groupTestFile = flag.String("test-file", "group_test.json", "The test file to use for running the group tests") var groupTestFile = flag.String("test-file", "group_test.json", "The test file to use for running the group tests")
func testStore() string {
v, _ := filepath.Abs(".test_state/state.gdbm")
return v
}
func GenerateSessionId() string { func GenerateSessionId() string {
uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g)) uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g))
v, err := uu.NewV4() v, err := uu.NewV4()
@@ -81,8 +86,8 @@ func extractSendAmount(response []byte) string {
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
sessionID = GenerateSessionId() sessionID = GenerateSessionId()
defer func() { defer func() {
if err := os.RemoveAll(testStore); err != nil { if err := os.RemoveAll(testStore()); err != nil {
log.Fatalf("Failed to delete state store %s: %v", testStore, err) log.Fatalf("Failed to delete state store %s: %v", testStore(), err)
} }
}() }()
m.Run() m.Run()

65
request/request.go Normal file
View File

@@ -0,0 +1,65 @@
package request
import (
"context"
"io"
"net/http"
"strconv"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/storage"
)
var (
logg = logging.NewVanilla().WithDomain("visedriver.request")
)
type RequestSession struct {
Ctx context.Context
Config engine.Config
Engine engine.Engine
Input []byte
Storage *storage.Storage
Writer io.Writer
Continue bool
}
// TODO: seems like can remove this.
type RequestParser interface {
GetSessionId(ctx context.Context, rq any) (string, error)
GetInput(rq any) ([]byte, error)
}
type RequestHandler interface {
GetConfig() engine.Config
GetRequestParser() RequestParser
GetEngine(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine
Process(rs RequestSession) (RequestSession, error)
Output(rs RequestSession) (RequestSession, error)
Reset(rs RequestSession) (RequestSession, error)
Shutdown()
}
type SessionHandler struct {
RequestHandler
}
func (f *SessionHandler) WriteError(w http.ResponseWriter, code int, err error) {
s := err.Error()
w.Header().Set("Content-Length", strconv.Itoa(len(s)))
w.WriteHeader(code)
_, err = w.Write([]byte(s))
if err != nil {
logg.Errorf("error writing error!!", "err", err, "olderr", s)
w.WriteHeader(500)
}
}
func ToSessionHandler(h RequestHandler) *SessionHandler {
return &SessionHandler{
RequestHandler: h,
}
}

View 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/request"
)
// MockRequestHandler implements request.RequestHandler interface for testing
type MockRequestHandler struct {
ProcessFunc func(request.RequestSession) (request.RequestSession, error)
GetConfigFunc func() engine.Config
GetEngineFunc func(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine
OutputFunc func(rs request.RequestSession) (request.RequestSession, error)
ResetFunc func(rs request.RequestSession) (request.RequestSession, error)
ShutdownFunc func()
GetRequestParserFunc func() request.RequestParser
}
func (m *MockRequestHandler) Process(rqs request.RequestSession) (request.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 request.RequestSession) (request.RequestSession, error) {
return m.OutputFunc(rs)
}
func (m *MockRequestHandler) Reset(rs request.RequestSession) (request.RequestSession, error) {
return m.ResetFunc(rs)
}
func (m *MockRequestHandler) Shutdown() {
m.ShutdownFunc()
}
func (m *MockRequestHandler) GetRequestParser() request.RequestParser {
return m.GetRequestParserFunc()
}