package dnfbridge import ( "testing" ) // TestDecodeCompanionToolRequestAcceptsToolCamelCase 是「GM 工具能存」的直接护栏。 // // GM 工具页(GmTool90CN v2 的魔改页)发的是 camelCase + 字符串 ID; // 服务端内部与 Server-90 用 snake_case + 数字。两者必须都能解析成功, // 否则工具会得到 400 / character_id=0,表现为"保存失败"。 func TestDecodeCompanionToolRequestAcceptsToolCamelCase(t *testing.T) { body := []byte(`{ "characterId":"1", "expectedRevision":7, "enabled":true, "balanceEnabled":true, "progressionEnabled":false, "randomEquipmentEnabled":true, "sourceCharacterId":"11", "sourceCharacterIds":["3","11"], "ai":{"buff_policy":"leader_first","combo_preset":"auto"}, "aiProfiles":{"11":{"behavior":"protective"}} }`) request, err := DecodeCompanionToolRequest(body) if err != nil { t.Fatalf("decode camelCase: %v", err) } if request.CharacterID != 1 { t.Fatalf("character_id=%d want=1", request.CharacterID) } if request.ExpectedRevision != 7 { t.Fatalf("expected_revision=%d want=7", request.ExpectedRevision) } if !request.Enabled || !request.BalanceEnabled || request.ProgressionEnabled || !request.RandomEquipmentEnabled { t.Fatalf("switch decode wrong: %+v", request) } if request.SourceCharacterID != 11 { t.Fatalf("source_character_id=%d want=11", request.SourceCharacterID) } if len(request.SourceCharacterIDs) != 2 || request.SourceCharacterIDs[0] != 3 || request.SourceCharacterIDs[1] != 11 { t.Fatalf("source_character_ids=%v want=[3 11]", request.SourceCharacterIDs) } if request.AI.BuffPolicy != "leader_first" { t.Fatalf("ai buff_policy=%q want=leader_first", request.AI.BuffPolicy) } if profile, ok := request.AIProfiles["11"]; !ok || profile.Behavior != "protective" { t.Fatalf("ai_profiles=%+v want profile 11 protective", request.AIProfiles) } } // TestDecodeCompanionToolRequestAcceptsServerSnakeCase 保证内部/Server-90 协议不回归。 func TestDecodeCompanionToolRequestAcceptsServerSnakeCase(t *testing.T) { body := []byte(`{ "character_id":1, "expected_revision":7, "enabled":true, "balance_enabled":true, "source_character_id":11, "source_character_ids":[3,11] }`) request, err := DecodeCompanionToolRequest(body) if err != nil { t.Fatalf("decode snake_case: %v", err) } if request.CharacterID != 1 || request.ExpectedRevision != 7 || request.SourceCharacterID != 11 { t.Fatalf("snake_case decode wrong: %+v", request) } if len(request.SourceCharacterIDs) != 2 || request.SourceCharacterIDs[1] != 11 { t.Fatalf("source_character_ids=%v want=[3 11]", request.SourceCharacterIDs) } if !request.BalanceEnabled { t.Fatalf("balance_enabled not decoded: %+v", request) } } // TestDecodeCompanionToolCharacterIDAcceptsBothSpellings 覆盖 GET 查询串。 func TestDecodeCompanionToolCharacterIDAcceptsBothSpellings(t *testing.T) { cases := map[string]uint16{ "character_id=1": 1, "characterId=12": 12, "characterId=%31%32": 12, "a=1&characterId=65535": 65535, } for query, want := range cases { got, ok := DecodeCompanionToolCharacterID(query) if !ok || got != want { t.Fatalf("query %q -> (%d,%t) want %d", query, got, ok, want) } } for _, query := range []string{"", "characterId=0", "characterId=abc", "characterId=70000"} { if got, ok := DecodeCompanionToolCharacterID(query); ok { t.Fatalf("query %q should be rejected, got %d", query, got) } } } // TestDecodeCompanionToolRequestEmptyBodyIsZeroValue 保证空体不 panic, // 由调用方按 character_id==0 走 400。 func TestDecodeCompanionToolRequestEmptyBodyIsZeroValue(t *testing.T) { request, err := DecodeCompanionToolRequest(nil) if err != nil { t.Fatalf("empty body: %v", err) } if request.CharacterID != 0 { t.Fatalf("empty body character_id=%d want=0", request.CharacterID) } }