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

165 lines
5.5 KiB
Go
Raw Normal View History

package gameserver
import (
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"gac-hkrpg-go/internal/common"
goproto "gac-hkrpg-go/protocol/proto"
"google.golang.org/protobuf/proto"
)
func TestCurrencyWarsEntranceUsesCapturedRoomConditionWithoutMutatingScene(t *testing.T) {
scene := &common.LevelOutputConfig{SavedValues: map[string]int32{
"FSV_370GridFight": 0,
"unrelated": 7,
}}
group := common.CurrencyWarsRoomGroup{GroupID: 1}
group.Condition.Name = "FSV_370GridFight"
group.Condition.Comparison = "GreaterEqual"
group.Condition.Value = 1
entrance := common.CurrencyWarsEntrance{
EntryID: 2,
RoomGroups: []common.CurrencyWarsRoomGroup{group},
SavedValues: map[string]int32{"FSV_370GridFight": 2},
}
got, err := currencyWarsEntranceScene(scene, entrance)
if err != nil {
t.Fatalf("apply captured CW entrance profile: %v", err)
}
if got.SavedValues["FSV_370GridFight"] != 2 || got.SavedValues["unrelated"] != 7 {
t.Fatalf("CW entrance profile = %v; want selected and unrelated values preserved", got.SavedValues)
}
if scene.SavedValues["FSV_370GridFight"] != 0 {
t.Fatal("applying CW freeplay profile mutated shared scene data")
}
got.SavedValues["FSV_370GridFight"] = 99
if scene.SavedValues["FSV_370GridFight"] != 0 {
t.Fatal("returned CW scene shares its saved-value map with the original")
}
}
func TestUnimplementedCurrencyWarsRequestsRemainSuppressed(t *testing.T) {
for _, name := range []string{
"GridFightEnterBattleStageCsReq",
"GridFightRefreshShopCsReq",
} {
t.Run(name, func(t *testing.T) {
id, ok := goproto.CmdIDByName[name]
if !ok {
t.Fatalf("protocol command table is missing %s", name)
}
if !isDangerCmd(uint16(id)) {
t.Fatalf("unsupported CW request %s is not protected from an empty response", name)
}
if typedHandlers[uint16(id)] != nil || rawHandlers[uint16(id)] != nil {
t.Fatalf("unsupported CW request %s unexpectedly has a handler", name)
}
if _, ok := dummyResponses[uint16(id)]; ok {
t.Fatalf("unsupported CW request %s unexpectedly has a dummy response", name)
}
if _, ok := goproto.RspIDByReqID[uint32(id)]; !ok {
t.Fatalf("test request %s has no paired response to protect", name)
}
})
}
getDataID, ok := goproto.CmdIDByName["GridFightGetDataCsReq"]
if !ok || typedHandlers[uint16(getDataID)] == nil {
t.Fatal("the verified CW bootstrap request must keep its typed handler")
}
}
func TestCurrencyWarsStartGameplayLogsRequestedModeWithoutReply(t *testing.T) {
id := uint16(goproto.CmdIDByName["GridFightStartGamePlayCsReq"])
handler := rawHandlers[id]
if handler == nil {
t.Fatal("CW start request must have a mode-observation handler")
}
if typedHandlers[id] != nil {
t.Fatal("CW start request must not send an unverified typed response")
}
if _, ok := dummyResponses[id]; ok {
t.Fatal("CW start request must not receive an empty response")
}
if !isDangerCmd(id) {
t.Fatal("CW start request must remain protected from dispatcher auto-replies")
}
if err := handler(nil, nil); err == nil {
t.Fatal("empty start request must not be misreported as Standard Gambit")
}
oldWD, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
if err := os.Chdir(t.TempDir()); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = os.Chdir(oldWD) })
for _, tc := range []struct {
name string
overclock bool
wantMode string
divisionID uint32
}{
{name: "standard", wantMode: "standard", divisionID: 42},
{name: "overclock", overclock: true, wantMode: "overclock", divisionID: 42},
} {
t.Run(tc.name, func(t *testing.T) {
req := &goproto.GridFightStartGamePlayCsReq{DivisionId: tc.divisionID, IsOverlock: tc.overclock}
payload, err := proto.Marshal(req)
if err != nil {
t.Fatal(err)
}
// A nil session makes any accidental attempt to send a response fail.
if err := handler(nil, payload); err != nil {
t.Fatalf("observe mode: %v", err)
}
logData, err := os.ReadFile(filepath.Join("gameserver.log"))
if err != nil {
t.Fatal(err)
}
log := string(logData)
if !strings.Contains(log, "CW_START_GAMEPLAY observed mode="+tc.wantMode+" division="+strconv.FormatUint(uint64(tc.divisionID), 10)) || !strings.Contains(log, "response_suppressed=true reason=run_state_unverified") {
t.Fatalf("mode diagnostic missing or malformed: %s", log)
}
})
}
}
func TestCurrencyWarsEntranceRejectsUnsupportedOrUnmetConditions(t *testing.T) {
tests := []struct {
name string
value int32
comparison string
wantErr string
}{
{name: "condition unmet", value: 0, comparison: "GreaterEqual", wantErr: "condition is not satisfied"},
{name: "unsupported comparison", value: 2, comparison: "Equal", wantErr: "unsupported CW room comparison"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
scene := &common.LevelOutputConfig{SavedValues: map[string]int32{"condition": 0}}
group := common.CurrencyWarsRoomGroup{GroupID: 1}
group.Condition.Name = "condition"
group.Condition.Comparison = tt.comparison
group.Condition.Value = 1
entrance := common.CurrencyWarsEntrance{
RoomGroups: []common.CurrencyWarsRoomGroup{group},
SavedValues: map[string]int32{"condition": tt.value},
}
if _, err := currencyWarsEntranceScene(scene, entrance); err == nil || !strings.Contains(err.Error(), tt.wantErr) {
t.Fatalf("entrance error = %v; want %q", err, tt.wantErr)
}
if scene.SavedValues["condition"] != 0 {
t.Fatal("rejected CW profile mutated shared scene data")
}
})
}
}