IPTest.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. <?php
  2. declare(strict_types=1);
  3. namespace Tests\Unit\Utils;
  4. use App\Utils\IP;
  5. use Exception;
  6. use Illuminate\Http\Client\ConnectionException;
  7. use Illuminate\Support\Facades\App;
  8. use Illuminate\Support\Facades\Cache;
  9. use Illuminate\Support\Facades\Http;
  10. use ReflectionClass;
  11. use Tests\TestCase;
  12. class IPTest extends TestCase
  13. {
  14. public static function providerApiCases(): array
  15. {
  16. return [
  17. 'ipApi' => [[
  18. 'name' => 'ipApi',
  19. 'endpoint' => '*ip-api.com/json/*',
  20. 'response' => '{"city":"沈阳市","country":"中国","district":"","isp":"China Unicom CHINA169 Network","lat":41.8357,"lon":123.429,"query":"8.8.8.8","regionName":"辽宁","status":"success"}',
  21. 'expected' => [
  22. 'country' => '中国',
  23. 'region' => '辽宁',
  24. 'city' => '沈阳市',
  25. 'isp' => 'China Unicom CHINA169 Network',
  26. 'latitude' => 41.8357,
  27. 'longitude' => 123.429,
  28. ],
  29. ]],
  30. 'baidu' => [[
  31. 'name' => 'Baidu',
  32. 'config' => ['services.ip.baidu_ak' => 'fake_baidu_ak'],
  33. 'endpoint' => 'https://api.map.baidu.com/location/ip*',
  34. 'response' => '{"status":0,"address":"CN|辽宁省|沈阳市|None|None|100|65|0","content":{"address":"辽宁省沈阳市","address_detail":{"adcode":"210100","city":"沈阳市","city_code":58,"district":"","province":"辽宁省","street":"","street_number":""},"point":{"x":"123.46466579069178","y":"41.67756788393409"}}}',
  35. 'expected' => [
  36. 'country' => 'CN',
  37. 'region' => '辽宁省',
  38. 'city' => '沈阳市',
  39. 'latitude' => '41.67756788393409',
  40. 'longitude' => '123.46466579069178',
  41. ],
  42. ]],
  43. 'baiduBce' => [[
  44. 'name' => 'baiduBce',
  45. 'endpoint' => 'https://qifu-api.baidubce.com/ip/geo/v1/district*',
  46. 'response' => '{"code":"Success","data":{"continent":"亚洲","country":"中国","zipcode":"110000","owner":"中国联通","isp":"中国联通","adcode":"210100","prov":"辽宁省","city":"沈阳市","district":""},"ip":"8.8.8.8"}',
  47. 'expected' => [
  48. 'country' => '中国',
  49. 'region' => '辽宁省',
  50. 'city' => '沈阳市',
  51. 'isp' => '中国联通',
  52. ],
  53. ]],
  54. 'ipGeoLocation' => [[
  55. 'name' => 'ipGeoLocation',
  56. 'endpoint' => 'https://api.ipgeolocation.io/ipgeo?ip=*',
  57. 'response' => '{"ip":"8.8.8.8","country_name":"中国","country_name_official":"","state_prov":"辽宁","district":"沈阳","city":"沈阳","isp":"China Unicom CHINA169 Network","latitude":"41.79680","longitude":"123.42910"}',
  58. 'expected' => [
  59. 'country' => '中国',
  60. 'region' => '辽宁',
  61. 'city' => '沈阳',
  62. 'isp' => 'China Unicom CHINA169 Network',
  63. 'area' => '沈阳',
  64. 'latitude' => '41.79680',
  65. 'longitude' => '123.42910',
  66. ],
  67. ]],
  68. 'taobao' => [[
  69. 'name' => 'TaoBao',
  70. 'endpoint' => 'https://ip.taobao.com/outGetIpInfo?ip=*',
  71. 'response' => '{"data":{"area":"","country":"中国","isp_id":"100026","queryIp":"103.250.104.0","city":"沈阳","ip":"8.8.8.8","isp":"联通","county":"","region_id":"210000","area_id":"","county_id":null,"region":"辽宁","country_id":"CN","city_id":"210100"},"msg":"query success","code":0}',
  72. 'expected' => [
  73. 'country' => '中国',
  74. 'region' => '辽宁',
  75. 'city' => '沈阳',
  76. 'isp' => '联通',
  77. ],
  78. ]],
  79. 'speedtest' => [[
  80. 'name' => 'speedtest',
  81. 'endpoint' => 'https://api-v3.speedtest.cn/ip*',
  82. 'response' => '{"code":0,"data":"1z41bLnGrmhViAP9vBtxaTvcnepxEF7nyynwu4VDL1s6YCnSK48PoPFgNf6lDQQ3GV9cmRtDJTrLLrU16eItDnmB+8+3stMtsFBhaLaRH1ece5b+D4lR73Cy1FvaxFXmIfGuPxIOjV/g4Mh4F7GuvEy1gm5/tj9gm4egANOyl3vkzMFvp9tB1ET9PUhaP29DTMQOwxCV4CendJn2LdwF6tP6elucLUoy3xweFC4h2w20oha/GcOiQAxKLB+h6aslydvXqDAzvMmeXRV6e0CQ6A==","\'msg\'":"ok"}',
  83. 'expected' => [
  84. 'country' => '中国',
  85. 'region' => '辽宁',
  86. 'city' => '沈阳',
  87. 'isp' => '中国联通',
  88. 'area' => '沈河区',
  89. 'latitude' => '41.796767',
  90. 'longitude' => '123.429096',
  91. ],
  92. ]],
  93. 'juHe' => [[
  94. 'name' => 'juHe',
  95. 'endpoint' => 'https://apis.juhe.cn/ip/Example/query.php*',
  96. 'response' => '{"resultcode":"200","reason":"success","result":{"Country":"中国","Province":"辽宁","City":"沈阳","District":"","Isp":"联通"},"error_code":0}',
  97. 'expected' => [
  98. 'country' => '中国',
  99. 'region' => '辽宁',
  100. 'city' => '沈阳',
  101. 'isp' => '联通',
  102. ],
  103. ]],
  104. 'ip2Region' => [[
  105. 'name' => 'ip2Region',
  106. 'ip' => '103.250.104.0',
  107. 'expected' => [
  108. 'country' => '中国',
  109. 'region' => '辽宁省',
  110. 'city' => '沈阳市',
  111. 'isp' => '联通',
  112. ],
  113. ]],
  114. 'IPDB' => [[
  115. 'name' => 'IPDB',
  116. 'ip' => '103.250.104.0',
  117. 'expected' => [
  118. 'country' => '中国',
  119. 'region' => '辽宁',
  120. 'isp' => '联通',
  121. ],
  122. ]],
  123. 'IPSB' => [[
  124. 'name' => 'IPSB',
  125. 'endpoint' => 'https://api.ip.sb/geoip/*',
  126. 'response' => '{"country":"China","organization":"China Unicom","country_code":"CN","ip":"8.8.8.8","isp":"China Unicom","asn_organization":"CHINA UNICOM China169 Backbone","asn":4837,"offset":28800,"latitude":34.7732,"timezone":"Asia\/Shanghai","continent_code":"AS","longitude":113.722}',
  127. 'expected' => [
  128. 'country' => 'China',
  129. 'isp' => 'China Unicom',
  130. 'latitude' => 34.7732,
  131. 'longitude' => 113.722,
  132. ],
  133. ]],
  134. 'ipinfo' => [[
  135. 'name' => 'ipinfo',
  136. 'endpoint' => 'https://ipinfo.io*',
  137. 'response' => '{"input":"103.250.104.0","data":{"ip":"8.8.8.8","city":"Shenyang","region":"Liaoning","country":"CN","loc":"41.7922,123.4328","org":"AS4837 CHINA UNICOM China169 Backbone","postal":"110000","timezone":"Asia/Shanghai"}}',
  138. 'expected' => [
  139. 'country' => 'CN',
  140. 'region' => 'Liaoning',
  141. 'city' => 'Shenyang',
  142. 'isp' => 'AS4837 CHINA UNICOM China169 Backbone',
  143. 'latitude' => '41.7922',
  144. 'longitude' => '123.4328',
  145. ],
  146. ]],
  147. 'ip234' => [[
  148. 'name' => 'ip234',
  149. 'endpoint' => 'https://ip234.in/search_ip*',
  150. 'response' => '{"code":0,"data":{"asn":4837,"city":"Shenyang","continent":"Asia","continent_code":"AS","country":"china","country_code":"CN","ip":"8.8.8.8","latitude":41.8357,"longitude":123.429,"metro_code":null,"network":"103.250.104.0/22","organization":"CHINA UNICOM China169 Backbone","postal":"210000","region":"Liaoning","timezone":"Asia/Shanghai"},"msg":""}',
  151. 'expected' => [
  152. 'country' => 'china',
  153. 'region' => 'Liaoning',
  154. 'city' => 'Shenyang',
  155. 'isp' => 'CHINA UNICOM China169 Backbone',
  156. 'latitude' => '41.8357',
  157. 'longitude' => '123.429',
  158. ],
  159. ]],
  160. 'dbIP' => [[
  161. 'name' => 'dbIP',
  162. 'endpoint' => 'https://api.db-ip.com/v2/free/*',
  163. 'response' => '{"ipAddress":"8.8.8.8","continentCode":"AS","continentName":"Asia","countryCode":"CN","countryName":"China","stateProv":"Liaoning","city":"Shenyang"}',
  164. 'expected' => [
  165. 'country' => 'China',
  166. 'region' => 'Liaoning',
  167. 'city' => 'Shenyang',
  168. ],
  169. ]],
  170. 'IP2Online' => [[
  171. 'name' => 'IP2Online',
  172. 'config' => ['services.ip.IP2Location_key' => 'fake_ip2location_key'],
  173. 'endpoint' => 'https://api.ip2location.io/*',
  174. 'response' => '{"ip":"8.8.8.8","country_code":"CN","country_name":"China","region_name":"Liaoning","city_name":"Shenyang","latitude":41.79222,"longitude":123.43288,"zip_code":"210000","time_zone":"+08:00","asn":"4837","as":"China Unicom China169 Backbone","is_proxy":false}',
  175. 'expected' => [
  176. 'country' => 'China',
  177. 'region' => 'Liaoning',
  178. 'city' => 'Shenyang',
  179. 'isp' => 'China Unicom China169 Backbone',
  180. 'latitude' => '41.79222',
  181. 'longitude' => '123.43288',
  182. ],
  183. ]],
  184. 'ipdata' => [[
  185. 'name' => 'ipdata',
  186. 'config' => ['services.ip.ipdata_key' => 'fake_ipdata_key'],
  187. 'endpoint' => 'https://api.ipdata.co/*',
  188. 'response' => '{"ip":"8.8.8.8","city":null,"region":null,"country_name":"China","latitude":34.77320098876953,"longitude":113.72200012207031,"asn":null}',
  189. 'expected' => [
  190. 'country' => 'China',
  191. 'latitude' => 34.77320098876953,
  192. 'longitude' => 113.72200012207031,
  193. ],
  194. ]],
  195. 'ipApiCo' => [[
  196. 'name' => 'ipApiCo',
  197. 'endpoint' => 'https://ipapi.co/*/json/*',
  198. 'response' => '{"ip":"8.8.8.8","network":"103.250.104.0/22","version":"IPv4","city":"Shenyang","region":"Liaoning","region_code":"LN","country":"CN","country_name":"China","country_code":"CN","country_code_iso3":"CHN","country_capital":"Beijing","country_tld":".cn","continent_code":"AS","in_eu":false,"postal":null,"latitude":41.79222,"longitude":123.43278,"timezone":"Asia/Shanghai","utc_offset":"+0800","country_calling_code":"+86","currency":"CNY","currency_name":"Yuan Renminbi","languages":"zh-CN,yue,wuu,dta,ug,za","country_area":9596960,"country_population":1411778724,"asn":"AS4837","org":"CHINA UNICOM China169 Backbone"}',
  199. 'expected' => [
  200. 'country' => 'China',
  201. 'region' => 'Liaoning',
  202. 'city' => 'Shenyang',
  203. 'isp' => 'CHINA UNICOM China169 Backbone',
  204. 'latitude' => 41.79222,
  205. 'longitude' => 123.43278,
  206. ],
  207. ]],
  208. 'ip2Location' => [[
  209. 'name' => 'ip2Location',
  210. 'ip' => '103.250.104.0',
  211. 'expected' => [
  212. 'country' => 'China',
  213. 'region' => 'Liaoning',
  214. 'city' => 'Shenyang',
  215. 'latitude' => 41.792221,
  216. 'longitude' => 123.432877,
  217. ],
  218. ]],
  219. 'GeoIP2' => [[
  220. 'name' => 'GeoIP2',
  221. 'ip' => '103.250.104.0',
  222. 'expected' => [
  223. 'country' => 'China',
  224. 'latitude' => 34.7732,
  225. 'longitude' => 113.722,
  226. ],
  227. ]],
  228. 'ipApiCom' => [[
  229. 'name' => 'ipApiCom',
  230. 'config' => ['services.ip.ipApiCom_acess_key' => 'fake_acess_key'],
  231. 'endpoint' => 'https://api.ipapi.com/api/*',
  232. 'response' => '{"ip": "8.8.8.8", "type": "ipv4", "continent_code": "AS", "continent_name": "Asia", "country_code": "CN", "country_name": "China", "region_code": "LN", "region_name": "Liaoning", "city": "Shenyang", "zip": "110000", "latitude": 41.801021575927734, "longitude": 123.40206909179688, "msa": null, "dma": null, "radius": "0", "ip_routing_type": "fixed", "connection_type": "tx", "location": {"geoname_id": 2034937, "capital": "Beijing", "languages": [{"code": "zh", "name": "Chinese", "native": "\u4e2d\u6587"}], "country_flag": "https://assets.ipstack.com/flags/cn.svg", "country_flag_emoji": "\ud83c\udde8\ud83c\uddf3", "country_flag_emoji_unicode": "U+1F1E8 U+1F1F3", "calling_code": "86", "is_eu": false}}',
  233. 'expected' => [
  234. 'country' => 'China',
  235. 'region' => 'Liaoning',
  236. 'city' => 'Shenyang',
  237. 'latitude' => 41.801021575927734,
  238. 'longitude' => 123.40206909179688,
  239. 'address' => 'China Liaoning Shenyang',
  240. ],
  241. ]],
  242. 'vore' => [[
  243. 'name' => 'vore',
  244. 'endpoint' => 'https://api.vore.top/api/IPdata*',
  245. 'response' => '{"code":200,"msg":"SUCCESS","ipinfo":{"type":"ipv4","text":"103.250.104.0","cnip":true},"ipdata":{"info1":"辽宁省","info2":"沈阳市","info3":"","isp":"联通"},"adcode":{"o":"辽宁省沈阳市 - 联通","p":"辽宁","c":"沈阳","n":"辽宁-沈阳","r":"辽宁-沈阳","a":"210100","i":true},"tips":"接口由VORE-API(https://api.vore.top/)免费提供","time":1757149038}',
  246. 'expected' => [
  247. 'country' => '辽宁省',
  248. 'region' => '沈阳市',
  249. 'isp' => '联通',
  250. ],
  251. ]],
  252. 'ipw_v4' => [[
  253. 'name' => 'ipw',
  254. 'endpoint' => 'https://rest.ipw.cn/api/aw/v1/ipv4*',
  255. 'response' => '{"code":"Success","data":{"continent":"亚洲","country":"中国","zipcode":"110000","timezone":"UTC+8","accuracy":"城市","owner":"中国联通","isp":"中国联通","source":"数据挖掘","areacode":"CN","adcode":"210100","asnumber":"4837","lat":"41.800551","lng":"123.420011","radius":"109.2745","prov":"辽宁省","city":"沈阳市","district":""},"charge":false,"msg":"查询成功","ip":"8.8.8.8","coordsys":"WGS84"}',
  256. 'expected' => [
  257. 'country' => '中国',
  258. 'region' => '辽宁省',
  259. 'city' => '沈阳市',
  260. 'isp' => '中国联通',
  261. 'latitude' => '41.800551',
  262. 'longitude' => '123.420011',
  263. ],
  264. ]],
  265. 'ipw_v6' => [[
  266. 'name' => 'ipw',
  267. 'ip' => '2408:8207:1850:2a60::4c8',
  268. 'endpoint' => 'https://rest.ipw.cn/api/aw/v1/ipv6*',
  269. 'response' => '{"code":"Success","data":{"continent":"亚洲","country":"日本","zipcode":"167-0033","timezone":"UTC+9","accuracy":"城市","owner":"亚马逊","isp":"亚马逊","source":"数据挖掘","areacode":"JP","adcode":"","asnumber":"16509","lat":"35.713914","lng":"139.616508","radius":"","prov":"东京都","city":"Suginami","district":"","currency_code":"JPY","currency_name":"日元"},"charge":false,"msg":"查询成功","ip":"2408:8207:1850:2a60::4c8","coordsys":"WGS84"}',
  270. 'expected' => [
  271. 'country' => '日本',
  272. 'region' => '东京都',
  273. 'city' => 'Suginami',
  274. 'isp' => '亚马逊',
  275. 'latitude' => '35.713914',
  276. 'longitude' => '139.616508',
  277. 'address' => '日本 东京都 Suginami',
  278. ],
  279. ]],
  280. 'bjjii' => [[
  281. 'name' => 'bjjii',
  282. 'config' => ['services.ip.bjjii_key' => 'fake_acess_key'],
  283. 'endpoint' => 'https://api.bjjii.com/api/ip/query*',
  284. 'response' => '{"code":200,"msg":"请求成功","data":{"ip":"8.8.8.8","info":{"StartIPNum":1744463872,"StartIPText":"103.250.104.0","EndIPNum":1744464895,"EndIPText":"103.250.107.255","Country":"辽宁省沈阳市","Local":"联通","lat":41.835709999999999,"lng":123.42925,"nation":"中国","province":"辽宁省","city":"沈阳市","district":"","adcode":210000,"nation_code":156,"update":"2025-09-06 17:48:39"}},"exec_time":0.023085000000000001,"ip":"117.147.44.132"}',
  285. 'expected' => [
  286. 'country' => '中国',
  287. 'region' => '辽宁省',
  288. 'city' => '沈阳市',
  289. 'latitude' => 41.83571,
  290. 'longitude' => 123.42925,
  291. ],
  292. ]],
  293. 'pconline' => [[
  294. 'name' => 'pconline',
  295. 'endpoint' => 'https://whois.pconline.com.cn/*',
  296. 'response' => '{"ip":"8.8.8.8","pro":"辽宁省","proCode":"210000","city":"沈阳市","cityCode":"210100","region":"","regionCode":"0","addr":"辽宁省沈阳市 联通","regionNames":"","err":""}',
  297. 'expected' => [
  298. 'region' => '辽宁省',
  299. 'city' => '沈阳市',
  300. ],
  301. ]],
  302. 'ipApiIO' => [[
  303. 'name' => 'ipApiIO',
  304. 'endpoint' => 'https://ip-api.io/api/v1/ip/*',
  305. 'response' => '{"ip":"8.8.8.8","suspicious_factors":{"is_proxy":false,"is_tor_node":false,"is_spam":false,"is_crawler":false,"is_datacenter":false,"is_vpn":false,"is_threat":false},"location":{"country":"China","country_code":"CN","city":null,"latitude":34.7732,"longitude":113.722,"zip":null,"timezone":"Asia/Shanghai","local_time":"2025-09-07T14:37:47+08:00","local_time_unix":1757227067,"is_daylight_savings":false}}',
  306. 'expected' => [
  307. 'country' => 'China',
  308. 'latitude' => 34.7732,
  309. 'longitude' => 113.722,
  310. ],
  311. ]],
  312. 'ipApiIS' => [[
  313. 'name' => 'ipApiIS',
  314. 'endpoint' => 'https://api.ipapi.is/*',
  315. 'response' => '{"ip":"8.8.8.8","asn":{"asn":4837,"abuser_score":"0.001 (Low)","route":"103.250.104.0/22","descr":"CHINA169-BACKBONE CHINA UNICOM China169 Backbone, CN","country":"cn","active":true,"org":"CHINA UNICOM China169 Backbone","domain":"chinaunicom.cn","abuse":"[email protected]","type":"isp","updated":"2024-02-06","rir":"APNIC","whois":"https://api.ipapi.is/?whois=AS4837"},"location":{"is_eu_member":false,"calling_code":"86","currency_code":"CNY","continent":"AS","country":"China","country_code":"CN","state":"Liaoning","city":"Shenyang","latitude":41.79222,"longitude":123.43278,"zip":"110000","timezone":"Asia/Shanghai","local_time":"2025-09-07T14:40:03+08:00","local_time_unix":1757227203,"is_dst":false},"elapsed_ms":1.05}',
  316. 'expected' => [
  317. 'country' => 'China',
  318. 'region' => 'Liaoning',
  319. 'city' => 'Shenyang',
  320. 'isp' => 'CHINA UNICOM China169 Backbone',
  321. 'latitude' => 41.79222,
  322. 'longitude' => 123.43278,
  323. ],
  324. ]],
  325. 'freeipapi' => [[
  326. 'name' => 'freeipapi',
  327. 'endpoint' => 'https://free.freeipapi.com/*',
  328. 'response' => '{"ipVersion":4,"ipAddress":"8.8.8.8","latitude":41.8357,"longitude":123.429,"countryName":"China","countryCode":"CN","capital":"Beijing","phoneCodes":[86],"timeZones":["Asia\/Shanghai","Asia\/Urumqi"],"zipCode":"210000","cityName":"Shenyang","regionName":"Liaoning","continent":"Asia","continentCode":"AS","currencies":["CNY"],"languages":["zh"],"asn":"4837","asnOrganization":"CHINA UNICOM China169 Backbone","isProxy":false}',
  329. 'expected' => [
  330. 'country' => 'China',
  331. 'region' => 'Liaoning',
  332. 'city' => 'Shenyang',
  333. 'isp' => 'CHINA UNICOM China169 Backbone',
  334. 'latitude' => 41.8357,
  335. 'longitude' => 123.429,
  336. ],
  337. ]],
  338. 'ipwhois' => [[
  339. 'name' => 'ipwhois',
  340. 'endpoint' => 'https://ipwhois.app/json/*',
  341. 'response' => '{"ip":"8.8.8.8","success":true,"type":"IPv4","continent":"Asia","continent_code":"AS","country":"China","country_code":"CN","country_flag":"https://cdn.ipwhois.io/flags/cn.svg","country_capital":"Beijing","country_phone":"+86","country_neighbours":"AF,BT,HK,IN,KG,KP,KZ,LA,MM,MN,MO,NP,PK,RU,TJ,VN","region":"Liaoning","city":"Shenyang","latitude":41.805699,"longitude":123.431472,"asn":"AS4837","org":"China Unicom Liaoning Province Network","isp":"China Unicom China1 Backbone","timezone":"Asia/Shanghai","timezone_name":"CST","timezone_dstOffset":0,"timezone_gmtOffset":28800,"timezone_gmt":"+08:00","currency":"Chinese Yuan","currency_code":"CNY","currency_symbol":"¥","currency_rates":7.133,"currency_plural":"Chinese yuan"}',
  342. 'expected' => [
  343. 'country' => 'China',
  344. 'region' => 'Liaoning',
  345. 'city' => 'Shenyang',
  346. 'isp' => 'China Unicom China1 Backbone',
  347. 'latitude' => 41.805699,
  348. 'longitude' => 123.431472,
  349. ],
  350. ]],
  351. ];
  352. }
  353. public function test_localhost_returns_false_for_loopback_ips(): void
  354. {
  355. $this->assertFalse(IP::getIPInfo('127.0.0.1'));
  356. $this->assertFalse(IP::getIPInfo('::1'));
  357. }
  358. public function test_get_client_ip_is_string_or_null(): void
  359. {
  360. $ip = IP::getClientIP();
  361. $this->assertTrue(is_null($ip) || is_string($ip));
  362. }
  363. /**
  364. * @dataProvider providerApiCases
  365. */
  366. public function test_get_ip_info_from_each_provider(array $case): void
  367. {
  368. App::setLocale($case['locale'] ?? 'zh_CN');
  369. if (! empty($case['config'])) { // 设置可能存在的假token参数,来激活 API 访问
  370. foreach ($case['config'] as $k => $v) {
  371. config([$k => $v]);
  372. }
  373. }
  374. // 模拟HTTP响应
  375. if (isset($case['response'])) {
  376. $fakeResponses[$case['endpoint']] = Http::response($case['response']);
  377. }
  378. $fakeResponses['*'] = Http::response([], 500);
  379. Http::fake($fakeResponses);
  380. $result = IP::getIPInfo($case['ip'] ?? '8.8.8.8', $case['name']);
  381. $this->assertIsArray($result, "Provider {$case['name']} should return an array");
  382. foreach ($case['expected'] as $k => $v) {
  383. $this->assertEquals($v, $result[$k] ?? null, "Provider {$case['name']} field {$k} mismatch");
  384. }
  385. }
  386. public function test_get_ip_info_caches_result_and_prevents_http_calls(): void
  387. {
  388. $ip = '9.9.9.9';
  389. $cached = [
  390. 'country' => 'CachedLand',
  391. 'region' => 'CachedRegion',
  392. 'city' => 'CachedCity',
  393. 'latitude' => 1.23,
  394. 'longitude' => 4.56,
  395. ];
  396. // 将结果写入缓存
  397. Cache::tags('IP_INFO')->put($ip, $cached, now()->addMinutes(10));
  398. // 伪造 HTTP,如果有请求发生将返回 500(测试应从缓存直接返回)
  399. Http::fake(['*' => Http::response([], 500)]);
  400. $result = IP::getIPInfo($ip);
  401. $this->assertIsArray($result);
  402. $this->assertEquals('CachedLand', $result['country']);
  403. // 确保没有执行任何外部 HTTP 请求
  404. Http::assertNothingSent();
  405. }
  406. public function test_get_ip_geo_returns_lat_lon_consistent_with_get_ip_info(): void
  407. {
  408. $ip = '5.6.7.8';
  409. Http::fake([
  410. 'http://ip-api.com/*' => Http::response([
  411. 'status' => 'success',
  412. 'country' => 'GeoTest',
  413. 'lat' => 11.11,
  414. 'lon' => 22.22,
  415. ]),
  416. '*' => Http::response([], 500),
  417. ]);
  418. // getIPInfo 会缓存并返回完整数据,getIPGeo 只返回 lat/lon
  419. $info = IP::getIPInfo($ip);
  420. $geo = IP::getIPGeo($ip);
  421. $this->assertIsArray($info);
  422. $this->assertIsArray($geo);
  423. $this->assertArrayHasKey('latitude', $geo);
  424. $this->assertArrayHasKey('longitude', $geo);
  425. $this->assertEquals($info['latitude'], $geo['latitude']);
  426. $this->assertEquals($info['longitude'], $geo['longitude']);
  427. }
  428. public function test_local_database_providers_are_used_when_http_fails(): void
  429. {
  430. // 设为中文以便优先走本地库(例如 IPDB / ip2region / ipip 等)
  431. App::setLocale('zh_CN');
  432. $ip = '123.123.123.123';
  433. // 强制 HTTP 全部失败,确保使用本地数据库驱动(已在测试环境通过 eval 注入本地驱动 mock)
  434. Http::fake(['*' => Http::response([], 500)]);
  435. Cache::tags('IP_INFO')->forget($ip);
  436. $result = IP::getIPInfo($ip);
  437. $this->assertIsArray($result);
  438. // 这些值依赖于测试时注入的本地 DB mock(原测试中为 中国 / 北京 / 北京市)
  439. $this->assertEquals('中国', $result['country'] ?? null);
  440. $this->assertEquals('北京', $result['region'] ?? null);
  441. $this->assertEquals('北京市', $result['city'] ?? null);
  442. }
  443. public function test_get_ip_info_returns_null_when_http_timeout(): void
  444. {
  445. App::setLocale('en_US');
  446. $ip = '2.2.2.2';
  447. // 模拟超时情况
  448. Http::fake([
  449. '*' => function () {
  450. // 模拟超时,抛出异常或返回超时错误
  451. throw new ConnectionException('cURL error 28: Operation timed out');
  452. },
  453. ]);
  454. $result = IP::getIPInfo($ip, 'ipApi');
  455. $this->assertNull($result);
  456. }
  457. public function test_get_ip_info_returns_null_when_invalid_json_response(): void
  458. {
  459. App::setLocale('en_US');
  460. $ip = '3.3.3.3';
  461. // 模拟返回无效JSON
  462. Http::fake([
  463. 'http://ip-api.com/*' => Http::response('Invalid JSON response', 200),
  464. '*' => Http::response([], 500),
  465. ]);
  466. $result = IP::getIPInfo($ip, 'ipApi');
  467. $this->assertNull($result);
  468. }
  469. public function test_get_ip_info_with_specific_checker_returns_null_when_provider_fails(): void
  470. {
  471. App::setLocale('en_US');
  472. $ip = '5.5.5.5';
  473. // 模拟特定检查器失败,并阻止其他检查器被调用
  474. Http::fake([
  475. '*' => Http::response([], 500), // 确保其他所有请求也失败
  476. ]);
  477. // 使用指定的checker
  478. $result = IP::getIPInfo($ip, 'ipApi');
  479. $this->assertNull($result);
  480. }
  481. public function test_real_api_requests(): void
  482. {
  483. $testIp = '8.8.8.8'; // 使用一个公共的IP地址进行测试
  484. $checkers = ['ipApi', 'Baidu', 'baiduBce', 'ipw', 'ipGeoLocation', 'TaoBao', 'speedtest', 'bjjii', 'vore', 'juHe', 'ip2Region', 'IPDB', 'IPSB', 'ipinfo', 'ip234', 'dbIP', 'IP2Online', 'ipdata', 'ipApiCo', 'ip2Location', 'GeoIP2', 'ipApiCom', 'pconline', 'ipApiIO', 'ipApiIS', 'freeipapi', 'ipwhois'];
  485. $successfulRequests = 0;
  486. $failedRequests = 0;
  487. $results = [];
  488. // 为每个检查器执行实际请求
  489. foreach ($checkers as $checker) {
  490. try {
  491. // 清除之前的缓存
  492. Cache::tags('IP_INFO')->forget($testIp);
  493. // 执行实际的API请求
  494. $result = IP::getIPInfo($testIp, $checker);
  495. if (is_array($result) && ! empty(array_filter($result))) {
  496. $successfulRequests++;
  497. } else {
  498. $failedRequests++;
  499. }
  500. $results[$checker] = $result;
  501. } catch (Exception $e) {
  502. $failedRequests++;
  503. echo "Checker {$checker} failed with exception: ".$e->getMessage()."\n";
  504. }
  505. }
  506. // 输出测试结果摘要
  507. echo "实际API请求测试结果:\n";
  508. echo "成功请求: {$successfulRequests}\n";
  509. echo "失败请求: {$failedRequests}\n";
  510. echo '总请求数: '.($successfulRequests + $failedRequests)."\n";
  511. $this->assertGreaterThan(0, $successfulRequests, '至少应有一个API请求成功');
  512. foreach ($results as $checker => $result) {
  513. echo "[$checker] - ".json_encode($result, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE)."\n";
  514. }
  515. }
  516. /**
  517. * 测试单个API的实际请求
  518. *
  519. * @param string $checker 检查器名称
  520. * @param string $ip 测试IP地址
  521. */
  522. public function test_single_real_api_request(string $checker = 'speedtest', string $ip = '8.8.8.8'): void
  523. {
  524. try {
  525. // 执行实际的API请求
  526. $result = IP::getIPInfo($ip, $checker);
  527. // 输出结果
  528. echo "检查器: {$checker}\n";
  529. echo "测试IP: {$ip}\n";
  530. echo '结果: '.json_encode($result, JSON_UNESCAPED_UNICODE)."\n";
  531. // 验证结果
  532. if (is_array($result) && ! empty(array_filter($result))) {
  533. $this->assertIsArray($result);
  534. echo "测试成功: {$checker} 返回了有效数据\n";
  535. } else {
  536. echo "警告: {$checker} 没有返回有效数据\n";
  537. }
  538. } catch (Exception $e) {
  539. echo "检查器 {$checker} 失败,异常信息: ".$e->getMessage()."\n";
  540. $this->markTestIncomplete("检查器 {$checker} 请求失败: ".$e->getMessage());
  541. }
  542. }
  543. protected function setUp(): void
  544. {
  545. parent::setUp();
  546. // 清理 HTTP 假造与缓存
  547. Http::fake([]);
  548. Cache::tags('IP_INFO')->flush();
  549. // 重置 basicRequest
  550. $ref = new ReflectionClass(IP::class);
  551. if ($ref->hasProperty('basicRequest')) {
  552. $prop = $ref->getProperty('basicRequest');
  553. $prop->setAccessible(true);
  554. $prop->setValue(null, null);
  555. }
  556. }
  557. }