Files
bd2/go/internal/server/npcinn/inn_test.go
T

167 lines
5.2 KiB
Go

package npcinn
import (
"bd2server/internal/server/accountstate"
"bd2server/internal/server/gamedata"
"bd2server/internal/server/player"
"bd2server/internal/server/stateio"
"bd2server/internal/server/wire"
"bytes"
"errors"
"path/filepath"
"testing"
)
func innTestService(t *testing.T, gold, level, state uint64) (*Service, *player.CharacterStore, *player.Wallet, stateio.Store) {
t.Helper()
store := stateio.NewMemory()
return innTestServiceWithStore(t, store, gold, level, state)
}
func innTestServiceWithStore(t *testing.T, store stateio.Store, gold, level, state uint64) (*Service, *player.CharacterStore, *player.Wallet, stateio.Store) {
t.Helper()
inv, err := player.OpenInventory(store, &player.Starter{Version: "2.35.10"})
if err != nil {
t.Fatal(err)
}
chars, err := player.OpenCharacterStore(store, []player.Character{{InvenIndex: 71, ID: 350, Level: 1, HP: 10}, {InvenIndex: 72, ID: 360, Level: 1, HP: 10}}, inv, "", "")
if err != nil {
t.Fatal(err)
}
chars.AttachMaxHealth(func(player.Character) (uint64, error) { return 100, nil })
if err = chars.EnsurePersisted(); err != nil {
t.Fatal(err)
}
wallet, err := player.OpenWallet(store, player.Currency{Gold: gold})
if err != nil {
t.Fatal(err)
}
if err = wallet.EnsurePersisted(); err != nil {
t.Fatal(err)
}
s, err := New(store, chars, wallet, func(pack, npc uint64) (gamedata.InnRule, uint64, error) {
return gamedata.InnRule{NPCID: 7, MapID: 33, ItemCount: 2, Currency: 4, FreeSquadLevel: 20, GoodDiscount: 10}, state, nil
}, func() (uint64, error) { return level, nil }, func() bool { return false })
if err != nil {
t.Fatal(err)
}
s.BeginSession("test")
return s, chars, wallet, store
}
type innReceiptFailStore struct{ stateio.Store }
func (s innReceiptFailStore) Save(name string, b []byte) error {
if name == "npcinn" {
return errors.New("receipt write failed")
}
return s.Store.Save(name, b)
}
func TestInnReceiptFailureRollsBackGoldAndHealthTogether(t *testing.T) {
path := filepath.Join(t.TempDir(), "state.db")
repo, err := accountstate.Open(path)
if err != nil {
t.Fatal(err)
}
s, _, _, _ := innTestServiceWithStore(t, repo, 10, 21, 1)
s.store = innReceiptFailStore{repo}
op, err := repo.BeginOperation()
if err != nil {
t.Fatal(err)
}
if _, _, _, err = s.Handle("/CharAllRevival", recoveryRequest(1, 71)); err == nil {
t.Fatal("receipt failure ignored")
}
if err = op.Rollback(); err != nil && !errors.Is(err, stateio.ErrStateRecoveryRequired) {
t.Fatal(err)
}
repo.Close()
repo, err = accountstate.Open(path)
if err != nil {
t.Fatal(err)
}
defer repo.Close()
next, chars, wallet, _ := innTestServiceWithStore(t, repo, 0, 21, 1)
hp, _ := chars.CurrentHealth(71)
if hp != 10 || wallet.Snapshot().Gold != 10 {
t.Fatal("failed inn request committed health or charge")
}
op, err = repo.BeginOperation()
if err != nil {
t.Fatal(err)
}
if _, _, _, err = next.Handle("/CharAllRevival", recoveryRequest(1, 71)); err != nil {
t.Fatal(err)
}
if err = op.Commit(); err != nil {
t.Fatal(err)
}
hp, _ = chars.CurrentHealth(71)
if hp != 100 || wallet.Snapshot().Gold != 5 {
t.Fatal("retry did not commit recovery once")
}
}
func recoveryRequest(seq uint64, indices ...uint64) []byte {
b := wire.AppendVarint(nil, 1, seq)
b = wire.AppendVarint(b, 2, 1)
b = wire.AppendVarint(b, 3, 7)
for _, i := range indices {
b = wire.AppendVarint(b, 4, i)
}
return b
}
func TestInnPartialRecoveryAndReplayUsePersistedHealth(t *testing.T) {
s, chars, wallet, store := innTestService(t, 2, 21, 1)
b := recoveryRequest(1, 71, 72)
code, response, handled, err := s.Handle("/CharAllRevival", b)
if err != nil || !handled || code != 11 {
t.Fatal(err)
}
hp, _ := chars.CurrentHealth(71)
second, _ := chars.CurrentHealth(72)
if hp != 50 || second != 10 || wallet.Snapshot().Gold != 0 {
t.Fatalf("partial recovery hp=%d second=%d wallet=%+v", hp, second, wallet.Snapshot())
}
next, err := New(store, chars, wallet, s.context, s.level, s.battle)
if err != nil {
t.Fatal(err)
}
next.BeginSession("test")
_, replay, _, err := next.Handle("/CharAllRevival", b)
if err != nil || !bytes.Equal(response, replay) {
t.Fatal("receipt lost on reopening")
}
changed := recoveryRequest(1, 72)
if _, _, _, err = next.Handle("/CharAllRevival", changed); err == nil {
t.Fatal("changed recovery replay accepted")
}
}
func TestInnFreeLevelDeadRevivalAndDiscountPrice(t *testing.T) {
s, chars, wallet, _ := innTestService(t, 0, 20, 1)
if err := chars.SetCurrentHealth(72, 0); err != nil {
t.Fatal(err)
}
if _, _, _, err := s.Handle("/CharAllRevival", recoveryRequest(1, 71, 72)); err != nil {
t.Fatal(err)
}
a, _ := chars.CurrentHealth(71)
b, _ := chars.CurrentHealth(72)
if a != 100 || b != 1 || wallet.Snapshot().Gold != 0 {
t.Fatal("free squad recovery or free 1 HP resurrection incorrect")
}
s, chars, wallet, _ = innTestService(t, 10, 21, 2)
if _, _, _, err := s.Handle("/CharAllRevival", recoveryRequest(1, 71)); err != nil {
t.Fatal(err)
}
a, _ = chars.CurrentHealth(71)
if a != 100 || wallet.Snapshot().Gold != 5 {
t.Fatal("discount recovery must ceil 90 * 2 /40 *90%=4.05 to 5 gold")
}
if _, _, _, err := s.Handle("/CharAllRevival", recoveryRequest(2, 71, 71)); err == nil {
t.Fatal("duplicate targets accepted")
}
}