// GeoLocationTest.cpp - IP地理位置API单元测试 // 测试 location.h 中的 GetGeoLocation 功能 #include #ifdef _WIN32 #include #include #include #include #pragma comment(lib, "wininet.lib") #pragma comment(lib, "ws2_32.lib") #include #include // ============================================ // 简化的测试版本 - 不依赖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