tests: cover headless character delete
Some checks failed
build / Linux asan (push) Has been cancelled
build / Linux release (push) Has been cancelled
build / FreeBSD build (push) Has been cancelled

This commit is contained in:
server
2026-04-14 12:56:46 +02:00
parent 5f11a4fef0
commit bc1175eb35

View File

@@ -155,6 +155,14 @@ struct PacketCGPlayerCreate
uint8_t dex;
};
struct PacketCGPlayerDelete
{
uint16_t header;
uint16_t length;
uint8_t index;
char private_code[8];
};
struct PacketGCPlayerCreateSuccess
{
uint16_t header;
@@ -170,6 +178,13 @@ struct PacketGCCreateFailure
uint8_t type;
};
struct PacketGCPlayerDeleteSuccess
{
uint16_t header;
uint16_t length;
uint8_t account_index;
};
struct PacketCGPlayerSelect
{
uint16_t header;
@@ -247,6 +262,7 @@ struct SmokeOptions
std::string client_version = "1215955205";
std::string expect_auth_failure;
std::string expect_channel_failure;
std::string delete_private_code;
std::string mall_password;
};
@@ -262,14 +278,17 @@ struct SmokeResult
int character_index = -1;
std::string character_name;
bool character_created = false;
bool character_deleted = false;
int game_channel = -1;
bool mall_opened = false;
int mall_size = -1;
int deleted_character_index = -1;
int64_t auth_handshake_ms = -1;
int64_t auth_login_ms = -1;
int64_t channel_handshake_ms = -1;
int64_t channel_login_ms = -1;
int64_t character_create_ms = -1;
int64_t character_delete_ms = -1;
int64_t character_select_ms = -1;
int64_t entergame_ms = -1;
int64_t mall_open_ms = -1;
@@ -358,6 +377,8 @@ void PrintJson(const SmokeResult& result)
<< ",\"character_index\":" << result.character_index
<< ",\"character_name\":\"" << JsonEscape(result.character_name) << "\""
<< ",\"character_created\":" << (result.character_created ? "true" : "false")
<< ",\"character_deleted\":" << (result.character_deleted ? "true" : "false")
<< ",\"deleted_character_index\":" << result.deleted_character_index
<< ",\"game_channel\":" << result.game_channel
<< ",\"mall_opened\":" << (result.mall_opened ? "true" : "false")
<< ",\"mall_size\":" << result.mall_size
@@ -367,6 +388,7 @@ void PrintJson(const SmokeResult& result)
<< ",\"channel_handshake\":" << result.channel_handshake_ms
<< ",\"channel_login\":" << result.channel_login_ms
<< ",\"character_create\":" << result.character_create_ms
<< ",\"character_delete\":" << result.character_delete_ms
<< ",\"character_select\":" << result.character_select_ms
<< ",\"entergame\":" << result.entergame_ms
<< ",\"mall_open\":" << result.mall_open_ms
@@ -735,6 +757,53 @@ uint8_t CreateCharacter(EncryptedClient& channel_client, std::string_view create
throw std::runtime_error("did not receive PLAYER_CREATE_SUCCESS");
}
void DeleteCharacter(EncryptedClient& channel_client, uint8_t character_index, std::string_view private_code, SmokeResult& result, bool json)
{
Expect(private_code.size() == 7, "delete private code must be exactly 7 characters");
PacketCGPlayerDelete delete_packet {};
delete_packet.header = CG::CHARACTER_DELETE;
delete_packet.length = sizeof(delete_packet);
delete_packet.index = character_index;
std::strncpy(delete_packet.private_code, private_code.data(), sizeof(delete_packet.private_code) - 1);
channel_client.SendEncryptedPacket(delete_packet);
std::vector<uint8_t> frame;
for (int i = 0; i < 16; ++i)
{
Expect(channel_client.WaitForFrame(frame, 5000), "timed out waiting for character delete response");
const auto header = FrameHeader(frame);
if (header == GC::PHASE)
continue;
if (header == GC::LOGIN_FAILURE)
{
Expect(frame.size() == sizeof(PacketGCLoginFailure), "unexpected character delete failure size");
const auto* failure = reinterpret_cast<const PacketGCLoginFailure*>(frame.data());
throw std::runtime_error(std::string("character delete failed: ") + failure->status);
}
if (header == GC::PLAYER_DELETE_WRONG_SOCIAL_ID)
{
throw std::runtime_error("character delete failed: wrong private code");
}
if (header == GC::PLAYER_DELETE_SUCCESS)
{
Expect(frame.size() == sizeof(PacketGCPlayerDeleteSuccess), "unexpected player delete success size");
const auto* success = reinterpret_cast<const PacketGCPlayerDeleteSuccess*>(frame.data());
Expect(success->account_index == character_index, "character delete returned unexpected index");
result.character_deleted = true;
result.deleted_character_index = success->account_index;
EmitEvent(result, json, "character_deleted index=" + std::to_string(static_cast<int>(success->account_index)));
return;
}
}
throw std::runtime_error("did not receive PLAYER_DELETE_SUCCESS");
}
void SelectCharacter(EncryptedClient& channel_client, uint8_t character_index, SmokeResult& result, bool json)
{
PacketCGPlayerSelect select_packet {};
@@ -918,7 +987,8 @@ int main(int argc, char** argv)
"usage: metin_login_smoke <host> <auth_port> <channel_port> <login> <password>\n"
" or: metin_login_smoke <host> <auth_port> <channel_port> <login> --password-env=ENV_NAME\n"
" optional: --create-character-name=NAME [--client-version=VERSION] [--json] "
"[--expect-auth-failure=STATUS] [--expect-channel-failure=STATUS] [--mall-password=PASSWORD]");
"[--expect-auth-failure=STATUS] [--expect-channel-failure=STATUS] [--delete-private-code=CODE] "
"[--mall-password=PASSWORD]");
const std::string host = argv[1];
const uint16_t auth_port = static_cast<uint16_t>(std::stoi(argv[2]));
@@ -946,6 +1016,7 @@ int main(int argc, char** argv)
const std::string version_prefix = "--client-version=";
const std::string auth_failure_prefix = "--expect-auth-failure=";
const std::string channel_failure_prefix = "--expect-channel-failure=";
const std::string delete_private_code_prefix = "--delete-private-code=";
const std::string mall_password_prefix = "--mall-password=";
if (create_arg.rfind(create_prefix, 0) == 0)
{
@@ -976,6 +1047,14 @@ int main(int argc, char** argv)
continue;
}
if (create_arg.rfind(delete_private_code_prefix, 0) == 0)
{
options.delete_private_code = create_arg.substr(delete_private_code_prefix.size());
Expect(!options.delete_private_code.empty(), "delete private code is empty");
Expect(options.delete_private_code.size() == 7, "delete private code must be exactly 7 characters");
continue;
}
if (create_arg.rfind(mall_password_prefix, 0) == 0)
{
options.mall_password = create_arg.substr(mall_password_prefix.size());
@@ -1087,6 +1166,21 @@ int main(int argc, char** argv)
result.character_index = character_index;
}
if (!options.delete_private_code.empty())
{
result.stage = "character_delete";
started_at = std::chrono::steady_clock::now();
DeleteCharacter(channel_client, character_index, options.delete_private_code, result, options.json);
result.character_delete_ms = ElapsedMs(started_at);
result.ok = true;
result.result = "delete_success";
result.stage = "complete";
EmitEvent(result, options.json, "delete_success");
if (options.json)
PrintJson(result);
return 0;
}
result.stage = "character_select";
started_at = std::chrono::steady_clock::now();
SelectCharacter(channel_client, character_index, result, options.json);