#include "telemetry.hpp" #include #include #include #include #include namespace fs = std::filesystem; using utils::telemetry::outbox; using utils::telemetry::new_match_key; using utils::telemetry::parse_acknowledgement; const std::string server = "t6_zombie_buried_1"; const std::string endpoint = "https://example.test/api/telemetry"; std::string body(const std::string& match, int seq, const std::string& event) { nlohmann::json value = {{"server", server}, {"map", "zm_buried"}, {"match", match}, {"seq", seq}, {"event", event}, {"players", nlohmann::json::array()}}; if (event != "game_start") value["round"] = 12; return value.dump(); } template void must_fail(F action) { bool failed = false; try { action(); } catch (const std::exception&) { failed = true; } assert(failed); } int main(int argc, char** argv) { assert(argc >= 2); const fs::path root(argv[1]); const outbox queue(root, server, endpoint); if (argc == 3) { const std::string mode(argv[2]); if (mode == "produce") { const auto match = new_match_key(); queue.store(body(match, 1, "game_start")); queue.store(body(match, 14, "game_end")); } else if (mode == "recover") { const auto reports = queue.load(); assert(reports.size() == 2); const auto next = new_match_key(); assert(reports[0].match != next); queue.store(body(next, 1, "game_start")); const auto all = queue.load(); assert(all.size() == 3 && all[0].event == "game_start" && all[1].event == "game_end"); assert(all[0].match == all[1].match && all[2].match == next); for (const auto& value : all) queue.erase(value.match, value.seq); } else assert(mode == "empty" && queue.load().empty()); return 0; } fs::create_directories(root); assert(queue.load().empty()); std::set keys; for (int i = 0; i < 1000; ++i) { const auto key = new_match_key(); assert(!key.empty() && key.size() <= 64 && keys.insert(key).second); } const auto match = new_match_key(); const auto start = body(match, 1, "game_start"); const auto end = body(match, 30, "game_end"); queue.store(start); queue.store(end); queue.store(start); // A retry cannot add a duplicate or reorder the queue. const auto replay = outbox(root, server, endpoint).load(); assert(replay.size() == 2 && replay[0].body == start && replay[1].body == end); assert(replay[0].seq == 1 && replay[1].seq == 30 && replay[1].round == 12); assert(outbox(root, "other-server", endpoint).load().empty()); assert(outbox(root, server, "https://different.test/api").load().empty()); auto changed = nlohmann::json::parse(start); changed["map"] = "another-map"; must_fail([&] { queue.store(changed.dump()); }); must_fail([&] { queue.store(body(match, 0, "game_start")); }); must_fail([&] { queue.store(body(match, 2, "game_start")); }); must_fail([&] { queue.store(body(match, 3, "heartbeat")); }); must_fail([&] { queue.store("error"); }); changed = nlohmann::json::parse(end); changed["server"] = "wrong-server"; must_fail([&] { queue.store(changed.dump()); }); const auto file = fs::directory_iterator(root)->path(); auto temporary = file; temporary += ".tmp"; { std::ofstream stream(temporary); stream << "interrupted write"; } assert(queue.load().size() == 2); // Incomplete temp file never replaces committed data. fs::remove(temporary); fs::create_directory(temporary); // Force replacement writes to fail even under root. must_fail([&] { queue.erase(match, 1); }); must_fail([&] { queue.store(body(new_match_key(), 1, "game_start")); }); assert(queue.load().size() == 2); fs::remove(temporary); queue.erase(match, 1); queue.erase(match, 1); // Cleanup is idempotent after an uncertain completion. assert(queue.load().size() == 1 && queue.load()[0].body == end); queue.erase(match, 30); assert(queue.load().empty()); { std::ofstream stream(file); stream << "corrupt"; } must_fail([&] { queue.load(); }); must_fail([&] { queue.store(start); }); assert(fs::file_size(file) == 7); // Never silently overwrite corrupt retained data. fs::remove(file); for (const std::string response : {"", "bad gateway", "{", "null", "[]", "true", "{}", "{\"ok\":false}", "{\"ok\":1}", "{\"ok\":\"true\"}", "{\"ok\":true}garbage"}) assert(!parse_acknowledgement(response).accepted); const auto ack = parse_acknowledgement(R"({"ok":true,"telemetry":{"timing":{"matchId":921}}})"); assert(ack.accepted && ack.match_id == "921"); assert(parse_acknowledgement(R"({"ok":true})").accepted); const auto large_id = parse_acknowledgement(R"({"ok":true,"telemetry":{"timing":{"matchId":9223372036854775807}}})"); assert(large_id.match_id == "9223372036854775807"); assert(parse_acknowledgement(R"({"ok":true,"telemetry":null})").match_id.empty()); assert(parse_acknowledgement(R"({"ok":true,"telemetry":{"timing":{"matchId":"wrong"}}})").match_id.empty()); assert(!parse_acknowledgement("{\"ok\":true}" + std::string(0x5000, ' ')).accepted); std::cout << "PASS native telemetry fixtures\n"; }