diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..13021b8 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,32 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Go + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23.x' + + - name: Test + run: go test -v ./util ./api + + - name: Build + run: go build -v . diff --git a/Dockerfile b/Dockerfile index 2e181b0..2ee90ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ COPY go.mod go.sum ./ RUN go mod download # Copy all files and subfolders in current state as is. COPY . . -COPY ./confdata /go/bin/config +COPY ./appdata /go/bin/config # Set executable rights to all shell-scripts. RUN chmod +x ./task/*.sh diff --git a/api/auth.go b/api/auth.go index f9f0bf4..23a6103 100644 --- a/api/auth.go +++ b/api/auth.go @@ -43,7 +43,7 @@ var ( ErrBadJwtID = errors.New("jwt-token id does not refer to registered user") ErrNoAuth = errors.New("authorization is required") ErrNoScheme = errors.New("authorization does not have expected scheme") - ErrNoSecret = errors.New("expected password or SHA25 hash on it and current time as a nonce") + ErrNoSecret = errors.New("expected password or SHA256 hash on it and current time as a nonce") ErrSmallKey = errors.New("password too small") ErrNoCred = errors.New("user with given credentials does not registered") ErrActivate = errors.New("activation required for this account") diff --git a/api/play_test.go b/api/play_test.go new file mode 100644 index 0000000..ed29685 --- /dev/null +++ b/api/play_test.go @@ -0,0 +1,166 @@ +package api_test + +import ( + "bytes" + "encoding/json" + "io" + "math/rand" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/slotopol/server/api" + "github.com/slotopol/server/cmd" + cfg "github.com/slotopol/server/config" + + "github.com/gin-gonic/gin" +) + +const email, secret = "player@example.org", "iVI05M" + +func ping(t *testing.T, r *gin.Engine) { + var req = httptest.NewRequest("GET", "/ping", nil) + var w = httptest.NewRecorder() + r.ServeHTTP(w, req) + if w.Code != http.StatusOK { + t.Error("ping is not ok") + } + var resp = w.Result() + if !strings.HasPrefix(resp.Header.Get("Server"), "slotopol/") { + t.Error("alien server") + } +} + +func post(t *testing.T, r *gin.Engine, path string, token string, arg any) (ret gin.H) { + var err error + var b []byte + + if b, err = json.Marshal(arg); err != nil { + t.Error(err) + } + var req = httptest.NewRequest("POST", path, bytes.NewReader(b)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + if token != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + var w = httptest.NewRecorder() + r.ServeHTTP(w, req) + if w.Code == http.StatusNoContent { + return + } + if w.Code != http.StatusOK { + t.Errorf("'%s' is not ok, status %d", path, w.Code) + } + var resp = w.Result() + if b, err = io.ReadAll(resp.Body); err != nil { + t.Error(err) + } + if err = json.Unmarshal(b, &ret); err != nil { + t.Error(err) + } + if w.Code != http.StatusOK { + t.Logf("message: %v, code: %v", ret["what"], ret["code"]) + } + return +} + +func TestPlay(t *testing.T) { + var err error + var arg, ret gin.H + var token string + var gid any + + // Prepare in-memory database + cfg.CfgPath = "../appdata" + if err = cmd.Init(); err != nil { + t.Fatal(err) + } + + gin.SetMode(gin.DebugMode) + var r = gin.New() + r.HandleMethodNotAllowed = true + api.Router(r) + + // Send "ping" and check that server is our + ping(t, r) + + // Sign-in player + arg = gin.H{ + "email": email, + "secret": secret, + } + ret = post(t, r, "/signin", "", arg) + token = ret["access"].(string) + + // Join game + arg = gin.H{ + "cid": 1, // 'virtual' club + "uid": 3, // player ID + "alias": "Novomatic / Dolphins Pearl", + } + ret = post(t, r, "/game/join", token, arg) + gid = ret["gid"] + + arg = gin.H{ + "gid": gid, + "bet": 1, + } + post(t, r, "/slot/bet/set", token, arg) + arg = gin.H{ + "gid": gid, + "sel": 5, + } + post(t, r, "/slot/sel/set", token, arg) + + // Play the game with 50 spins + for range 50 { + // make spin + arg = gin.H{ + "gid": gid, + } + ret = post(t, r, "/slot/spin", token, arg) + t.Logf("[spin] gid: %v, sid: %v, wallet: %v", gid, ret["sid"], ret["wallet"]) + var game = ret["game"].(map[string]any) + + // no any more actions on free spins + if _, ok := game["fsr"]; ok { + continue + } + if _, ok := game["gain"]; !ok { + continue + } + t.Logf("gain: %v", game["gain"]) + + // if there has a win, make double-ups sometime + if rand.Float64() < 0.3 { + for { + arg = gin.H{ + "gid": gid, + "mult": 2, + } + ret = post(t, r, "/slot/doubleup", token, arg) + var gain = ret["gain"].(float64) + t.Logf("[doubleup] gid: %v, id: %v, wallet: %v, gain: %g", gid, ret["id"], ret["wallet"], gain) + if gain == 0 { + break + } + if rand.Float64() < 0.5 { + arg = gin.H{ + "gid": gid, + } + post(t, r, "/slot/collect", token, arg) + t.Logf("[collect] gid: %v", gid) + break + } + } + } + } + + // Part game + arg = gin.H{ + "gid": gid, + } + post(t, r, "/game/part", token, arg) +} diff --git a/cmd/startup.go b/cmd/startup.go index fa2704a..5bc1f69 100644 --- a/cmd/startup.go +++ b/cmd/startup.go @@ -86,7 +86,13 @@ func InitStorage() (err error) { } if Cfg.DriverName == "sqlite3" { - cfg.XormStorage, err = xorm.NewEngine(Cfg.DriverName, util.JoinPath(cfg.SqlPath, Cfg.ClubSourceName)) + var fpath string + if Cfg.ClubSourceName != ":memory:" { + fpath = util.JoinPath(cfg.SqlPath, Cfg.ClubSourceName) + } else { + fpath = Cfg.ClubSourceName + } + cfg.XormStorage, err = xorm.NewEngine(Cfg.DriverName, fpath) } else { cfg.XormStorage, err = xorm.NewEngine(Cfg.DriverName, Cfg.ClubSourceName) } @@ -226,7 +232,13 @@ func InitSpinlog() (err error) { } if Cfg.DriverName == "sqlite3" { - cfg.XormSpinlog, err = xorm.NewEngine(Cfg.DriverName, util.JoinPath(cfg.SqlPath, Cfg.SpinSourceName)) + var fpath string + if Cfg.SpinSourceName != ":memory:" { + fpath = util.JoinPath(cfg.SqlPath, Cfg.SpinSourceName) + } else { + fpath = Cfg.SpinSourceName + } + cfg.XormSpinlog, err = xorm.NewEngine(Cfg.DriverName, fpath) } else { cfg.XormSpinlog, err = xorm.NewEngine(Cfg.DriverName, Cfg.SpinSourceName) } diff --git a/config/cfgpath.go b/config/cfgpath.go index 29a269d..d92373a 100644 --- a/config/cfgpath.go +++ b/config/cfgpath.go @@ -74,6 +74,7 @@ func InitConfig() { viper.AddConfigPath(filepath.Join(ExePath, sub)) viper.AddConfigPath(ExePath) viper.AddConfigPath(sub) + viper.AddConfigPath("appdata") viper.AddConfigPath(".") if home, err := os.UserHomeDir(); err == nil { viper.AddConfigPath(filepath.Join(home, sub)) diff --git a/config/config.go b/config/config.go index 2a20e9c..abeb502 100644 --- a/config/config.go +++ b/config/config.go @@ -131,12 +131,12 @@ var Cfg = &Config{ }, CfgXormDrv: CfgXormDrv{ DriverName: "sqlite3", - ClubSourceName: "slot-club.sqlite", - SpinSourceName: "slot-spin.sqlite", + ClubSourceName: ":memory:", + SpinSourceName: ":memory:", SqlFlushTick: 2500 * time.Millisecond, - ClubUpdateBuffer: 200, - ClubInsertBuffer: 150, - SpinInsertBuffer: 250, + ClubUpdateBuffer: 1, + ClubInsertBuffer: 1, + SpinInsertBuffer: 1, }, CfgGameplay: CfgGameplay{ AdjunctLimit: 100000, diff --git a/task/build-linux-x64.cmd b/task/build-linux-x64.cmd index da392bc..3978232 100644 --- a/task/build-linux-x64.cmd +++ b/task/build-linux-x64.cmd @@ -3,7 +3,7 @@ rem This script compiles project for Linux amd64. rem It produces static C-libraries linkage. set wd=%~dp0.. -xcopy %wd%\confdata %GOPATH%\bin\config /f /d /i /e /k /y +xcopy %wd%\appdata %GOPATH%\bin\config /f /d /i /e /k /y for /F "tokens=*" %%g in ('git describe --tags') do (set buildvers=%%g) for /f "tokens=2 delims==" %%g in ('wmic os get localdatetime /value') do set dt=%%g diff --git a/task/build-linux-x64.sh b/task/build-linux-x64.sh index 4a552e7..8de5fb3 100644 --- a/task/build-linux-x64.sh +++ b/task/build-linux-x64.sh @@ -3,7 +3,7 @@ # It produces static C-libraries linkage. wd=$(realpath -s "$(dirname "$0")/..") -cp -ruv "$wd/confdata/"* "$GOPATH/bin/config" +cp -ruv "$wd/appdata/"* "$GOPATH/bin/config" buildvers=$(git describe --tags) # See https://tc39.es/ecma262/#sec-date-time-string-format diff --git a/task/build-win-x64-slotopol.sh b/task/build-win-x64-slotopol.sh index a9a1df2..8b769c8 100644 --- a/task/build-win-x64-slotopol.sh +++ b/task/build-win-x64-slotopol.sh @@ -3,7 +3,7 @@ # It produces static C-libraries linkage. wd=$(realpath -s "$(dirname "$0")/..") -cp -ruv "$wd/confdata/"* "$GOPATH/bin/config" +cp -ruv "$wd/appdata/"* "$GOPATH/bin/config" buildvers=$(git describe --tags) # See https://tc39.es/ecma262/#sec-date-time-string-format diff --git a/task/build-win-x64.cmd b/task/build-win-x64.cmd index 6e068d0..e1c414f 100644 --- a/task/build-win-x64.cmd +++ b/task/build-win-x64.cmd @@ -3,7 +3,7 @@ rem This script compiles project for Windows amd64. rem It produces static C-libraries linkage. set wd=%~dp0.. -xcopy %wd%\confdata %GOPATH%\bin\config /f /d /i /e /k /y +xcopy %wd%\appdata %GOPATH%\bin\config /f /d /i /e /k /y for /F "tokens=*" %%g in ('git describe --tags') do (set buildvers=%%g) for /f "tokens=2 delims==" %%g in ('wmic os get localdatetime /value') do set dt=%%g diff --git a/task/build-win-x64.sh b/task/build-win-x64.sh index 2cfd4d4..02ac6cb 100644 --- a/task/build-win-x64.sh +++ b/task/build-win-x64.sh @@ -3,7 +3,7 @@ # It produces static C-libraries linkage. wd=$(realpath -s "$(dirname "$0")/..") -cp -ruv "$wd/confdata/"* "$GOPATH/bin/config" +cp -ruv "$wd/appdata/"* "$GOPATH/bin/config" buildvers=$(git describe --tags) # See https://tc39.es/ecma262/#sec-date-time-string-format diff --git a/task/build-win-x86.cmd b/task/build-win-x86.cmd index d4cdf1c..b932535 100644 --- a/task/build-win-x86.cmd +++ b/task/build-win-x86.cmd @@ -3,7 +3,7 @@ rem This script compiles project for Windows x86. rem It produces static C-libraries linkage. set wd=%~dp0.. -xcopy %wd%\confdata %GOPATH%\bin\config /f /d /i /e /k /y +xcopy %wd%\appdata %GOPATH%\bin\config /f /d /i /e /k /y for /F "tokens=*" %%g in ('git describe --tags') do (set buildvers=%%g) for /f "tokens=2 delims==" %%g in ('wmic os get localdatetime /value') do set dt=%%g diff --git a/task/build-win-x86.sh b/task/build-win-x86.sh index 6800fea..002af82 100644 --- a/task/build-win-x86.sh +++ b/task/build-win-x86.sh @@ -3,7 +3,7 @@ # It produces static C-libraries linkage. wd=$(realpath -s "$(dirname "$0")/..") -cp -ruv "$wd/confdata/"* "$GOPATH/bin/config" +cp -ruv "$wd/appdata/"* "$GOPATH/bin/config" buildvers=$(git describe --tags) # See https://tc39.es/ecma262/#sec-date-time-string-format