Files
DarkflameServer/tests/dWebTests/ClientSysInfoViewTests.cpp
Aaron Kimbrell c44af311ad feat(dashboard): client system info, as reported by the client
Account pages get a Client system info (as reported) card (client_sysinfo):
every description the account's client sent at login, newest first, with the
raw values, the memory text split into numbers and each field's caveat. The
address is only shown with logs_audit. The Client System Info page (Logs &
Health) shows the spread across players from each account's newest report:
Windows version, video card, memory buckets, processor count and client build,
marked as client-reported and approximate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 19:09:22 -05:00

125 lines
5.2 KiB
C++

#include <gtest/gtest.h>
#include "ClientSysInfoView.h"
#include "Permissions.h"
namespace {
IClientSysInfo::SysInfoRow Row(uint32_t account, uint32_t build, const std::string& card, uint32_t cpus, uint64_t memoryKb) {
IClientSysInfo::SysInfoRow r;
r.id = account;
r.accountId = account;
r.firstSeen = 1700000000;
r.lastSeen = 1700000100;
r.logins = 2;
r.ip = "203.0.113.7";
r.clientOs = 1;
r.memoryStats = " 12345 p, 67890 vbytes.40 n-use." + std::to_string(memoryKb) + " TKb-pmem.";
r.memoryTotalKb = memoryKb;
r.videoCard = card;
r.numberOfProcessors = cpus;
r.processorType = 586;
r.processorLevel = 6;
r.processorRevision = 0x9e0a;
r.osVersionInfoSize = 276;
r.majorVersion = build == 2600 ? 5 : 6;
r.minorVersion = build == 2600 ? 1 : 2;
r.buildNumber = build;
r.platformId = 2;
return r;
}
}
// What GET /api/accounts/:id/client_sysinfo sends for each row
TEST(ClientSysInfoViewTests, RowKeepsTheRawValues) {
const auto row = Row(7, 9200, "NVIDIA GeForce GTX 1080 (HAL-hw vp)", 16, 16717048);
const auto json = ClientSysInfoView::RowJson(row, true);
EXPECT_EQ(json["memory_stats"], row.memoryStats);
EXPECT_EQ(json["video_card"], row.videoCard);
EXPECT_EQ(json["processor_type"], 586);
EXPECT_EQ(json["processor_revision"], 0x9e0a);
EXPECT_EQ(json["processor_model"], 0x9e);
EXPECT_EQ(json["processor_stepping"], 0x0a);
EXPECT_EQ(json["os_version"], "6.2.9200");
EXPECT_EQ(json["os_version_info_size"], 276);
EXPECT_EQ(json["memory"]["total_phys_kb"], 16717048);
EXPECT_EQ(json["memory"]["memory_load_percent"], 40);
EXPECT_TRUE(json["memory"]["avail_phys_kb"].is_null()); // not in the text
EXPECT_EQ(json["memory_bucket"], "8-16 GB");
EXPECT_EQ(json["ip"], "203.0.113.7");
}
TEST(ClientSysInfoViewTests, AddressOnlyWithLogsAudit) {
const auto json = ClientSysInfoView::RowJson(Row(7, 9200, "Card", 4, 0), false);
EXPECT_FALSE(json.contains("ip"));
EXPECT_EQ(json["memory_bucket"], "Not read");
}
TEST(ClientSysInfoViewTests, EveryShownFieldHasACaveat) {
const auto& caveats = ClientSysInfoView::Caveats();
for (const auto* key : { "ip", "clientOs", "memoryStats", "videoCard", "numberOfProcessors", "processorType", "processorLevel",
"processorRevision", "osVersionInfoSize", "osVersion", "platformId" }) {
ASSERT_TRUE(caveats.contains(key)) << key;
EXPECT_FALSE(caveats[key].get<std::string>().empty()) << key;
}
// The Windows version caveat says why it's not the real version
EXPECT_NE(caveats["osVersion"].get<std::string>().find("6.2 build 9200"), std::string::npos);
}
TEST(ClientSysInfoViewTests, OsLabelsSayWhatIsReported) {
EXPECT_NE(ClientSysInfoView::OsLabel(Row(1, 9200, "", 1, 0)).find("8.1, 10 and 11"), std::string::npos);
EXPECT_NE(ClientSysInfoView::OsLabel(Row(1, 2600, "", 1, 0)).find("compatibility mode"), std::string::npos);
}
TEST(ClientSysInfoViewTests, MemoryBuckets) {
constexpr uint64_t GB = 1024 * 1024;
EXPECT_EQ(ClientSysInfoView::MemoryBucket(1 * GB), "Under 2 GB");
EXPECT_EQ(ClientSysInfoView::MemoryBucket(4 * GB - 100000), "2-4 GB"); // a 4 GB machine reports a little under 4 GB
EXPECT_EQ(ClientSysInfoView::MemoryBucket(16 * GB), "8-16 GB");
EXPECT_EQ(ClientSysInfoView::MemoryBucket(33 * GB), "32-64 GB");
EXPECT_EQ(ClientSysInfoView::MemoryBucket(128 * GB), "Over 64 GB");
}
// What GET /api/client_sysinfo/spread sends
TEST(ClientSysInfoViewTests, SpreadCountsEachAccountOnce) {
constexpr uint64_t GB = 1024 * 1024;
const std::vector<IClientSysInfo::SysInfoRow> latest{
Row(1, 9200, "NVIDIA GeForce GTX 1080 (HAL-hw vp)", 16, 16 * GB),
Row(2, 9200, "NVIDIA GeForce GTX 1080 (HAL-mixed vp)", 8, 8 * GB),
Row(3, 2600, "AMD Radeon RX 580 (HAL-hw vp)", 16, 32 * GB),
};
const auto spread = ClientSysInfoView::Spread(latest);
EXPECT_EQ(spread["accounts"], 3);
// The same adapter with a different vertex processing mode is one card
ASSERT_EQ(spread["video"].size(), 2u);
EXPECT_EQ(spread["video"][0]["label"], "NVIDIA GeForce GTX 1080");
EXPECT_EQ(spread["video"][0]["count"], 2);
EXPECT_EQ(spread["os"][0]["count"], 2);
EXPECT_NE(spread["os"][0]["label"].get<std::string>().find("6.2.9200"), std::string::npos);
// Memory in size order, processors by count
ASSERT_EQ(spread["memory"].size(), 3u);
EXPECT_EQ(spread["memory"][0]["label"], "4-8 GB");
EXPECT_EQ(spread["memory"][2]["label"], "16-32 GB");
ASSERT_EQ(spread["processors"].size(), 2u);
EXPECT_EQ(spread["processors"][0]["label"], "8");
EXPECT_EQ(spread["processors"][1]["count"], 2);
EXPECT_EQ(spread["clientOs"][0]["label"], "Windows");
}
TEST(ClientSysInfoViewTests, SpreadFoldsTheTail) {
std::vector<IClientSysInfo::SysInfoRow> latest;
for (uint32_t i = 0; i < 5; i++) latest.push_back(Row(i, 9200, "Card " + std::to_string(i), 4, 0));
const auto spread = ClientSysInfoView::Spread(latest, 2);
ASSERT_EQ(spread["video"].size(), 3u);
EXPECT_EQ(spread["video"][2]["label"], "Other");
EXPECT_EQ(spread["video"][2]["count"], 3);
}
TEST(ClientSysInfoViewTests, StaffOnlyPermission) {
const auto* permission = Permissions::Find("client_sysinfo");
ASSERT_NE(permission, nullptr);
EXPECT_EQ(permission->category, "Accounts");
EXPECT_GE(permission->defaultLevel, 1);
EXPECT_FALSE(permission->locked); // can be granted per account
ASSERT_NE(Permissions::Find("logs_audit"), nullptr);
}