165 lines
5.5 KiB
Go
165 lines
5.5 KiB
Go
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")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|