Files
SimpleRemoter/test/unit/network/GeoLocationTest.cpp
2026-04-19 22:55:21 +02:00

379 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// GeoLocationTest.cpp - IP地理位置API单元测试
// 测试 location.h 中的 GetGeoLocation 功能
#include <gtest/gtest.h>
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#include <wininet.h>
#include <ws2tcpip.h>
#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "ws2_32.lib")
#include <string>
#include <vector>
// ============================================
// 简化的测试版本 - 不依赖jsoncpp
// ============================================
// UTF-8 转 ANSI
inline std::string Utf8ToAnsi(const std::string& utf8)
{
if (utf8.empty()) return "";
int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, NULL, 0);
if (wideLen <= 0) return utf8;
std::wstring wideStr(wideLen, 0);
MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, &wideStr[0], wideLen);
int ansiLen = WideCharToMultiByte(CP_ACP, 0, wideStr.c_str(), -1, NULL, 0, NULL, NULL);
if (ansiLen <= 0) return utf8;
std::string ansiStr(ansiLen, 0);
WideCharToMultiByte(CP_ACP, 0, wideStr.c_str(), -1, &ansiStr[0], ansiLen, NULL, NULL);
if (!ansiStr.empty() && ansiStr.back() == '\0') ansiStr.pop_back();
return ansiStr;
}
// 简单JSON值提取 (仅用于测试不依赖jsoncpp)
std::string ExtractJsonString(const std::string& json, const std::string& key)
{
std::string searchKey = "\"" + key + "\"";
size_t keyPos = json.find(searchKey);
if (keyPos == std::string::npos) return "";
size_t colonPos = json.find(':', keyPos);
if (colonPos == std::string::npos) return "";
size_t valueStart = json.find_first_not_of(" \t\n\r", colonPos + 1);
if (valueStart == std::string::npos) return "";
if (json[valueStart] == '"') {
size_t valueEnd = json.find('"', valueStart + 1);
if (valueEnd == std::string::npos) return "";
return json.substr(valueStart + 1, valueEnd - valueStart - 1);
}
// 数字或布尔值
size_t valueEnd = json.find_first_of(",}\n\r", valueStart);
if (valueEnd == std::string::npos) valueEnd = json.length();
std::string value = json.substr(valueStart, valueEnd - valueStart);
// 去除尾部空格
while (!value.empty() && (value.back() == ' ' || value.back() == '\t')) {
value.pop_back();
}
return value;
}
// API配置结构
struct GeoApiConfig {
const char* name;
const char* urlFmt;
const char* cityField;
const char* countryField;
const char* checkField;
const char* checkValue;
bool useHttps;
};
// 测试用API配置
static const GeoApiConfig testApis[] = {
{"ip-api.com", "http://ip-api.com/json/%s?fields=status,country,city", "city", "country", "status", "success", false},
{"ipinfo.io", "http://ipinfo.io/%s/json", "city", "country", "", "", false},
{"ipapi.co", "https://ipapi.co/%s/json/", "city", "country_name", "error", "", true},
};
// 测试单个API
struct ApiTestResult {
bool success;
int httpStatus;
std::string city;
std::string country;
std::string error;
double latencyMs;
};
ApiTestResult TestSingleApi(const GeoApiConfig& api, const std::string& ip)
{
ApiTestResult result = {false, 0, "", "", "", 0};
DWORD startTime = GetTickCount();
HINTERNET hInternet = InternetOpenA("GeoLocationTest", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
if (!hInternet) {
result.error = "InternetOpen failed";
return result;
}
DWORD timeout = 10000;
InternetSetOptionA(hInternet, INTERNET_OPTION_CONNECT_TIMEOUT, &timeout, sizeof(timeout));
InternetSetOptionA(hInternet, INTERNET_OPTION_SEND_TIMEOUT, &timeout, sizeof(timeout));
InternetSetOptionA(hInternet, INTERNET_OPTION_RECEIVE_TIMEOUT, &timeout, sizeof(timeout));
char urlBuf[256];
sprintf_s(urlBuf, api.urlFmt, ip.c_str());
DWORD flags = INTERNET_FLAG_RELOAD;
if (api.useHttps) flags |= INTERNET_FLAG_SECURE;
HINTERNET hConnect = InternetOpenUrlA(hInternet, urlBuf, NULL, 0, flags, 0);
if (!hConnect) {
result.error = "InternetOpenUrl failed: " + std::to_string(GetLastError());
InternetCloseHandle(hInternet);
return result;
}
// 获取HTTP状态码
DWORD statusCode = 0;
DWORD statusSize = sizeof(statusCode);
if (HttpQueryInfoA(hConnect, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &statusCode, &statusSize, NULL)) {
result.httpStatus = statusCode;
}
// 读取响应
std::string readBuffer;
char buffer[4096];
DWORD bytesRead;
while (InternetReadFile(hConnect, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0) {
readBuffer.append(buffer, bytesRead);
}
InternetCloseHandle(hConnect);
InternetCloseHandle(hInternet);
result.latencyMs = (double)(GetTickCount() - startTime);
// 检查HTTP错误
if (result.httpStatus >= 400) {
result.error = "HTTP " + std::to_string(result.httpStatus);
if (result.httpStatus == 429) {
result.error += " (Rate Limited)";
}
return result;
}
// 检查响应体错误
if (readBuffer.find("Rate limit") != std::string::npos ||
readBuffer.find("rate limit") != std::string::npos) {
result.error = "Rate limited (body)";
return result;
}
// 解析JSON
if (api.checkField && api.checkField[0]) {
std::string checkVal = ExtractJsonString(readBuffer, api.checkField);
if (api.checkValue && api.checkValue[0]) {
if (checkVal != api.checkValue) {
result.error = "Check failed: " + std::string(api.checkField) + "=" + checkVal;
return result;
}
} else {
if (checkVal == "true") {
result.error = "Error flag set";
return result;
}
}
}
result.city = Utf8ToAnsi(ExtractJsonString(readBuffer, api.cityField));
result.country = Utf8ToAnsi(ExtractJsonString(readBuffer, api.countryField));
result.success = !result.city.empty() || !result.country.empty();
if (!result.success) {
result.error = "No city/country in response";
}
return result;
}
// ============================================
// 测试用例
// ============================================
class GeoLocationTest : public ::testing::Test {
protected:
void SetUp() override {
// 初始化Winsock
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
}
void TearDown() override {
WSACleanup();
}
};
// 测试公网IP (Google DNS)
TEST_F(GeoLocationTest, TestPublicIP_GoogleDNS) {
std::string testIP = "8.8.8.8";
std::cout << "\n=== Testing IP: " << testIP << " ===\n";
int successCount = 0;
for (const auto& api : testApis) {
auto result = TestSingleApi(api, testIP);
std::cout << "[" << api.name << "] ";
if (result.success) {
std::cout << "OK - " << result.city << ", " << result.country;
std::cout << " (HTTP " << result.httpStatus << ", " << result.latencyMs << "ms)\n";
successCount++;
} else {
std::cout << "FAIL - " << result.error;
std::cout << " (HTTP " << result.httpStatus << ", " << result.latencyMs << "ms)\n";
}
}
// 至少一个API应该成功
EXPECT_GE(successCount, 1) << "At least one API should succeed";
}
// 测试另一个公网IP (Cloudflare DNS)
TEST_F(GeoLocationTest, TestPublicIP_CloudflareDNS) {
std::string testIP = "1.1.1.1";
std::cout << "\n=== Testing IP: " << testIP << " ===\n";
int successCount = 0;
for (const auto& api : testApis) {
auto result = TestSingleApi(api, testIP);
std::cout << "[" << api.name << "] ";
if (result.success) {
std::cout << "OK - " << result.city << ", " << result.country;
std::cout << " (HTTP " << result.httpStatus << ", " << result.latencyMs << "ms)\n";
successCount++;
} else {
std::cout << "FAIL - " << result.error;
std::cout << " (HTTP " << result.httpStatus << ", " << result.latencyMs << "ms)\n";
}
}
EXPECT_GE(successCount, 1) << "At least one API should succeed";
}
// 测试中国IP
TEST_F(GeoLocationTest, TestPublicIP_ChinaIP) {
std::string testIP = "114.114.114.114"; // 114DNS
std::cout << "\n=== Testing IP: " << testIP << " (China) ===\n";
int successCount = 0;
for (const auto& api : testApis) {
auto result = TestSingleApi(api, testIP);
std::cout << "[" << api.name << "] ";
if (result.success) {
std::cout << "OK - " << result.city << ", " << result.country;
std::cout << " (HTTP " << result.httpStatus << ", " << result.latencyMs << "ms)\n";
successCount++;
} else {
std::cout << "FAIL - " << result.error;
std::cout << " (HTTP " << result.httpStatus << ", " << result.latencyMs << "ms)\n";
}
}
EXPECT_GE(successCount, 1) << "At least one API should succeed";
}
// 测试ip-api.com单独
TEST_F(GeoLocationTest, TestIpApiCom) {
std::string testIP = "8.8.8.8";
auto result = TestSingleApi(testApis[0], testIP);
std::cout << "\n[ip-api.com] HTTP: " << result.httpStatus
<< ", Latency: " << result.latencyMs << "ms\n";
if (result.success) {
std::cout << "City: " << result.city << ", Country: " << result.country << "\n";
EXPECT_FALSE(result.country.empty());
} else {
std::cout << "Error: " << result.error << "\n";
// 如果是429就跳过不算失败
if (result.httpStatus == 429) {
GTEST_SKIP() << "Rate limited, skipping";
}
}
}
// 测试ipinfo.io单独
TEST_F(GeoLocationTest, TestIpInfoIo) {
std::string testIP = "8.8.8.8";
auto result = TestSingleApi(testApis[1], testIP);
std::cout << "\n[ipinfo.io] HTTP: " << result.httpStatus
<< ", Latency: " << result.latencyMs << "ms\n";
if (result.success) {
std::cout << "City: " << result.city << ", Country: " << result.country << "\n";
EXPECT_FALSE(result.country.empty());
} else {
std::cout << "Error: " << result.error << "\n";
if (result.httpStatus == 429) {
GTEST_SKIP() << "Rate limited, skipping";
}
}
}
// 测试ipapi.co单独
TEST_F(GeoLocationTest, TestIpApiCo) {
std::string testIP = "8.8.8.8";
auto result = TestSingleApi(testApis[2], testIP);
std::cout << "\n[ipapi.co] HTTP: " << result.httpStatus
<< ", Latency: " << result.latencyMs << "ms\n";
if (result.success) {
std::cout << "City: " << result.city << ", Country: " << result.country << "\n";
EXPECT_FALSE(result.country.empty());
} else {
std::cout << "Error: " << result.error << "\n";
if (result.httpStatus == 429) {
GTEST_SKIP() << "Rate limited, skipping";
}
}
}
// 测试HTTP状态码检测
TEST_F(GeoLocationTest, TestHttpStatusCodeDetection) {
// 使用一个会返回错误的请求
std::string testIP = "invalid-ip";
for (const auto& api : testApis) {
auto result = TestSingleApi(api, testIP);
std::cout << "[" << api.name << "] Invalid IP test: HTTP "
<< result.httpStatus << ", Error: " << result.error << "\n";
// 应该失败
EXPECT_FALSE(result.success);
}
}
// 测试所有API的响应时间
TEST_F(GeoLocationTest, TestApiLatency) {
std::string testIP = "8.8.8.8";
std::cout << "\n=== API Latency Test ===\n";
for (const auto& api : testApis) {
auto result = TestSingleApi(api, testIP);
std::cout << "[" << api.name << "] " << result.latencyMs << "ms";
if (result.success) {
std::cout << " (OK)";
} else {
std::cout << " (FAIL: " << result.error << ")";
}
std::cout << "\n";
// 响应时间应该在合理范围内 (< 15秒)
EXPECT_LT(result.latencyMs, 15000);
}
}
#else
// 非Windows平台 - 跳过测试
TEST(GeoLocationTest, SkipNonWindows) {
GTEST_SKIP() << "GeoLocation tests only run on Windows";
}
#endif