Files
GaCy-GO/internal/gameserver/currency_wars_end_test.go
T

294 lines
9.7 KiB
Go
Raw Normal View History

package gameserver
import (
"bytes"
"encoding/json"
"errors"
"os"
"testing"
"gac-hkrpg-go/internal/common"
pb "gac-hkrpg-go/protocol/proto"
"google.golang.org/protobuf/proto"
)
func cwEndFixture(t *testing.T) (*common.CurrencyWarsCatalog, *pb.GridFightGetDataScRsp, *Session, uint32) {
t.Helper()
c := isolatedCWTest(t)
data, err := newCurrencyWarsData(c)
if err != nil {
t.Fatal(err)
}
request := &pb.GridFightStartGamePlayCsReq{DivisionId: c.CapturedStages[0].ID, LJNGPCKPJLF: c.ActiveSeasonID}
// Select a division actually present in the captured initial-run resource.
raw, err := common.ReadResourceFile("resources/CurrencyWarsInitialRuns.json")
if err != nil {
t.Fatal(err)
}
var resource struct {
Entries []struct {
Division uint32 `json:"division_id"`
Overclock bool `json:"is_overclock"`
} `json:"entries"`
}
if err := json.Unmarshal(raw, &resource); err != nil || len(resource.Entries) == 0 {
t.Fatalf("initial runs: %v", err)
}
request.DivisionId, request.IsOverlock = resource.Entries[0].Division, resource.Entries[0].Overclock
run, err := buildCurrencyWarsInitialRun(c, request, 7)
if err != nil {
t.Fatal(err)
}
offer := run.FMIGOKIDKJL.GetMFFCPLBNHCF()
if offer == nil || len(offer.NPHOIMEEDHM) == 0 {
t.Fatal("portal offer missing")
}
run, _, err = currencyWarsEntryChoice(c, run, run.FMIGOKIDKJL.QueuePosition, offer.NPHOIMEEDHM[0])
if err != nil {
t.Fatal(err)
}
data.DHFOFLFCLAH = run
s := &Session{Token: 444, currencyWarsOpen: true}
if err := s.saveCurrencyWarsData(data, c); err != nil {
t.Fatal(err)
}
cfg, err := common.LoadRuntimeConfig()
if err != nil {
t.Fatal(err)
}
return c, data, s, cfg.PlayerUID
}
func TestCurrencyWarsEndWaitsForBattleAndSettlesPendingRun(t *testing.T) {
c, data, s, uid := cwEndFixture(t)
runID := data.DHFOFLFCLAH.UniqueId
if data.DHFOFLFCLAH.FMIGOKIDKJL == nil {
t.Fatal("fixture must have a pending choice")
}
if err := common.PatchPersistentFields(map[string]json.RawMessage{"ex_ranks_unlocked": json.RawMessage(`{"1204":false}`), "unrelated": json.RawMessage(`{"keep":true}`)}); err != nil {
t.Fatal(err)
}
// An ordinary grant queued first must not prevent ending a stuck page.
if _, err := common.QueueCurrencyWarsCommand(uid, runID, "gold", 99); err != nil {
t.Fatal(err)
}
endID, err := common.QueueCurrencyWarsCommand(uid, runID, common.CurrencyWarsCommandEnd, 0)
if err != nil {
t.Fatal(err)
}
before, _ := os.ReadFile("persistent")
CURRENT_BATTLE.Store(s.Token, &pb.SceneBattleInfo{})
if err := s.applyCurrencyWarsCommands(); err != nil {
t.Fatal(err)
}
CURRENT_BATTLE.Delete(s.Token)
after, _ := os.ReadFile("persistent")
if !bytes.Equal(before, after) {
t.Fatal("end ran while Battle was active")
}
if err := s.applyCurrencyWarsCommands(); err != nil {
t.Fatal(err)
}
saved, _, err := readCurrencyWarsData(c)
if err != nil || saved.DHFOFLFCLAH != nil {
t.Fatalf("run not ended: %v", err)
}
snapshot, err := cwArchiveSnapshot(c)
if err != nil {
t.Fatal(err)
}
archives, err := cwReadArchives(snapshot)
if err != nil || len(archives) != 1 {
t.Fatalf("archive: %v", err)
}
if archives[0].BFLLJJBHLDF.Reason != pb.PEIJNEFOCHI_PEIJNEFOCHI_NOCIJDINKPJ {
t.Fatal("end must settle as quit")
}
queued, err := common.ReadCurrencyWarsCommands()
if err != nil {
t.Fatal(err)
}
for _, cmd := range queued {
if cmd.ID == endID {
t.Fatal("end was not atomically acknowledged")
}
}
var root map[string]json.RawMessage
raw, _ := os.ReadFile("persistent")
if err := json.Unmarshal(raw, &root); err != nil {
t.Fatal(err)
}
var ex, unrelated map[string]bool
if err := json.Unmarshal(root["ex_ranks_unlocked"], &ex); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(root["unrelated"], &unrelated); err != nil {
t.Fatal(err)
}
if len(ex) != 1 || ex["1204"] || !unrelated["keep"] || len(unrelated) != 1 {
t.Fatal("unrelated state changed")
}
if err := s.applyCurrencyWarsCommands(); err != nil {
t.Fatal(err)
}
again, err := cwArchiveSnapshot(c)
if err != nil || len(again.Archives) != 1 {
t.Fatal("duplicate settlement")
}
}
func TestCurrencyWarsEndRejectsStaleAndDifferentRun(t *testing.T) {
c, data, s, uid := cwEndFixture(t)
runID := data.DHFOFLFCLAH.UniqueId
cmdID, err := common.QueueCurrencyWarsCommand(uid, runID, common.CurrencyWarsCommandEnd, 0)
if err != nil {
t.Fatal(err)
}
s.currencyWarsRevision--
before, _ := os.ReadFile("persistent")
if err := s.applyCurrencyWarsCommands(); !errors.Is(err, common.ErrCurrencyWarsSaveConflict) {
t.Fatalf("stale open session: %v", err)
}
after, _ := os.ReadFile("persistent")
if !bytes.Equal(before, after) {
t.Fatal("stale session changed save")
}
if err := common.AcknowledgeCurrencyWarsCommand(cmdID); err != nil {
t.Fatal(err)
}
if _, err := common.QueueCurrencyWarsCommand(uid, runID+1, common.CurrencyWarsCommandEnd, 0); err != nil {
t.Fatal(err)
}
if err := s.applyCurrencyWarsCommands(); err != nil {
t.Fatal(err)
}
saved, _, err := readCurrencyWarsData(c)
if err != nil || saved.DHFOFLFCLAH == nil || saved.DHFOFLFCLAH.UniqueId != runID {
t.Fatal("wrong-run command ended active run")
}
}
func TestCurrencyWarsEndWorksAfterLeavingCW(t *testing.T) {
c, data, s, uid := cwEndFixture(t)
if _, err := common.QueueCurrencyWarsCommand(uid, data.DHFOFLFCLAH.UniqueId, common.CurrencyWarsCommandEnd, 0); err != nil {
t.Fatal(err)
}
s.currencyWarsOpen = false
s.currencyWarsRevision = 0 // A newly connected session has not read CW yet.
if err := s.applyCurrencyWarsCommands(); err != nil {
t.Fatal(err)
}
saved, _, err := readCurrencyWarsData(c)
if err != nil || saved.DHFOFLFCLAH != nil {
t.Fatal("closed CW did not end")
}
}
func TestCurrencyWarsEndNotifiesOpenClient(t *testing.T) {
_, data, savedSession, uid := cwEndFixture(t)
s, receive := replySession(t)
s.currencyWarsRevision = savedSession.currencyWarsRevision
s.currencyWarsOpen = true
if _, err := common.QueueCurrencyWarsCommand(uid, data.DHFOFLFCLAH.UniqueId, common.CurrencyWarsCommandEnd, 0); err != nil {
t.Fatal(err)
}
if err := s.applyCurrencyWarsCommands(); err != nil {
t.Fatal(err)
}
receive(&pb.GridFightSyncKeepWinCntNotify{})
settlement := &pb.GridFightSettleNotify{}
receive(settlement)
if settlement.BFLLJJBHLDF == nil || settlement.BFLLJJBHLDF.Reason != pb.PEIJNEFOCHI_PEIJNEFOCHI_NOCIJDINKPJ {
t.Fatal("client did not receive quit settlement")
}
}
func TestCurrencyWarsEndDoesNotCommitIfCommandIsMissing(t *testing.T) {
_, data, s, uid := cwEndFixture(t)
before, _ := os.ReadFile("persistent")
cmd := common.CurrencyWarsCommand{ID: "missing", UID: uid, RunID: data.DHFOFLFCLAH.UniqueId, Field: common.CurrencyWarsCommandEnd}
if err := s.applyCurrencyWarsEnd(cmd); err == nil {
t.Fatal("missing command should reject settlement")
}
after, _ := os.ReadFile("persistent")
if !bytes.Equal(before, after) {
t.Fatal("failed acknowledgement committed settlement or archive")
}
}
func TestCurrencyWarsOpenStateTracksStartLeaveAndResume(t *testing.T) {
_, data, writer, uid := cwEndFixture(t)
previous := data.DHFOFLFCLAH
if _, err := common.QueueCurrencyWarsCommand(uid, previous.UniqueId, common.CurrencyWarsCommandEnd, 0); err != nil {
t.Fatal(err)
}
writer.currencyWarsOpen = false
if err := writer.applyCurrencyWarsCommands(); err != nil {
t.Fatal(err)
}
s, receive := replySession(t)
s.currencyWarsRevision = writer.currencyWarsRevision
req := &pb.GridFightStartGamePlayCsReq{DivisionId: previous.DivisionId, IsOverlock: previous.IsOverlock, LJNGPCKPJLF: previous.LJNGPCKPJLF}
callReviewRequest(t, s, req)
start := &pb.GridFightStartGamePlayScRsp{}
receive(start)
if start.Retcode != 0 || start.DHFOFLFCLAH == nil || !s.currencyWarsOpen {
t.Fatal("successful start did not mark CW open")
}
callReviewRequest(t, s, &pb.GridFightQuitLeaveGamePlayCsReq{})
receive(&pb.GridFightQuitLeaveGamePlayScRsp{})
if s.currencyWarsOpen {
t.Fatal("leave did not close CW")
}
callReviewRequest(t, s, &pb.GridFightResumeGamePlayCsReq{UniqueId: start.DHFOFLFCLAH.UniqueId})
resume := &pb.GridFightResumeGamePlayScRsp{}
receive(resume)
if resume.Retcode != 0 || !s.currencyWarsOpen {
t.Fatal("successful resume did not mark CW open")
}
}
func TestCurrencyWarsInitialRoundReservesHigherStarStarter(t *testing.T) {
c, data, _, _ := cwEndFixture(t)
run := data.DHFOFLFCLAH
_, team, shop := cwComponents(run)
if team == nil || shop == nil || len(team.GridGameRoleList) == 0 {
t.Fatal("starter team fixture missing")
}
pool, err := c.NewCapturedCardPool()
if err != nil {
t.Fatal(err)
}
var selected common.CurrencyWarsRoleVariant
for _, v := range c.CapturedVariants {
base, err := c.CapturedRole(v.Config.ID)
if err != nil || v.Star <= base.DefaultStar {
continue
}
if _, present := pool.Roles[v.Config.ID]; present {
selected = v
break
}
}
if selected.Config.ID == 0 {
t.Fatal("higher-star pool role missing")
}
// The live failure occurs after a portal has supplied a non-default star.
team.GridGameRoleList = []*pb.CDEAABODKCP{{Id: selected.Config.ID, RoleStar: selected.Star, UniqueId: 1, Pos: 1}}
if run.FMIGOKIDKJL.GetNNDGEJFLGGF() == nil || len(shop.OGDKJPDFFAI) != 0 {
t.Fatal("fixture is not at initial round")
}
invalid := proto.Clone(run).(*pb.MPIMAEMCEFH)
_, invalidTeam, _ := cwComponents(invalid)
invalidTeam.GridGameRoleList[0].RoleStar = ^uint32(0)
before := proto.Clone(invalid)
if _, err := cwInitialRound(c, invalid); err == nil || !proto.Equal(before, invalid) {
t.Fatal("invalid starter star was accepted or changed state")
}
if _, err := cwInitialRound(c, run); err != nil {
t.Fatalf("higher-star starter rejected: %v", err)
}
if run.FMIGOKIDKJL.GetLGNPHHBGKFH() == nil || len(shop.OGDKJPDFFAI) == 0 {
t.Fatal("initial round did not advance to preparation/shop")
}
}