Compare commits
No commits in common. "master" and "lash/admin-tool" have entirely different histories.
master
...
lash/admin
@ -1,16 +0,0 @@
|
||||
/**
|
||||
!/args
|
||||
!/cmd/africastalking
|
||||
!/cmd/ssh
|
||||
!/config
|
||||
!/debug
|
||||
!/handlers
|
||||
!/internal
|
||||
!/profile
|
||||
!/services
|
||||
!/ssh
|
||||
!/store
|
||||
!/LICENSE
|
||||
!/README.md
|
||||
!/go.*
|
||||
!/.env.example
|
11
.env.example
11
.env.example
@ -18,14 +18,3 @@ DATA_URL_BASE=http://localhost:5006
|
||||
#Language
|
||||
DEFAULT_LANGUAGE=eng
|
||||
LANGUAGES=eng, swa
|
||||
|
||||
#Alias search domains
|
||||
ALIAS_SEARCH_DOMAINS=sarafu.local, sarafu.eth
|
||||
|
||||
#Pool swap
|
||||
DEFAULT_POOL_NAME="Kenya ROLA Pool"
|
||||
DEFAULT_POOL_SYMBOL=ROLA
|
||||
DEFAULT_POOL_CONTRACT_ADDRESS=0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e
|
||||
DEFAULT_LIMITER_ADDRESS=
|
||||
DEFAULT_VOUCHER_REGISTRY=
|
||||
INCLUDE_STABLES_PARAM=false
|
||||
|
56
.github/workflows/docker.yaml
vendored
56
.github/workflows/docker.yaml
vendored
@ -1,56 +0,0 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Login to GHCR Docker registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set outputs
|
||||
run: |
|
||||
echo "RELEASE_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV \
|
||||
&& echo "RELEASE_SHORT_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
BUILD=${{ env.RELEASE_SHORT_COMMIT }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
tags: |
|
||||
ghcr.io/grassrootseconomics/sarafu-vise:latest
|
||||
ghcr.io/grassrootseconomics/sarafu-vise:${{ env.RELEASE_TAG }}
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,4 +7,3 @@ cmd/.state/
|
||||
id_*
|
||||
*.gdbm
|
||||
*.log
|
||||
user-data
|
||||
|
47
Dockerfile
47
Dockerfile
@ -1,47 +0,0 @@
|
||||
FROM golang:1.23.4-bookworm AS build
|
||||
|
||||
ENV CGO_ENABLED=1
|
||||
|
||||
ARG BUILDPLATFORM
|
||||
ARG TARGETPLATFORM
|
||||
ARG BUILD=dev
|
||||
|
||||
WORKDIR /build
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libgdbm-dev \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN git clone https://git.defalsify.org/vise.git go-vise
|
||||
COPY . ./sarafu-vise
|
||||
|
||||
WORKDIR /build/sarafu-vise/services/registration
|
||||
RUN echo "Compiling go-vise files"
|
||||
RUN make VISE_PATH=/build/go-vise -B
|
||||
|
||||
WORKDIR /build/sarafu-vise
|
||||
RUN echo "Building on $BUILDPLATFORM, building for $TARGETPLATFORM"
|
||||
RUN go mod download
|
||||
RUN go build -tags logdebug,online -o sarafu-at -ldflags="-X main.build=${BUILD} -s -w" cmd/africastalking/main.go
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libgdbm-dev \
|
||||
ca-certificates \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /service
|
||||
|
||||
COPY --from=build /build/sarafu-vise/sarafu-at .
|
||||
COPY --from=build /build/sarafu-vise/LICENSE .
|
||||
COPY --from=build /build/sarafu-vise/README.md .
|
||||
COPY --from=build /build/sarafu-vise/services ./services
|
||||
COPY --from=build /build/sarafu-vise/.env.example .
|
||||
RUN mv .env.example .env
|
||||
|
||||
EXPOSE 7123
|
||||
|
||||
CMD ["./sarafu-at"]
|
10
args/lang.go
10
args/lang.go
@ -10,7 +10,7 @@ type LangVar struct {
|
||||
v []lang.Language
|
||||
}
|
||||
|
||||
func (lv *LangVar) Set(s string) error {
|
||||
func(lv *LangVar) Set(s string) error {
|
||||
v, err := lang.LanguageFromCode(s)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -19,14 +19,16 @@ func (lv *LangVar) Set(s string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (lv *LangVar) String() string {
|
||||
func(lv *LangVar) String() string {
|
||||
var s []string
|
||||
for _, v := range lv.v {
|
||||
for _, v := range(lv.v) {
|
||||
s = append(s, v.Code)
|
||||
}
|
||||
return strings.Join(s, ",")
|
||||
}
|
||||
|
||||
func (lv *LangVar) Langs() []lang.Language {
|
||||
func(lv *LangVar) Langs() []lang.Language {
|
||||
return lv.v
|
||||
}
|
||||
|
||||
|
||||
|
@ -12,18 +12,18 @@ import (
|
||||
"syscall"
|
||||
|
||||
"git.defalsify.org/vise.git/engine"
|
||||
"git.defalsify.org/vise.git/lang"
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
"git.defalsify.org/vise.git/lang"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
||||
|
||||
at "git.grassecon.net/grassrootseconomics/visedriver-africastalking/africastalking"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/services"
|
||||
at "git.grassecon.net/grassrootseconomics/visedriver-africastalking/africastalking"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -36,7 +36,8 @@ var (
|
||||
func main() {
|
||||
config.LoadConfig()
|
||||
|
||||
override := config.NewOverride()
|
||||
var connStr string
|
||||
var resourceDir string
|
||||
var size uint
|
||||
var engineDebug bool
|
||||
var host string
|
||||
@ -44,29 +45,28 @@ func main() {
|
||||
var err error
|
||||
var gettextDir string
|
||||
var langs args.LangVar
|
||||
var logDbConnStr string
|
||||
|
||||
|
||||
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
||||
flag.StringVar(&connStr, "c", "", "connection string")
|
||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||
flag.StringVar(&override.ResourceConn, "resource", "?", "resource data directory")
|
||||
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||
flag.UintVar(&size, "s", 160, "max size of output")
|
||||
flag.StringVar(&host, "h", config.Host(), "http host")
|
||||
flag.UintVar(&port, "p", config.Port(), "http port")
|
||||
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
|
||||
flag.Var(&langs, "language", "add symbol resolution for language")
|
||||
flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string")
|
||||
flag.Parse()
|
||||
|
||||
config.Apply(override)
|
||||
conns, err := config.GetConns()
|
||||
if connStr == "" {
|
||||
connStr = config.DbConn()
|
||||
}
|
||||
connData, err := storage.ToConnData(connStr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logg.Infof("start command", "build", build, "conn", conns, "outputsize", size)
|
||||
logg.Infof("start command", "build", build, "conn", connData, "resourcedir", resourceDir, "outputsize", size)
|
||||
|
||||
ctx := context.Background()
|
||||
ln, err := lang.LanguageFromCode(config.Language())
|
||||
@ -83,14 +83,13 @@ func main() {
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
MenuSeparator: menuSeparator,
|
||||
ResetOnEmptyInput: true,
|
||||
}
|
||||
|
||||
if engineDebug {
|
||||
cfg.EngineDebug = true
|
||||
}
|
||||
|
||||
menuStorageService := storage.NewMenuStorageService(conns)
|
||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
||||
rs, err := menuStorageService.GetResource(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "menustorageservice: %v\n", err)
|
||||
@ -102,11 +101,7 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, "userdatadb: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
logdb, err := menuStorageService.GetLogDb(ctx, userdataStore, logDbConnStr, "user-data")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "get log db error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer userdataStore.Close()
|
||||
|
||||
dbResource, ok := rs.(*resource.DbResource)
|
||||
if !ok {
|
||||
@ -120,14 +115,13 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
lhs.SetDataStore(&userdataStore)
|
||||
lhs.SetLogDb(&logdb)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "setdatastore: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
accountService := services.New(ctx, menuStorageService)
|
||||
|
||||
accountService := services.New(ctx, menuStorageService, connData)
|
||||
|
||||
hl, err := lhs.GetHandler(accountService)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "httpaccountservice: %v\n", err)
|
||||
@ -139,10 +133,10 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, "getstatestore: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer stateStore.Close()
|
||||
|
||||
rp := &at.ATRequestParser{}
|
||||
bsh := request.NewBaseRequestHandler(cfg, rs, stateStore, userdataStore, rp, hl)
|
||||
bsh = bsh.WithEngineFunc(lhs.GetEngine)
|
||||
sh := at.NewATRequestHandler(bsh)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
@ -152,10 +146,7 @@ func main() {
|
||||
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
|
||||
Handler: mux,
|
||||
}
|
||||
shutdownFunc := func() {
|
||||
sh.Shutdown(ctx)
|
||||
}
|
||||
s.RegisterOnShutdown(shutdownFunc)
|
||||
s.RegisterOnShutdown(sh.Shutdown)
|
||||
|
||||
cint := make(chan os.Signal)
|
||||
cterm := make(chan os.Signal)
|
||||
|
@ -1,15 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"git.defalsify.org/vise.git/engine"
|
||||
@ -17,12 +14,12 @@ import (
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/services"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/services"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -47,8 +44,9 @@ func (p *asyncRequestParser) GetInput(r any) ([]byte, error) {
|
||||
func main() {
|
||||
config.LoadConfig()
|
||||
|
||||
override := config.NewOverride()
|
||||
var connStr string
|
||||
var sessionId string
|
||||
var resourceDir string
|
||||
var size uint
|
||||
var engineDebug bool
|
||||
var host string
|
||||
@ -56,31 +54,28 @@ func main() {
|
||||
var err error
|
||||
var gettextDir string
|
||||
var langs args.LangVar
|
||||
var logDbConnStr string
|
||||
|
||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||
flag.StringVar(&override.ResourceConn, "resource", "?", "resource data directory")
|
||||
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||
|
||||
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
||||
flag.StringVar(&connStr, "c", "", "connection string")
|
||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||
flag.UintVar(&size, "s", 160, "max size of output")
|
||||
flag.StringVar(&host, "h", config.Host(), "http host")
|
||||
flag.UintVar(&port, "p", config.Port(), "http port")
|
||||
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
|
||||
flag.Var(&langs, "language", "add symbol resolution for language")
|
||||
flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string")
|
||||
flag.Parse()
|
||||
|
||||
config.Apply(override)
|
||||
conns, err := config.GetConns()
|
||||
if connStr == "" {
|
||||
connStr = config.DbConn()
|
||||
}
|
||||
connData, err := storage.ToConnData(connStr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logg.Infof("start command", "conn", conns, "outputsize", size, "sessionId", sessionId)
|
||||
logg.Infof("start command", "conn", connData, "resourcedir", resourceDir, "outputsize", size, "sessionId", sessionId)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@ -98,14 +93,13 @@ func main() {
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
MenuSeparator: menuSeparator,
|
||||
ResetOnEmptyInput: true,
|
||||
}
|
||||
|
||||
if engineDebug {
|
||||
cfg.EngineDebug = true
|
||||
}
|
||||
|
||||
menuStorageService := storage.NewMenuStorageService(conns)
|
||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
@ -122,13 +116,7 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logdb, err := menuStorageService.GetLogDb(ctx, userdataStore, logDbConnStr, "user-data")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "get log db error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
//defer userdataStore.Close(ctx)
|
||||
defer userdataStore.Close()
|
||||
|
||||
dbResource, ok := rs.(*resource.DbResource)
|
||||
if !ok {
|
||||
@ -137,9 +125,9 @@ func main() {
|
||||
|
||||
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
|
||||
lhs.SetDataStore(&userdataStore)
|
||||
lhs.SetLogDb(&logdb)
|
||||
|
||||
accountService := services.New(ctx, menuStorageService)
|
||||
accountService := services.New(ctx, menuStorageService, connData)
|
||||
|
||||
hl, err := lhs.GetHandler(accountService)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
@ -151,14 +139,12 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
//defer stateStore.Close(ctx)
|
||||
defer stateStore.Close()
|
||||
|
||||
rp := &asyncRequestParser{
|
||||
sessionId: sessionId,
|
||||
}
|
||||
sh := request.NewBaseRequestHandler(cfg, rs, stateStore, userdataStore, rp, hl)
|
||||
sh = sh.WithEngineFunc(lhs.GetEngine)
|
||||
|
||||
cfg.SessionId = sessionId
|
||||
rqs := request.RequestSession{
|
||||
Ctx: ctx,
|
||||
@ -175,7 +161,7 @@ func main() {
|
||||
case _ = <-cint:
|
||||
case _ = <-cterm:
|
||||
}
|
||||
sh.Shutdown(ctx)
|
||||
sh.Shutdown()
|
||||
}()
|
||||
|
||||
for true {
|
||||
@ -191,26 +177,18 @@ func main() {
|
||||
fmt.Errorf("error in output: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
rqs, err = sh.Reset(ctx, rqs)
|
||||
rqs, err = sh.Reset(rqs)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "error in reset: %v", "err", err)
|
||||
fmt.Errorf("error in reset: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("")
|
||||
in := bufio.NewReader(os.Stdin)
|
||||
s, err := in.ReadString('\n')
|
||||
_, err = fmt.Scanln(&rqs.Input)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
logg.DebugCtxf(ctx, "have EOF, bailing")
|
||||
break
|
||||
}
|
||||
logg.ErrorCtxf(ctx, "error in input", "err", err)
|
||||
fmt.Errorf("error in input: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
rqs.Input = []byte{}
|
||||
s = strings.TrimSpace(s)
|
||||
rqs.Input = []byte(s)
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,13 @@ import (
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
||||
httprequest "git.grassecon.net/grassrootseconomics/visedriver/request/http"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/services"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/services"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -35,7 +35,8 @@ var (
|
||||
func main() {
|
||||
config.LoadConfig()
|
||||
|
||||
override := config.NewOverride()
|
||||
var connStr string
|
||||
var resourceDir string
|
||||
var size uint
|
||||
var engineDebug bool
|
||||
var host string
|
||||
@ -43,30 +44,27 @@ func main() {
|
||||
var err error
|
||||
var gettextDir string
|
||||
var langs args.LangVar
|
||||
var logDbConnStr string
|
||||
|
||||
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||
flag.StringVar(&override.ResourceConn, "resource", "?", "resource data directory")
|
||||
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||
|
||||
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
||||
flag.StringVar(&connStr, "c", "", "connection string")
|
||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||
flag.UintVar(&size, "s", 160, "max size of output")
|
||||
flag.StringVar(&host, "h", config.Host(), "http host")
|
||||
flag.UintVar(&port, "p", config.Port(), "http port")
|
||||
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
|
||||
flag.Var(&langs, "language", "add symbol resolution for language")
|
||||
flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string")
|
||||
flag.Parse()
|
||||
|
||||
config.Apply(override)
|
||||
conns, err := config.GetConns()
|
||||
if connStr == "" {
|
||||
connStr = config.DbConn()
|
||||
}
|
||||
connData, err := storage.ToConnData(connStr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logg.Infof("start command", "conn", conns, "outputsize", size)
|
||||
logg.Infof("start command", "conn", connData, "resourcedir", resourceDir, "outputsize", size)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@ -84,14 +82,13 @@ func main() {
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
MenuSeparator: menuSeparator,
|
||||
ResetOnEmptyInput: true,
|
||||
}
|
||||
|
||||
if engineDebug {
|
||||
cfg.EngineDebug = true
|
||||
}
|
||||
|
||||
menuStorageService := storage.NewMenuStorageService(conns)
|
||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
||||
|
||||
rs, err := menuStorageService.GetResource(ctx)
|
||||
if err != nil {
|
||||
@ -104,12 +101,7 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logdb, err := menuStorageService.GetLogDb(ctx, userdataStore, logDbConnStr, "user-data")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "get log db error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer userdataStore.Close()
|
||||
|
||||
dbResource, ok := rs.(*resource.DbResource)
|
||||
if !ok {
|
||||
@ -118,15 +110,14 @@ func main() {
|
||||
|
||||
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
|
||||
lhs.SetDataStore(&userdataStore)
|
||||
lhs.SetLogDb(&logdb)
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
accountService := services.New(ctx, menuStorageService)
|
||||
|
||||
accountService := services.New(ctx, menuStorageService, connData)
|
||||
|
||||
hl, err := lhs.GetHandler(accountService)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
@ -138,21 +129,18 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
defer stateStore.Close()
|
||||
|
||||
//accountService := services.New(ctx, menuStorageService, connData)
|
||||
|
||||
rp := &httprequest.DefaultRequestParser{}
|
||||
bsh := request.NewBaseRequestHandler(cfg, rs, stateStore, userdataStore, rp, hl)
|
||||
bsh = bsh.WithEngineFunc(lhs.GetEngine)
|
||||
sh := httprequest.NewHTTPRequestHandler(bsh)
|
||||
s := &http.Server{
|
||||
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
|
||||
Handler: sh,
|
||||
}
|
||||
shutdownFunc := func() {
|
||||
sh.Shutdown(ctx)
|
||||
}
|
||||
s.RegisterOnShutdown(shutdownFunc)
|
||||
s.RegisterOnShutdown(sh.Shutdown)
|
||||
|
||||
cint := make(chan os.Signal)
|
||||
cterm := make(chan os.Signal)
|
||||
|
79
cmd/main.go
79
cmd/main.go
@ -5,19 +5,17 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"syscall"
|
||||
|
||||
"git.defalsify.org/vise.git/engine"
|
||||
"git.defalsify.org/vise.git/lang"
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
||||
"git.defalsify.org/vise.git/lang"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/services"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/services"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -29,41 +27,41 @@ var (
|
||||
func main() {
|
||||
config.LoadConfig()
|
||||
|
||||
override := config.NewOverride()
|
||||
var connStr string
|
||||
var size uint
|
||||
var sessionId string
|
||||
var engineDebug bool
|
||||
var resourceDir string
|
||||
var err error
|
||||
var gettextDir string
|
||||
var langs args.LangVar
|
||||
var logDbConnStr string
|
||||
|
||||
flag.StringVar(&resourceDir, "resourcedir", scriptDir, "resource dir")
|
||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||
flag.StringVar(&override.ResourceConn, "resource", "?", "resource data directory")
|
||||
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||
flag.StringVar(&connStr, "c", "", "connection string")
|
||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||
flag.UintVar(&size, "s", 160, "max size of output")
|
||||
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
|
||||
flag.Var(&langs, "language", "add symbol resolution for language")
|
||||
flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string")
|
||||
flag.Parse()
|
||||
|
||||
config.Apply(override)
|
||||
conns, err := config.GetConns()
|
||||
if connStr == "" {
|
||||
connStr = config.DbConn()
|
||||
}
|
||||
connData, err := storage.ToConnData(connStr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "connstr err: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logg.Infof("start command", "conn", conns, "outputsize", size)
|
||||
logg.Infof("start command", "conn", connData, "outputsize", size)
|
||||
|
||||
if len(langs.Langs()) == 0 {
|
||||
langs.Set(config.Language())
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
ln, err := lang.LanguageFromCode(config.Language())
|
||||
if err != nil {
|
||||
@ -75,21 +73,19 @@ func main() {
|
||||
pfp := path.Join(scriptDir, "pp.csv")
|
||||
|
||||
cfg := engine.Config{
|
||||
Root: "root",
|
||||
SessionId: sessionId,
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
MenuSeparator: menuSeparator,
|
||||
EngineDebug: engineDebug,
|
||||
ResetOnEmptyInput: true,
|
||||
Root: "root",
|
||||
SessionId: sessionId,
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
MenuSeparator: menuSeparator,
|
||||
}
|
||||
|
||||
menuStorageService := storage.NewMenuStorageService(conns)
|
||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "menu storage service error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
if gettextDir != "" {
|
||||
menuStorageService = menuStorageService.WithGettext(gettextDir, langs.Langs())
|
||||
}
|
||||
@ -112,12 +108,6 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logdb, err := menuStorageService.GetLogDb(ctx, userdatastore, logDbConnStr, "user-data")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "get log db error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
dbResource, ok := rs.(*resource.DbResource)
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "get dbresource error: %v\n", err)
|
||||
@ -126,35 +116,24 @@ func main() {
|
||||
|
||||
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
|
||||
lhs.SetDataStore(&userdatastore)
|
||||
lhs.SetLogDb(&logdb)
|
||||
lhs.SetPersister(pe)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "localhandler service error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
accountService := services.New(ctx, menuStorageService)
|
||||
_, err = lhs.GetHandler(accountService)
|
||||
accountService := services.New(ctx, menuStorageService, connData)
|
||||
hl, err := lhs.GetHandler(accountService)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "get accounts service handler: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
en := lhs.GetEngine(cfg, rs, pe)
|
||||
|
||||
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() {
|
||||
var s os.Signal
|
||||
select {
|
||||
case s = <-cterm:
|
||||
case s = <-cint:
|
||||
}
|
||||
logg.InfoCtxf(ctx, "stopping on signal", "sig", s)
|
||||
en.Finish(ctx)
|
||||
os.Exit(0)
|
||||
}()
|
||||
en := lhs.GetEngine()
|
||||
en = en.WithFirst(hl.Init)
|
||||
if engineDebug {
|
||||
en = en.WithDebug(nil)
|
||||
}
|
||||
|
||||
err = engine.Loop(ctx, en, os.Stdin, os.Stdout, nil)
|
||||
if err != nil {
|
||||
|
@ -31,31 +31,34 @@ var (
|
||||
func main() {
|
||||
config.LoadConfig()
|
||||
|
||||
override := config.NewOverride()
|
||||
var connStr string
|
||||
var authConnStr string
|
||||
var resourceDir string
|
||||
var size uint
|
||||
var engineDebug bool
|
||||
var stateDebug bool
|
||||
var host string
|
||||
var port uint
|
||||
|
||||
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||
flag.StringVar(&override.ResourceConn, "resource", "?", "resource connection string")
|
||||
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||
flag.StringVar(&connStr, "c", "", "connection string")
|
||||
flag.StringVar(&authConnStr, "authdb", "", "auth connection string")
|
||||
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||
flag.UintVar(&size, "s", 160, "max size of output")
|
||||
flag.StringVar(&host, "h", config.HostSSH(), "socket host")
|
||||
flag.UintVar(&port, "p", config.PortSSH(), "socket port")
|
||||
flag.Parse()
|
||||
|
||||
config.Apply(override)
|
||||
conns, err := config.GetConns()
|
||||
if connStr == "" {
|
||||
connStr = config.DbConn()
|
||||
}
|
||||
if authConnStr == "" {
|
||||
authConnStr = connStr
|
||||
}
|
||||
connData, err := storage.ToConnData(connStr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
authConnData, err := storage.ToConnData(authConnStr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "auth connstr err: %v", err)
|
||||
@ -76,7 +79,7 @@ func main() {
|
||||
logg.WarnCtxf(ctx, "!!!!! Do not expose to internet and only use with tunnel!")
|
||||
logg.WarnCtxf(ctx, "!!!!! (See ssh -L <...>)")
|
||||
|
||||
logg.Infof("start command", "conn", conns, "authconn", authConnData, "outputsize", size, "keyfile", sshKeyFile, "host", host, "port", port)
|
||||
logg.Infof("start command", "conn", connData, "authconn", authConnData, "resourcedir", resourceDir, "outputsize", size, "keyfile", sshKeyFile, "host", host, "port", port)
|
||||
|
||||
pfp := path.Join(scriptDir, "pp.csv")
|
||||
|
||||
@ -84,7 +87,6 @@ func main() {
|
||||
Root: "root",
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
ResetOnEmptyInput: true,
|
||||
}
|
||||
if stateDebug {
|
||||
cfg.StateDebug = true
|
||||
@ -100,7 +102,7 @@ func main() {
|
||||
}
|
||||
defer func() {
|
||||
logg.TraceCtxf(ctx, "shutdown auth key store reached")
|
||||
err = authKeyStore.Close(ctx)
|
||||
err = authKeyStore.Close()
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "keystore close error", "err", err)
|
||||
}
|
||||
@ -112,13 +114,14 @@ func main() {
|
||||
signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
runner := &ssh.SshRunner{
|
||||
Cfg: cfg,
|
||||
Debug: engineDebug,
|
||||
FlagFile: pfp,
|
||||
Conn: conns,
|
||||
SrvKeyFile: sshKeyFile,
|
||||
Host: host,
|
||||
Port: port,
|
||||
Cfg: cfg,
|
||||
Debug: engineDebug,
|
||||
FlagFile: pfp,
|
||||
Conn: connData,
|
||||
ResourceDir: resourceDir,
|
||||
SrvKeyFile: sshKeyFile,
|
||||
Host: host,
|
||||
Port: port,
|
||||
}
|
||||
go func() {
|
||||
select {
|
||||
|
@ -34,7 +34,7 @@ func main() {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer store.Close(ctx)
|
||||
defer store.Close()
|
||||
|
||||
err = store.AddFromFile(ctx, sshKeyFile, sessionId)
|
||||
if err != nil {
|
||||
|
@ -1,20 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
viseconfig "git.grassecon.net/grassrootseconomics/visedriver/config"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||
)
|
||||
|
||||
func NewOverride() *viseconfig.Override {
|
||||
o := &viseconfig.Override{
|
||||
StateConnMode: storage.DBMODE_TEXT,
|
||||
ResourceConnMode: storage.DBMODE_TEXT,
|
||||
UserConnMode: storage.DBMODE_BINARY,
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func Apply(o *viseconfig.Override) error {
|
||||
viseconfig.ApplyConn(o)
|
||||
return nil
|
||||
}
|
@ -1,36 +1,24 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
apiconfig "git.grassecon.net/grassrootseconomics/sarafu-api/config"
|
||||
viseconfig "git.grassecon.net/grassrootseconomics/visedriver/config"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/env"
|
||||
viseconfig "git.grassecon.net/grassrootseconomics/visedriver/config"
|
||||
apiconfig "git.grassecon.net/grassrootseconomics/sarafu-api/config"
|
||||
)
|
||||
|
||||
var (
|
||||
GetConns = viseconfig.GetConns
|
||||
EnvPath string
|
||||
)
|
||||
|
||||
func loadEnv() {
|
||||
if EnvPath == "" {
|
||||
env.LoadEnvVariables()
|
||||
} else {
|
||||
env.LoadEnvVariablesPath(EnvPath)
|
||||
}
|
||||
func init() {
|
||||
env.LoadEnvVariables()
|
||||
}
|
||||
|
||||
const (
|
||||
defaultSSHHost string = "127.0.0.1"
|
||||
defaultSSHPort uint = 7122
|
||||
defaultSSHHost string = "127.0.0.1"
|
||||
defaultSSHPort uint = 7122
|
||||
defaultHTTPHost string = "127.0.0.1"
|
||||
defaultHTTPPort uint = 7123
|
||||
defaultDomain = "sarafu.local"
|
||||
defaultHTTPPort uint = 7123
|
||||
)
|
||||
|
||||
|
||||
func LoadConfig() error {
|
||||
loadEnv()
|
||||
err := viseconfig.LoadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -42,14 +30,8 @@ func LoadConfig() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func SearchDomains() []string {
|
||||
var ParsedDomains []string
|
||||
SearchDomains := env.GetEnv("ALIAS_SEARCH_DOMAINS", defaultDomain)
|
||||
SearchDomainList := strings.Split(env.GetEnv("ALIAS_SEARCH_DOMAINS", SearchDomains), ",")
|
||||
for _, domain := range SearchDomainList {
|
||||
ParsedDomains = append(ParsedDomains, strings.ReplaceAll(domain, " ", ""))
|
||||
}
|
||||
return ParsedDomains
|
||||
func DbConn() string {
|
||||
return viseconfig.DbConn
|
||||
}
|
||||
|
||||
func Language() string {
|
||||
@ -75,15 +57,3 @@ func PortSSH() uint {
|
||||
func ATEndpoint() string {
|
||||
return env.GetEnv("AT_ENDPOINT", "/")
|
||||
}
|
||||
|
||||
func DefaultPoolAddress() string {
|
||||
return env.GetEnv("DEFAULT_POOL_CONTRACT_ADDRESS", "")
|
||||
}
|
||||
|
||||
func DefaultPoolName() string {
|
||||
return env.GetEnv("DEFAULT_POOL_NAME", "")
|
||||
}
|
||||
|
||||
func DefaultPoolSymbol() string {
|
||||
return env.GetEnv("DEFAULT_POOL_SYMBOL", "")
|
||||
}
|
||||
|
41
debug/db.go
41
debug/db.go
@ -1,11 +1,11 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"encoding/binary"
|
||||
|
||||
visedb "git.defalsify.org/vise.git/db"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
visedb "git.defalsify.org/vise.git/db"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -13,10 +13,10 @@ var (
|
||||
)
|
||||
|
||||
type KeyInfo struct {
|
||||
SessionId string
|
||||
Typ uint8
|
||||
SubTyp storedb.DataTyp
|
||||
Label string
|
||||
SessionId string
|
||||
Typ uint8
|
||||
SubTyp storedb.DataTyp
|
||||
Label string
|
||||
Description string
|
||||
}
|
||||
|
||||
@ -32,10 +32,18 @@ func (k KeyInfo) String() string {
|
||||
|
||||
func ToKeyInfo(k []byte, sessionId string) (KeyInfo, error) {
|
||||
o := KeyInfo{}
|
||||
b := []byte(sessionId)
|
||||
|
||||
if len(k) <= len(b) {
|
||||
return o, fmt.Errorf("storage key missing")
|
||||
}
|
||||
|
||||
o.SessionId = sessionId
|
||||
|
||||
o.Typ = uint8(k[0])
|
||||
k = k[1:]
|
||||
o.SessionId = string(k[:len(b)])
|
||||
k = k[len(b):]
|
||||
|
||||
if o.Typ == visedb.DATATYPE_USERDATA {
|
||||
if len(k) == 0 {
|
||||
@ -45,19 +53,30 @@ func ToKeyInfo(k []byte, sessionId string) (KeyInfo, error) {
|
||||
o.SubTyp = storedb.DataTyp(v)
|
||||
o.Label = subTypToString(o.SubTyp)
|
||||
k = k[2:]
|
||||
if len(k) != 0 {
|
||||
return o, fmt.Errorf("excess key information: %x", k)
|
||||
}
|
||||
} else {
|
||||
o.Label = typToString(o.Typ)
|
||||
k = k[2:]
|
||||
}
|
||||
|
||||
if len(k) != 0 {
|
||||
return o, fmt.Errorf("excess key information")
|
||||
}
|
||||
|
||||
return o, nil
|
||||
}
|
||||
|
||||
func FromKey(k []byte) (KeyInfo, error) {
|
||||
o := KeyInfo{}
|
||||
|
||||
if len(k) < 4 {
|
||||
return o, fmt.Errorf("insufficient key length")
|
||||
}
|
||||
|
||||
sessionIdBytes := k[1:len(k)-2]
|
||||
return ToKeyInfo(k, string(sessionIdBytes))
|
||||
}
|
||||
|
||||
func subTypToString(v storedb.DataTyp) string {
|
||||
return dbTypStr[v+visedb.DATATYPE_USERDATA+1]
|
||||
return dbTypStr[v + visedb.DATATYPE_USERDATA + 1]
|
||||
}
|
||||
|
||||
func typToString(v uint8) string {
|
||||
|
@ -1,4 +1,3 @@
|
||||
//go:build debugdb
|
||||
// +build debugdb
|
||||
|
||||
package debug
|
||||
@ -12,37 +11,34 @@ import (
|
||||
func init() {
|
||||
DebugCap |= 1
|
||||
dbTypStr[db.DATATYPE_STATE] = "internal state"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TRACKING_ID] = "tracking id"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_PUBLIC_KEY] = "public key"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_ACCOUNT_PIN] = "account pin"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_FIRST_NAME] = "first name"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_FAMILY_NAME] = "family name"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_YOB] = "year of birth"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_LOCATION] = "location"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_GENDER] = "gender"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_OFFERINGS] = "offerings"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_RECIPIENT] = "recipient"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_AMOUNT] = "amount"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TEMPORARY_VALUE] = "temporary value"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_ACTIVE_SYM] = "active sym"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_ACTIVE_BAL] = "active bal"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_BLOCKED_NUMBER] = "blocked number"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_PUBLIC_KEY_REVERSE] = "public_key_reverse"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_ACTIVE_DECIMAL] = "active decimal"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_ACTIVE_ADDRESS] = "active address"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_INCORRECT_PIN_ATTEMPTS] = "incorrect pin attempts"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_SELECTED_LANGUAGE_CODE] = "selected language"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_INITIAL_LANGUAGE_CODE] = "initial language"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_VOUCHER_SYMBOLS] = "voucher symbols"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_VOUCHER_BALANCES] = "voucher balances"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_VOUCHER_DECIMALS] = "voucher decimals"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_VOUCHER_ADDRESSES] = "voucher addresses"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_SENDERS] = "tx senders"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_RECIPIENTS] = "tx recipients"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_VALUES] = "tx values"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_ADDRESSES] = "tx addresses"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_HASHES] = "tx hashes"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_DATES] = "tx dates"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_SYMBOLS] = "tx symbols"
|
||||
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_DECIMALS] = "tx decimals"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TRACKING_ID] = "tracking id"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_PUBLIC_KEY] = "public key"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_ACCOUNT_PIN] = "account pin"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_FIRST_NAME] = "first name"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_FAMILY_NAME] = "family name"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_YOB] = "year of birth"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_LOCATION] = "location"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_GENDER] = "gender"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_OFFERINGS] = "offerings"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_RECIPIENT] = "recipient"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_AMOUNT] = "amount"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TEMPORARY_VALUE] = "temporary value"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_ACTIVE_SYM] = "active sym"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_ACTIVE_BAL] = "active bal"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_BLOCKED_NUMBER] = "blocked number"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_PUBLIC_KEY_REVERSE] = "public_key_reverse"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_ACTIVE_DECIMAL] = "active decimal"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_ACTIVE_ADDRESS] = "active address"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_VOUCHER_SYMBOLS] = "voucher symbols"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_VOUCHER_BALANCES] = "voucher balances"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_VOUCHER_DECIMALS] = "voucher decimals"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_VOUCHER_ADDRESSES] = "voucher addresses"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_SENDERS] = "tx senders"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_RECIPIENTS] = "tx recipients"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_VALUES] = "tx values"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_ADDRESSES] = "tx addresses"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_HASHES] = "tx hashes"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_DATES] = "tx dates"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_SYMBOLS] = "tx symbols"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_DECIMALS] = "tx decimals"
|
||||
}
|
||||
|
@ -3,13 +3,14 @@ package debug
|
||||
import (
|
||||
"testing"
|
||||
|
||||
visedb "git.defalsify.org/vise.git/db"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
visedb "git.defalsify.org/vise.git/db"
|
||||
)
|
||||
|
||||
func TestDebugDbSubKeyInfo(t *testing.T) {
|
||||
s := "foo"
|
||||
b := []byte{0x20}
|
||||
b = append(b, []byte(s)...)
|
||||
b = append(b, []byte{0x00, 0x02}...)
|
||||
r, err := ToKeyInfo(b, s)
|
||||
if err != nil {
|
||||
@ -24,7 +25,7 @@ func TestDebugDbSubKeyInfo(t *testing.T) {
|
||||
if r.SubTyp != 2 {
|
||||
t.Fatalf("expected 2, got %d", r.SubTyp)
|
||||
}
|
||||
if DebugCap&1 > 0 {
|
||||
if DebugCap & 1 > 0 {
|
||||
if r.Label != "tracking id" {
|
||||
t.Fatalf("expected 'tracking id', got '%s'", r.Label)
|
||||
}
|
||||
@ -45,7 +46,7 @@ func TestDebugDbKeyInfo(t *testing.T) {
|
||||
if r.Typ != 16 {
|
||||
t.Fatalf("expected 16, got %d", r.Typ)
|
||||
}
|
||||
if DebugCap&1 > 0 {
|
||||
if DebugCap & 1 > 0 {
|
||||
if r.Label != "internal state" {
|
||||
t.Fatalf("expected 'internal_state', got '%s'", r.Label)
|
||||
}
|
||||
@ -55,6 +56,7 @@ func TestDebugDbKeyInfo(t *testing.T) {
|
||||
func TestDebugDbKeyInfoRestore(t *testing.T) {
|
||||
s := "bar"
|
||||
b := []byte{visedb.DATATYPE_USERDATA}
|
||||
b = append(b, []byte(s)...)
|
||||
k := storedb.ToBytes(storedb.DATA_ACTIVE_SYM)
|
||||
b = append(b, k...)
|
||||
|
||||
@ -68,7 +70,7 @@ func TestDebugDbKeyInfoRestore(t *testing.T) {
|
||||
if r.Typ != 32 {
|
||||
t.Fatalf("expected 32, got %d", r.Typ)
|
||||
}
|
||||
if DebugCap&1 > 0 {
|
||||
if DebugCap & 1 > 0 {
|
||||
if r.Label != "active sym" {
|
||||
t.Fatalf("expected 'active sym', got '%s'", r.Label)
|
||||
}
|
||||
|
@ -10,38 +10,37 @@ import (
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/application"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/internal/cmd"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/internal/cmd"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/application"
|
||||
)
|
||||
|
||||
var (
|
||||
logg = logging.NewVanilla().WithContextKey("SessionId")
|
||||
scriptDir = path.Join("services", "registration")
|
||||
logg = logging.NewVanilla().WithContextKey("SessionId")
|
||||
scriptDir = path.Join("services", "registration")
|
||||
)
|
||||
|
||||
func main() {
|
||||
config.LoadConfig()
|
||||
|
||||
override := config.NewOverride()
|
||||
var sessionId string
|
||||
var connStr string
|
||||
|
||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||
flag.StringVar(&override.ResourceConn, "resource", "?", "resource data directory")
|
||||
|
||||
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||
flag.StringVar(&connStr, "c", "", "connection string")
|
||||
flag.Parse()
|
||||
|
||||
config.Apply(override)
|
||||
conns, err := config.GetConns()
|
||||
|
||||
if connStr == "" {
|
||||
connStr = config.DbConn()
|
||||
}
|
||||
connData, err := storage.ToConnData(connStr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "connstr err: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
pfp := path.Join(scriptDir, "pp.csv")
|
||||
flagParser, err := application.NewFlagManager(pfp)
|
||||
@ -50,16 +49,16 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
x := cmd.NewCmd(sessionId, flagParser)
|
||||
x := cmd.NewCmd(connData, sessionId, flagParser)
|
||||
err = x.Parse(flag.Args())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cmd parse fail: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logg.Infof("start command", "conn", conns, "subcmd", x)
|
||||
logg.Infof("start command", "conn", connData, "subcmd", x)
|
||||
|
||||
menuStorageService := storage.NewMenuStorageService(conns)
|
||||
menuStorageService := storage.NewMenuStorageService(connData, "")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "menu storage service error: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -8,17 +8,18 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"git.defalsify.org/vise.git/lang"
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
"git.defalsify.org/vise.git/lang"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/config"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
changeHeadSrc = `LOAD reset_account_authorized 0
|
||||
LOAD reset_incorrect_pin 0
|
||||
LOAD reset_incorrect 0
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH pin_entry flag_account_authorized 0
|
||||
`
|
||||
`
|
||||
|
||||
selectSrc = `LOAD set_language 6
|
||||
RELOAD set_language
|
||||
@ -28,8 +29,8 @@ MOVE language_changed
|
||||
)
|
||||
|
||||
var (
|
||||
logg = logging.NewVanilla()
|
||||
mouts string
|
||||
logg = logging.NewVanilla()
|
||||
mouts string
|
||||
incmps string
|
||||
)
|
||||
|
||||
@ -62,7 +63,7 @@ func main() {
|
||||
}
|
||||
logg.Tracef("using languages", "lang", config.Languages)
|
||||
|
||||
for i, v := range config.Languages {
|
||||
for i, v := range(config.Languages) {
|
||||
ln, err := lang.LanguageFromCode(v)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error parsing language: %s\n", v)
|
||||
@ -75,7 +76,7 @@ func main() {
|
||||
incmps += fmt.Sprintf("INCMP %s %v\n", v, n)
|
||||
|
||||
p := path.Join(srcDir, v)
|
||||
w, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||
w, err := os.OpenFile(p, os.O_WRONLY | os.O_CREATE | os.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed open language set template output: %v\n", err)
|
||||
os.Exit(1)
|
||||
@ -92,7 +93,7 @@ func main() {
|
||||
src += "INCMP . *\n"
|
||||
|
||||
p := path.Join(srcDir, "select_language.vis")
|
||||
w, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||
w, err := os.OpenFile(p, os.O_WRONLY | os.O_CREATE | os.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed open select language vis output: %v\n", err)
|
||||
os.Exit(1)
|
||||
@ -106,7 +107,7 @@ func main() {
|
||||
|
||||
src = changeHeadSrc + src
|
||||
p = path.Join(srcDir, "change_language.vis")
|
||||
w, err = os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||
w, err = os.OpenFile(p, os.O_WRONLY | os.O_CREATE | os.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed open select language vis output: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -7,11 +7,11 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/debug"
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/debug"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -19,58 +19,55 @@ var (
|
||||
scriptDir = path.Join("services", "registration")
|
||||
)
|
||||
|
||||
func formatItem(k []byte, v []byte, sessionId string) (string, error) {
|
||||
o, err := debug.ToKeyInfo(k, sessionId)
|
||||
func formatItem(k []byte, v []byte) (string, error) {
|
||||
o, err := debug.FromKey(k)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s := fmt.Sprintf("%v\t%v\n", o.Label, string(v))
|
||||
|
||||
s := fmt.Sprintf("%vValue: %v\n\n", o, string(v))
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
config.LoadConfig()
|
||||
|
||||
override := config.NewOverride()
|
||||
var connStr string
|
||||
var sessionId string
|
||||
var database string
|
||||
var engineDebug bool
|
||||
var err error
|
||||
var first bool
|
||||
|
||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||
flag.StringVar(&override.ResourceConn, "resource", "?", "resource data directory")
|
||||
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||
flag.StringVar(&connStr, "c", ".state", "connection string")
|
||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||
flag.Parse()
|
||||
|
||||
config.Apply(override)
|
||||
conns, err := config.GetConns()
|
||||
if connStr != "" {
|
||||
connStr = config.DbConn()
|
||||
}
|
||||
connData, err := storage.ToConnData(connStr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logg.Infof("start command", "conn", conns)
|
||||
logg.Infof("start command", "conn", connData)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
ctx = context.WithValue(ctx, "Database", database)
|
||||
|
||||
menuStorageService := storage.NewMenuStorageService(conns)
|
||||
resourceDir := scriptDir
|
||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
||||
|
||||
store, err := menuStorageService.GetUserdataDb(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "get userdata db: %v\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
store.SetSession(sessionId)
|
||||
store.SetPrefix(db.DATATYPE_USERDATA)
|
||||
|
||||
d, err := store.Dump(ctx, []byte(""))
|
||||
d, err := store.Dump(ctx, []byte(sessionId))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "store dump fail: %v\n", err.Error())
|
||||
os.Exit(1)
|
||||
@ -81,19 +78,15 @@ func main() {
|
||||
if k == nil {
|
||||
break
|
||||
}
|
||||
if !first {
|
||||
fmt.Printf("Session ID: %s\n---\n", sessionId)
|
||||
first = true
|
||||
}
|
||||
r, err := formatItem(append([]byte{db.DATATYPE_USERDATA}, k...), v, sessionId)
|
||||
r, err := formatItem(k, v)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "format db item error: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "format db item error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf(r)
|
||||
}
|
||||
|
||||
err = store.Close(ctx)
|
||||
err = store.Close()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
|
86
devtools/store/generate/main.go
Normal file
86
devtools/store/generate/main.go
Normal file
@ -0,0 +1,86 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
testdataloader "github.com/peteole/testdata-loader"
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
)
|
||||
|
||||
var (
|
||||
logg = logging.NewVanilla()
|
||||
baseDir = testdataloader.GetBasePath()
|
||||
scriptDir = path.Join("services", "registration")
|
||||
)
|
||||
|
||||
func main() {
|
||||
config.LoadConfig()
|
||||
|
||||
var connStr string
|
||||
var sessionId string
|
||||
var database string
|
||||
var engineDebug bool
|
||||
var err error
|
||||
|
||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||
flag.StringVar(&connStr, "c", "", "connection string")
|
||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||
flag.Parse()
|
||||
|
||||
if connStr != "" {
|
||||
connStr = config.DbConn()
|
||||
}
|
||||
connData, err := storage.ToConnData(connStr)
|
||||
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.WithValue(ctx, "SessionId", sessionId)
|
||||
ctx = context.WithValue(ctx, "Database", database)
|
||||
|
||||
resourceDir := scriptDir
|
||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
||||
|
||||
userDb, err := menuStorageService.GetUserdataDb(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
userStore := store.UserDataStore{userDb}
|
||||
|
||||
h := sha1.New()
|
||||
h.Write([]byte(sessionId))
|
||||
address := h.Sum(nil)
|
||||
addressString := fmt.Sprintf("%x", address)
|
||||
|
||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(addressString))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = userStore.WriteEntry(ctx, addressString, storedb.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = userDb.Close()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
38
go.mod
38
go.mod
@ -3,15 +3,14 @@ module git.grassecon.net/grassrootseconomics/sarafu-vise
|
||||
go 1.23.4
|
||||
|
||||
require (
|
||||
git.defalsify.org/vise.git v0.3.2-0.20250528124150-03bf7bfc1b66
|
||||
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630214912-814bef2b209a
|
||||
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306
|
||||
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694
|
||||
git.defalsify.org/vise.git v0.2.3-0.20250115000535-e2d329b3f739
|
||||
git.grassecon.net/grassrootseconomics/common v0.0.0-20250113174703-6afccefd1f05
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.0.0-20250115072214-bca7c5de969f
|
||||
git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250115003256-c0534ede1b63
|
||||
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250113103030-f0b2056fd87d
|
||||
github.com/alecthomas/assert/v2 v2.2.2
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/grassrootseconomics/ethutils v1.3.1
|
||||
github.com/grassrootseconomics/ussd-data-service v1.6.0-beta
|
||||
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta
|
||||
github.com/jackc/pgx/v5 v5.7.1
|
||||
github.com/peteole/testdata-loader v0.3.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
@ -20,49 +19,24 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
github.com/alecthomas/participle/v2 v2.0.0 // indirect
|
||||
github.com/alecthomas/repr v0.2.0 // indirect
|
||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect
|
||||
github.com/bits-and-blooms/bitset v1.14.3 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
|
||||
github.com/consensys/bavard v0.1.13 // indirect
|
||||
github.com/consensys/gnark-crypto v0.12.1 // indirect
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect
|
||||
github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/ethereum/c-kzg-4844 v1.0.0 // indirect
|
||||
github.com/ethereum/go-ethereum v1.14.9 // indirect
|
||||
github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta // indirect
|
||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/holiman/uint256 v1.3.1 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/lmittmann/w3 v0.17.1 // indirect
|
||||
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect
|
||||
github.com/mmcloughlin/addchain v0.4.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/supranational/blst v0.3.11 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
rsc.io/tmplfunc v0.0.3 // indirect
|
||||
)
|
||||
|
175
go.sum
175
go.sum
@ -1,37 +1,13 @@
|
||||
git.defalsify.org/vise.git v0.3.2-0.20250528124150-03bf7bfc1b66 h1:hmtb2Q3lHxq+SXqG+Gn43pKhTRYx+sw5j1LpgBfXN1o=
|
||||
git.defalsify.org/vise.git v0.3.2-0.20250528124150-03bf7bfc1b66/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
|
||||
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e h1:DcC9qkNl9ny3hxQmsMK6W81+5R/j4ZwYUbvewMI/rlc=
|
||||
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623063234-c1797e7a32b5 h1:VnRe01kHkZUBK/QjE7iV6gElSqSwQnAkWV3yCHtuYrI=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623063234-c1797e7a32b5/go.mod h1:H97hR+VOnZvR5BiGVb0ScCPwH/IoKBOlKM+yrQNVpq0=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623070026-d945964b0b46 h1:0+XkSRe7XSHa9WHXKpGPuC0myDszjchr4syH006lQ28=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623070026-d945964b0b46/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623075057-7b42d509e6d4 h1:W+8CC7x5eCPylkGy2TEoOpfJuiIlqzEzyYTzCLlY/u8=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623075057-7b42d509e6d4/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250624074830-5aa032400c12 h1:vD8biQmN36eouuE+TdxgXQjKisRf5cTGu/tMPv3afs0=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250624074830-5aa032400c12/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250624090744-339ba854c997 h1:8bCKyYoV4YiVBvCZlRclc3aQlBYpWhgtM35mvniDFD8=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250624090744-339ba854c997/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250626065419-57ee409f9629 h1:ew3vCFrLS/7/8uULTTPCbsHzFntQ6X68SScnBEy3pl0=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250626065419-57ee409f9629/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630213135-50ee455e7069 h1:re+hdr5NAC6JqhyvjMCkgX17fFi0u3Mawc6RBnBJW8I=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630213135-50ee455e7069/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630213606-12940bb5f284 h1:2zMU9jPd6xEO6oY9oxr84sdT9G3d09eyAkjVBAz9eco=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630213606-12940bb5f284/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630214912-814bef2b209a h1:KuhJ/WY4RCGmrXUA680ciaponM4vM5zBOJfnCpUo2fc=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630214912-814bef2b209a/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 h1:Jo+yWysWw/N5BJQtAyEMN8ePVvAyPHv+JG4lQti+1N4=
|
||||
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306/go.mod h1:FdLwYtzsjOIcDiW4uDgDYnB4Wdzq12uJUe0QHSSPbSo=
|
||||
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E=
|
||||
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694/go.mod h1:DpibtYpnT3nG4Kn556hRAkdu4+CtiI/6MbnQHal51mQ=
|
||||
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
|
||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI=
|
||||
git.defalsify.org/vise.git v0.2.3-0.20250115000535-e2d329b3f739 h1:w7pj1oh7jXrfajahVYU7m7AfHst4C6jNVzDVoaqJ7e8=
|
||||
git.defalsify.org/vise.git v0.2.3-0.20250115000535-e2d329b3f739/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
|
||||
git.grassecon.net/grassrootseconomics/common v0.0.0-20250113174703-6afccefd1f05 h1:BenzGx6aDHKDwE23/mWIFD2InYIXyzHroZWV3MF5WUk=
|
||||
git.grassecon.net/grassrootseconomics/common v0.0.0-20250113174703-6afccefd1f05/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.0.0-20250115072214-bca7c5de969f h1:FgccQi8vipX6dUt+GRiRDYHMR3UqC+plz73vw7y3fyU=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.0.0-20250115072214-bca7c5de969f/go.mod h1:tbA4whUGMUIDgQVdIW0sxWPuuXOvZRSny5zeku5hX4k=
|
||||
git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250115003256-c0534ede1b63 h1:bX7klKZpX+ZZu1LKbtOXDAhV/KK0YwExehiIi0jusAM=
|
||||
git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250115003256-c0534ede1b63/go.mod h1:Syw9TZyigPAM7t9FvicOm36iUnidt45f0SxzT2JniQ8=
|
||||
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250113103030-f0b2056fd87d h1:q/NO1rEgK3pia32D/tCq5hXfEuJp84COZRwceFvy/MM=
|
||||
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250113103030-f0b2056fd87d/go.mod h1:AH15xABcvaJr1TCGlih3oGSuwWC0E5IdbHQwuu+E1KI=
|
||||
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/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g=
|
||||
@ -40,91 +16,21 @@ github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk
|
||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE=
|
||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.14.3 h1:Gd2c8lSNf9pKXom5JtD7AaKO8o7fGQ2LtFj1436qilA=
|
||||
github.com/bits-and-blooms/bitset v1.14.3/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
|
||||
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4=
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
|
||||
github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA=
|
||||
github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU=
|
||||
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
|
||||
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
|
||||
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
|
||||
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
|
||||
github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M=
|
||||
github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
|
||||
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
|
||||
github.com/ethereum/go-ethereum v1.14.9 h1:J7iwXDrtUyE9FUjUYbd4c9tyzwMh6dTJsKzo9i6SrwA=
|
||||
github.com/ethereum/go-ethereum v1.14.9/go.mod h1:QeW+MtTpRdBEm2pUFoonByee8zfHv7kGp0wK0odvU1I=
|
||||
github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 h1:8NfxH2iXvJ60YRB8ChToFTUzl8awsc3cJ8CbLjGIl/A=
|
||||
github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
||||
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU=
|
||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo=
|
||||
github.com/grassrootseconomics/ethutils v1.3.1 h1:LlQO90HjJkl7ejC8fv6jP7RJUrAm1j4VMMCYfsoIrhU=
|
||||
github.com/grassrootseconomics/ethutils v1.3.1/go.mod h1:Wuv1VEZrkLIXqTSEYI3Nh9HG/ZHOUQ+U+xvWJ8QtjgQ=
|
||||
github.com/grassrootseconomics/ussd-data-service v1.5.0-beta h1:BSSQL/yPEvTVku9ja/ENZyZdwZkEaiTzzOUfg72bTy4=
|
||||
github.com/grassrootseconomics/ussd-data-service v1.5.0-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI=
|
||||
github.com/grassrootseconomics/ussd-data-service v1.6.0-beta h1:pY6zns6ifXyClRxP+JJaWrged3oRE7tTS2xaftF9clA=
|
||||
github.com/grassrootseconomics/ussd-data-service v1.6.0-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI=
|
||||
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta h1:fn1gwbWIwHVEBtUC2zi5OqTlfI/5gU1SMk0fgGixIXk=
|
||||
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta/go.mod h1:omfI0QtUwIdpu9gMcUqLMCG8O1XWjqJGBx1qUMiGWC0=
|
||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
|
||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
|
||||
github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs=
|
||||
github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
@ -135,52 +41,21 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
|
||||
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
|
||||
github.com/lmittmann/w3 v0.17.1 h1:zdXIimmNmYfqOFur+Jqc9Yqwtq6jwnsQufbTOnSAtW4=
|
||||
github.com/lmittmann/w3 v0.17.1/go.mod h1:WVUGMbL83WYBu4Sge3SVlW3qIG4VaHe+S8+UUnwz9Eg=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a h1:0Q3H0YXzMHiciXtRcM+j0jiCe8WKPQHoRgQiRTnfcLY=
|
||||
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a/go.mod h1:CdTTBOYzS5E4mWS1N8NWP6AHI19MP0A2B18n3hLzRMk=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
|
||||
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
|
||||
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pashagolub/pgxmock/v4 v4.3.0 h1:DqT7fk0OCK6H0GvqtcMsLpv8cIwWqdxWgfZNLeHCb/s=
|
||||
github.com/pashagolub/pgxmock/v4 v4.3.0/go.mod h1:9VoVHXwS3XR/yPtKGzwQvwZX1kzGB9sM8SviDcHDa3A=
|
||||
github.com/peteole/testdata-loader v0.3.0 h1:8jckE9KcyNHgyv/VPoaljvKZE0Rqr8+dPVYH6rfNr9I=
|
||||
github.com/peteole/testdata-loader v0.3.0/go.mod h1:Mt0ZbRtb56u8SLJpNP+BnQbENljMorYBpqlvt3cS83U=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg=
|
||||
github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y=
|
||||
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
@ -188,45 +63,23 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4=
|
||||
github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc=
|
||||
gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
|
||||
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
|
||||
|
@ -1,122 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
)
|
||||
|
||||
// CheckAccountStatus queries the API using the TrackingId and sets flags
|
||||
// based on the account status.
|
||||
func (h *MenuHandlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
flag_account_success, _ := h.flagManager.GetFlag("flag_account_success")
|
||||
flag_account_pending, _ := h.flagManager.GetFlag("flag_account_pending")
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
store := h.userdataStore
|
||||
publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
r, err := h.accountService.TrackAccountStatus(ctx, string(publicKey))
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
logg.ErrorCtxf(ctx, "failed on TrackAccountStatus", "error", err)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||
|
||||
if r.Active {
|
||||
res.FlagSet = append(res.FlagSet, flag_account_success)
|
||||
res.FlagReset = append(res.FlagReset, flag_account_pending)
|
||||
} else {
|
||||
res.FlagReset = append(res.FlagReset, flag_account_success)
|
||||
res.FlagSet = append(res.FlagSet, flag_account_pending)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h *MenuHandlers) CheckAccountCreated(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
flag_language_set, _ := h.flagManager.GetFlag("flag_language_set")
|
||||
flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
|
||||
|
||||
store := h.userdataStore
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
_, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
// reset major flags
|
||||
res.FlagReset = append(res.FlagReset, flag_language_set)
|
||||
res.FlagReset = append(res.FlagReset, flag_account_created)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.FlagSet = append(res.FlagSet, flag_account_created)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// CheckBlockedStatus:
|
||||
// 1. Checks whether the DATA_SELF_PIN_RESET is 1 and sets the flag_account_pin_reset
|
||||
// 2. resets the account blocked flag if the PIN attempts have been reset by an admin.
|
||||
func (h *MenuHandlers) CheckBlockedStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
store := h.userdataStore
|
||||
|
||||
flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked")
|
||||
flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset")
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_account_pin_reset)
|
||||
|
||||
selfPinReset, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET)
|
||||
if err == nil {
|
||||
pinResetValue, _ := strconv.ParseUint(string(selfPinReset), 0, 64)
|
||||
if pinResetValue == 1 {
|
||||
res.FlagSet = append(res.FlagSet, flag_account_pin_reset)
|
||||
}
|
||||
}
|
||||
|
||||
currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS)
|
||||
if err != nil {
|
||||
if !db.IsNotFound(err) {
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64)
|
||||
if pinAttemptsValue == 0 {
|
||||
res.FlagReset = append(res.FlagReset, flag_account_blocked)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/models"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"github.com/alecthomas/assert/v2"
|
||||
)
|
||||
|
||||
func TestCheckAccountStatus(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, store := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
flag_account_success, _ := fm.GetFlag("flag_account_success")
|
||||
flag_account_pending, _ := fm.GetFlag("flag_account_pending")
|
||||
flag_api_error, _ := fm.GetFlag("flag_api_call_error")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
publicKey []byte
|
||||
response *models.TrackStatusResult
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test when account is on the Sarafu network",
|
||||
publicKey: []byte("TrackingId1234"),
|
||||
response: &models.TrackStatusResult{
|
||||
Active: true,
|
||||
},
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_account_success},
|
||||
FlagReset: []uint32{flag_api_error, flag_account_pending},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test when the account is not yet on the sarafu network",
|
||||
publicKey: []byte("TrackingId1234"),
|
||||
response: &models.TrackStatusResult{
|
||||
Active: false,
|
||||
},
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_account_pending},
|
||||
FlagReset: []uint32{flag_api_error, flag_account_success},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
accountService: mockAccountService,
|
||||
flagManager: fm,
|
||||
}
|
||||
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(tt.publicKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mockAccountService.On("TrackAccountStatus", string(tt.publicKey)).Return(tt.response, nil)
|
||||
|
||||
// Call the method under test
|
||||
res, _ := h.CheckAccountStatus(ctx, "check_account_status", []byte(""))
|
||||
|
||||
// Assert that no errors occurred
|
||||
assert.NoError(t, err)
|
||||
|
||||
//Assert that the account created flag has been set to the result
|
||||
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckBlockedStatus(t *testing.T) {
|
||||
ctx, store := InitializeTestStore(t)
|
||||
sessionId := "session123"
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
flag_account_blocked, _ := fm.GetFlag("flag_account_blocked")
|
||||
flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset")
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
flagManager: fm,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
currentWrongPinAttempts string
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Currently blocked account",
|
||||
currentWrongPinAttempts: "4",
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_account_pin_reset},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Account with 0 wrong PIN attempts",
|
||||
currentWrongPinAttempts: "0",
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_account_pin_reset, flag_account_blocked},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(tt.currentWrongPinAttempts)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := h.CheckBlockedStatus(ctx, "", []byte(""))
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedResult, res)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"unicode"
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
)
|
||||
|
||||
// RequestCustomAlias requests an ENS based alias name based on a user's input,then saves it as temporary value
|
||||
func (h *MenuHandlers) RequestCustomAlias(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var alias string
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
if string(input) == "0" {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||
flag_alias_unavailable, _ := h.flagManager.GetFlag("flag_alias_unavailable")
|
||||
|
||||
store := h.userdataStore
|
||||
aliasHint, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
return res, nil
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
//Ensures that the call doesn't happen twice for the same alias hint
|
||||
if !bytes.Equal(aliasHint, input) {
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(string(input)))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
sanitizedInput := sanitizeAliasHint(string(input))
|
||||
// Check if an alias already exists
|
||||
existingAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS)
|
||||
if err == nil && len(existingAlias) > 0 {
|
||||
logg.InfoCtxf(ctx, "Current alias", "alias", string(existingAlias))
|
||||
|
||||
unavailable, err := h.isAliasUnavailable(ctx, sanitizedInput)
|
||||
if err == nil && unavailable {
|
||||
res.FlagSet = append(res.FlagSet, flag_alias_unavailable)
|
||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_alias_unavailable)
|
||||
|
||||
// Update existing alias
|
||||
aliasResult, err := h.accountService.UpdateAlias(ctx, sanitizedInput, string(publicKey))
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
logg.ErrorCtxf(ctx, "failed to update alias", "alias", sanitizedInput, "error", err)
|
||||
return res, nil
|
||||
}
|
||||
alias = aliasResult.Alias
|
||||
logg.InfoCtxf(ctx, "Updated alias", "alias", alias)
|
||||
} else {
|
||||
logg.InfoCtxf(ctx, "Registering a new alias", "err", err)
|
||||
|
||||
unavailable, err := h.isAliasUnavailable(ctx, sanitizedInput)
|
||||
if err == nil && unavailable {
|
||||
res.FlagSet = append(res.FlagSet, flag_alias_unavailable)
|
||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_alias_unavailable)
|
||||
|
||||
// Register a new alias
|
||||
aliasResult, err := h.accountService.RequestAlias(ctx, string(publicKey), sanitizedInput)
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
logg.ErrorCtxf(ctx, "failed to retrieve alias", "alias", sanitizedInput, "error_alias_request", err)
|
||||
return res, nil
|
||||
}
|
||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||
|
||||
alias = aliasResult.Alias
|
||||
logg.InfoCtxf(ctx, "Registered alias", "alias", alias)
|
||||
}
|
||||
|
||||
//Store the new account alias
|
||||
logg.InfoCtxf(ctx, "Final registered alias", "alias", alias)
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(alias))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write account alias", "key", storedb.DATA_ACCOUNT_ALIAS, "value", alias, "error", err)
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func sanitizeAliasHint(input string) string {
|
||||
for i, r := range input {
|
||||
// Check if the character is a special character (non-alphanumeric)
|
||||
if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
|
||||
return input[:i]
|
||||
}
|
||||
}
|
||||
// If no special character is found, return the whole input
|
||||
return input
|
||||
}
|
||||
|
||||
func (h *MenuHandlers) isAliasUnavailable(ctx context.Context, alias string) (bool, error) {
|
||||
fqdn := fmt.Sprintf("%s.%s", alias, "sarafu.eth")
|
||||
logg.InfoCtxf(ctx, "Checking if the fqdn alias is taken", "fqdn", fqdn)
|
||||
|
||||
aliasAddress, err := h.accountService.CheckAliasAddress(ctx, fqdn)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(aliasAddress.Address) > 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/common/pin"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
)
|
||||
|
||||
// Authorize attempts to unlock the next sequential nodes by verifying the provided PIN against the already set PIN.
|
||||
// It sets the required flags that control the flow.
|
||||
func (h *MenuHandlers) Authorize(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
|
||||
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
|
||||
pinInput := string(input)
|
||||
|
||||
if !pin.IsValidPIN(pinInput) {
|
||||
res.FlagReset = append(res.FlagReset, flag_account_authorized, flag_allow_update)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
store := h.userdataStore
|
||||
AccountPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read AccountPin entry with", "key", storedb.DATA_ACCOUNT_PIN, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// verify that the user provided the correct PIN
|
||||
if pin.VerifyPIN(string(AccountPin), pinInput) {
|
||||
// set the required flags for a valid PIN
|
||||
res.FlagSet = append(res.FlagSet, flag_allow_update, flag_account_authorized)
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
|
||||
|
||||
err := h.resetIncorrectPINAttempts(ctx, sessionId)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
// set the required flags for an incorrect PIN
|
||||
res.FlagSet = append(res.FlagSet, flag_incorrect_pin)
|
||||
res.FlagReset = append(res.FlagReset, flag_account_authorized, flag_allow_update)
|
||||
|
||||
err = h.incrementIncorrectPINAttempts(ctx, sessionId)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ResetAllowUpdate resets the allowupdate flag that allows a user to update profile data.
|
||||
func (h *MenuHandlers) ResetAllowUpdate(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
res.FlagReset = append(res.FlagReset, flag_allow_update)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ResetAccountAuthorized resets the account authorization flag after a successful PIN entry.
|
||||
func (h *MenuHandlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
||||
res.FlagReset = append(res.FlagReset, flag_account_authorized)
|
||||
return res, nil
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.defalsify.org/vise.git/state"
|
||||
"git.grassecon.net/grassrootseconomics/common/pin"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"github.com/alecthomas/assert/v2"
|
||||
)
|
||||
|
||||
func TestAuthorize(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, store := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
|
||||
// Create required mocks
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
mockState := state.NewState(16)
|
||||
flag_incorrect_pin, _ := fm.GetFlag("flag_incorrect_pin")
|
||||
flag_account_authorized, _ := fm.GetFlag("flag_account_authorized")
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
|
||||
// Set 1234 is the correct account pin
|
||||
accountPIN := "1234"
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
accountService: mockAccountService,
|
||||
flagManager: fm,
|
||||
st: mockState,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test with correct PIN",
|
||||
input: []byte("1234"),
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_incorrect_pin},
|
||||
FlagSet: []uint32{flag_allow_update, flag_account_authorized},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with incorrect PIN",
|
||||
input: []byte("1235"),
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_account_authorized, flag_allow_update},
|
||||
FlagSet: []uint32{flag_incorrect_pin},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with PIN that is not a 4 digit",
|
||||
input: []byte("1235aqds"),
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_account_authorized, flag_allow_update},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Hash the PIN
|
||||
hashedPIN, err := pin.HashPIN(accountPIN)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedPIN))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Call the method under test
|
||||
res, err := h.Authorize(ctx, "authorize", []byte(tt.input))
|
||||
|
||||
// Assert that no errors occurred
|
||||
assert.NoError(t, err)
|
||||
|
||||
//Assert that the account created flag has been set to the result
|
||||
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetAllowUpdate(t *testing.T) {
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
|
||||
// Define test cases
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Resets allow update",
|
||||
input: []byte(""),
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_allow_update},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create the MenuHandlers instance with the mock flag manager
|
||||
h := &MenuHandlers{
|
||||
flagManager: fm,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
res, err := h.ResetAllowUpdate(context.Background(), "reset_allow update", tt.input)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Assert that the Result FlagSet has the required flags after language switch
|
||||
assert.Equal(t, res, tt.expectedResult, "Flags should be equal to account created")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetAccountAuthorized(t *testing.T) {
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
flag_account_authorized, _ := fm.GetFlag("flag_account_authorized")
|
||||
|
||||
// Define test cases
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Resets account authorized",
|
||||
input: []byte(""),
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_account_authorized},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create the MenuHandlers instance with the mock flag manager
|
||||
h := &MenuHandlers{
|
||||
flagManager: fm,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
res, err := h.ResetAccountAuthorized(context.Background(), "reset_account_authorized", tt.input)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Assert that the Result FlagSet has the required flags after language switch
|
||||
assert.Equal(t, res, tt.expectedResult, "Result should contain flag(s) that have been reset")
|
||||
})
|
||||
}
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"gopkg.in/leonelquinteros/gotext.v1"
|
||||
)
|
||||
|
||||
// CheckBalance retrieves the balance of the active voucher and sets
|
||||
// the balance as the result content.
|
||||
func (h *MenuHandlers) CheckBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var (
|
||||
res resource.Result
|
||||
err error
|
||||
content string
|
||||
)
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
store := h.userdataStore
|
||||
|
||||
// get the active sym and active balance
|
||||
activeSym, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
|
||||
if err != nil {
|
||||
logg.InfoCtxf(ctx, "could not find the activeSym in checkBalance:", "err", err)
|
||||
if !db.IsNotFound(err) {
|
||||
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err)
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
activeBal, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL)
|
||||
if err != nil {
|
||||
if !db.IsNotFound(err) {
|
||||
logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err)
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
accAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS)
|
||||
if err != nil {
|
||||
if !db.IsNotFound(err) {
|
||||
logg.ErrorCtxf(ctx, "failed to read account alias entry with", "key", storedb.DATA_ACCOUNT_ALIAS, "error", err)
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
content, err = loadUserContent(ctx, string(activeSym), string(activeBal), string(accAlias))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
res.Content = content
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// loadUserContent loads the main user content in the main menu: the alias, balance and active symbol associated with active voucher
|
||||
func loadUserContent(ctx context.Context, activeSym string, balance string, alias string) (string, error) {
|
||||
var content string
|
||||
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
|
||||
// Format the balance to 2 decimal places or default to 0.00
|
||||
formattedAmount, err := store.TruncateDecimalString(balance, 2)
|
||||
if err != nil {
|
||||
formattedAmount = "0.00"
|
||||
}
|
||||
|
||||
// format the final output
|
||||
balStr := fmt.Sprintf("%s %s", formattedAmount, activeSym)
|
||||
if alias != "" {
|
||||
content = l.Get("%s\nBalance: %s\n", alias, balStr)
|
||||
} else {
|
||||
content = l.Get("Balance: %s\n", balStr)
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// FetchCommunityBalance retrieves and displays the balance for community accounts in user's preferred language.
|
||||
func (h *MenuHandlers) FetchCommunityBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
// retrieve the language code from the context
|
||||
code := codeFromCtx(ctx)
|
||||
// Initialize the localization system with the appropriate translation directory
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
//TODO:
|
||||
//Check if the address is a community account,if then,get the actual balance
|
||||
res.Content = l.Get("Community Balance: 0.00")
|
||||
return res, nil
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/vise.git/lang"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.defalsify.org/vise.git/state"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"github.com/alecthomas/assert/v2"
|
||||
)
|
||||
|
||||
func TestCheckBalance(t *testing.T) {
|
||||
ctx, store := InitializeTestStore(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
sessionId string
|
||||
publicKey string
|
||||
alias string
|
||||
activeSym string
|
||||
activeBal string
|
||||
expectedResult resource.Result
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "User with no active sym",
|
||||
sessionId: "session123",
|
||||
publicKey: "0X98765432109",
|
||||
alias: "",
|
||||
activeSym: "",
|
||||
activeBal: "",
|
||||
expectedResult: resource.Result{Content: "Balance: 0.00 \n"},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "User with active sym",
|
||||
sessionId: "session123",
|
||||
publicKey: "0X98765432109",
|
||||
alias: "",
|
||||
activeSym: "ETH",
|
||||
activeBal: "1.5",
|
||||
expectedResult: resource.Result{Content: "Balance: 1.50 ETH\n"},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "User with active sym and alias",
|
||||
sessionId: "session123",
|
||||
publicKey: "0X98765432109",
|
||||
alias: "user72",
|
||||
activeSym: "SRF",
|
||||
activeBal: "10.967",
|
||||
expectedResult: resource.Result{Content: "user72 balance: 10.96 SRF\n"},
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
ctx := context.WithValue(ctx, "SessionId", tt.sessionId)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
accountService: mockAccountService,
|
||||
}
|
||||
|
||||
if tt.alias != "" {
|
||||
err := store.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(tt.alias))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if tt.activeSym != "" {
|
||||
err := store.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.activeSym))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if tt.activeBal != "" {
|
||||
err := store.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACTIVE_BAL, []byte(tt.activeBal))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
res, err := h.CheckBalance(ctx, "check_balance", []byte(""))
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedResult, res, "Result should match expected output")
|
||||
}
|
||||
|
||||
mockAccountService.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchCommunityBalance(t *testing.T) {
|
||||
// Define test data
|
||||
sessionId := "session123"
|
||||
ctx, store := InitializeTestStore(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
languageCode string
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test community balance content when language is english",
|
||||
expectedResult: resource.Result{
|
||||
Content: "Community Balance: 0.00",
|
||||
},
|
||||
languageCode: "eng",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
mockState := state.NewState(16)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
st: mockState,
|
||||
accountService: mockAccountService,
|
||||
}
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
ctx = context.WithValue(ctx, "Language", lang.Language{
|
||||
Code: tt.languageCode,
|
||||
})
|
||||
|
||||
// Call the method
|
||||
res, _ := h.FetchCommunityBalance(ctx, "fetch_community_balance", []byte(""))
|
||||
|
||||
//Assert that the result set to content is what was expected
|
||||
assert.Equal(t, res, tt.expectedResult, "Result should match expected result")
|
||||
})
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.defalsify.org/vise.git/state"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
commonlang "git.grassecon.net/grassrootseconomics/common/lang"
|
||||
)
|
||||
|
||||
// SetLanguage sets the language across the menu.
|
||||
func (h *MenuHandlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
symbol, _ := h.st.Where()
|
||||
code := strings.Split(symbol, "_")[1]
|
||||
|
||||
// TODO: Use defaultlanguage from config
|
||||
if !commonlang.IsValidISO639(code) {
|
||||
//Fallback to english instead?
|
||||
code = "eng"
|
||||
}
|
||||
err := h.persistLanguageCode(ctx, code)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
res.Content = code
|
||||
res.FlagSet = append(res.FlagSet, state.FLAG_LANG)
|
||||
languageSetFlag, err := h.flagManager.GetFlag("flag_language_set")
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Error setting the languageSetFlag", "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, languageSetFlag)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// persistLanguageCode persists the selected ISO 639 language code
|
||||
func (h *MenuHandlers) persistLanguageCode(ctx context.Context, code string) error {
|
||||
store := h.userdataStore
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("missing session")
|
||||
}
|
||||
err := store.WriteEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE, []byte(code))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to persist language code", "key", storedb.DATA_SELECTED_LANGUAGE_CODE, "value", code, "error", err)
|
||||
return err
|
||||
}
|
||||
return h.persistInitialLanguageCode(ctx, sessionId, code)
|
||||
}
|
||||
|
||||
// persistInitialLanguageCode receives an initial language code and persists it to the store
|
||||
func (h *MenuHandlers) persistInitialLanguageCode(ctx context.Context, sessionId string, code string) error {
|
||||
store := h.userdataStore
|
||||
_, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !db.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE, []byte(code))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to persist initial language code", "key", storedb.DATA_INITIAL_LANGUAGE_CODE, "value", code, "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.defalsify.org/vise.git/state"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"github.com/alecthomas/assert/v2"
|
||||
)
|
||||
|
||||
func TestSetLanguage(t *testing.T) {
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
sessionId := "session123"
|
||||
ctx, store := InitializeTestStore(t)
|
||||
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
// Define test cases
|
||||
tests := []struct {
|
||||
name string
|
||||
execPath []string
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Set Default Language (English)",
|
||||
execPath: []string{"set_eng"},
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{state.FLAG_LANG, 8},
|
||||
Content: "eng",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Set Swahili Language",
|
||||
execPath: []string{"set_swa"},
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{state.FLAG_LANG, 8},
|
||||
Content: "swa",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockState := state.NewState(16)
|
||||
// Set the ExecPath
|
||||
mockState.ExecPath = tt.execPath
|
||||
|
||||
// Create the MenuHandlers instance with the mock flag manager
|
||||
h := &MenuHandlers{
|
||||
flagManager: fm,
|
||||
userdataStore: store,
|
||||
st: mockState,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
res, err := h.SetLanguage(ctx, "set_language", nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Assert that the Result FlagSet has the required flags after language switch
|
||||
assert.Equal(t, res, tt.expectedResult, "Result should match expected result")
|
||||
code, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, string(code), tt.expectedResult.Content)
|
||||
code, err = store.ReadEntry(ctx, sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, string(code), "eng")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistLanguageCode(t *testing.T) {
|
||||
ctx, store := InitializeTestStore(t)
|
||||
|
||||
sessionId := "session123"
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
expectedLanguageCode string
|
||||
}{
|
||||
{
|
||||
name: "Set Default Language (English)",
|
||||
code: "eng",
|
||||
expectedLanguageCode: "eng",
|
||||
},
|
||||
{
|
||||
name: "Set Swahili Language",
|
||||
code: "swa",
|
||||
expectedLanguageCode: "swa",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := h.persistLanguageCode(ctx, test.code)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
code, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE)
|
||||
|
||||
assert.Equal(t, test.expectedLanguageCode, string(code))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistInitialLanguageCode(t *testing.T) {
|
||||
ctx, store := InitializeTestStore(t)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
sessionId string
|
||||
}{
|
||||
{
|
||||
name: "Persist initial Language (English)",
|
||||
code: "eng",
|
||||
sessionId: "session123",
|
||||
},
|
||||
{
|
||||
name: "Persist initial Language (Swahili)",
|
||||
code: "swa",
|
||||
sessionId: "session456",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := h.persistInitialLanguageCode(ctx, tt.sessionId, tt.code)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
code, err := store.ReadEntry(ctx, tt.sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE)
|
||||
|
||||
assert.Equal(t, tt.code, string(code))
|
||||
})
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,372 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/common/phone"
|
||||
"git.grassecon.net/grassrootseconomics/common/pin"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
)
|
||||
|
||||
// ResetIncorrectPin resets the incorrect pin flag after a new PIN attempt.
|
||||
func (h *MenuHandlers) ResetIncorrectPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
store := h.userdataStore
|
||||
|
||||
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
|
||||
flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked")
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
|
||||
|
||||
currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS)
|
||||
if err != nil {
|
||||
if !db.IsNotFound(err) {
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64)
|
||||
remainingPINAttempts := pin.AllowedPINAttempts - uint8(pinAttemptsValue)
|
||||
if remainingPINAttempts == 0 {
|
||||
res.FlagSet = append(res.FlagSet, flag_account_blocked)
|
||||
return res, nil
|
||||
}
|
||||
if remainingPINAttempts < pin.AllowedPINAttempts {
|
||||
res.Content = strconv.Itoa(int(remainingPINAttempts))
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SaveTemporaryPin saves the valid PIN input to the DATA_TEMPORARY_VALUE,
|
||||
// during the account creation process
|
||||
// and during the change PIN process.
|
||||
func (h *MenuHandlers) SaveTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
flag_invalid_pin, _ := h.flagManager.GetFlag("flag_invalid_pin")
|
||||
|
||||
if string(input) == "0" {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
accountPIN := string(input)
|
||||
|
||||
// Validate that the PIN has a valid format.
|
||||
if !pin.IsValidPIN(accountPIN) {
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_pin)
|
||||
return res, nil
|
||||
}
|
||||
res.FlagReset = append(res.FlagReset, flag_invalid_pin)
|
||||
|
||||
// Hash the PIN
|
||||
hashedPIN, err := pin.HashPIN(string(accountPIN))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to hash the PIN", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
store := h.userdataStore
|
||||
logdb := h.logDb
|
||||
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryAccountPIN entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write temporaryAccountPIN log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ResetInvalidPIN resets the invalid PIN flag
|
||||
func (h *MenuHandlers) ResetInvalidPIN(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
flag_invalid_pin, _ := h.flagManager.GetFlag("flag_invalid_pin")
|
||||
res.FlagReset = append(res.FlagReset, flag_invalid_pin)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ConfirmPinChange validates user's new PIN. If input matches the temporary PIN, saves it as the new account PIN.
|
||||
func (h *MenuHandlers) ConfirmPinChange(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
|
||||
flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset")
|
||||
|
||||
if string(input) == "0" {
|
||||
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
store := h.userdataStore
|
||||
logdb := h.logDb
|
||||
hashedTemporaryPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err)
|
||||
return res, err
|
||||
}
|
||||
if len(hashedTemporaryPin) == 0 {
|
||||
logg.ErrorCtxf(ctx, "hashedTemporaryPin is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||
return res, fmt.Errorf("Data error encountered")
|
||||
}
|
||||
|
||||
if pin.VerifyPIN(string(hashedTemporaryPin), string(input)) {
|
||||
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
|
||||
} else {
|
||||
res.FlagSet = append(res.FlagSet, flag_pin_mismatch)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// save the hashed PIN as the new account PIN
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write DATA_ACCOUNT_PIN entry with", "key", storedb.DATA_ACCOUNT_PIN, "hashedPIN value", hashedTemporaryPin, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write AccountPIN log entry", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err)
|
||||
}
|
||||
|
||||
// set the DATA_SELF_PIN_RESET as 0
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET, []byte("0"))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write DATA_SELF_PIN_RESET entry with", "key", storedb.DATA_SELF_PIN_RESET, "self PIN reset value", "0", "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagReset = append(res.FlagReset, flag_account_pin_reset)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ValidateBlockedNumber performs validation of phone numbers during the Reset other's PIN.
|
||||
// It checks phone number format and verifies registration status.
|
||||
// If valid, it writes the number under DATA_BLOCKED_NUMBER on the admin account
|
||||
func (h *MenuHandlers) ValidateBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
|
||||
flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number")
|
||||
store := h.userdataStore
|
||||
logdb := h.logDb
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
if string(input) == "0" {
|
||||
res.FlagReset = append(res.FlagReset, flag_unregistered_number)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
blockedNumber := string(input)
|
||||
formattedNumber, err := phone.FormatPhoneNumber(blockedNumber)
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
|
||||
logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", blockedNumber, "error", err)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
_, err = store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
logg.InfoCtxf(ctx, "Invalid or unregistered number")
|
||||
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
|
||||
return res, nil
|
||||
} else {
|
||||
logg.ErrorCtxf(ctx, "Error on ValidateBlockedNumber", "error", err)
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber))
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write blocked number log entry", "key", storedb.DATA_BLOCKED_NUMBER, "value", formattedNumber, "error", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ResetOthersPin handles the PIN reset process for other users' accounts by:
|
||||
// 1. Retrieving the blocked phone number from the session
|
||||
// 2. Writing the DATA_SELF_PIN_RESET on the blocked phone number
|
||||
// 3. Resetting the DATA_INCORRECT_PIN_ATTEMPTS to 0 for the blocked phone number
|
||||
func (h *MenuHandlers) ResetOthersPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
store := h.userdataStore
|
||||
smsservice := h.smsService
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
blockedPhonenumber, err := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read blockedPhonenumber entry with", "key", storedb.DATA_BLOCKED_NUMBER, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// set the DATA_SELF_PIN_RESET for the account
|
||||
err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_SELF_PIN_RESET, []byte("1"))
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0")))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "error", err)
|
||||
return res, err
|
||||
}
|
||||
blockedPhoneStr := string(blockedPhonenumber)
|
||||
//Trigger an SMS to inform a user that the blocked account has been reset
|
||||
if phone.IsValidPhoneNumber(blockedPhoneStr) {
|
||||
err = smsservice.SendPINResetSMS(ctx, sessionId, blockedPhoneStr)
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to send PIN reset SMS", "error", err)
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// incrementIncorrectPINAttempts keeps track of the number of incorrect PIN attempts
|
||||
func (h *MenuHandlers) incrementIncorrectPINAttempts(ctx context.Context, sessionId string) error {
|
||||
var pinAttemptsCount uint8
|
||||
store := h.userdataStore
|
||||
|
||||
currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
//First time Wrong PIN attempt: initialize with a count of 1
|
||||
pinAttemptsCount = 1
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount))))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "value", currentWrongPinAttempts, "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64)
|
||||
pinAttemptsCount = uint8(pinAttemptsValue) + 1
|
||||
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount))))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "value", pinAttemptsCount, "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resetIncorrectPINAttempts resets the number of incorrect PIN attempts after a correct PIN entry
|
||||
func (h *MenuHandlers) resetIncorrectPINAttempts(ctx context.Context, sessionId string) error {
|
||||
store := h.userdataStore
|
||||
err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0")))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyCreatePin checks whether the confirmation PIN is similar to the temporary PIN
|
||||
// If similar, it sets the USERFLAG_PIN_SET flag and writes the account PIN allowing the user
|
||||
// to access the main menu.
|
||||
func (h *MenuHandlers) VerifyCreatePin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin")
|
||||
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
|
||||
flag_pin_set, _ := h.flagManager.GetFlag("flag_pin_set")
|
||||
|
||||
if string(input) == "0" {
|
||||
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
store := h.userdataStore
|
||||
logdb := h.logDb
|
||||
|
||||
hashedTemporaryPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err)
|
||||
return res, err
|
||||
}
|
||||
if len(hashedTemporaryPin) == 0 {
|
||||
logg.ErrorCtxf(ctx, "hashedTemporaryPin is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||
return res, fmt.Errorf("Data error encountered")
|
||||
}
|
||||
|
||||
if pin.VerifyPIN(string(hashedTemporaryPin), string(input)) {
|
||||
res.FlagSet = append(res.FlagSet, flag_valid_pin)
|
||||
res.FlagSet = append(res.FlagSet, flag_pin_set)
|
||||
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
|
||||
} else {
|
||||
res.FlagSet = append(res.FlagSet, flag_pin_mismatch)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// save the hashed PIN as the new account PIN
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write DATA_ACCOUNT_PIN entry with", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write DATA_ACCOUNT_PIN log entry", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// RetrieveBlockedNumber gets the current number during the pin reset for other's is in progress.
|
||||
func (h *MenuHandlers) RetrieveBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
store := h.userdataStore
|
||||
blockedNumber, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER)
|
||||
|
||||
res.Content = string(blockedNumber)
|
||||
|
||||
return res, nil
|
||||
}
|
@ -1,293 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.defalsify.org/vise.git/state"
|
||||
"git.grassecon.net/grassrootseconomics/common/pin"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"github.com/alecthomas/assert/v2"
|
||||
)
|
||||
|
||||
func TestCountIncorrectPINAttempts(t *testing.T) {
|
||||
ctx, store := InitializeTestStore(t)
|
||||
sessionId := "session123"
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
attempts := uint8(2)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
}
|
||||
err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(attempts))))
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
err = h.incrementIncorrectPINAttempts(ctx, sessionId)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
|
||||
attemptsAfterCount, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
pinAttemptsValue, _ := strconv.ParseUint(string(attemptsAfterCount), 0, 64)
|
||||
pinAttemptsCount := uint8(pinAttemptsValue)
|
||||
expectedAttempts := attempts + 1
|
||||
assert.Equal(t, pinAttemptsCount, expectedAttempts)
|
||||
}
|
||||
|
||||
func TestResetIncorrectPINAttempts(t *testing.T) {
|
||||
ctx, store := InitializeTestStore(t)
|
||||
sessionId := "session123"
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("2")))
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
}
|
||||
h.resetIncorrectPINAttempts(ctx, sessionId)
|
||||
incorrectAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS)
|
||||
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
assert.Equal(t, "0", string(incorrectAttempts))
|
||||
}
|
||||
|
||||
func TestSaveTemporaryPin(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
|
||||
ctx, userdatastore := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
_, logdb := InitializeTestLogdbStore(t)
|
||||
logDb := store.LogDb{
|
||||
Db: logdb,
|
||||
}
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
flag_invalid_pin, _ := fm.GetFlag("flag_invalid_pin")
|
||||
|
||||
// Create the MenuHandlers instance with the mock flag manager
|
||||
h := &MenuHandlers{
|
||||
flagManager: fm,
|
||||
userdataStore: userdatastore,
|
||||
logDb: logDb,
|
||||
}
|
||||
|
||||
// Define test cases
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Valid Pin entry",
|
||||
input: []byte("1234"),
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_invalid_pin},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid Pin entry",
|
||||
input: []byte("12343"),
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_invalid_pin},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Call the method
|
||||
res, err := h.SaveTemporaryPin(ctx, "save_pin", tt.input)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// Assert that the Result FlagSet has the required flags after language switch
|
||||
assert.Equal(t, res, tt.expectedResult, "Result should match expected result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfirmPinChange(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
|
||||
mockState := state.NewState(16)
|
||||
ctx, store := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
flag_pin_mismatch, _ := fm.GetFlag("flag_pin_mismatch")
|
||||
flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset")
|
||||
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
flagManager: fm,
|
||||
accountService: mockAccountService,
|
||||
st: mockState,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
temporarypin string
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test with correct pin confirmation",
|
||||
input: []byte("1234"),
|
||||
temporarypin: "1234",
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_pin_mismatch, flag_account_pin_reset},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Hash the PIN
|
||||
hashedPIN, err := pin.HashPIN(tt.temporarypin)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Set up the expected behavior of the mock
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//Call the function under test
|
||||
res, _ := h.ConfirmPinChange(ctx, "confirm_pin_change", tt.input)
|
||||
|
||||
//Assert that the result set to content is what was expected
|
||||
assert.Equal(t, res, tt.expectedResult, "Result should contain flags set according to user input")
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateBlockedNumber(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
validNumber := "+254712345678"
|
||||
invalidNumber := "12343" // Invalid phone number
|
||||
unregisteredNumber := "+254734567890" // Valid but unregistered number
|
||||
publicKey := "0X13242618721"
|
||||
mockState := state.NewState(128)
|
||||
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
flag_unregistered_number, _ := fm.GetFlag("flag_unregistered_number")
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
st: mockState,
|
||||
flagManager: fm,
|
||||
}
|
||||
|
||||
err = userStore.WriteEntry(ctx, validNumber, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Valid and registered number",
|
||||
input: []byte(validNumber),
|
||||
expectedResult: resource.Result{},
|
||||
},
|
||||
{
|
||||
name: "Invalid Phone Number",
|
||||
input: []byte(invalidNumber),
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_unregistered_number},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Unregistered Phone Number",
|
||||
input: []byte(unregisteredNumber),
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_unregistered_number},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
res, err := h.ValidateBlockedNumber(ctx, "validate_blocked_number", tt.input)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.expectedResult, res)
|
||||
|
||||
if tt.name == "Valid and registered number" {
|
||||
blockedNumber, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, validNumber, string(blockedNumber))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetOthersPin(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
blockedNumber := "+254712345678"
|
||||
testPin := "1234"
|
||||
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
hashedPIN, err := pin.HashPIN(testPin)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to hash testPin", "error", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
}
|
||||
|
||||
// Write initial data to the store
|
||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(blockedNumber))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = userStore.WriteEntry(ctx, blockedNumber, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = h.ResetOthersPin(ctx, "reset_others_pin", []byte(""))
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"gopkg.in/leonelquinteros/gotext.v1"
|
||||
)
|
||||
|
||||
// GetPools fetches a list of 5 top pools
|
||||
func (h *MenuHandlers) GetPools(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
userStore := h.userdataStore
|
||||
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
|
||||
|
||||
// call the api to get a list of top 5 pools sorted by swaps
|
||||
topPools, err := h.accountService.FetchTopPools(ctx)
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Return if there are no pools
|
||||
if len(topPools) == 0 {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
data := store.ProcessPools(topPools)
|
||||
|
||||
// Store all Pool data
|
||||
dataMap := map[storedb.DataTyp]string{
|
||||
storedb.DATA_POOL_NAMES: data.PoolNames,
|
||||
storedb.DATA_POOL_SYMBOLS: data.PoolSymbols,
|
||||
storedb.DATA_POOL_ADDRESSES: data.PoolContractAdrresses,
|
||||
}
|
||||
|
||||
// Write data entries
|
||||
for key, value := range dataMap {
|
||||
if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
res.Content = h.ReplaceSeparatorFunc(data.PoolSymbols)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetDefaultPool returns the current user's Pool. If none is set, it returns the default config pool.
|
||||
func (h *MenuHandlers) GetDefaultPool(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
userStore := h.userdataStore
|
||||
activePoolSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
// set the default as the response
|
||||
res.Content = config.DefaultPoolSymbol()
|
||||
return res, nil
|
||||
}
|
||||
|
||||
logg.ErrorCtxf(ctx, "failed to read the activePoolSym entry with", "key", storedb.DATA_ACTIVE_POOL_SYM, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
res.Content = string(activePoolSym)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ViewPool retrieves the pool details from the user store
|
||||
// and displays it to the user for them to select it.
|
||||
func (h *MenuHandlers) ViewPool(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
|
||||
flag_incorrect_pool, _ := h.flagManager.GetFlag("flag_incorrect_pool")
|
||||
|
||||
inputStr := string(input)
|
||||
|
||||
poolData, err := store.GetPoolData(ctx, h.userdataStore, sessionId, inputStr)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("failed to retrieve pool data: %v", err)
|
||||
}
|
||||
|
||||
if poolData == nil {
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||
|
||||
// no match found. Call the API using the inputStr as the symbol
|
||||
poolResp, err := h.accountService.RetrievePoolDetails(ctx, inputStr)
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if len(poolResp.PoolSymbol) == 0 {
|
||||
// If the API does not return the data, set the flag
|
||||
res.FlagSet = append(res.FlagSet, flag_incorrect_pool)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
poolData = poolResp
|
||||
}
|
||||
|
||||
if err := store.StoreTemporaryPool(ctx, h.userdataStore, sessionId, poolData); err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed on StoreTemporaryPool", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_pool)
|
||||
res.Content = l.Get("Name: %s\nSymbol: %s", poolData.PoolName, poolData.PoolSymbol)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SetPool retrieves the temp pool data and sets it as the active data.
|
||||
func (h *MenuHandlers) SetPool(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
// Get temporary data
|
||||
tempData, err := store.GetTemporaryPoolData(ctx, h.userdataStore, sessionId)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed on GetTemporaryPoolData", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Set as active and clear temporary data
|
||||
if err := store.UpdatePoolData(ctx, h.userdataStore, sessionId, tempData); err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed on UpdatePoolData", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
res.Content = tempData.PoolSymbol
|
||||
return res, nil
|
||||
}
|
@ -1,381 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"gopkg.in/leonelquinteros/gotext.v1"
|
||||
)
|
||||
|
||||
// LoadSwapFromList returns a list of possible vouchers to swap to
|
||||
func (h *MenuHandlers) LoadSwapToList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
userStore := h.userdataStore
|
||||
|
||||
// get the active address and symbol
|
||||
activeAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read activeAddress entry with", "key", storedb.DATA_ACTIVE_ADDRESS, "error", err)
|
||||
return res, err
|
||||
}
|
||||
activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
|
||||
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
|
||||
|
||||
inputStr := string(input)
|
||||
if inputStr == "0" {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Get active pool address and symbol or fall back to default
|
||||
var activePoolAddress []byte
|
||||
activePoolAddress, err = userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
defaultPoolAddress := config.DefaultPoolAddress()
|
||||
// store the default as the active pool address
|
||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS, []byte(defaultPoolAddress))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write default PoolContractAdrress", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "value", defaultPoolAddress, "error", err)
|
||||
return res, err
|
||||
}
|
||||
activePoolAddress = []byte(defaultPoolAddress)
|
||||
} else {
|
||||
logg.ErrorCtxf(ctx, "failed to read active PoolContractAdrress", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "error", err)
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
var activePoolSymbol []byte
|
||||
activePoolSymbol, err = userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
defaultPoolSym := config.DefaultPoolName()
|
||||
// store the default as the active pool symbol
|
||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM, []byte(defaultPoolSym))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write default Pool Symbol", "key", storedb.DATA_ACTIVE_POOL_SYM, "value", defaultPoolSym, "error", err)
|
||||
return res, err
|
||||
}
|
||||
activePoolSymbol = []byte(defaultPoolSym)
|
||||
} else {
|
||||
logg.ErrorCtxf(ctx, "failed to read active Pool symbol", "key", storedb.DATA_ACTIVE_POOL_SYM, "error", err)
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
// call the api using the ActivePoolAddress and ActiveVoucherAddress to check if it is part of the pool
|
||||
r, err := h.accountService.CheckTokenInPool(ctx, string(activePoolAddress), string(activeAddress))
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
logg.ErrorCtxf(ctx, "failed on CheckTokenInPool", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
logg.InfoCtxf(ctx, "CheckTokenInPool", "response", r, "active_pool_address", string(activePoolAddress), "active_symbol_address", string(activeAddress))
|
||||
|
||||
if !r.CanSwapFrom {
|
||||
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
||||
res.Content = l.Get(
|
||||
"%s is not in %s. Please update your voucher and try again.",
|
||||
activeSym,
|
||||
activePoolSymbol,
|
||||
)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
||||
|
||||
// call the api using the activePoolAddress to get a list of SwapToSymbolsData
|
||||
swapToList, err := h.accountService.GetPoolSwappableVouchers(ctx, string(activePoolAddress))
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
logg.InfoCtxf(ctx, "GetPoolSwappableVouchers", "swapToList", swapToList)
|
||||
|
||||
// Return if there are no vouchers
|
||||
if len(swapToList) == 0 {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
data := store.ProcessVouchers(swapToList)
|
||||
|
||||
logg.InfoCtxf(ctx, "ProcessVouchers", "data", data)
|
||||
|
||||
// Store all swap_to tokens data
|
||||
dataMap := map[storedb.DataTyp]string{
|
||||
storedb.DATA_POOL_TO_SYMBOLS: data.Symbols,
|
||||
storedb.DATA_POOL_TO_BALANCES: data.Balances,
|
||||
storedb.DATA_POOL_TO_DECIMALS: data.Decimals,
|
||||
storedb.DATA_POOL_TO_ADDRESSES: data.Addresses,
|
||||
}
|
||||
|
||||
for key, value := range dataMap {
|
||||
if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
res.Content = h.ReplaceSeparatorFunc(data.Symbols)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SwapMaxLimit returns the max FROM token
|
||||
// check if max/tokenDecimals > 0.1 for UX purposes and to prevent swapping of dust values
|
||||
func (h *MenuHandlers) SwapMaxLimit(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
|
||||
flag_low_swap_amount, _ := h.flagManager.GetFlag("flag_low_swap_amount")
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher, flag_low_swap_amount)
|
||||
|
||||
inputStr := string(input)
|
||||
if inputStr == "0" {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
userStore := h.userdataStore
|
||||
metadata, err := store.GetSwapToVoucherData(ctx, userStore, sessionId, inputStr)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("failed to retrieve swap to voucher data: %v", err)
|
||||
}
|
||||
if metadata == nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
logg.InfoCtxf(ctx, "Metadata from GetSwapToVoucherData:", "metadata", metadata)
|
||||
|
||||
// Store the active swap_to data
|
||||
if err := store.UpdateSwapToVoucherData(ctx, userStore, sessionId, metadata); err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed on UpdateSwapToVoucherData", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
swapData, err := store.ReadSwapData(ctx, userStore, sessionId)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// call the api using the ActivePoolAddress, ActiveSwapFromAddress, ActiveSwapToAddress and PublicKey to get the swap max limit
|
||||
logg.InfoCtxf(ctx, "Call GetSwapFromTokenMaxLimit with:", "ActivePoolAddress", swapData.ActivePoolAddress, "ActiveSwapFromAddress", swapData.ActiveSwapFromAddress, "ActiveSwapToAddress", swapData.ActiveSwapToAddress, "publicKey", swapData.PublicKey)
|
||||
r, err := h.accountService.GetSwapFromTokenMaxLimit(ctx, swapData.ActivePoolAddress, swapData.ActiveSwapFromAddress, swapData.ActiveSwapToAddress, swapData.PublicKey)
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
logg.ErrorCtxf(ctx, "failed on GetSwapFromTokenMaxLimit", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Scale down the amount
|
||||
maxAmountStr := store.ScaleDownBalance(r.Max, swapData.ActiveSwapFromDecimal)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
maxAmountFloat, err := strconv.ParseFloat(maxAmountStr, 64)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to parse maxAmountStr as float", "value", maxAmountStr, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Format to 2 decimal places
|
||||
maxStr := fmt.Sprintf("%.2f", maxAmountFloat)
|
||||
|
||||
if maxAmountFloat < 0.1 {
|
||||
// return with low amount flag
|
||||
res.Content = maxStr
|
||||
res.FlagSet = append(res.FlagSet, flag_low_swap_amount)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, []byte(maxStr))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write swap max amount entry with", "key", storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, "value", maxStr, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
res.Content = fmt.Sprintf(
|
||||
"Maximum: %s\n\nEnter amount of %s to swap for %s:",
|
||||
maxStr, swapData.ActiveSwapFromSym, swapData.ActiveSwapToSym,
|
||||
)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SwapPreview displays the swap preview and estimates
|
||||
func (h *MenuHandlers) SwapPreview(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
inputStr := string(input)
|
||||
if inputStr == "0" {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
|
||||
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
|
||||
userStore := h.userdataStore
|
||||
|
||||
swapData, err := store.ReadSwapPreviewData(ctx, userStore, sessionId)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
maxValue, err := strconv.ParseFloat(swapData.ActiveSwapMaxAmount, 64)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to convert the swapMaxAmount to a float", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
inputAmount, err := strconv.ParseFloat(inputStr, 64)
|
||||
if err != nil || inputAmount > maxValue {
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
||||
res.Content = inputStr
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Format the amount to 2 decimal places
|
||||
formattedAmount, err := store.TruncateDecimalString(inputStr, 2)
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
||||
res.Content = inputStr
|
||||
return res, nil
|
||||
}
|
||||
|
||||
finalAmountStr, err := store.ParseAndScaleAmount(formattedAmount, swapData.ActiveSwapFromDecimal)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT, []byte(finalAmountStr))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err)
|
||||
return res, err
|
||||
}
|
||||
// store the user's input amount in the temporary value
|
||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(inputStr))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// call the API to get the quote
|
||||
r, err := h.accountService.GetPoolSwapQuote(ctx, finalAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress)
|
||||
if err != nil {
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
res.Content = l.Get("Your request failed. Please try again later.")
|
||||
logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Scale down the quoted amount
|
||||
quoteAmountStr := store.ScaleDownBalance(r.OutValue, swapData.ActiveSwapToDecimal)
|
||||
qouteAmount, err := strconv.ParseFloat(quoteAmountStr, 64)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to parse quoteAmountStr as float", "value", quoteAmountStr, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Format to 2 decimal places
|
||||
qouteStr := fmt.Sprintf("%.2f", qouteAmount)
|
||||
|
||||
res.Content = fmt.Sprintf(
|
||||
"You will swap:\n%s %s for %s %s:",
|
||||
inputStr, swapData.ActiveSwapFromSym, qouteStr, swapData.ActiveSwapToSym,
|
||||
)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// InitiateSwap calls the poolSwap and returns a confirmation based on the result.
|
||||
func (h *MenuHandlers) InitiateSwap(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
||||
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
|
||||
userStore := h.userdataStore
|
||||
|
||||
swapData, err := store.ReadSwapPreviewData(ctx, userStore, sessionId)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
swapAmount, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read swapAmount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
swapAmountStr := string(swapAmount)
|
||||
|
||||
// Call the poolSwap API
|
||||
r, err := h.accountService.PoolSwap(ctx, swapAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress)
|
||||
if err != nil {
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
res.Content = l.Get("Your request failed. Please try again later.")
|
||||
logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
trackingId := r.TrackingId
|
||||
logg.InfoCtxf(ctx, "poolSwap", "trackingId", trackingId)
|
||||
|
||||
res.Content = l.Get(
|
||||
"Your request has been sent. You will receive an SMS when your %s %s has been swapped for %s.",
|
||||
swapData.TemporaryValue,
|
||||
swapData.ActiveSwapFromSym,
|
||||
swapData.ActiveSwapToSym,
|
||||
)
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_account_authorized)
|
||||
return res, nil
|
||||
}
|
@ -1,616 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.defalsify.org/vise.git/lang"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/common/person"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
)
|
||||
|
||||
// SaveFirstname updates the first name in the gdbm with the provided input.
|
||||
func (h *MenuHandlers) SaveFirstname(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
firstName := string(input)
|
||||
|
||||
store := h.userdataStore
|
||||
logdb := h.logDb
|
||||
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set")
|
||||
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
firstNameSet := h.st.MatchFlag(flag_firstname_set, true)
|
||||
if allowUpdate {
|
||||
temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||
if len(temporaryFirstName) == 0 {
|
||||
logg.ErrorCtxf(ctx, "temporaryFirstName is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||
return res, fmt.Errorf("Data error encountered")
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryFirstName))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write firstName entry with", "key", storedb.DATA_FIRST_NAME, "value", temporaryFirstName, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_firstname_set)
|
||||
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryFirstName))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write firtname db log entry", "key", storedb.DATA_FIRST_NAME, "value", temporaryFirstName)
|
||||
}
|
||||
} else {
|
||||
if firstNameSet {
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryFirstName entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", firstName, "error", err)
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
h.profile.InsertOrShift(0, firstName)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SaveFamilyname updates the family name in the gdbm with the provided input.
|
||||
func (h *MenuHandlers) SaveFamilyname(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
store := h.userdataStore
|
||||
logdb := h.logDb
|
||||
familyName := string(input)
|
||||
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set")
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
familyNameSet := h.st.MatchFlag(flag_familyname_set, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||
if len(temporaryFamilyName) == 0 {
|
||||
logg.ErrorCtxf(ctx, "temporaryFamilyName is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||
return res, fmt.Errorf("Data error encountered")
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME, []byte(temporaryFamilyName))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write familyName entry with", "key", storedb.DATA_FAMILY_NAME, "value", temporaryFamilyName, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_familyname_set)
|
||||
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME, []byte(temporaryFamilyName))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write firtname db log entry", "key", storedb.DATA_FAMILY_NAME, "value", temporaryFamilyName)
|
||||
}
|
||||
} else {
|
||||
if familyNameSet {
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryFamilyName entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", familyName, "error", err)
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
h.profile.InsertOrShift(1, familyName)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// VerifyYob verifies the length of the given input.
|
||||
func (h *MenuHandlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
|
||||
flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format")
|
||||
date := string(input)
|
||||
_, err = strconv.Atoi(date)
|
||||
if err != nil {
|
||||
// If conversion fails, input is not numeric
|
||||
res.FlagSet = append(res.FlagSet, flag_incorrect_date_format)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if person.IsValidYOb(date) {
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
|
||||
} else {
|
||||
res.FlagSet = append(res.FlagSet, flag_incorrect_date_format)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ResetIncorrectYob resets the incorrect date format flag after a new attempt.
|
||||
func (h *MenuHandlers) ResetIncorrectYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format")
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SaveYOB updates the Year of Birth(YOB) in the gdbm with the provided input.
|
||||
func (h *MenuHandlers) SaveYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
yob := string(input)
|
||||
store := h.userdataStore
|
||||
logdb := h.logDb
|
||||
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set")
|
||||
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
yobSet := h.st.MatchFlag(flag_yob_set, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryYob, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||
if len(temporaryYob) == 0 {
|
||||
logg.ErrorCtxf(ctx, "temporaryYob is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||
return res, fmt.Errorf("Data error encountered")
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_YOB, []byte(temporaryYob))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write yob entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", temporaryYob, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_yob_set)
|
||||
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_YOB, []byte(temporaryYob))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write yob db log entry", "key", storedb.DATA_YOB, "value", temporaryYob)
|
||||
}
|
||||
} else {
|
||||
if yobSet {
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryYob entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", yob, "error", err)
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
h.profile.InsertOrShift(3, yob)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SaveLocation updates the location in the gdbm with the provided input.
|
||||
func (h *MenuHandlers) SaveLocation(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
location := string(input)
|
||||
store := h.userdataStore
|
||||
logdb := h.logDb
|
||||
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
flag_location_set, _ := h.flagManager.GetFlag("flag_location_set")
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
locationSet := h.st.MatchFlag(flag_location_set, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryLocation, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||
if len(temporaryLocation) == 0 {
|
||||
logg.ErrorCtxf(ctx, "temporaryLocation is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||
return res, fmt.Errorf("Data error encountered")
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_LOCATION, []byte(temporaryLocation))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write location entry with", "key", storedb.DATA_LOCATION, "value", temporaryLocation, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_location_set)
|
||||
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_LOCATION, []byte(temporaryLocation))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write location db log entry", "key", storedb.DATA_LOCATION, "value", temporaryLocation)
|
||||
}
|
||||
} else {
|
||||
if locationSet {
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryLocation entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", location, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_location_set)
|
||||
} else {
|
||||
h.profile.InsertOrShift(4, location)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SaveGender updates the gender in the gdbm with the provided input.
|
||||
func (h *MenuHandlers) SaveGender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
symbol, _ := h.st.Where()
|
||||
var res resource.Result
|
||||
var err error
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
gender := strings.Split(symbol, "_")[1]
|
||||
store := h.userdataStore
|
||||
logdb := h.logDb
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set")
|
||||
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
genderSet := h.st.MatchFlag(flag_gender_set, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryGender, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||
if len(temporaryGender) == 0 {
|
||||
logg.ErrorCtxf(ctx, "temporaryGender is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||
return res, fmt.Errorf("Data error encountered")
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_GENDER, []byte(temporaryGender))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write gender entry with", "key", storedb.DATA_GENDER, "value", gender, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_gender_set)
|
||||
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_GENDER, []byte(temporaryGender))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write gender db log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", temporaryGender)
|
||||
}
|
||||
|
||||
} else {
|
||||
if genderSet {
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(gender))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryGender entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", gender, "error", err)
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
h.profile.InsertOrShift(2, gender)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SaveOfferings updates the offerings(goods and services provided by the user) in the gdbm with the provided input.
|
||||
func (h *MenuHandlers) SaveOfferings(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
offerings := string(input)
|
||||
store := h.userdataStore
|
||||
logdb := h.logDb
|
||||
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set")
|
||||
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
offeringsSet := h.st.MatchFlag(flag_offerings_set, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||
if len(temporaryOfferings) == 0 {
|
||||
logg.ErrorCtxf(ctx, "temporaryOfferings is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||
return res, fmt.Errorf("Data error encountered")
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_OFFERINGS, []byte(temporaryOfferings))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write offerings entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_offerings_set)
|
||||
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryOfferings))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write offerings db log entry", "key", storedb.DATA_OFFERINGS, "value", offerings)
|
||||
}
|
||||
} else {
|
||||
if offeringsSet {
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryOfferings entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
h.profile.InsertOrShift(5, offerings)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetCurrentProfileInfo retrieves specific profile fields based on the current state of the USSD session.
|
||||
// Uses flag management system to track profile field status and handle menu navigation.
|
||||
func (h *MenuHandlers) GetCurrentProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var profileInfo []byte
|
||||
var defaultValue string
|
||||
var err error
|
||||
|
||||
flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set")
|
||||
flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set")
|
||||
flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set")
|
||||
flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set")
|
||||
flag_location_set, _ := h.flagManager.GetFlag("flag_location_set")
|
||||
flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set")
|
||||
flag_back_set, _ := h.flagManager.GetFlag("flag_back_set")
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_back_set)
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
language, ok := ctx.Value("Language").(lang.Language)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("value for 'Language' is not of type lang.Language")
|
||||
}
|
||||
code := language.Code
|
||||
if code == "swa" {
|
||||
defaultValue = "Haipo"
|
||||
} else {
|
||||
defaultValue = "Not Provided"
|
||||
}
|
||||
|
||||
sm, _ := h.st.Where()
|
||||
parts := strings.SplitN(sm, "_", 2)
|
||||
filename := parts[1]
|
||||
dbKeyStr := "DATA_" + strings.ToUpper(filename)
|
||||
logg.InfoCtxf(ctx, "GetCurrentProfileInfo", "filename", filename, "dbKeyStr:", dbKeyStr)
|
||||
dbKey, err := storedb.StringToDataTyp(dbKeyStr)
|
||||
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
store := h.userdataStore
|
||||
|
||||
switch dbKey {
|
||||
case storedb.DATA_FIRST_NAME:
|
||||
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
res.Content = defaultValue
|
||||
break
|
||||
}
|
||||
logg.ErrorCtxf(ctx, "Failed to read first name entry with", "key", "error", storedb.DATA_FIRST_NAME, err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_firstname_set)
|
||||
res.Content = string(profileInfo)
|
||||
case storedb.DATA_FAMILY_NAME:
|
||||
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
res.Content = defaultValue
|
||||
break
|
||||
}
|
||||
logg.ErrorCtxf(ctx, "Failed to read family name entry with", "key", "error", storedb.DATA_FAMILY_NAME, err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_familyname_set)
|
||||
res.Content = string(profileInfo)
|
||||
|
||||
case storedb.DATA_GENDER:
|
||||
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_GENDER)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
res.Content = defaultValue
|
||||
break
|
||||
}
|
||||
logg.ErrorCtxf(ctx, "Failed to read gender entry with", "key", "error", storedb.DATA_GENDER, err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_gender_set)
|
||||
res.Content = string(profileInfo)
|
||||
case storedb.DATA_YOB:
|
||||
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_YOB)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
res.Content = defaultValue
|
||||
break
|
||||
}
|
||||
logg.ErrorCtxf(ctx, "Failed to read year of birth(yob) entry with", "key", "error", storedb.DATA_YOB, err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_yob_set)
|
||||
res.Content = string(profileInfo)
|
||||
case storedb.DATA_LOCATION:
|
||||
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
res.Content = defaultValue
|
||||
break
|
||||
}
|
||||
logg.ErrorCtxf(ctx, "Failed to read location entry with", "key", "error", storedb.DATA_LOCATION, err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_location_set)
|
||||
res.Content = string(profileInfo)
|
||||
case storedb.DATA_OFFERINGS:
|
||||
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
res.Content = defaultValue
|
||||
break
|
||||
}
|
||||
logg.ErrorCtxf(ctx, "Failed to read offerings entry with", "key", "error", storedb.DATA_OFFERINGS, err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_offerings_set)
|
||||
res.Content = string(profileInfo)
|
||||
case storedb.DATA_ACCOUNT_ALIAS:
|
||||
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
res.Content = defaultValue
|
||||
break
|
||||
}
|
||||
logg.ErrorCtxf(ctx, "Failed to read account alias entry with", "key", "error", storedb.DATA_ACCOUNT_ALIAS, err)
|
||||
return res, err
|
||||
}
|
||||
alias := string(profileInfo)
|
||||
if alias == "" {
|
||||
res.Content = defaultValue
|
||||
} else {
|
||||
res.Content = alias
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetProfileInfo provides a comprehensive view of a user's profile.
|
||||
func (h *MenuHandlers) GetProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var defaultValue string
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
language, ok := ctx.Value("Language").(lang.Language)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("value for 'Language' is not of type lang.Language")
|
||||
}
|
||||
code := language.Code
|
||||
if code == "swa" {
|
||||
defaultValue = "Haipo"
|
||||
} else {
|
||||
defaultValue = "Not Provided"
|
||||
}
|
||||
|
||||
// Helper function to handle nil byte slices and convert them to string
|
||||
getEntryOrDefault := func(entry []byte, err error) string {
|
||||
if err != nil || entry == nil {
|
||||
return defaultValue
|
||||
}
|
||||
return string(entry)
|
||||
}
|
||||
store := h.userdataStore
|
||||
// Retrieve user data as strings with fallback to defaultValue
|
||||
firstName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME))
|
||||
familyName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME))
|
||||
yob := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_YOB))
|
||||
gender := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_GENDER))
|
||||
location := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION))
|
||||
offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS))
|
||||
alias := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS))
|
||||
|
||||
if alias != defaultValue && alias != "" {
|
||||
alias = strings.Split(alias, ".")[0]
|
||||
} else {
|
||||
alias = defaultValue
|
||||
}
|
||||
|
||||
// Construct the full name
|
||||
name := person.ConstructName(firstName, familyName, defaultValue)
|
||||
|
||||
// Calculate age from year of birth
|
||||
age := defaultValue
|
||||
if yob != defaultValue {
|
||||
if yobInt, err := strconv.Atoi(yob); err == nil {
|
||||
age = strconv.Itoa(person.CalculateAgeWithYOB(yobInt))
|
||||
} else {
|
||||
return res, fmt.Errorf("invalid year of birth: %v", err)
|
||||
}
|
||||
}
|
||||
switch language.Code {
|
||||
case "eng":
|
||||
res.Content = fmt.Sprintf(
|
||||
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n",
|
||||
name, gender, age, location, offerings, alias,
|
||||
)
|
||||
case "swa":
|
||||
res.Content = fmt.Sprintf(
|
||||
"Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\nLakabu yako: %s\n",
|
||||
name, gender, age, location, offerings, alias,
|
||||
)
|
||||
default:
|
||||
res.Content = fmt.Sprintf(
|
||||
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n",
|
||||
name, gender, age, location, offerings, alias,
|
||||
)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// handles bulk updates of profile information.
|
||||
func (h *MenuHandlers) insertProfileItems(ctx context.Context, sessionId string, res *resource.Result) error {
|
||||
var err error
|
||||
userStore := h.userdataStore
|
||||
profileFlagNames := []string{
|
||||
"flag_firstname_set",
|
||||
"flag_familyname_set",
|
||||
"flag_yob_set",
|
||||
"flag_gender_set",
|
||||
"flag_location_set",
|
||||
"flag_offerings_set",
|
||||
}
|
||||
profileDataKeys := []storedb.DataTyp{
|
||||
storedb.DATA_FIRST_NAME,
|
||||
storedb.DATA_FAMILY_NAME,
|
||||
storedb.DATA_GENDER,
|
||||
storedb.DATA_YOB,
|
||||
storedb.DATA_LOCATION,
|
||||
storedb.DATA_OFFERINGS,
|
||||
}
|
||||
for index, profileItem := range h.profile.ProfileItems {
|
||||
// Ensure the profileItem is not "0"(is set)
|
||||
if profileItem != "0" {
|
||||
flag, _ := h.flagManager.GetFlag(profileFlagNames[index])
|
||||
isProfileItemSet := h.st.MatchFlag(flag, true)
|
||||
if !isProfileItemSet {
|
||||
err = userStore.WriteEntry(ctx, sessionId, profileDataKeys[index], []byte(profileItem))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write profile entry with", "key", profileDataKeys[index], "value", profileItem, "error", err)
|
||||
return err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateAllProfileItems is used to persist all the new profile information and setup the required profile flags.
|
||||
func (h *MenuHandlers) UpdateAllProfileItems(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
err := h.insertProfileItems(ctx, sessionId, &res)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
@ -1,766 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/vise.git/lang"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.defalsify.org/vise.git/state"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/profile"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"github.com/alecthomas/assert/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSaveFirstname(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
_, logdb := InitializeTestLogdbStore(t)
|
||||
|
||||
logDb := store.LogDb{
|
||||
Db: logdb,
|
||||
}
|
||||
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
flag_firstname_set, _ := fm.GetFlag("flag_firstname_set")
|
||||
|
||||
// Set the flag in the State
|
||||
mockState := state.NewState(128)
|
||||
mockState.SetFlag(flag_allow_update)
|
||||
|
||||
expectedResult := resource.Result{}
|
||||
|
||||
// Define test data
|
||||
firstName := "John"
|
||||
|
||||
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedResult.FlagSet = []uint32{flag_firstname_set}
|
||||
|
||||
// Create the MenuHandlers instance with the mock store
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
flagManager: fm,
|
||||
st: mockState,
|
||||
logDb: logDb,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
res, err := h.SaveFirstname(ctx, "save_firstname", []byte(firstName))
|
||||
|
||||
// Assert results
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedResult, res)
|
||||
|
||||
// Verify that the DATA_FIRST_NAME entry has been updated with the temporary value
|
||||
storedFirstName, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME)
|
||||
assert.Equal(t, firstName, string(storedFirstName))
|
||||
}
|
||||
|
||||
func TestSaveFamilyname(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
_, logdb := InitializeTestLogdbStore(t)
|
||||
|
||||
logDb := store.LogDb{
|
||||
Db: logdb,
|
||||
}
|
||||
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
flag_firstname_set, _ := fm.GetFlag("flag_familyname_set")
|
||||
|
||||
// Set the flag in the State
|
||||
mockState := state.NewState(128)
|
||||
mockState.SetFlag(flag_allow_update)
|
||||
|
||||
expectedResult := resource.Result{}
|
||||
|
||||
expectedResult.FlagSet = []uint32{flag_firstname_set}
|
||||
|
||||
// Define test data
|
||||
familyName := "Doeee"
|
||||
|
||||
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create the MenuHandlers instance with the mock store
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
st: mockState,
|
||||
flagManager: fm,
|
||||
logDb: logDb,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
res, err := h.SaveFamilyname(ctx, "save_familyname", []byte(familyName))
|
||||
|
||||
// Assert results
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedResult, res)
|
||||
|
||||
// Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value
|
||||
storedFamilyName, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME)
|
||||
assert.Equal(t, familyName, string(storedFamilyName))
|
||||
}
|
||||
|
||||
func TestVerifyYob(t *testing.T) {
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
|
||||
sessionId := "session123"
|
||||
// Create required mocks
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
mockState := state.NewState(16)
|
||||
flag_incorrect_date_format, _ := fm.GetFlag("flag_incorrect_date_format")
|
||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||
|
||||
h := &MenuHandlers{
|
||||
accountService: mockAccountService,
|
||||
flagManager: fm,
|
||||
st: mockState,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test with correct yob",
|
||||
input: []byte("1980"),
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_incorrect_date_format},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with incorrect yob",
|
||||
input: []byte("sgahaha"),
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_incorrect_date_format},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with numeric but less 4 digits",
|
||||
input: []byte("123"),
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_incorrect_date_format},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Call the method under test
|
||||
res, err := h.VerifyYob(ctx, "verify_yob", []byte(tt.input))
|
||||
|
||||
// Assert that no errors occurred
|
||||
assert.NoError(t, err)
|
||||
|
||||
//Assert that the account created flag has been set to the result
|
||||
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetIncorrectYob(t *testing.T) {
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
flag_incorrect_date_format, _ := fm.GetFlag("flag_incorrect_date_format")
|
||||
|
||||
// Define test cases
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test incorrect yob reset",
|
||||
input: []byte(""),
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_incorrect_date_format},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create the MenuHandlers instance with the mock flag manager
|
||||
h := &MenuHandlers{
|
||||
flagManager: fm,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
res, err := h.ResetIncorrectYob(context.Background(), "reset_incorrect_yob", tt.input)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Assert that the Result FlagSet has the required flags after language switch
|
||||
assert.Equal(t, res, tt.expectedResult, "Result should contain flag(s) that have been reset")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveYob(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
_, logdb := InitializeTestLogdbStore(t)
|
||||
|
||||
logDb := store.LogDb{
|
||||
Db: logdb,
|
||||
}
|
||||
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
flag_yob_set, _ := fm.GetFlag("flag_yob_set")
|
||||
|
||||
// Set the flag in the State
|
||||
mockState := state.NewState(108)
|
||||
mockState.SetFlag(flag_allow_update)
|
||||
|
||||
expectedResult := resource.Result{}
|
||||
|
||||
// Define test data
|
||||
yob := "1980"
|
||||
|
||||
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedResult.FlagSet = []uint32{flag_yob_set}
|
||||
|
||||
// Create the MenuHandlers instance with the mock store
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
flagManager: fm,
|
||||
st: mockState,
|
||||
logDb: logDb,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
res, err := h.SaveYob(ctx, "save_yob", []byte(yob))
|
||||
|
||||
// Assert results
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedResult, res)
|
||||
|
||||
// Verify that the DATA_YOB entry has been updated with the temporary value
|
||||
storedYob, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_YOB)
|
||||
assert.Equal(t, yob, string(storedYob))
|
||||
}
|
||||
|
||||
func TestSaveLocation(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
_, logdb := InitializeTestLogdbStore(t)
|
||||
|
||||
logDb := store.LogDb{
|
||||
Db: logdb,
|
||||
}
|
||||
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
flag_location_set, _ := fm.GetFlag("flag_location_set")
|
||||
|
||||
// Set the flag in the State
|
||||
mockState := state.NewState(108)
|
||||
mockState.SetFlag(flag_allow_update)
|
||||
|
||||
expectedResult := resource.Result{}
|
||||
|
||||
// Define test data
|
||||
location := "Kilifi"
|
||||
|
||||
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedResult.FlagSet = []uint32{flag_location_set}
|
||||
|
||||
// Create the MenuHandlers instance with the mock store
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
flagManager: fm,
|
||||
st: mockState,
|
||||
logDb: logDb,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
res, err := h.SaveLocation(ctx, "save_location", []byte(location))
|
||||
|
||||
// Assert results
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedResult, res)
|
||||
|
||||
// Verify that the DATA_LOCATION entry has been updated with the temporary value
|
||||
storedLocation, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION)
|
||||
assert.Equal(t, location, string(storedLocation))
|
||||
}
|
||||
|
||||
func TestSaveGender(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
_, logdb := InitializeTestLogdbStore(t)
|
||||
|
||||
logDb := store.LogDb{
|
||||
Db: logdb,
|
||||
}
|
||||
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
flag_gender_set, _ := fm.GetFlag("flag_gender_set")
|
||||
|
||||
// Set the flag in the State
|
||||
mockState := state.NewState(108)
|
||||
mockState.SetFlag(flag_allow_update)
|
||||
|
||||
// Define test cases
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectedGender string
|
||||
executingSymbol string
|
||||
}{
|
||||
{
|
||||
name: "Valid Male Input",
|
||||
input: []byte("1"),
|
||||
expectedGender: "male",
|
||||
executingSymbol: "set_male",
|
||||
},
|
||||
{
|
||||
name: "Valid Female Input",
|
||||
input: []byte("2"),
|
||||
expectedGender: "female",
|
||||
executingSymbol: "set_female",
|
||||
},
|
||||
{
|
||||
name: "Valid Unspecified Input",
|
||||
input: []byte("3"),
|
||||
executingSymbol: "set_unspecified",
|
||||
expectedGender: "unspecified",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mockState.ExecPath = append(mockState.ExecPath, tt.executingSymbol)
|
||||
// Create the MenuHandlers instance with the mock store
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
st: mockState,
|
||||
flagManager: fm,
|
||||
logDb: logDb,
|
||||
}
|
||||
|
||||
expectedResult := resource.Result{}
|
||||
|
||||
// Call the method
|
||||
res, err := h.SaveGender(ctx, "save_gender", tt.input)
|
||||
|
||||
expectedResult.FlagSet = []uint32{flag_gender_set}
|
||||
|
||||
// Assert results
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedResult, res)
|
||||
|
||||
// Verify that the DATA_GENDER entry has been updated with the temporary value
|
||||
storedGender, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_GENDER)
|
||||
assert.Equal(t, tt.expectedGender, string(storedGender))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveOfferings(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
_, logdb := InitializeTestLogdbStore(t)
|
||||
|
||||
logDb := store.LogDb{
|
||||
Db: logdb,
|
||||
}
|
||||
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
flag_offerings_set, _ := fm.GetFlag("flag_offerings_set")
|
||||
|
||||
// Set the flag in the State
|
||||
mockState := state.NewState(108)
|
||||
mockState.SetFlag(flag_allow_update)
|
||||
|
||||
expectedResult := resource.Result{}
|
||||
|
||||
// Define test data
|
||||
offerings := "Bananas"
|
||||
|
||||
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedResult.FlagSet = []uint32{flag_offerings_set}
|
||||
|
||||
// Create the MenuHandlers instance with the mock store
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
flagManager: fm,
|
||||
st: mockState,
|
||||
logDb: logDb,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
res, err := h.SaveOfferings(ctx, "save_offerings", []byte(offerings))
|
||||
|
||||
// Assert results
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedResult, res)
|
||||
|
||||
// Verify that the DATA_OFFERINGS entry has been updated with the temporary value
|
||||
storedOfferings, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS)
|
||||
assert.Equal(t, offerings, string(storedOfferings))
|
||||
}
|
||||
|
||||
func TestGetCurrentProfileInfo(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, store := InitializeTestStore(t)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
flag_firstname_set, _ := fm.GetFlag("flag_firstname_set")
|
||||
flag_familyname_set, _ := fm.GetFlag("flag_familyname_set")
|
||||
flag_yob_set, _ := fm.GetFlag("flag_yob_set")
|
||||
flag_gender_set, _ := fm.GetFlag("flag_gender_set")
|
||||
flag_location_set, _ := fm.GetFlag("flag_location_set")
|
||||
flag_offerings_set, _ := fm.GetFlag("flag_offerings_set")
|
||||
flag_back_set, _ := fm.GetFlag("flag_back_set")
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
flagManager: fm,
|
||||
st: state.NewState(16),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
execPath string
|
||||
dbKey storedb.DataTyp
|
||||
value string
|
||||
expected resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test fetching first name",
|
||||
execPath: "edit_first_name",
|
||||
dbKey: storedb.DATA_FIRST_NAME,
|
||||
value: "John",
|
||||
expected: resource.Result{
|
||||
FlagReset: []uint32{flag_back_set},
|
||||
FlagSet: []uint32{flag_firstname_set},
|
||||
Content: "John",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test fetching family name",
|
||||
execPath: "edit_family_name",
|
||||
dbKey: storedb.DATA_FAMILY_NAME,
|
||||
value: "Doe",
|
||||
expected: resource.Result{
|
||||
FlagReset: []uint32{flag_back_set},
|
||||
FlagSet: []uint32{flag_familyname_set},
|
||||
Content: "Doe",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test fetching year of birth",
|
||||
execPath: "edit_yob",
|
||||
dbKey: storedb.DATA_YOB,
|
||||
value: "1980",
|
||||
expected: resource.Result{
|
||||
FlagReset: []uint32{flag_back_set},
|
||||
FlagSet: []uint32{flag_yob_set},
|
||||
Content: "1980",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test fetching gender",
|
||||
execPath: "edit_gender",
|
||||
dbKey: storedb.DATA_GENDER,
|
||||
value: "Male",
|
||||
expected: resource.Result{
|
||||
FlagReset: []uint32{flag_back_set},
|
||||
FlagSet: []uint32{flag_gender_set},
|
||||
Content: "Male",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test fetching location",
|
||||
execPath: "edit_location",
|
||||
dbKey: storedb.DATA_LOCATION,
|
||||
value: "Nairobi",
|
||||
expected: resource.Result{
|
||||
FlagReset: []uint32{flag_back_set},
|
||||
FlagSet: []uint32{flag_location_set},
|
||||
Content: "Nairobi",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test fetching offerings",
|
||||
execPath: "edit_offerings",
|
||||
dbKey: storedb.DATA_OFFERINGS,
|
||||
value: "Fruits",
|
||||
expected: resource.Result{
|
||||
FlagReset: []uint32{flag_back_set},
|
||||
FlagSet: []uint32{flag_offerings_set},
|
||||
Content: "Fruits",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
ctx = context.WithValue(ctx, "Language", lang.Language{
|
||||
Code: "eng",
|
||||
})
|
||||
// Set ExecPath to include tt.execPath
|
||||
h.st.ExecPath = []string{tt.execPath}
|
||||
|
||||
if tt.value != "" {
|
||||
err := store.WriteEntry(ctx, sessionId, tt.dbKey, []byte(tt.value))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
res, err := h.GetCurrentProfileInfo(ctx, tt.execPath, []byte(""))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.expected, res, "Result should match the expected output")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProfileInfo(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, store := InitializeTestStore(t)
|
||||
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
mockState := state.NewState(16)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
accountService: mockAccountService,
|
||||
st: mockState,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
languageCode string
|
||||
keys []storedb.DataTyp
|
||||
profileInfo []string
|
||||
result resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test with full profile information in eng",
|
||||
keys: []storedb.DataTyp{storedb.DATA_FAMILY_NAME, storedb.DATA_FIRST_NAME, storedb.DATA_GENDER, storedb.DATA_OFFERINGS, storedb.DATA_LOCATION, storedb.DATA_YOB, storedb.DATA_ACCOUNT_ALIAS},
|
||||
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976", "DoeJohn"},
|
||||
languageCode: "eng",
|
||||
result: resource.Result{
|
||||
Content: fmt.Sprintf(
|
||||
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n",
|
||||
"John Doee", "Male", "49", "Kilifi", "Bananas", "DoeJohn",
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with with profile information in swa",
|
||||
keys: []storedb.DataTyp{storedb.DATA_FAMILY_NAME, storedb.DATA_FIRST_NAME, storedb.DATA_GENDER, storedb.DATA_OFFERINGS, storedb.DATA_LOCATION, storedb.DATA_YOB, storedb.DATA_ACCOUNT_ALIAS},
|
||||
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976", "DoeJohn"},
|
||||
languageCode: "swa",
|
||||
result: resource.Result{
|
||||
Content: fmt.Sprintf(
|
||||
"Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\nLakabu yako: %s\n",
|
||||
"John Doee", "Male", "49", "Kilifi", "Bananas", "DoeJohn",
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with with profile information with language that is not yet supported",
|
||||
keys: []storedb.DataTyp{storedb.DATA_FAMILY_NAME, storedb.DATA_FIRST_NAME, storedb.DATA_GENDER, storedb.DATA_OFFERINGS, storedb.DATA_LOCATION, storedb.DATA_YOB, storedb.DATA_ACCOUNT_ALIAS},
|
||||
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976", "DoeJohn"},
|
||||
languageCode: "nor",
|
||||
result: resource.Result{
|
||||
Content: fmt.Sprintf(
|
||||
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n",
|
||||
"John Doee", "Male", "49", "Kilifi", "Bananas", "DoeJohn",
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
ctx = context.WithValue(ctx, "Language", lang.Language{
|
||||
Code: tt.languageCode,
|
||||
})
|
||||
for index, key := range tt.keys {
|
||||
err := store.WriteEntry(ctx, sessionId, key, []byte(tt.profileInfo[index]))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
res, _ := h.GetProfileInfo(ctx, "get_profile_info", []byte(""))
|
||||
|
||||
//Assert that the result set to content is what was expected
|
||||
assert.Equal(t, res, tt.result, "Result should contain profile information served back to user")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertProfileItems(t *testing.T) {
|
||||
ctx, store := InitializeTestStore(t)
|
||||
sessionId := "session123"
|
||||
mockState := state.NewState(128)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
profileDataKeys := []storedb.DataTyp{
|
||||
storedb.DATA_FIRST_NAME,
|
||||
storedb.DATA_FAMILY_NAME,
|
||||
storedb.DATA_GENDER,
|
||||
storedb.DATA_YOB,
|
||||
storedb.DATA_LOCATION,
|
||||
storedb.DATA_OFFERINGS,
|
||||
}
|
||||
|
||||
profileItems := []string{"John", "Doe", "Male", "1990", "Nairobi", "Software"}
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
flagManager: fm,
|
||||
st: mockState,
|
||||
profile: &profile.Profile{
|
||||
ProfileItems: profileItems,
|
||||
Max: 6,
|
||||
},
|
||||
}
|
||||
|
||||
res := &resource.Result{}
|
||||
err = h.insertProfileItems(ctx, sessionId, res)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Loop through profileDataKeys to validate stored values
|
||||
for i, key := range profileDataKeys {
|
||||
storedValue, err := store.ReadEntry(ctx, sessionId, key)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, profileItems[i], string(storedValue))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAllProfileItems(t *testing.T) {
|
||||
ctx, store := InitializeTestStore(t)
|
||||
sessionId := "session123"
|
||||
publicKey := "0X13242618721"
|
||||
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
mockState := state.NewState(128)
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
flag_firstname_set, _ := fm.GetFlag("flag_firstname_set")
|
||||
flag_familyname_set, _ := fm.GetFlag("flag_familyname_set")
|
||||
flag_yob_set, _ := fm.GetFlag("flag_yob_set")
|
||||
flag_gender_set, _ := fm.GetFlag("flag_gender_set")
|
||||
flag_location_set, _ := fm.GetFlag("flag_location_set")
|
||||
flag_offerings_set, _ := fm.GetFlag("flag_offerings_set")
|
||||
|
||||
profileDataKeys := []storedb.DataTyp{
|
||||
storedb.DATA_FIRST_NAME,
|
||||
storedb.DATA_FAMILY_NAME,
|
||||
storedb.DATA_GENDER,
|
||||
storedb.DATA_YOB,
|
||||
storedb.DATA_LOCATION,
|
||||
storedb.DATA_OFFERINGS,
|
||||
}
|
||||
|
||||
profileItems := []string{"John", "Doe", "Male", "1990", "Nairobi", "Software"}
|
||||
|
||||
expectedResult := resource.Result{
|
||||
FlagSet: []uint32{
|
||||
flag_firstname_set,
|
||||
flag_familyname_set,
|
||||
flag_yob_set,
|
||||
flag_gender_set,
|
||||
flag_location_set,
|
||||
flag_offerings_set,
|
||||
},
|
||||
}
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
flagManager: fm,
|
||||
st: mockState,
|
||||
accountService: mockAccountService,
|
||||
profile: &profile.Profile{
|
||||
ProfileItems: profileItems,
|
||||
Max: 6,
|
||||
},
|
||||
}
|
||||
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Call the function under test
|
||||
res, err := h.UpdateAllProfileItems(ctx, "symbol", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Loop through profileDataKeys to validate stored values
|
||||
for i, key := range profileDataKeys {
|
||||
storedValue, err := store.ReadEntry(ctx, sessionId, key)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, profileItems[i], string(storedValue))
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedResult, res)
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
|
||||
"git.grassecon.net/grassrootseconomics/common/hex"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
)
|
||||
|
||||
// handles the account creation when no existing account is present for the session and stores associated data in the user data store.
|
||||
func (h *MenuHandlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error {
|
||||
flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
|
||||
flag_account_creation_failed, _ := h.flagManager.GetFlag("flag_account_creation_failed")
|
||||
|
||||
r, err := h.accountService.CreateAccount(ctx)
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_account_creation_failed)
|
||||
logg.ErrorCtxf(ctx, "failed to create an account", "error", err)
|
||||
return nil
|
||||
}
|
||||
res.FlagReset = append(res.FlagReset, flag_account_creation_failed)
|
||||
|
||||
trackingId := r.TrackingId
|
||||
publicKey := r.PublicKey
|
||||
|
||||
data := map[storedb.DataTyp]string{
|
||||
storedb.DATA_TRACKING_ID: trackingId,
|
||||
storedb.DATA_PUBLIC_KEY: publicKey,
|
||||
storedb.DATA_ACCOUNT_ALIAS: "",
|
||||
}
|
||||
store := h.userdataStore
|
||||
logdb := h.logDb
|
||||
for key, value := range data {
|
||||
err = store.WriteEntry(ctx, sessionId, key, []byte(value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write log entry", "key", key, "value", value)
|
||||
}
|
||||
}
|
||||
publicKeyNormalized, err := hex.NormalizeHex(publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = store.WriteEntry(ctx, publicKeyNormalized, storedb.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write log entry", "key", storedb.DATA_PUBLIC_KEY_REVERSE, "value", sessionId)
|
||||
}
|
||||
|
||||
res.FlagSet = append(res.FlagSet, flag_account_created)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateAccount checks if any account exists on the JSON data file, and if not,
|
||||
// creates an account on the API,
|
||||
// sets the default values and flags.
|
||||
func (h *MenuHandlers) CreateAccount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
store := h.userdataStore
|
||||
_, err = store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
logg.InfoCtxf(ctx, "Creating an account because it doesn't exist")
|
||||
err = h.createAccountNoExist(ctx, sessionId, &res)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed on createAccountNoExist", "error", err)
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/models"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
"github.com/alecthomas/assert/v2"
|
||||
)
|
||||
|
||||
func TestCreateAccount(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
_, logdb := InitializeTestLogdbStore(t)
|
||||
|
||||
logDb := store.LogDb{
|
||||
Db: logdb,
|
||||
}
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
|
||||
flag_account_created, err := fm.GetFlag("flag_account_created")
|
||||
flag_account_creation_failed, _ := fm.GetFlag("flag_account_creation_failed")
|
||||
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
serverResponse *models.AccountResult
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test account creation success",
|
||||
serverResponse: &models.AccountResult{
|
||||
TrackingId: "1234567890",
|
||||
PublicKey: "0xD3adB33f",
|
||||
},
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_account_created},
|
||||
FlagReset: []uint32{flag_account_creation_failed},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
accountService: mockAccountService,
|
||||
logDb: logDb,
|
||||
flagManager: fm,
|
||||
}
|
||||
|
||||
mockAccountService.On("CreateAccount").Return(tt.serverResponse, nil)
|
||||
|
||||
// Call the method you want to test
|
||||
res, err := h.CreateAccount(ctx, "create_account", []byte(""))
|
||||
|
||||
// Assert that no errors occurred
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Assert that the account created flag has been set to the result
|
||||
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||
})
|
||||
}
|
||||
}
|
@ -1,380 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/common/identity"
|
||||
"git.grassecon.net/grassrootseconomics/common/phone"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/models"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"github.com/grassrootseconomics/ethutils"
|
||||
"gopkg.in/leonelquinteros/gotext.v1"
|
||||
)
|
||||
|
||||
// ValidateRecipient validates that the given input is valid.
|
||||
//
|
||||
// TODO: split up functino
|
||||
func (h *MenuHandlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var AliasAddressResult string
|
||||
var AliasAddress *models.AliasAddress
|
||||
store := h.userdataStore
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient")
|
||||
flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite")
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||
|
||||
// remove white spaces
|
||||
recipient := strings.ReplaceAll(string(input), " ", "")
|
||||
|
||||
if recipient != "0" {
|
||||
recipientType, err := identity.CheckRecipient(recipient)
|
||||
if err != nil {
|
||||
// Invalid recipient format (not a phone number, address, or valid alias format)
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_recipient)
|
||||
res.Content = recipient
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// save the recipient as the temporaryRecipient
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(recipient))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryRecipient entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", recipient, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
switch recipientType {
|
||||
case "phone number":
|
||||
// format the phone number
|
||||
formattedNumber, err := phone.FormatPhoneNumber(recipient)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", recipient, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Check if the phone number is registered
|
||||
publicKey, err := store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
logg.InfoCtxf(ctx, "Unregistered phone number: %s", recipient)
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite)
|
||||
res.Content = recipient
|
||||
return res, nil
|
||||
}
|
||||
|
||||
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Save the publicKey as the recipient
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, publicKey)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", string(publicKey), "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
case "address":
|
||||
// checksum the address
|
||||
address := ethutils.ChecksumAddress(recipient)
|
||||
|
||||
// Save the valid Ethereum address as the recipient
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(address))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", recipient, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
case "alias":
|
||||
if strings.Contains(recipient, ".") {
|
||||
AliasAddress, err = h.accountService.CheckAliasAddress(ctx, recipient)
|
||||
if err == nil {
|
||||
AliasAddressResult = AliasAddress.Address
|
||||
} else {
|
||||
logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", recipient, "error_alias_check", err)
|
||||
}
|
||||
} else {
|
||||
//Perform a search for each search domain,break on first match
|
||||
for _, domain := range config.SearchDomains() {
|
||||
fqdn := fmt.Sprintf("%s.%s", recipient, domain)
|
||||
logg.InfoCtxf(ctx, "Resolving with fqdn alias", "alias", fqdn)
|
||||
AliasAddress, err = h.accountService.CheckAliasAddress(ctx, fqdn)
|
||||
if err == nil {
|
||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||
AliasAddressResult = AliasAddress.Address
|
||||
continue
|
||||
} else {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", recipient, "error_alias_check", err)
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if AliasAddressResult == "" {
|
||||
res.Content = recipient
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_recipient)
|
||||
return res, nil
|
||||
} else {
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(AliasAddressResult))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", AliasAddressResult, "error", err)
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// TransactionReset resets the previous transaction data (Recipient and Amount)
|
||||
// as well as the invalid flags.
|
||||
func (h *MenuHandlers) TransactionReset(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient")
|
||||
flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite")
|
||||
store := h.userdataStore
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(""))
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(""))
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_invalid_recipient, flag_invalid_recipient_with_invite)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ResetTransactionAmount resets the transaction amount and invalid flag.
|
||||
func (h *MenuHandlers) ResetTransactionAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
|
||||
store := h.userdataStore
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(""))
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_invalid_amount)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// MaxAmount gets the current balance from the API and sets it as
|
||||
// the result content.
|
||||
func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
store := h.userdataStore
|
||||
|
||||
activeBal, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
res.Content = string(activeBal)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ValidateAmount ensures that the given input is a valid amount and that
|
||||
// it is not more than the current balance.
|
||||
func (h *MenuHandlers) ValidateAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
|
||||
userStore := h.userdataStore
|
||||
|
||||
var balanceValue float64
|
||||
|
||||
// retrieve the active balance
|
||||
activeBal, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err)
|
||||
return res, err
|
||||
}
|
||||
balanceValue, err = strconv.ParseFloat(string(activeBal), 64)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to convert the activeBal to a float", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Extract numeric part from the input amount
|
||||
amountStr := strings.TrimSpace(string(input))
|
||||
inputAmount, err := strconv.ParseFloat(amountStr, 64)
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
||||
res.Content = amountStr
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if inputAmount > balanceValue {
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
||||
res.Content = amountStr
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Format the amount to 2 decimal places before saving (truncated)
|
||||
formattedAmount, err := store.TruncateDecimalString(amountStr, 2)
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
||||
res.Content = amountStr
|
||||
return res, nil
|
||||
}
|
||||
|
||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(formattedAmount))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write amount entry with", "key", storedb.DATA_AMOUNT, "value", formattedAmount, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
res.Content = formattedAmount
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetRecipient returns the transaction recipient phone number from the gdbm.
|
||||
func (h *MenuHandlers) GetRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
store := h.userdataStore
|
||||
recipient, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||
if len(recipient) == 0 {
|
||||
logg.ErrorCtxf(ctx, "recipient is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||
return res, fmt.Errorf("Data error encountered")
|
||||
}
|
||||
|
||||
res.Content = string(recipient)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetSender returns the sessionId (phoneNumber).
|
||||
func (h *MenuHandlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
res.Content = sessionId
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetAmount retrieves the amount from teh Gdbm Db.
|
||||
func (h *MenuHandlers) GetAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
store := h.userdataStore
|
||||
|
||||
// retrieve the active symbol
|
||||
activeSym, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
amount, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_AMOUNT)
|
||||
|
||||
res.Content = fmt.Sprintf("%s %s", string(amount), string(activeSym))
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// InitiateTransaction calls the TokenTransfer and returns a confirmation based on the result.
|
||||
func (h *MenuHandlers) InitiateTransaction(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
||||
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
|
||||
data, err := store.ReadTransactionData(ctx, h.userdataStore, sessionId)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
finalAmountStr, err := store.ParseAndScaleAmount(data.Amount, data.ActiveDecimal)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Call TokenTransfer
|
||||
r, err := h.accountService.TokenTransfer(ctx, finalAmountStr, data.PublicKey, data.Recipient, data.ActiveAddress)
|
||||
if err != nil {
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
res.Content = l.Get("Your request failed. Please try again later.")
|
||||
logg.ErrorCtxf(ctx, "failed on TokenTransfer", "error", err)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
trackingId := r.TrackingId
|
||||
logg.InfoCtxf(ctx, "TokenTransfer", "trackingId", trackingId)
|
||||
|
||||
res.Content = l.Get(
|
||||
"Your request has been sent. %s will receive %s %s from %s.",
|
||||
data.TemporaryValue,
|
||||
data.Amount,
|
||||
data.ActiveSym,
|
||||
sessionId,
|
||||
)
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_account_authorized)
|
||||
return res, nil
|
||||
}
|
@ -1,550 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/models"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"github.com/alecthomas/assert/v2"
|
||||
)
|
||||
|
||||
func TestValidateRecipient(t *testing.T) {
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
sessionId := "session123"
|
||||
publicKey := "0X13242618721"
|
||||
ctx, store := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
flag_invalid_recipient, _ := fm.GetFlag("flag_invalid_recipient")
|
||||
flag_invalid_recipient_with_invite, _ := fm.GetFlag("flag_invalid_recipient_with_invite")
|
||||
|
||||
// Define test cases
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectError bool
|
||||
expectedRecipient []byte
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test with invalid recepient",
|
||||
input: []byte("7?1234"),
|
||||
expectError: true,
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_invalid_recipient},
|
||||
Content: "7?1234",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with valid unregistered recepient",
|
||||
input: []byte("0712345678"),
|
||||
expectError: true,
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_invalid_recipient_with_invite},
|
||||
Content: "0712345678",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with valid registered recepient",
|
||||
input: []byte("0711223344"),
|
||||
expectError: false,
|
||||
expectedRecipient: []byte(publicKey),
|
||||
expectedResult: resource.Result{},
|
||||
},
|
||||
{
|
||||
name: "Test with address",
|
||||
input: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"),
|
||||
expectError: false,
|
||||
expectedRecipient: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"),
|
||||
expectedResult: resource.Result{},
|
||||
},
|
||||
{
|
||||
name: "Test with alias recepient",
|
||||
input: []byte("alias123.sarafu.local"),
|
||||
expectError: false,
|
||||
expectedRecipient: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"),
|
||||
expectedResult: resource.Result{},
|
||||
},
|
||||
{
|
||||
name: "Test for checksummed address",
|
||||
input: []byte("0x5523058cdffe5f3c1eadadd5015e55c6e00fb439"),
|
||||
expectError: false,
|
||||
expectedRecipient: []byte("0x5523058cdFfe5F3c1EaDADD5015E55C6E00fb439"),
|
||||
expectedResult: resource.Result{},
|
||||
},
|
||||
{
|
||||
name: "Test with valid registered recepient that has white spaces",
|
||||
input: []byte("0711 22 33 44"),
|
||||
expectError: false,
|
||||
expectedRecipient: []byte(publicKey),
|
||||
expectedResult: resource.Result{},
|
||||
},
|
||||
}
|
||||
|
||||
// store a public key for the valid recipient
|
||||
err = store.WriteEntry(ctx, "+254711223344", storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
// Create the MenuHandlers instance
|
||||
h := &MenuHandlers{
|
||||
flagManager: fm,
|
||||
userdataStore: store,
|
||||
accountService: mockAccountService,
|
||||
}
|
||||
|
||||
aliasResponse := &models.AliasAddress{
|
||||
Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
||||
}
|
||||
|
||||
mockAccountService.On("CheckAliasAddress", string(tt.input)).Return(aliasResponse, nil)
|
||||
|
||||
// Call the method
|
||||
res, err := h.ValidateRecipient(ctx, "validate_recepient", tt.input)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !tt.expectError {
|
||||
storedRecipientAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_RECIPIENT)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedRecipient, storedRecipientAddress)
|
||||
}
|
||||
|
||||
// Assert that the Result FlagSet has the required flags after language switch
|
||||
assert.Equal(t, res, tt.expectedResult, "Result should contain flag(s) that have been reset")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionReset(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, store := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
|
||||
flag_invalid_recipient, _ := fm.GetFlag("flag_invalid_recipient")
|
||||
flag_invalid_recipient_with_invite, _ := fm.GetFlag("flag_invalid_recipient_with_invite")
|
||||
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
accountService: mockAccountService,
|
||||
flagManager: fm,
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
status string
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test transaction reset for amount and recipient",
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_invalid_recipient, flag_invalid_recipient_with_invite},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Call the method under test
|
||||
res, _ := h.TransactionReset(ctx, "transaction_reset", tt.input)
|
||||
|
||||
// Assert that no errors occurred
|
||||
assert.NoError(t, err)
|
||||
|
||||
//Assert that the account created flag has been set to the result
|
||||
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetTransactionAmount(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, store := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
|
||||
flag_invalid_amount, _ := fm.GetFlag("flag_invalid_amount")
|
||||
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
accountService: mockAccountService,
|
||||
flagManager: fm,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test amount reset",
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_invalid_amount},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Call the method under test
|
||||
res, _ := h.ResetTransactionAmount(ctx, "transaction_reset_amount", []byte(""))
|
||||
|
||||
// Assert that no errors occurred
|
||||
assert.NoError(t, err)
|
||||
|
||||
//Assert that the account created flag has been set to the result
|
||||
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxAmount(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
activeBal := "500"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
sessionId string
|
||||
activeBal string
|
||||
expectedError bool
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Valid session ID and active balance",
|
||||
sessionId: sessionId,
|
||||
activeBal: activeBal,
|
||||
expectedError: false,
|
||||
expectedResult: resource.Result{Content: activeBal},
|
||||
},
|
||||
{
|
||||
name: "Missing Session ID",
|
||||
sessionId: "",
|
||||
activeBal: activeBal,
|
||||
expectedError: true,
|
||||
expectedResult: resource.Result{},
|
||||
},
|
||||
{
|
||||
name: "Failed to Read Active Balance",
|
||||
sessionId: sessionId,
|
||||
activeBal: "", // failure to read active balance
|
||||
expectedError: true,
|
||||
expectedResult: resource.Result{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
if tt.sessionId != "" {
|
||||
ctx = context.WithValue(ctx, "SessionId", tt.sessionId)
|
||||
}
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
}
|
||||
|
||||
// Write active balance to the store only if it's not empty
|
||||
if tt.activeBal != "" {
|
||||
err := userStore.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACTIVE_BAL, []byte(tt.activeBal))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
res, err := h.MaxAmount(ctx, "max_amount", []byte(""))
|
||||
|
||||
if tt.expectedError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.expectedResult, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAmount(t *testing.T) {
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
|
||||
sessionId := "session123"
|
||||
|
||||
ctx, store := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
flag_invalid_amount, _ := fm.GetFlag("flag_invalid_amount")
|
||||
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
accountService: mockAccountService,
|
||||
flagManager: fm,
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
activeBal []byte
|
||||
balance string
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test with valid amount",
|
||||
input: []byte("4.10"),
|
||||
activeBal: []byte("5"),
|
||||
expectedResult: resource.Result{
|
||||
Content: "4.10",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with amount larger than active balance",
|
||||
input: []byte("5.02"),
|
||||
activeBal: []byte("5"),
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_invalid_amount},
|
||||
Content: "5.02",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with invalid amount format",
|
||||
input: []byte("0.02ms"),
|
||||
activeBal: []byte("5"),
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_invalid_amount},
|
||||
Content: "0.02ms",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with valid decimal amount",
|
||||
input: []byte("0.149"),
|
||||
activeBal: []byte("5"),
|
||||
expectedResult: resource.Result{
|
||||
Content: "0.14",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with valid large decimal amount",
|
||||
input: []byte("1.8599999999"),
|
||||
activeBal: []byte("5"),
|
||||
expectedResult: resource.Result{
|
||||
Content: "1.85",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL, []byte(tt.activeBal))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Call the method under test
|
||||
res, _ := h.ValidateAmount(ctx, "test_validate_amount", tt.input)
|
||||
|
||||
// Assert no errors occurred
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Assert the result matches the expected result
|
||||
assert.Equal(t, tt.expectedResult, res, "Expected result should match actual result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRecipient(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, store := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
recepient := "0712345678"
|
||||
|
||||
err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(recepient))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create the MenuHandlers instance with the mock store
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
res, _ := h.GetRecipient(ctx, "get_recipient", []byte(""))
|
||||
|
||||
//Assert that the retrieved recepient is what was set as the content
|
||||
assert.Equal(t, recepient, res.Content)
|
||||
}
|
||||
|
||||
func TestGetSender(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, _ := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
// Create the MenuHandlers instance
|
||||
h := &MenuHandlers{}
|
||||
|
||||
// Call the method
|
||||
res, _ := h.GetSender(ctx, "get_sender", []byte(""))
|
||||
|
||||
//Assert that the sessionId is what was set as the result content.
|
||||
assert.Equal(t, sessionId, res.Content)
|
||||
}
|
||||
|
||||
func TestGetAmount(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
ctx, store := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
// Define test data
|
||||
amount := "0.03"
|
||||
activeSym := "SRF"
|
||||
|
||||
err := store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(amount))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(activeSym))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create the MenuHandlers instance with the mock store
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
}
|
||||
|
||||
// Call the method
|
||||
res, _ := h.GetAmount(ctx, "get_amount", []byte(""))
|
||||
|
||||
formattedAmount := fmt.Sprintf("%s %s", amount, activeSym)
|
||||
|
||||
//Assert that the retrieved amount is what was set as the content
|
||||
assert.Equal(t, formattedAmount, res.Content)
|
||||
}
|
||||
|
||||
func TestInitiateTransaction(t *testing.T) {
|
||||
sessionId := "254712345678"
|
||||
ctx, store := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
account_authorized_flag, _ := fm.GetFlag("flag_account_authorized")
|
||||
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
accountService: mockAccountService,
|
||||
flagManager: fm,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
TemporaryValue []byte
|
||||
ActiveSym []byte
|
||||
StoredAmount []byte
|
||||
TransferAmount string
|
||||
PublicKey []byte
|
||||
Recipient []byte
|
||||
ActiveDecimal []byte
|
||||
ActiveAddress []byte
|
||||
TransferResponse *models.TokenTransferResponse
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test initiate transaction",
|
||||
TemporaryValue: []byte("0711223344"),
|
||||
ActiveSym: []byte("SRF"),
|
||||
StoredAmount: []byte("1.00"),
|
||||
TransferAmount: "1000000",
|
||||
PublicKey: []byte("0X13242618721"),
|
||||
Recipient: []byte("0x12415ass27192"),
|
||||
ActiveDecimal: []byte("6"),
|
||||
ActiveAddress: []byte("0xd4c288865Ce"),
|
||||
TransferResponse: &models.TokenTransferResponse{
|
||||
TrackingId: "1234567890",
|
||||
},
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{account_authorized_flag},
|
||||
Content: "Your request has been sent. 0711223344 will receive 1.00 SRF from 254712345678.",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tt.TemporaryValue))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.ActiveSym))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(tt.StoredAmount))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(tt.PublicKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(tt.Recipient))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_DECIMAL, []byte(tt.ActiveDecimal))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte(tt.ActiveAddress))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mockAccountService.On("TokenTransfer").Return(tt.TransferResponse, nil)
|
||||
|
||||
// Call the method under test
|
||||
res, _ := h.InitiateTransaction(ctx, "transaction_reset_amount", []byte(""))
|
||||
|
||||
// Assert that no errors occurred
|
||||
assert.NoError(t, err)
|
||||
|
||||
//Assert that the account created flag has been set to the result
|
||||
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||
})
|
||||
}
|
||||
}
|
@ -1,190 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
)
|
||||
|
||||
// CheckTransactions retrieves the transactions from the API using the "PublicKey" and stores to prefixDb.
|
||||
func (h *MenuHandlers) CheckTransactions(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
flag_no_transfers, _ := h.flagManager.GetFlag("flag_no_transfers")
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
|
||||
|
||||
userStore := h.userdataStore
|
||||
logdb := h.logDb
|
||||
publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Fetch transactions from the API using the public key
|
||||
transactionsResp, err := h.accountService.FetchTransactions(ctx, string(publicKey))
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||
|
||||
// Return if there are no transactions
|
||||
if len(transactionsResp) == 0 {
|
||||
res.FlagSet = append(res.FlagSet, flag_no_transfers)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
data := store.ProcessTransfers(transactionsResp)
|
||||
|
||||
// Store all transaction data
|
||||
dataMap := map[storedb.DataTyp]string{
|
||||
storedb.DATA_TX_SENDERS: data.Senders,
|
||||
storedb.DATA_TX_RECIPIENTS: data.Recipients,
|
||||
storedb.DATA_TX_VALUES: data.TransferValues,
|
||||
storedb.DATA_TX_ADDRESSES: data.Addresses,
|
||||
storedb.DATA_TX_HASHES: data.TxHashes,
|
||||
storedb.DATA_TX_DATES: data.Dates,
|
||||
storedb.DATA_TX_SYMBOLS: data.Symbols,
|
||||
storedb.DATA_TX_DECIMALS: data.Decimals,
|
||||
}
|
||||
|
||||
for key, value := range dataMap {
|
||||
if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write tx db log entry", "key", key, "value", value)
|
||||
}
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_no_transfers)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetTransactionsList fetches the list of transactions and formats them.
|
||||
func (h *MenuHandlers) GetTransactionsList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
userStore := h.userdataStore
|
||||
publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Read transactions from the store and format them
|
||||
TransactionSenders, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_SENDERS))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read the TransactionSenders from prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
TransactionSyms, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_SYMBOLS))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read the TransactionSyms from prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
TransactionValues, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_VALUES))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read the TransactionValues from prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
TransactionDates, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_DATES))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read the TransactionDates from prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Parse the data
|
||||
senders := strings.Split(string(TransactionSenders), "\n")
|
||||
syms := strings.Split(string(TransactionSyms), "\n")
|
||||
values := strings.Split(string(TransactionValues), "\n")
|
||||
dates := strings.Split(string(TransactionDates), "\n")
|
||||
|
||||
var formattedTransactions []string
|
||||
for i := 0; i < len(senders); i++ {
|
||||
sender := strings.TrimSpace(senders[i])
|
||||
sym := strings.TrimSpace(syms[i])
|
||||
value := strings.TrimSpace(values[i])
|
||||
date := strings.Split(strings.TrimSpace(dates[i]), " ")[0]
|
||||
|
||||
status := "Received"
|
||||
if sender == string(publicKey) {
|
||||
status = "Sent"
|
||||
}
|
||||
|
||||
// Use the ReplaceSeparator function for the menu separator
|
||||
transactionLine := fmt.Sprintf("%d%s%s %s %s %s", i+1, h.ReplaceSeparatorFunc(":"), status, value, sym, date)
|
||||
formattedTransactions = append(formattedTransactions, transactionLine)
|
||||
}
|
||||
|
||||
res.Content = strings.Join(formattedTransactions, "\n")
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ViewTransactionStatement retrieves the transaction statement
|
||||
// and displays it to the user.
|
||||
func (h *MenuHandlers) ViewTransactionStatement(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
userStore := h.userdataStore
|
||||
publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
flag_incorrect_statement, _ := h.flagManager.GetFlag("flag_incorrect_statement")
|
||||
|
||||
inputStr := string(input)
|
||||
if inputStr == "0" || inputStr == "99" || inputStr == "11" || inputStr == "22" {
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_statement)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Convert input string to integer
|
||||
index, err := strconv.Atoi(strings.TrimSpace(inputStr))
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("invalid input: must be a number between 1 and 10")
|
||||
}
|
||||
|
||||
if index < 1 || index > 10 {
|
||||
return res, fmt.Errorf("invalid input: index must be between 1 and 10")
|
||||
}
|
||||
|
||||
statement, err := store.GetTransferData(ctx, h.prefixDb, string(publicKey), index)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("failed to retrieve transfer data: %v", err)
|
||||
}
|
||||
|
||||
if statement == "" {
|
||||
res.FlagSet = append(res.FlagSet, flag_incorrect_statement)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_statement)
|
||||
res.Content = statement
|
||||
|
||||
return res, nil
|
||||
}
|
@ -1,265 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"github.com/alecthomas/assert/v2"
|
||||
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||
)
|
||||
|
||||
func TestCheckTransactions(t *testing.T) {
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
sessionId := "session123"
|
||||
publicKey := "0X13242618721"
|
||||
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
_, logdb := InitializeTestLogdbStore(t)
|
||||
|
||||
logDb := store.LogDb{
|
||||
Db: logdb,
|
||||
}
|
||||
|
||||
spdb := InitializeTestSubPrefixDb(t, ctx)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
accountService: mockAccountService,
|
||||
prefixDb: spdb,
|
||||
logDb: logDb,
|
||||
flagManager: fm,
|
||||
}
|
||||
|
||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mockTXResponse := []dataserviceapi.Last10TxResponse{
|
||||
{
|
||||
Sender: "0X13242618721", Recipient: "0x41c188d63Qa", TransferValue: "100", ContractAddress: "0X1324262343rfdGW23",
|
||||
TxHash: "0x123wefsf34rf", DateBlock: time.Now(), TokenSymbol: "SRF", TokenDecimals: "6",
|
||||
},
|
||||
{
|
||||
Sender: "0x41c188d63Qa", Recipient: "0X13242618721", TransferValue: "200", ContractAddress: "0X1324262343rfdGW23",
|
||||
TxHash: "0xq34wresfdb44", DateBlock: time.Now(), TokenSymbol: "SRF", TokenDecimals: "6",
|
||||
},
|
||||
}
|
||||
|
||||
expectedSenders := []byte("0X13242618721\n0x41c188d63Qa")
|
||||
|
||||
mockAccountService.On("FetchTransactions", string(publicKey)).Return(mockTXResponse, nil)
|
||||
|
||||
_, err = h.CheckTransactions(ctx, "check_transactions", []byte(""))
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Read tranfers senders data from the store
|
||||
senderData, err := spdb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_SENDERS))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// assert that the data is stored correctly
|
||||
assert.Equal(t, expectedSenders, senderData)
|
||||
|
||||
mockAccountService.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestGetTransactionsList(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
publicKey := "0X13242618721"
|
||||
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
spdb := InitializeTestSubPrefixDb(t, ctx)
|
||||
|
||||
// Initialize MenuHandlers
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
prefixDb: spdb,
|
||||
ReplaceSeparatorFunc: mockReplaceSeparator,
|
||||
}
|
||||
|
||||
err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dateBlock, err := time.Parse(time.RFC3339, "2024-10-03T07:23:12Z")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mockTXResponse := []dataserviceapi.Last10TxResponse{
|
||||
{
|
||||
Sender: "0X13242618721", Recipient: "0x41c188d63Qa", TransferValue: "1000", ContractAddress: "0X1324262343rfdGW23",
|
||||
TxHash: "0x123wefsf34rf", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2",
|
||||
},
|
||||
{
|
||||
Sender: "0x41c188d63Qa", Recipient: "0X13242618721", TransferValue: "2000", ContractAddress: "0X1324262343rfdGW23",
|
||||
TxHash: "0xq34wresfdb44", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2",
|
||||
},
|
||||
}
|
||||
|
||||
data := store.ProcessTransfers(mockTXResponse)
|
||||
|
||||
// Store all transaction data
|
||||
dataMap := map[storedb.DataTyp]string{
|
||||
storedb.DATA_TX_SENDERS: data.Senders,
|
||||
storedb.DATA_TX_RECIPIENTS: data.Recipients,
|
||||
storedb.DATA_TX_VALUES: data.TransferValues,
|
||||
storedb.DATA_TX_ADDRESSES: data.Addresses,
|
||||
storedb.DATA_TX_HASHES: data.TxHashes,
|
||||
storedb.DATA_TX_DATES: data.Dates,
|
||||
storedb.DATA_TX_SYMBOLS: data.Symbols,
|
||||
storedb.DATA_TX_DECIMALS: data.Decimals,
|
||||
}
|
||||
|
||||
for key, value := range dataMap {
|
||||
if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
expectedTransactionList := []byte("1: Sent 10 SRF 2024-10-03\n2: Received 20 SRF 2024-10-03")
|
||||
|
||||
res, err := h.GetTransactionsList(ctx, "", []byte(""))
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, res.Content, string(expectedTransactionList))
|
||||
}
|
||||
|
||||
func TestViewTransactionStatement(t *testing.T) {
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
sessionId := "session123"
|
||||
publicKey := "0X13242618721"
|
||||
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
spdb := InitializeTestSubPrefixDb(t, ctx)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
flag_incorrect_statement, _ := fm.GetFlag("flag_incorrect_statement")
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
prefixDb: spdb,
|
||||
flagManager: fm,
|
||||
}
|
||||
|
||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dateBlock, err := time.Parse(time.RFC3339, "2024-10-03T07:23:12Z")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mockTXResponse := []dataserviceapi.Last10TxResponse{
|
||||
{
|
||||
Sender: "0X13242618721", Recipient: "0x41c188d63Qa", TransferValue: "1000", ContractAddress: "0X1324262343rfdGW23",
|
||||
TxHash: "0x123wefsf34rf", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2",
|
||||
},
|
||||
{
|
||||
Sender: "0x41c188d63Qa", Recipient: "0X13242618721", TransferValue: "2000", ContractAddress: "0X1324262343rfdGW23",
|
||||
TxHash: "0xq34wresfdb44", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2",
|
||||
},
|
||||
}
|
||||
|
||||
data := store.ProcessTransfers(mockTXResponse)
|
||||
|
||||
// Store all transaction data
|
||||
dataMap := map[storedb.DataTyp]string{
|
||||
storedb.DATA_TX_SENDERS: data.Senders,
|
||||
storedb.DATA_TX_RECIPIENTS: data.Recipients,
|
||||
storedb.DATA_TX_VALUES: data.TransferValues,
|
||||
storedb.DATA_TX_ADDRESSES: data.Addresses,
|
||||
storedb.DATA_TX_HASHES: data.TxHashes,
|
||||
storedb.DATA_TX_DATES: data.Dates,
|
||||
storedb.DATA_TX_SYMBOLS: data.Symbols,
|
||||
storedb.DATA_TX_DECIMALS: data.Decimals,
|
||||
}
|
||||
|
||||
for key, value := range dataMap {
|
||||
if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectedError error
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Valid input - index 1",
|
||||
input: []byte("1"),
|
||||
expectedError: nil,
|
||||
expectedResult: resource.Result{
|
||||
Content: "Sent 10 SRF\nTo: 0x41c188d63Qa\nContract address: 0X1324262343rfdGW23\nTxhash: 0x123wefsf34rf\nDate: 2024-10-03 07:23:12 AM",
|
||||
FlagReset: []uint32{flag_incorrect_statement},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid input - index 2",
|
||||
input: []byte("2"),
|
||||
expectedError: nil,
|
||||
expectedResult: resource.Result{
|
||||
Content: "Received 20 SRF\nFrom: 0x41c188d63Qa\nContract address: 0X1324262343rfdGW23\nTxhash: 0xq34wresfdb44\nDate: 2024-10-03 07:23:12 AM",
|
||||
FlagReset: []uint32{flag_incorrect_statement},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid input - index 0",
|
||||
input: []byte("0"),
|
||||
expectedError: nil,
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_incorrect_statement},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid input - index 12",
|
||||
input: []byte("12"),
|
||||
expectedError: fmt.Errorf("invalid input: index must be between 1 and 10"),
|
||||
expectedResult: resource.Result{},
|
||||
},
|
||||
{
|
||||
name: "Invalid input - non-numeric",
|
||||
input: []byte("abc"),
|
||||
expectedError: fmt.Errorf("invalid input: must be a number between 1 and 10"),
|
||||
expectedResult: resource.Result{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
res, err := h.ViewTransactionStatement(ctx, "view_transaction_statement", tt.input)
|
||||
|
||||
if tt.expectedError != nil {
|
||||
assert.EqualError(t, err, tt.expectedError.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.expectedResult, res)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/common/phone"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"gopkg.in/leonelquinteros/gotext.v1"
|
||||
)
|
||||
|
||||
// InviteValidRecipient sends an invitation to the valid phone number.
|
||||
func (h *MenuHandlers) InviteValidRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
store := h.userdataStore
|
||||
smsservice := h.smsService
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
|
||||
recipient, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read invalid recipient info", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
if !phone.IsValidPhoneNumber(string(recipient)) {
|
||||
logg.InfoCtxf(ctx, "corrupted recipient", "key", storedb.DATA_TEMPORARY_VALUE, "recipient", recipient)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
_, err = smsservice.Accountservice.SendUpsellSMS(ctx, sessionId, string(recipient))
|
||||
if err != nil {
|
||||
res.Content = l.Get("Your invite request for %s to Sarafu Network failed. Please try again later.", string(recipient))
|
||||
return res, nil
|
||||
}
|
||||
res.Content = l.Get("Your invitation to %s to join Sarafu Network has been sent.", string(recipient))
|
||||
return res, nil
|
||||
}
|
@ -1,272 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||
"gopkg.in/leonelquinteros/gotext.v1"
|
||||
)
|
||||
|
||||
// ManageVouchers retrieves the token holdings from the API using the "PublicKey" and
|
||||
// 1. sets the first as the default voucher if no active voucher is set.
|
||||
// 2. Stores list of vouchers
|
||||
// 3. updates the balance of the active voucher
|
||||
func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
userStore := h.userdataStore
|
||||
logdb := h.logDb
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
flag_no_active_voucher, _ := h.flagManager.GetFlag("flag_no_active_voucher")
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||
|
||||
publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read publicKey entry", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Fetch vouchers from API
|
||||
vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey))
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
return res, nil
|
||||
}
|
||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||
|
||||
if len(vouchersResp) == 0 {
|
||||
res.FlagSet = append(res.FlagSet, flag_no_active_voucher)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_no_active_voucher)
|
||||
|
||||
// Check if user has an active voucher with proper error handling
|
||||
activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
// No active voucher, set the first one as default
|
||||
firstVoucher := vouchersResp[0]
|
||||
defaultSym := firstVoucher.TokenSymbol
|
||||
defaultBal := firstVoucher.Balance
|
||||
defaultDec := firstVoucher.TokenDecimals
|
||||
defaultAddr := firstVoucher.TokenAddress
|
||||
|
||||
// Scale down the balance
|
||||
scaledBalance := store.ScaleDownBalance(defaultBal, defaultDec)
|
||||
|
||||
firstVoucherMap := map[storedb.DataTyp]string{
|
||||
storedb.DATA_ACTIVE_SYM: defaultSym,
|
||||
storedb.DATA_ACTIVE_BAL: scaledBalance,
|
||||
storedb.DATA_ACTIVE_DECIMAL: defaultDec,
|
||||
storedb.DATA_ACTIVE_ADDRESS: defaultAddr,
|
||||
}
|
||||
|
||||
for key, value := range firstVoucherMap {
|
||||
if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to write active voucher data", "key", key, "error", err)
|
||||
return res, err
|
||||
}
|
||||
err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value))
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to write voucher db log entry", "key", key, "value", value)
|
||||
}
|
||||
}
|
||||
|
||||
logg.InfoCtxf(ctx, "Default voucher set", "symbol", defaultSym, "balance", defaultBal, "decimals", defaultDec, "address", defaultAddr)
|
||||
} else {
|
||||
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err)
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
// Update active voucher balance
|
||||
activeSymStr := string(activeSym)
|
||||
|
||||
// Find the matching voucher data
|
||||
var activeData *dataserviceapi.TokenHoldings
|
||||
for _, voucher := range vouchersResp {
|
||||
if voucher.TokenSymbol == activeSymStr {
|
||||
activeData = &voucher
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if activeData == nil {
|
||||
logg.ErrorCtxf(ctx, "activeSym not found in vouchers, setting the first voucher as the default", "activeSym", activeSymStr)
|
||||
firstVoucher := vouchersResp[0]
|
||||
activeData = &firstVoucher
|
||||
}
|
||||
|
||||
// Scale down the balance
|
||||
scaledBalance := store.ScaleDownBalance(activeData.Balance, activeData.TokenDecimals)
|
||||
|
||||
// Update the balance field with the scaled value
|
||||
activeData.Balance = scaledBalance
|
||||
|
||||
// Pass the matching voucher data to UpdateVoucherData
|
||||
if err := store.UpdateVoucherData(ctx, h.userdataStore, sessionId, activeData); err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err)
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
// Store all voucher data
|
||||
data := store.ProcessVouchers(vouchersResp)
|
||||
dataMap := map[storedb.DataTyp]string{
|
||||
storedb.DATA_VOUCHER_SYMBOLS: data.Symbols,
|
||||
storedb.DATA_VOUCHER_BALANCES: data.Balances,
|
||||
storedb.DATA_VOUCHER_DECIMALS: data.Decimals,
|
||||
storedb.DATA_VOUCHER_ADDRESSES: data.Addresses,
|
||||
}
|
||||
|
||||
// Write data entries
|
||||
for key, value := range dataMap {
|
||||
if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetVoucherList fetches the list of vouchers and formats them.
|
||||
func (h *MenuHandlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
userStore := h.userdataStore
|
||||
|
||||
// Read vouchers from the store
|
||||
voucherData, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS)
|
||||
logg.InfoCtxf(ctx, "reading voucherData in GetVoucherList", "sessionId", sessionId, "key", storedb.DATA_VOUCHER_SYMBOLS, "voucherData", voucherData)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read voucherData entires with", "key", storedb.DATA_VOUCHER_SYMBOLS, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
voucherBalances, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_BALANCES)
|
||||
logg.InfoCtxf(ctx, "reading voucherBalances in GetVoucherList", "sessionId", sessionId, "key", storedb.DATA_VOUCHER_BALANCES, "voucherBalances", voucherBalances)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read voucherData entires with", "key", storedb.DATA_VOUCHER_BALANCES, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
formattedVoucherList := store.FormatVoucherList(ctx, string(voucherData), string(voucherBalances))
|
||||
finalOutput := strings.Join(formattedVoucherList, "\n")
|
||||
|
||||
logg.InfoCtxf(ctx, "final output for GetVoucherList", "sessionId", sessionId, "finalOutput", finalOutput)
|
||||
|
||||
res.Content = finalOutput
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ViewVoucher retrieves the token holding and balance from the subprefixDB
|
||||
// and displays it to the user for them to select it.
|
||||
func (h *MenuHandlers) ViewVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
|
||||
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
||||
|
||||
inputStr := string(input)
|
||||
|
||||
metadata, err := store.GetVoucherData(ctx, h.userdataStore, sessionId, inputStr)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("failed to retrieve voucher data: %v", err)
|
||||
}
|
||||
|
||||
if metadata == nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if err := store.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed on StoreTemporaryVoucher", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
||||
res.Content = l.Get("Symbol: %s\nBalance: %s", metadata.TokenSymbol, metadata.Balance)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SetVoucher retrieves the temp voucher data and sets it as the active data.
|
||||
func (h *MenuHandlers) SetVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
// Get temporary data
|
||||
tempData, err := store.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed on GetTemporaryVoucherData", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Set as active and clear temporary data
|
||||
if err := store.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
res.Content = tempData.TokenSymbol
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetVoucherDetails retrieves the voucher details.
|
||||
func (h *MenuHandlers) GetVoucherDetails(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
store := h.userdataStore
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||
|
||||
// get the active address
|
||||
activeAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read activeAddress entry with", "key", storedb.DATA_ACTIVE_ADDRESS, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// use the voucher contract address to get the data from the API
|
||||
voucherData, err := h.accountService.VoucherData(ctx, string(activeAddress))
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
return res, nil
|
||||
}
|
||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||
|
||||
res.Content = fmt.Sprintf(
|
||||
"Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", voucherData.TokenName, voucherData.TokenSymbol, voucherData.TokenCommodity, voucherData.TokenLocation,
|
||||
)
|
||||
|
||||
return res, nil
|
||||
}
|
@ -1,282 +0,0 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/models"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"github.com/alecthomas/assert/v2"
|
||||
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||
)
|
||||
|
||||
func TestManageVouchers(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
publicKey := "0X13242618721"
|
||||
|
||||
ctx, userStore := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
_, logdb := InitializeTestLogdbStore(t)
|
||||
|
||||
logDb := store.LogDb{
|
||||
Db: logdb,
|
||||
}
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
flag_no_active_voucher, err := fm.GetFlag("flag_no_active_voucher")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
flag_api_error, err := fm.GetFlag("flag_api_call_error")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
vouchersResp []dataserviceapi.TokenHoldings
|
||||
storedActiveVoucher string
|
||||
expectedVoucherSymbols []byte
|
||||
expectedUpdatedAddress []byte
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "No vouchers available",
|
||||
vouchersResp: []dataserviceapi.TokenHoldings{},
|
||||
expectedVoucherSymbols: []byte(""),
|
||||
expectedUpdatedAddress: []byte(""),
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_no_active_voucher},
|
||||
FlagReset: []uint32{flag_api_error},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Set default voucher when no active voucher is set",
|
||||
vouchersResp: []dataserviceapi.TokenHoldings{
|
||||
{
|
||||
TokenAddress: "0x123",
|
||||
TokenSymbol: "TOKEN1",
|
||||
TokenDecimals: "18",
|
||||
Balance: "100",
|
||||
},
|
||||
},
|
||||
expectedVoucherSymbols: []byte("1:TOKEN1"),
|
||||
expectedUpdatedAddress: []byte("0x123"),
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_api_error, flag_no_active_voucher},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Check and update active voucher balance",
|
||||
vouchersResp: []dataserviceapi.TokenHoldings{
|
||||
{TokenAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"},
|
||||
{TokenAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"},
|
||||
},
|
||||
storedActiveVoucher: "SRF",
|
||||
expectedVoucherSymbols: []byte("1:SRF\n2:MILO"),
|
||||
expectedUpdatedAddress: []byte("0xd4c288865Ce"),
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_api_error, flag_no_active_voucher},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: userStore,
|
||||
accountService: mockAccountService,
|
||||
flagManager: fm,
|
||||
logDb: logDb,
|
||||
}
|
||||
|
||||
mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil)
|
||||
|
||||
// Store active voucher if needed
|
||||
if tt.storedActiveVoucher != "" {
|
||||
err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.storedActiveVoucher))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte("0x41c188D45rfg6ds"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
res, err := h.ManageVouchers(ctx, "manage_vouchers", []byte(""))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedResult, res)
|
||||
|
||||
if tt.storedActiveVoucher != "" {
|
||||
// Validate stored voucher symbols
|
||||
voucherData, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedVoucherSymbols, voucherData)
|
||||
|
||||
// Validate stored active contract address
|
||||
updatedAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedUpdatedAddress, updatedAddress)
|
||||
|
||||
mockAccountService.AssertExpectations(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVoucherList(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
|
||||
ctx, store := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
// Initialize MenuHandlers
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
ReplaceSeparatorFunc: mockReplaceSeparator,
|
||||
}
|
||||
|
||||
mockSymbols := []byte("1:SRF\n2:MILO")
|
||||
mockBalances := []byte("1:10.099999\n2:40.7")
|
||||
|
||||
// Put voucher symnols and balances data to the store
|
||||
err := store.WriteEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS, mockSymbols)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_VOUCHER_BALANCES, mockBalances)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedList := []byte("1: SRF 10.09\n2: MILO 40.70")
|
||||
|
||||
res, err := h.GetVoucherList(ctx, "", []byte(""))
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, res.Content, string(expectedList))
|
||||
}
|
||||
|
||||
func TestViewVoucher(t *testing.T) {
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
ctx, store := InitializeTestStore(t)
|
||||
sessionId := "session123"
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
flagManager: fm,
|
||||
}
|
||||
|
||||
// Define mock voucher data
|
||||
mockData := map[storedb.DataTyp][]byte{
|
||||
storedb.DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"),
|
||||
storedb.DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"),
|
||||
storedb.DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"),
|
||||
storedb.DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
|
||||
}
|
||||
|
||||
// Put the data
|
||||
for key, value := range mockData {
|
||||
err := store.WriteEntry(ctx, sessionId, key, []byte(value))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
res, err := h.ViewVoucher(ctx, "view_voucher", []byte("1"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, res.Content, "Symbol: SRF\nBalance: 100")
|
||||
}
|
||||
|
||||
func TestSetVoucher(t *testing.T) {
|
||||
ctx, store := InitializeTestStore(t)
|
||||
sessionId := "session123"
|
||||
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
}
|
||||
|
||||
// Define the temporary voucher data
|
||||
tempData := &dataserviceapi.TokenHoldings{
|
||||
TokenSymbol: "SRF",
|
||||
Balance: "200",
|
||||
TokenDecimals: "6",
|
||||
TokenAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
||||
}
|
||||
|
||||
expectedData := fmt.Sprintf("%s,%s,%s,%s", tempData.TokenSymbol, tempData.Balance, tempData.TokenDecimals, tempData.TokenAddress)
|
||||
|
||||
// store the expectedData
|
||||
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(expectedData)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := h.SetVoucher(ctx, "set_voucher", []byte(""))
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, string(tempData.TokenSymbol), res.Content)
|
||||
}
|
||||
|
||||
func TestGetVoucherDetails(t *testing.T) {
|
||||
ctx, store := InitializeTestStore(t)
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
|
||||
flag_api_error, _ := fm.GetFlag("flag_api_call_error")
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
|
||||
sessionId := "session123"
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
expectedResult := resource.Result{}
|
||||
|
||||
tokA_AAddress := "0x0000000000000000000000000000000000000000"
|
||||
|
||||
h := &MenuHandlers{
|
||||
userdataStore: store,
|
||||
flagManager: fm,
|
||||
accountService: mockAccountService,
|
||||
}
|
||||
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte(tokA_AAddress))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tokenDetails := &models.VoucherDataResult{
|
||||
TokenName: "Token A",
|
||||
TokenSymbol: "TOKA",
|
||||
TokenLocation: "Kilifi,Kenya",
|
||||
TokenCommodity: "Farming",
|
||||
}
|
||||
expectedResult.Content = fmt.Sprintf(
|
||||
"Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", tokenDetails.TokenName, tokenDetails.TokenSymbol, tokenDetails.TokenCommodity, tokenDetails.TokenLocation,
|
||||
)
|
||||
mockAccountService.On("VoucherData", string(tokA_AAddress)).Return(tokenDetails, nil)
|
||||
res, err := h.GetVoucherDetails(ctx, "SessionId", []byte(""))
|
||||
expectedResult.FlagReset = append(expectedResult.FlagReset, flag_api_error)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedResult, res)
|
||||
}
|
@ -35,6 +35,13 @@ func (eu *EventsUpdater) HandleCustodialRegistration(ctx context.Context, ev *ap
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// err = pe.Load(identity.SessionId)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// st := pe.GetState()
|
||||
// st.SetFlag(accountCreatedFlag)
|
||||
// return pe.Save(identity.SessionId)
|
||||
logg.DebugCtxf(ctx, "received custodial registration event", "identity", identity)
|
||||
return nil
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
"git.defalsify.org/vise.git/persist"
|
||||
apievent "git.grassecon.net/grassrootseconomics/sarafu-api/event"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
|
||||
apievent "git.grassecon.net/grassrootseconomics/sarafu-api/event"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -17,9 +17,9 @@ var (
|
||||
)
|
||||
|
||||
type EventsUpdater struct {
|
||||
api remote.AccountService
|
||||
api remote.AccountService
|
||||
formatFunc func(string, int, any) string
|
||||
store storage.StorageService
|
||||
store storage.StorageService
|
||||
}
|
||||
|
||||
func NewEventsUpdater(api remote.AccountService, store storage.StorageService) *EventsUpdater {
|
||||
|
@ -6,13 +6,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.grassecon.net/grassrootseconomics/common/identity"
|
||||
apievent "git.grassecon.net/grassrootseconomics/sarafu-api/event"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
"git.grassecon.net/grassrootseconomics/common/identity"
|
||||
apievent "git.grassecon.net/grassrootseconomics/sarafu-api/event"
|
||||
)
|
||||
|
||||
// execute all
|
||||
// execute all
|
||||
func (eu *EventsUpdater) updateToken(ctx context.Context, identity identity.Identity, userStore *store.UserDataStore, tokenAddress string) error {
|
||||
err := eu.updateTokenList(ctx, identity, userStore)
|
||||
if err != nil {
|
||||
@ -47,16 +47,19 @@ func (eu *EventsUpdater) updateToken(ctx context.Context, identity identity.Iden
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// set default token to given symbol.
|
||||
func (eu *EventsUpdater) updateDefaultToken(ctx context.Context, identity identity.Identity, userStore *store.UserDataStore, activeSym string) error {
|
||||
pfxDb := toPrefixDb(userStore, identity.SessionId)
|
||||
// TODO: the activeSym input should instead be newline separated list?
|
||||
tokenData, err := store.GetVoucherData(ctx, userStore, identity.SessionId, activeSym)
|
||||
tokenData, err := store.GetVoucherData(ctx, pfxDb, activeSym)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return store.UpdateVoucherData(ctx, userStore, identity.SessionId, tokenData)
|
||||
}
|
||||
|
||||
|
||||
// handle token transfer.
|
||||
//
|
||||
// if from and to are NOT the same, handle code will be executed once for each side of the transfer.
|
||||
@ -186,7 +189,7 @@ func (eu *EventsUpdater) updateTokenTransferList(ctx context.Context, identity i
|
||||
return err
|
||||
}
|
||||
|
||||
for i, tx := range txs {
|
||||
for i, tx := range(txs) {
|
||||
r = append(r, eu.formatFunc(apievent.EventTokenTransferTag, i, tx))
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"git.defalsify.org/vise.git/db"
|
||||
"git.defalsify.org/vise.git/engine"
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
"git.defalsify.org/vise.git/persist"
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
|
||||
@ -14,10 +13,6 @@ import (
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/application"
|
||||
)
|
||||
|
||||
var (
|
||||
logg = logging.NewVanilla().WithDomain("sarafu-vise.engine")
|
||||
)
|
||||
|
||||
type HandlerService interface {
|
||||
GetHandler() (*application.MenuHandlers, error)
|
||||
}
|
||||
@ -27,10 +22,8 @@ type LocalHandlerService struct {
|
||||
DbRs *resource.DbResource
|
||||
Pe *persist.Persister
|
||||
UserdataStore *db.Db
|
||||
LogDb *db.Db
|
||||
Cfg engine.Config
|
||||
Rs resource.Resource
|
||||
first resource.EntryFunc
|
||||
}
|
||||
|
||||
func NewLocalHandlerService(ctx context.Context, fp string, debug bool, dbResource *resource.DbResource, cfg engine.Config, rs resource.Resource) (*LocalHandlerService, error) {
|
||||
@ -58,21 +51,17 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) {
|
||||
ls.UserdataStore = db
|
||||
}
|
||||
|
||||
func (ls *LocalHandlerService) SetLogDb(db *db.Db) {
|
||||
ls.LogDb = db
|
||||
}
|
||||
|
||||
func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) (*application.MenuHandlers, error) {
|
||||
replaceSeparatorFunc := func(input string) string {
|
||||
return strings.ReplaceAll(input, ":", ls.Cfg.MenuSeparator)
|
||||
}
|
||||
|
||||
appHandlers, err := application.NewMenuHandlers(ls.Parser, *ls.UserdataStore, *ls.LogDb, accountService, replaceSeparatorFunc)
|
||||
appHandlers, err := application.NewMenuHandlers(ls.Parser, *ls.UserdataStore, accountService, replaceSeparatorFunc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//appHandlers = appHandlers.WithPersister(ls.Pe)
|
||||
appHandlers.SetPersister(ls.Pe)
|
||||
ls.DbRs.AddLocalFunc("check_blocked_status", appHandlers.CheckBlockedStatus)
|
||||
ls.DbRs.AddLocalFunc("set_language", appHandlers.SetLanguage)
|
||||
ls.DbRs.AddLocalFunc("create_account", appHandlers.CreateAccount)
|
||||
ls.DbRs.AddLocalFunc("save_temporary_pin", appHandlers.SaveTemporaryPin)
|
||||
@ -91,7 +80,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService)
|
||||
ls.DbRs.AddLocalFunc("get_recipient", appHandlers.GetRecipient)
|
||||
ls.DbRs.AddLocalFunc("get_sender", appHandlers.GetSender)
|
||||
ls.DbRs.AddLocalFunc("get_amount", appHandlers.GetAmount)
|
||||
ls.DbRs.AddLocalFunc("reset_incorrect_pin", appHandlers.ResetIncorrectPin)
|
||||
ls.DbRs.AddLocalFunc("reset_incorrect", appHandlers.ResetIncorrectPin)
|
||||
ls.DbRs.AddLocalFunc("save_firstname", appHandlers.SaveFirstname)
|
||||
ls.DbRs.AddLocalFunc("save_familyname", appHandlers.SaveFamilyname)
|
||||
ls.DbRs.AddLocalFunc("save_gender", appHandlers.SaveGender)
|
||||
@ -104,22 +93,23 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService)
|
||||
ls.DbRs.AddLocalFunc("verify_yob", appHandlers.VerifyYob)
|
||||
ls.DbRs.AddLocalFunc("reset_incorrect_date_format", appHandlers.ResetIncorrectYob)
|
||||
ls.DbRs.AddLocalFunc("initiate_transaction", appHandlers.InitiateTransaction)
|
||||
ls.DbRs.AddLocalFunc("verify_new_pin", appHandlers.VerifyNewPin)
|
||||
ls.DbRs.AddLocalFunc("confirm_pin_change", appHandlers.ConfirmPinChange)
|
||||
ls.DbRs.AddLocalFunc("quit_with_help", appHandlers.QuitWithHelp)
|
||||
ls.DbRs.AddLocalFunc("fetch_community_balance", appHandlers.FetchCommunityBalance)
|
||||
ls.DbRs.AddLocalFunc("manage_vouchers", appHandlers.ManageVouchers)
|
||||
ls.DbRs.AddLocalFunc("set_default_voucher", appHandlers.SetDefaultVoucher)
|
||||
ls.DbRs.AddLocalFunc("check_vouchers", appHandlers.CheckVouchers)
|
||||
ls.DbRs.AddLocalFunc("get_vouchers", appHandlers.GetVoucherList)
|
||||
ls.DbRs.AddLocalFunc("view_voucher", appHandlers.ViewVoucher)
|
||||
ls.DbRs.AddLocalFunc("set_voucher", appHandlers.SetVoucher)
|
||||
ls.DbRs.AddLocalFunc("get_voucher_details", appHandlers.GetVoucherDetails)
|
||||
ls.DbRs.AddLocalFunc("get_default_pool", appHandlers.GetDefaultPool)
|
||||
ls.DbRs.AddLocalFunc("get_pools", appHandlers.GetPools)
|
||||
ls.DbRs.AddLocalFunc("view_pool", appHandlers.ViewPool)
|
||||
ls.DbRs.AddLocalFunc("set_pool", appHandlers.SetPool)
|
||||
ls.DbRs.AddLocalFunc("reset_valid_pin", appHandlers.ResetValidPin)
|
||||
ls.DbRs.AddLocalFunc("check_pin_mismatch", appHandlers.CheckBlockedNumPinMisMatch)
|
||||
ls.DbRs.AddLocalFunc("validate_blocked_number", appHandlers.ValidateBlockedNumber)
|
||||
ls.DbRs.AddLocalFunc("retrieve_blocked_number", appHandlers.RetrieveBlockedNumber)
|
||||
ls.DbRs.AddLocalFunc("reset_unregistered_number", appHandlers.ResetUnregisteredNumber)
|
||||
ls.DbRs.AddLocalFunc("reset_others_pin", appHandlers.ResetOthersPin)
|
||||
ls.DbRs.AddLocalFunc("save_others_temporary_pin", appHandlers.SaveOthersTemporaryPin)
|
||||
ls.DbRs.AddLocalFunc("get_current_profile_info", appHandlers.GetCurrentProfileInfo)
|
||||
ls.DbRs.AddLocalFunc("check_transactions", appHandlers.CheckTransactions)
|
||||
ls.DbRs.AddLocalFunc("get_transactions", appHandlers.GetTransactionsList)
|
||||
@ -127,28 +117,13 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService)
|
||||
ls.DbRs.AddLocalFunc("update_all_profile_items", appHandlers.UpdateAllProfileItems)
|
||||
ls.DbRs.AddLocalFunc("set_back", appHandlers.SetBack)
|
||||
ls.DbRs.AddLocalFunc("show_blocked_account", appHandlers.ShowBlockedAccount)
|
||||
ls.DbRs.AddLocalFunc("clear_temporary_value", appHandlers.ClearTemporaryValue)
|
||||
ls.DbRs.AddLocalFunc("reset_invalid_pin", appHandlers.ResetInvalidPIN)
|
||||
ls.DbRs.AddLocalFunc("request_custom_alias", appHandlers.RequestCustomAlias)
|
||||
ls.DbRs.AddLocalFunc("check_account_created", appHandlers.CheckAccountCreated)
|
||||
ls.DbRs.AddLocalFunc("reset_api_call_failure", appHandlers.ResetApiCallFailure)
|
||||
ls.DbRs.AddLocalFunc("swap_to_list", appHandlers.LoadSwapToList)
|
||||
ls.DbRs.AddLocalFunc("swap_max_limit", appHandlers.SwapMaxLimit)
|
||||
ls.DbRs.AddLocalFunc("swap_preview", appHandlers.SwapPreview)
|
||||
ls.DbRs.AddLocalFunc("initiate_swap", appHandlers.InitiateSwap)
|
||||
ls.first = appHandlers.Init
|
||||
|
||||
return appHandlers, nil
|
||||
}
|
||||
|
||||
func (ls *LocalHandlerService) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine {
|
||||
en := engine.NewEngine(cfg, rs)
|
||||
if ls.first != nil {
|
||||
en = en.WithFirst(ls.first)
|
||||
}
|
||||
en = en.WithPersister(pr)
|
||||
if cfg.EngineDebug {
|
||||
en = en.WithDebug(nil)
|
||||
}
|
||||
// TODO: enable setting of sessionId on engine init time
|
||||
func (ls *LocalHandlerService) GetEngine() *engine.DefaultEngine {
|
||||
en := engine.NewEngine(ls.Cfg, ls.Rs)
|
||||
en = en.WithPersister(ls.Pe)
|
||||
return en
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/application"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/application"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -14,17 +14,18 @@ var (
|
||||
)
|
||||
|
||||
type Cmd struct {
|
||||
sessionId string
|
||||
conn storage.ConnData
|
||||
sessionId string
|
||||
conn storage.ConnData
|
||||
flagParser *application.FlagManager
|
||||
cmd int
|
||||
enable bool
|
||||
exec func(ctx context.Context, ss storage.StorageService) error
|
||||
cmd int
|
||||
enable bool
|
||||
exec func(ctx context.Context, ss storage.StorageService) error
|
||||
}
|
||||
|
||||
func NewCmd(sessionId string, flagParser *application.FlagManager) *Cmd {
|
||||
func NewCmd(conn storage.ConnData, sessionId string, flagParser *application.FlagManager) *Cmd {
|
||||
return &Cmd{
|
||||
sessionId: sessionId,
|
||||
conn: conn,
|
||||
sessionId: sessionId,
|
||||
flagParser: flagParser,
|
||||
}
|
||||
}
|
||||
@ -94,3 +95,5 @@ func (c *Cmd) Parse(args []string) error {
|
||||
|
||||
return fmt.Errorf("unknown subcommand: %s", cmd)
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,96 +0,0 @@
|
||||
package sms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
"git.grassecon.net/grassrootseconomics/common/phone"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
)
|
||||
|
||||
var (
|
||||
logg = logging.NewVanilla().WithDomain("smsservice")
|
||||
)
|
||||
|
||||
type SmsService struct {
|
||||
Accountservice remote.AccountService
|
||||
Userdatastore store.UserDataStore
|
||||
}
|
||||
|
||||
// SendUpsellSMS will send an invitation SMS to an unregistered phone number
|
||||
func (smsservice *SmsService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) error {
|
||||
if !phone.IsValidPhoneNumber(inviterPhone) {
|
||||
return fmt.Errorf("invalid inviter phone number %v", inviterPhone)
|
||||
}
|
||||
|
||||
if !phone.IsValidPhoneNumber(inviteePhone) {
|
||||
return fmt.Errorf("Invalid invitee phone number %v", inviteePhone)
|
||||
}
|
||||
_, err := smsservice.Accountservice.SendUpsellSMS(ctx, inviterPhone, inviteePhone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to send upsell sms: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendPINResetSMS will send an SMS to a user's phonenumber in the event that the associated account's PIN has been reset.
|
||||
func (smsService *SmsService) SendPINResetSMS(ctx context.Context, adminPhoneNumber, blockedPhoneNumber string) error {
|
||||
formattedAdminPhone, err := phone.FormatPhoneNumber(adminPhoneNumber)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to format admin phone number: %w", err)
|
||||
}
|
||||
|
||||
formattedBlockedPhone, err := phone.FormatPhoneNumber(blockedPhoneNumber)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to format blocked phone number: %w", err)
|
||||
}
|
||||
|
||||
if !phone.IsValidPhoneNumber(formattedAdminPhone) {
|
||||
return fmt.Errorf("invalid admin phone number")
|
||||
}
|
||||
if !phone.IsValidPhoneNumber(formattedBlockedPhone) {
|
||||
return fmt.Errorf("invalid blocked phone number")
|
||||
}
|
||||
|
||||
err = smsService.Accountservice.SendPINResetSMS(ctx, formattedAdminPhone, formattedBlockedPhone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send pin reset sms: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendAddressSMS will triger an SMS when a user navigates to the my address node.The SMS will be sent to the associated phonenumber.
|
||||
func (smsService *SmsService) SendAddressSMS(ctx context.Context) error {
|
||||
store := smsService.Userdatastore
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
originPhone, err := phone.FormatPhoneNumber(sessionId)
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to format origin phonenumber", "sessionid", sessionId)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !phone.IsValidPhoneNumber(originPhone) {
|
||||
logg.InfoCtxf(ctx, "Invalid origin phone number", "origin phonenumber", originPhone)
|
||||
return fmt.Errorf("invalid origin phone number")
|
||||
}
|
||||
err = smsService.Accountservice.SendAddressSMS(ctx, string(publicKey), originPhone)
|
||||
if err != nil {
|
||||
logg.DebugCtxf(ctx, "Failed to send address sms", "error", err)
|
||||
return fmt.Errorf("Failed to send address sms: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,67 +1,5 @@
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "main_my_vouchers_select_voucher_using_index",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "2",
|
||||
"expectedContent": "My vouchers\n1:Select voucher\n2:Voucher details\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Select number or symbol from your vouchers:\n1SRF\n0:Back\n99:Quit"
|
||||
},
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "Select number or symbol from your vouchers:\n1SRF\n0:Back\n99:Quit"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Enter PIN to confirm selection:\nSymbol: SRF\nBalance: 2.745987\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Success! SRF is now your active voucher.\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "main_my_vouchers_select_voucher_using_symbol",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "2",
|
||||
"expectedContent": "My vouchers\n1:Select voucher\n2:Voucher details\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Select number or symbol from your vouchers:\n1SRF\n0:Back\n99:Quit"
|
||||
},
|
||||
{
|
||||
"input": "SRF",
|
||||
"expectedContent": "Enter PIN to confirm selection:\nSymbol: SRF\nBalance: 2.745987\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Success! SRF is now your active voucher.\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "my_account_change_pin",
|
||||
"steps": [
|
||||
@ -71,7 +9,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "5",
|
||||
@ -108,11 +46,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "2",
|
||||
@ -120,7 +54,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1235",
|
||||
"expectedContent": "Incorrect PIN. You have: {attempts} remaining attempt(s).\n1:Retry\n9:Quit"
|
||||
"expectedContent": "Incorrect PIN. You have: 2 remaining attempt(s).\n1:Retry\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -140,105 +74,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "menu_my_account_reset_others_pin_with_unregistered_number",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "5",
|
||||
"expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "2",
|
||||
"expectedContent": "Enter other's phone number:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0700000001",
|
||||
"expectedContent": "The number you have entered is either not registered with Sarafu or is invalid.\n1:Retry\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Enter other's phone number:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "menu_my_account_reset_others_pin_with_registered_number",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "5",
|
||||
"expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "2",
|
||||
"expectedContent": "Enter other's phone number:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0700000000",
|
||||
"expectedContent": "{secondary_session_id} will get a PIN reset request.\nPlease enter your PIN to confirm:\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "PIN reset request for {secondary_session_id} was successful\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "menu_my_account_reset_others_pin_with_no_privileges",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "5",
|
||||
"expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "2",
|
||||
"expectedContent": "You do not have privileges to perform this action\n\n9:Quit\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "menu_my_account_check_my_balance",
|
||||
"steps": [
|
||||
@ -248,7 +83,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
@ -260,7 +95,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1235",
|
||||
"expectedContent": "Incorrect PIN. You have: {attempts} remaining attempt(s).\n1:Retry\n9:Quit"
|
||||
"expectedContent": "Incorrect PIN. You have: 2 remaining attempt(s).\n1:Retry\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -268,7 +103,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "{balance}\n\n0:Back\n9:Quit"
|
||||
"expectedContent": "Balance: {balance}\n\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
@ -276,7 +111,7 @@
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
@ -293,7 +128,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
@ -305,7 +140,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1235",
|
||||
"expectedContent": "Incorrect PIN. You have: {attempts} remaining attempt(s).\n1:Retry\n9:Quit"
|
||||
"expectedContent": "Incorrect PIN. You have: 2 remaining attempt(s).\n1:Retry\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -321,7 +156,7 @@
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
@ -338,7 +173,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -395,7 +230,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -432,7 +267,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -442,10 +277,6 @@
|
||||
"input": "3",
|
||||
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Please enter your PIN:"
|
||||
@ -473,7 +304,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -510,7 +341,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -547,7 +378,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -584,7 +415,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -596,7 +427,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 80\nLocation: Kilifi\nYou provide: Bananas\nYour alias: Not Provided\n\n0:Back\n9:Quit"
|
||||
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 80\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
@ -607,47 +438,6 @@
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "menu_block_account_via_view_profile",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "7",
|
||||
"expectedContent": "Please enter your PIN:"
|
||||
},
|
||||
{
|
||||
"input": "1254",
|
||||
"expectedContent": "Incorrect PIN. You have: {attempts} remaining attempt(s).\n1:Retry\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Please enter your PIN:"
|
||||
},
|
||||
{
|
||||
"input": "1254",
|
||||
"expectedContent": "Incorrect PIN. You have: {attempts} remaining attempt(s).\n1:Retry\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Please enter your PIN:"
|
||||
},
|
||||
{
|
||||
"input": "1254",
|
||||
"expectedContent": "Your account has been locked. For help on how to unblock your account, contact support at: 0757628885"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -9,22 +9,22 @@ import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/testutil"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/testutil/driver"
|
||||
"github.com/gofrs/uuid"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/testutil/driver"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/testutil"
|
||||
)
|
||||
|
||||
var (
|
||||
logg = logging.NewVanilla().WithDomain("menutraversaltest")
|
||||
testData = driver.ReadData()
|
||||
sessionID string
|
||||
src = rand.NewSource(42)
|
||||
g = rand.New(src)
|
||||
secondarySessionId = "+254700000000"
|
||||
testData = driver.ReadData()
|
||||
sessionID string
|
||||
src = rand.NewSource(42)
|
||||
g = rand.New(src)
|
||||
)
|
||||
|
||||
var groupTestFile = flag.String("test-file", "group_test.json", "The test file to use for running the group tests")
|
||||
var database = flag.String("db", "gdbm", "Specify the database (gdbm or postgres)")
|
||||
var connStr = flag.String("conn", ".test_state", "connection string")
|
||||
var dbSchema = flag.String("schema", "test", "Specify the database schema (default test)")
|
||||
|
||||
func GenerateSessionId() string {
|
||||
uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g))
|
||||
@ -68,16 +68,6 @@ func extractMaxAmount(response []byte) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractRemainingAttempts(response []byte) string {
|
||||
// Regex to match "You have: <number> remaining attempt(s)"
|
||||
re := regexp.MustCompile(`(?m)You have:\s+(\d+)\s+remaining attempt\(s\)`)
|
||||
match := re.FindSubmatch(response)
|
||||
if match != nil {
|
||||
return string(match[1]) // "<number>" of remaining attempts
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Extracts the send amount value from the engine response.
|
||||
func extractSendAmount(response []byte) string {
|
||||
// Regex to match the pattern "will receive X.XX SYM from"
|
||||
@ -90,7 +80,12 @@ func extractSendAmount(response []byte) string {
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Parse the flags
|
||||
flag.Parse()
|
||||
sessionID = GenerateSessionId()
|
||||
// set the db
|
||||
testutil.SetDatabase(*database, *connStr, *dbSchema)
|
||||
|
||||
// Cleanup the db after tests
|
||||
defer testutil.CleanDatabase()
|
||||
|
||||
@ -98,51 +93,14 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func TestAccountCreationSuccessful(t *testing.T) {
|
||||
en, fn, eventChannel, _, _ := testutil.TestEngine(sessionID)
|
||||
en, fn, eventChannel := testutil.TestEngine(sessionID)
|
||||
defer fn()
|
||||
ctx := context.Background()
|
||||
sessions := testData
|
||||
for _, session := range sessions {
|
||||
groups := driver.FilterGroupsByName(session.Groups, "account_creation_successful")
|
||||
for _, group := range groups {
|
||||
for i, step := range group.Steps {
|
||||
logg.TraceCtxf(ctx, "executing step", "i", i, "step", step)
|
||||
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||
if err != nil {
|
||||
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||
}
|
||||
if !cont {
|
||||
break
|
||||
}
|
||||
w := bytes.NewBuffer(nil)
|
||||
_, err = en.Flush(ctx, w)
|
||||
if err != nil {
|
||||
t.Fatalf("Test case '%s' failed during Flush: %v", group.Name, err)
|
||||
}
|
||||
b := w.Bytes()
|
||||
match, err := step.MatchesExpectedContent(b)
|
||||
if err != nil {
|
||||
t.Fatalf("Error compiling regex for step '%s': %v", step.Input, err)
|
||||
}
|
||||
if !match {
|
||||
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
<-eventChannel
|
||||
}
|
||||
|
||||
func TestSecondaryAccount(t *testing.T) {
|
||||
en, fn, eventChannel, _, _ := testutil.TestEngine(secondarySessionId)
|
||||
defer fn()
|
||||
ctx := context.Background()
|
||||
sessions := testData
|
||||
for _, session := range sessions {
|
||||
groups := driver.FilterGroupsByName(session.Groups, "account_creation_successful")
|
||||
for _, group := range groups {
|
||||
for i, step := range group.Steps {
|
||||
logg.TraceCtxf(ctx, "executing step", "i", i, "step", step)
|
||||
for _, step := range group.Steps {
|
||||
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||
if err != nil {
|
||||
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||
@ -177,15 +135,14 @@ func TestAccountRegistrationRejectTerms(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
edgeCaseSessionID := v.String()
|
||||
en, fn, _, _, _ := testutil.TestEngine(edgeCaseSessionID)
|
||||
en, fn, _ := testutil.TestEngine(edgeCaseSessionID)
|
||||
defer fn()
|
||||
ctx := context.Background()
|
||||
sessions := testData
|
||||
for _, session := range sessions {
|
||||
groups := driver.FilterGroupsByName(session.Groups, "account_creation_reject_terms")
|
||||
for _, group := range groups {
|
||||
for i, step := range group.Steps {
|
||||
logg.TraceCtxf(ctx, "executing step", "i", i, "step", step)
|
||||
for _, step := range group.Steps {
|
||||
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||
if err != nil {
|
||||
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||
@ -213,15 +170,14 @@ func TestAccountRegistrationRejectTerms(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMainMenuHelp(t *testing.T) {
|
||||
en, fn, _, _, _ := testutil.TestEngine(sessionID)
|
||||
en, fn, _ := testutil.TestEngine(sessionID)
|
||||
defer fn()
|
||||
ctx := context.Background()
|
||||
sessions := testData
|
||||
for _, session := range sessions {
|
||||
groups := driver.FilterGroupsByName(session.Groups, "main_menu_help")
|
||||
for _, group := range groups {
|
||||
for i, step := range group.Steps {
|
||||
logg.TraceCtxf(ctx, "executing step", "i", i, "step", step)
|
||||
for _, step := range group.Steps {
|
||||
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||
if err != nil {
|
||||
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||
@ -255,7 +211,7 @@ func TestMainMenuHelp(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMainMenuQuit(t *testing.T) {
|
||||
en, fn, _, _, _ := testutil.TestEngine(sessionID)
|
||||
en, fn, _ := testutil.TestEngine(sessionID)
|
||||
defer fn()
|
||||
ctx := context.Background()
|
||||
sessions := testData
|
||||
@ -296,7 +252,7 @@ func TestMainMenuQuit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMyAccount_MyAddress(t *testing.T) {
|
||||
en, fn, _, _, _ := testutil.TestEngine(sessionID)
|
||||
en, fn, _ := testutil.TestEngine(sessionID)
|
||||
defer fn()
|
||||
ctx := context.Background()
|
||||
sessions := testData
|
||||
@ -340,7 +296,7 @@ func TestMyAccount_MyAddress(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMainMenuSend(t *testing.T) {
|
||||
en, fn, _, _, _ := testutil.TestEngine(sessionID)
|
||||
en, fn, _ := testutil.TestEngine(sessionID)
|
||||
defer fn()
|
||||
ctx := context.Background()
|
||||
sessions := testData
|
||||
@ -391,12 +347,9 @@ func TestGroups(t *testing.T) {
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load test groups: %v", err)
|
||||
}
|
||||
en, fn, _, pe, flagParser := testutil.TestEngine(sessionID)
|
||||
en, fn, _ := testutil.TestEngine(sessionID)
|
||||
defer fn()
|
||||
ctx := context.Background()
|
||||
|
||||
flag_admin_privilege, _ := flagParser.GetFlag("flag_admin_privilege")
|
||||
|
||||
// Create test cases from loaded groups
|
||||
tests := driver.CreateTestCases(groups)
|
||||
for _, tt := range tests {
|
||||
@ -415,21 +368,9 @@ func TestGroups(t *testing.T) {
|
||||
}
|
||||
b := w.Bytes()
|
||||
balance := extractBalance(b)
|
||||
attempts := extractRemainingAttempts(b)
|
||||
|
||||
st := pe.GetState()
|
||||
|
||||
if st != nil {
|
||||
st.SetFlag(flag_admin_privilege)
|
||||
if tt.Name == "menu_my_account_reset_others_pin_with_no_privileges" {
|
||||
st.ResetFlag(flag_admin_privilege)
|
||||
}
|
||||
}
|
||||
|
||||
expectedContent := []byte(tt.ExpectedContent)
|
||||
expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
|
||||
expectedContent = bytes.Replace(expectedContent, []byte("{attempts}"), []byte(attempts), -1)
|
||||
expectedContent = bytes.Replace(expectedContent, []byte("{secondary_session_id}"), []byte(secondarySessionId), -1)
|
||||
|
||||
tt.ExpectedContent = string(expectedContent)
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
|
@ -9,7 +9,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
|
@ -9,7 +9,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
|
@ -9,7 +9,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
|
@ -9,7 +9,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
|
@ -9,7 +9,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
|
@ -9,7 +9,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
|
@ -11,7 +11,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/tos\n\n1:Yes\n2:No"
|
||||
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n1:Yes\n2:No"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -31,7 +31,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Your account is being created. Thank you for using Sarafu. Goodbye!"
|
||||
"expectedContent": "Your account is being created...Thank you for using Sarafu. Goodbye!"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -44,7 +44,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/tos\n\n1:Yes\n2:No"
|
||||
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n1:Yes\n2:No"
|
||||
},
|
||||
{
|
||||
"input": "2",
|
||||
@ -116,7 +116,7 @@
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "6",
|
||||
|
@ -12,7 +12,7 @@ func (p *Profile) InsertOrShift(index int, value string) {
|
||||
for len(p.ProfileItems) < index {
|
||||
p.ProfileItems = append(p.ProfileItems, "0")
|
||||
}
|
||||
p.ProfileItems = append(p.ProfileItems, "0")
|
||||
p.ProfileItems = append(p.ProfileItems, "0")
|
||||
p.ProfileItems[index] = value
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +0,0 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/assert/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInsertOrShift(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
profile Profile
|
||||
index int
|
||||
value string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "Insert within range",
|
||||
profile: Profile{ProfileItems: []string{"A", "B", "C"}, Max: 5},
|
||||
index: 1,
|
||||
value: "X",
|
||||
expected: []string{"A", "X"},
|
||||
},
|
||||
{
|
||||
name: "Insert beyond range",
|
||||
profile: Profile{ProfileItems: []string{"A"}, Max: 5},
|
||||
index: 3,
|
||||
value: "Y",
|
||||
expected: []string{"A", "0", "0", "Y"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := tt.profile
|
||||
p.InsertOrShift(tt.index, tt.value)
|
||||
require.NotNil(t, p.ProfileItems)
|
||||
assert.Equal(t, tt.expected, p.ProfileItems)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,17 +1,16 @@
|
||||
//go:build !online
|
||||
// +build !online
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"context"
|
||||
|
||||
devremote "git.grassecon.net/grassrootseconomics/sarafu-api/dev"
|
||||
apievent "git.grassecon.net/grassrootseconomics/sarafu-api/event"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/event"
|
||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||
devremote "git.grassecon.net/grassrootseconomics/sarafu-api/dev"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
|
||||
apievent "git.grassecon.net/grassrootseconomics/sarafu-api/event"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/event"
|
||||
)
|
||||
|
||||
type localEmitter struct {
|
||||
@ -38,7 +37,7 @@ func (d *localEmitter) emit(ctx context.Context, msg apievent.Msg) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func New(ctx context.Context, storageService storage.StorageService) remote.AccountService {
|
||||
func New(ctx context.Context, storageService storage.StorageService, conn storage.ConnData) remote.AccountService {
|
||||
svc := devremote.NewDevAccountService(ctx, storageService)
|
||||
svc = svc.WithAutoVoucher(ctx, "FOO", 42)
|
||||
eu := event.NewEventsUpdater(svc, storageService)
|
||||
|
@ -4,8 +4,8 @@ TXTS = $(wildcard ./*.txt.orig)
|
||||
VISE_PATH := ../../go-vise
|
||||
|
||||
# Rule to build .bin files from .vis files
|
||||
%.vis: buildasm
|
||||
./vise-asm -f pp.csv $(basename $@).vis > $(basename $@).bin
|
||||
%.vis:
|
||||
go run $(VISE_PATH)/dev/asm/main.go -f pp.csv $(basename $@).vis > $(basename $@).bin
|
||||
@echo "Built $(basename $@).bin from $(basename $@).vis"
|
||||
|
||||
# Rule to copy .orig files to .txt
|
||||
@ -19,10 +19,5 @@ all: $(INPUTS) $(TXTS)
|
||||
|
||||
clean:
|
||||
rm -vf *.bin
|
||||
rm -vf ./vise-asm
|
||||
|
||||
buildasm:
|
||||
go build -v -o ./vise-asm $(VISE_PATH)/dev/asm/main.go
|
||||
|
||||
|
||||
.PHONY: clean
|
||||
|
BIN
services/registration/_catch.bin
Normal file
BIN
services/registration/_catch.bin
Normal file
Binary file not shown.
@ -1 +1 @@
|
||||
Your account is being created.
|
||||
Your account is being created...
|
BIN
services/registration/account_creation.bin
Normal file
BIN
services/registration/account_creation.bin
Normal file
Binary file not shown.
BIN
services/registration/account_creation_failed.bin
Normal file
BIN
services/registration/account_creation_failed.bin
Normal file
Binary file not shown.
BIN
services/registration/account_pending.bin
Normal file
BIN
services/registration/account_pending.bin
Normal file
Binary file not shown.
BIN
services/registration/address.bin
Normal file
BIN
services/registration/address.bin
Normal file
Binary file not shown.
@ -1,4 +1,5 @@
|
||||
LOAD check_identifier 0
|
||||
RELOAD check_identifier
|
||||
MAP check_identifier
|
||||
MOUT back 0
|
||||
MOUT quit 9
|
||||
|
@ -1 +0,0 @@
|
||||
Your alias has been updated successfully
|
@ -1,5 +0,0 @@
|
||||
MOUT back 0
|
||||
MOUT quit 9
|
||||
HALT
|
||||
INCMP ^ 0
|
||||
INCMP quit 9
|
@ -1 +0,0 @@
|
||||
Ombi lako la kubadilisha lakabu limefanikiwa.
|
BIN
services/registration/amount.bin
Normal file
BIN
services/registration/amount.bin
Normal file
Binary file not shown.
@ -1,12 +1,12 @@
|
||||
LOAD reset_transaction_amount 0
|
||||
LOAD max_amount 40
|
||||
LOAD max_amount 10
|
||||
RELOAD max_amount
|
||||
MAP max_amount
|
||||
MOUT back 0
|
||||
HALT
|
||||
LOAD validate_amount 64
|
||||
RELOAD validate_amount
|
||||
CATCH api_failure flag_api_call_error 1
|
||||
CATCH api_failure flag_api_call_error 1
|
||||
CATCH invalid_amount flag_invalid_amount 1
|
||||
INCMP _ 0
|
||||
LOAD get_recipient 0
|
||||
|
@ -1 +1 @@
|
||||
Your request failed. Please try again later.
|
||||
Failed to connect to the custodial service.Please try again.
|
BIN
services/registration/api_failure.bin
Normal file
BIN
services/registration/api_failure.bin
Normal file
Binary file not shown.
@ -1,7 +1,5 @@
|
||||
LOAD reset_api_call_failure 6
|
||||
RELOAD reset_api_call_failure
|
||||
MOUT retry 1
|
||||
MOUT quit 9
|
||||
HALT
|
||||
INCMP ^ 1
|
||||
INCMP _ 1
|
||||
INCMP quit 9
|
||||
|
@ -1 +0,0 @@
|
||||
Ombi lako halikufaulu. Tafadhali jaribu tena baadaye.
|
@ -1,2 +0,0 @@
|
||||
{{.retrieve_blocked_number}} will get a PIN reset request.
|
||||
Please enter your PIN to confirm:
|
@ -1,12 +0,0 @@
|
||||
LOAD retrieve_blocked_number 0
|
||||
RELOAD retrieve_blocked_number
|
||||
MAP retrieve_blocked_number
|
||||
MOUT back 0
|
||||
MOUT quit 9
|
||||
LOAD authorize_account 6
|
||||
HALT
|
||||
RELOAD authorize_account
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
INCMP _ 0
|
||||
INCMP quit 9
|
||||
INCMP pin_reset_result *
|
@ -1,2 +0,0 @@
|
||||
{{.retrieve_blocked_number}} atapokea ombi la kuweka upya PIN.
|
||||
Tafadhali weka PIN yako kudhibitisha:
|
BIN
services/registration/balances.bin
Normal file
BIN
services/registration/balances.bin
Normal file
Binary file not shown.
BIN
services/registration/blocked_account.bin
Normal file
BIN
services/registration/blocked_account.bin
Normal file
Binary file not shown.
BIN
services/registration/change_language.bin
Normal file
BIN
services/registration/change_language.bin
Normal file
Binary file not shown.
@ -1,5 +1,5 @@
|
||||
LOAD reset_account_authorized 0
|
||||
LOAD reset_incorrect_pin 0
|
||||
LOAD reset_incorrect 0
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH pin_entry flag_account_authorized 0
|
||||
MOUT english 1
|
||||
|
BIN
services/registration/check_statement.bin
Normal file
BIN
services/registration/check_statement.bin
Normal file
Binary file not shown.
@ -1,6 +1,5 @@
|
||||
LOAD check_transactions 0
|
||||
RELOAD check_transactions
|
||||
CATCH api_failure flag_api_call_error 1
|
||||
CATCH no_transfers flag_no_transfers 1
|
||||
LOAD authorize_account 6
|
||||
MOUT back 0
|
||||
|
BIN
services/registration/community_balance.bin
Normal file
BIN
services/registration/community_balance.bin
Normal file
Binary file not shown.
@ -1,6 +1,6 @@
|
||||
LOAD reset_incorrect_pin 6
|
||||
LOAD reset_incorrect 6
|
||||
LOAD fetch_community_balance 0
|
||||
CATCH api_failure flag_api_call_error 1
|
||||
CATCH api_failure flag_api_call_error 1
|
||||
MAP fetch_community_balance
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH pin_entry flag_account_authorized 0
|
||||
|
BIN
services/registration/confirm_create_pin.bin
Normal file
BIN
services/registration/confirm_create_pin.bin
Normal file
Binary file not shown.
@ -1,7 +1,4 @@
|
||||
MOUT back 0
|
||||
LOAD save_temporary_pin 6
|
||||
HALT
|
||||
INCMP _ 0
|
||||
LOAD verify_create_pin 8
|
||||
RELOAD verify_create_pin
|
||||
CATCH pin_mismatch flag_pin_mismatch 1
|
||||
INCMP account_creation *
|
||||
|
1
services/registration/confirm_others_new_pin
Normal file
1
services/registration/confirm_others_new_pin
Normal file
@ -0,0 +1 @@
|
||||
Please confirm new PIN for:{{.retrieve_blocked_number}}
|
BIN
services/registration/confirm_others_new_pin.bin
Normal file
BIN
services/registration/confirm_others_new_pin.bin
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user