|
@@ -0,0 +1,606 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+declare(strict_types=1);
|
|
|
|
|
+
|
|
|
|
|
+namespace Tests\Unit\Utils;
|
|
|
|
|
+
|
|
|
|
|
+use App\Utils\IP;
|
|
|
|
|
+use Exception;
|
|
|
|
|
+use Illuminate\Http\Client\ConnectionException;
|
|
|
|
|
+use Illuminate\Support\Facades\App;
|
|
|
|
|
+use Illuminate\Support\Facades\Cache;
|
|
|
|
|
+use Illuminate\Support\Facades\Http;
|
|
|
|
|
+use ReflectionClass;
|
|
|
|
|
+use Tests\TestCase;
|
|
|
|
|
+
|
|
|
|
|
+class IPTest extends TestCase
|
|
|
|
|
+{
|
|
|
|
|
+ public static function providerApiCases(): array
|
|
|
|
|
+ {
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'ipApi' => [[
|
|
|
|
|
+ 'name' => 'ipApi',
|
|
|
|
|
+ 'endpoint' => '*ip-api.com/json/*',
|
|
|
|
|
+ 'response' => '{"city":"沈阳市","country":"中国","district":"","isp":"China Unicom CHINA169 Network","lat":41.8357,"lon":123.429,"query":"8.8.8.8","regionName":"辽宁","status":"success"}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => '中国',
|
|
|
|
|
+ 'region' => '辽宁',
|
|
|
|
|
+ 'city' => '沈阳市',
|
|
|
|
|
+ 'isp' => 'China Unicom CHINA169 Network',
|
|
|
|
|
+ 'latitude' => 41.8357,
|
|
|
|
|
+ 'longitude' => 123.429,
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'baidu' => [[
|
|
|
|
|
+ 'name' => 'Baidu',
|
|
|
|
|
+ 'config' => ['services.ip.baidu_ak' => 'fake_baidu_ak'],
|
|
|
|
|
+ 'endpoint' => 'https://api.map.baidu.com/location/ip*',
|
|
|
|
|
+ '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"}}}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'CN',
|
|
|
|
|
+ 'region' => '辽宁省',
|
|
|
|
|
+ 'city' => '沈阳市',
|
|
|
|
|
+ 'latitude' => '41.67756788393409',
|
|
|
|
|
+ 'longitude' => '123.46466579069178',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'baiduBce' => [[
|
|
|
|
|
+ 'name' => 'baiduBce',
|
|
|
|
|
+ 'endpoint' => 'https://qifu-api.baidubce.com/ip/geo/v1/district*',
|
|
|
|
|
+ 'response' => '{"code":"Success","data":{"continent":"亚洲","country":"中国","zipcode":"110000","owner":"中国联通","isp":"中国联通","adcode":"210100","prov":"辽宁省","city":"沈阳市","district":""},"ip":"8.8.8.8"}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => '中国',
|
|
|
|
|
+ 'region' => '辽宁省',
|
|
|
|
|
+ 'city' => '沈阳市',
|
|
|
|
|
+ 'isp' => '中国联通',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'ipGeoLocation' => [[
|
|
|
|
|
+ 'name' => 'ipGeoLocation',
|
|
|
|
|
+ 'endpoint' => 'https://api.ipgeolocation.io/ipgeo?ip=*',
|
|
|
|
|
+ '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"}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => '中国',
|
|
|
|
|
+ 'region' => '辽宁',
|
|
|
|
|
+ 'city' => '沈阳',
|
|
|
|
|
+ 'isp' => 'China Unicom CHINA169 Network',
|
|
|
|
|
+ 'area' => '沈阳',
|
|
|
|
|
+ 'latitude' => '41.79680',
|
|
|
|
|
+ 'longitude' => '123.42910',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'taobao' => [[
|
|
|
|
|
+ 'name' => 'TaoBao',
|
|
|
|
|
+ 'endpoint' => 'https://ip.taobao.com/outGetIpInfo?ip=*',
|
|
|
|
|
+ '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}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => '中国',
|
|
|
|
|
+ 'region' => '辽宁',
|
|
|
|
|
+ 'city' => '沈阳',
|
|
|
|
|
+ 'isp' => '联通',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'speedtest' => [[
|
|
|
|
|
+ 'name' => 'speedtest',
|
|
|
|
|
+ 'endpoint' => 'https://api-v3.speedtest.cn/ip*',
|
|
|
|
|
+ 'response' => '{"code":0,"data":"1z41bLnGrmhViAP9vBtxaTvcnepxEF7nyynwu4VDL1s6YCnSK48PoPFgNf6lDQQ3GV9cmRtDJTrLLrU16eItDnmB+8+3stMtsFBhaLaRH1ece5b+D4lR73Cy1FvaxFXmIfGuPxIOjV/g4Mh4F7GuvEy1gm5/tj9gm4egANOyl3vkzMFvp9tB1ET9PUhaP29DTMQOwxCV4CendJn2LdwF6tP6elucLUoy3xweFC4h2w20oha/GcOiQAxKLB+h6aslydvXqDAzvMmeXRV6e0CQ6A==","\'msg\'":"ok"}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => '中国',
|
|
|
|
|
+ 'region' => '辽宁',
|
|
|
|
|
+ 'city' => '沈阳',
|
|
|
|
|
+ 'isp' => '中国联通',
|
|
|
|
|
+ 'area' => '沈河区',
|
|
|
|
|
+ 'latitude' => '41.796767',
|
|
|
|
|
+ 'longitude' => '123.429096',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'juHe' => [[
|
|
|
|
|
+ 'name' => 'juHe',
|
|
|
|
|
+ 'endpoint' => 'https://apis.juhe.cn/ip/Example/query.php*',
|
|
|
|
|
+ 'response' => '{"resultcode":"200","reason":"success","result":{"Country":"中国","Province":"辽宁","City":"沈阳","District":"","Isp":"联通"},"error_code":0}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => '中国',
|
|
|
|
|
+ 'region' => '辽宁',
|
|
|
|
|
+ 'city' => '沈阳',
|
|
|
|
|
+ 'isp' => '联通',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'ip2Region' => [[
|
|
|
|
|
+ 'name' => 'ip2Region',
|
|
|
|
|
+ 'ip' => '103.250.104.0',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => '中国',
|
|
|
|
|
+ 'region' => '辽宁省',
|
|
|
|
|
+ 'city' => '沈阳市',
|
|
|
|
|
+ 'isp' => '联通',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'IPDB' => [[
|
|
|
|
|
+ 'name' => 'IPDB',
|
|
|
|
|
+ 'ip' => '103.250.104.0',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => '中国',
|
|
|
|
|
+ 'region' => '辽宁',
|
|
|
|
|
+ 'isp' => '联通',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'IPSB' => [[
|
|
|
|
|
+ 'name' => 'IPSB',
|
|
|
|
|
+ 'endpoint' => 'https://api.ip.sb/geoip/*',
|
|
|
|
|
+ '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}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'China',
|
|
|
|
|
+ 'isp' => 'China Unicom',
|
|
|
|
|
+ 'latitude' => 34.7732,
|
|
|
|
|
+ 'longitude' => 113.722,
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'ipinfo' => [[
|
|
|
|
|
+ 'name' => 'ipinfo',
|
|
|
|
|
+ 'endpoint' => 'https://ipinfo.io*',
|
|
|
|
|
+ '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"}}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'CN',
|
|
|
|
|
+ 'region' => 'Liaoning',
|
|
|
|
|
+ 'city' => 'Shenyang',
|
|
|
|
|
+ 'isp' => 'AS4837 CHINA UNICOM China169 Backbone',
|
|
|
|
|
+ 'latitude' => '41.7922',
|
|
|
|
|
+ 'longitude' => '123.4328',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'ip234' => [[
|
|
|
|
|
+ 'name' => 'ip234',
|
|
|
|
|
+ 'endpoint' => 'https://ip234.in/search_ip*',
|
|
|
|
|
+ '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":""}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'china',
|
|
|
|
|
+ 'region' => 'Liaoning',
|
|
|
|
|
+ 'city' => 'Shenyang',
|
|
|
|
|
+ 'isp' => 'CHINA UNICOM China169 Backbone',
|
|
|
|
|
+ 'latitude' => '41.8357',
|
|
|
|
|
+ 'longitude' => '123.429',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'dbIP' => [[
|
|
|
|
|
+ 'name' => 'dbIP',
|
|
|
|
|
+ 'endpoint' => 'https://api.db-ip.com/v2/free/*',
|
|
|
|
|
+ 'response' => '{"ipAddress":"8.8.8.8","continentCode":"AS","continentName":"Asia","countryCode":"CN","countryName":"China","stateProv":"Liaoning","city":"Shenyang"}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'China',
|
|
|
|
|
+ 'region' => 'Liaoning',
|
|
|
|
|
+ 'city' => 'Shenyang',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'IP2Online' => [[
|
|
|
|
|
+ 'name' => 'IP2Online',
|
|
|
|
|
+ 'config' => ['services.ip.IP2Location_key' => 'fake_ip2location_key'],
|
|
|
|
|
+ 'endpoint' => 'https://api.ip2location.io/*',
|
|
|
|
|
+ '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}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'China',
|
|
|
|
|
+ 'region' => 'Liaoning',
|
|
|
|
|
+ 'city' => 'Shenyang',
|
|
|
|
|
+ 'isp' => 'China Unicom China169 Backbone',
|
|
|
|
|
+ 'latitude' => '41.79222',
|
|
|
|
|
+ 'longitude' => '123.43288',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'ipdata' => [[
|
|
|
|
|
+ 'name' => 'ipdata',
|
|
|
|
|
+ 'config' => ['services.ip.ipdata_key' => 'fake_ipdata_key'],
|
|
|
|
|
+ 'endpoint' => 'https://api.ipdata.co/*',
|
|
|
|
|
+ 'response' => '{"ip":"8.8.8.8","city":null,"region":null,"country_name":"China","latitude":34.77320098876953,"longitude":113.72200012207031,"asn":null}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'China',
|
|
|
|
|
+ 'latitude' => 34.77320098876953,
|
|
|
|
|
+ 'longitude' => 113.72200012207031,
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'ipApiCo' => [[
|
|
|
|
|
+ 'name' => 'ipApiCo',
|
|
|
|
|
+ 'endpoint' => 'https://ipapi.co/*/json/*',
|
|
|
|
|
+ '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"}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'China',
|
|
|
|
|
+ 'region' => 'Liaoning',
|
|
|
|
|
+ 'city' => 'Shenyang',
|
|
|
|
|
+ 'isp' => 'CHINA UNICOM China169 Backbone',
|
|
|
|
|
+ 'latitude' => 41.79222,
|
|
|
|
|
+ 'longitude' => 123.43278,
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'ip2Location' => [[
|
|
|
|
|
+ 'name' => 'ip2Location',
|
|
|
|
|
+ 'ip' => '103.250.104.0',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'China',
|
|
|
|
|
+ 'region' => 'Liaoning',
|
|
|
|
|
+ 'city' => 'Shenyang',
|
|
|
|
|
+ 'latitude' => 41.792221,
|
|
|
|
|
+ 'longitude' => 123.432877,
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'GeoIP2' => [[
|
|
|
|
|
+ 'name' => 'GeoIP2',
|
|
|
|
|
+ 'ip' => '103.250.104.0',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'China',
|
|
|
|
|
+ 'latitude' => 34.7732,
|
|
|
|
|
+ 'longitude' => 113.722,
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'ipApiCom' => [[
|
|
|
|
|
+ 'name' => 'ipApiCom',
|
|
|
|
|
+ 'config' => ['services.ip.ipApiCom_acess_key' => 'fake_acess_key'],
|
|
|
|
|
+ 'endpoint' => 'https://api.ipapi.com/api/*',
|
|
|
|
|
+ '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}}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'China',
|
|
|
|
|
+ 'region' => 'Liaoning',
|
|
|
|
|
+ 'city' => 'Shenyang',
|
|
|
|
|
+ 'latitude' => 41.801021575927734,
|
|
|
|
|
+ 'longitude' => 123.40206909179688,
|
|
|
|
|
+ 'address' => 'China Liaoning Shenyang',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'vore' => [[
|
|
|
|
|
+ 'name' => 'vore',
|
|
|
|
|
+ 'endpoint' => 'https://api.vore.top/api/IPdata*',
|
|
|
|
|
+ '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}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => '辽宁省',
|
|
|
|
|
+ 'region' => '沈阳市',
|
|
|
|
|
+ 'isp' => '联通',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'ipw_v4' => [[
|
|
|
|
|
+ 'name' => 'ipw',
|
|
|
|
|
+ 'endpoint' => 'https://rest.ipw.cn/api/aw/v1/ipv4*',
|
|
|
|
|
+ '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"}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => '中国',
|
|
|
|
|
+ 'region' => '辽宁省',
|
|
|
|
|
+ 'city' => '沈阳市',
|
|
|
|
|
+ 'isp' => '中国联通',
|
|
|
|
|
+ 'latitude' => '41.800551',
|
|
|
|
|
+ 'longitude' => '123.420011',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'ipw_v6' => [[
|
|
|
|
|
+ 'name' => 'ipw',
|
|
|
|
|
+ 'ip' => '2408:8207:1850:2a60::4c8',
|
|
|
|
|
+ 'endpoint' => 'https://rest.ipw.cn/api/aw/v1/ipv6*',
|
|
|
|
|
+ '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"}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => '日本',
|
|
|
|
|
+ 'region' => '东京都',
|
|
|
|
|
+ 'city' => 'Suginami',
|
|
|
|
|
+ 'isp' => '亚马逊',
|
|
|
|
|
+ 'latitude' => '35.713914',
|
|
|
|
|
+ 'longitude' => '139.616508',
|
|
|
|
|
+ 'address' => '日本 东京都 Suginami',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'bjjii' => [[
|
|
|
|
|
+ 'name' => 'bjjii',
|
|
|
|
|
+ 'config' => ['services.ip.bjjii_key' => 'fake_acess_key'],
|
|
|
|
|
+ 'endpoint' => 'https://api.bjjii.com/api/ip/query*',
|
|
|
|
|
+ '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"}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => '中国',
|
|
|
|
|
+ 'region' => '辽宁省',
|
|
|
|
|
+ 'city' => '沈阳市',
|
|
|
|
|
+ 'latitude' => 41.83571,
|
|
|
|
|
+ 'longitude' => 123.42925,
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'pconline' => [[
|
|
|
|
|
+ 'name' => 'pconline',
|
|
|
|
|
+ 'endpoint' => 'https://whois.pconline.com.cn/*',
|
|
|
|
|
+ 'response' => '{"ip":"8.8.8.8","pro":"辽宁省","proCode":"210000","city":"沈阳市","cityCode":"210100","region":"","regionCode":"0","addr":"辽宁省沈阳市 联通","regionNames":"","err":""}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'region' => '辽宁省',
|
|
|
|
|
+ 'city' => '沈阳市',
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'ipApiIO' => [[
|
|
|
|
|
+ 'name' => 'ipApiIO',
|
|
|
|
|
+ 'endpoint' => 'https://ip-api.io/api/v1/ip/*',
|
|
|
|
|
+ '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}}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'China',
|
|
|
|
|
+ 'latitude' => 34.7732,
|
|
|
|
|
+ 'longitude' => 113.722,
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'ipApiIS' => [[
|
|
|
|
|
+ 'name' => 'ipApiIS',
|
|
|
|
|
+ 'endpoint' => 'https://api.ipapi.is/*',
|
|
|
|
|
+ '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}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'China',
|
|
|
|
|
+ 'region' => 'Liaoning',
|
|
|
|
|
+ 'city' => 'Shenyang',
|
|
|
|
|
+ 'isp' => 'CHINA UNICOM China169 Backbone',
|
|
|
|
|
+ 'latitude' => 41.79222,
|
|
|
|
|
+ 'longitude' => 123.43278,
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'freeipapi' => [[
|
|
|
|
|
+ 'name' => 'freeipapi',
|
|
|
|
|
+ 'endpoint' => 'https://free.freeipapi.com/*',
|
|
|
|
|
+ '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}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'China',
|
|
|
|
|
+ 'region' => 'Liaoning',
|
|
|
|
|
+ 'city' => 'Shenyang',
|
|
|
|
|
+ 'isp' => 'CHINA UNICOM China169 Backbone',
|
|
|
|
|
+ 'latitude' => 41.8357,
|
|
|
|
|
+ 'longitude' => 123.429,
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ 'ipwhois' => [[
|
|
|
|
|
+ 'name' => 'ipwhois',
|
|
|
|
|
+ 'endpoint' => 'https://ipwhois.app/json/*',
|
|
|
|
|
+ '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"}',
|
|
|
|
|
+ 'expected' => [
|
|
|
|
|
+ 'country' => 'China',
|
|
|
|
|
+ 'region' => 'Liaoning',
|
|
|
|
|
+ 'city' => 'Shenyang',
|
|
|
|
|
+ 'isp' => 'China Unicom China1 Backbone',
|
|
|
|
|
+ 'latitude' => 41.805699,
|
|
|
|
|
+ 'longitude' => 123.431472,
|
|
|
|
|
+ ],
|
|
|
|
|
+ ]],
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_localhost_returns_false_for_loopback_ips(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->assertFalse(IP::getIPInfo('127.0.0.1'));
|
|
|
|
|
+ $this->assertFalse(IP::getIPInfo('::1'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_get_client_ip_is_string_or_null(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $ip = IP::getClientIP();
|
|
|
|
|
+ $this->assertTrue(is_null($ip) || is_string($ip));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @dataProvider providerApiCases
|
|
|
|
|
+ */
|
|
|
|
|
+ public function test_get_ip_info_from_each_provider(array $case): void
|
|
|
|
|
+ {
|
|
|
|
|
+ App::setLocale($case['locale'] ?? 'zh_CN');
|
|
|
|
|
+
|
|
|
|
|
+ if (! empty($case['config'])) { // 设置可能存在的假token参数,来激活 API 访问
|
|
|
|
|
+ foreach ($case['config'] as $k => $v) {
|
|
|
|
|
+ config([$k => $v]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 模拟HTTP响应
|
|
|
|
|
+ if (isset($case['response'])) {
|
|
|
|
|
+ $fakeResponses[$case['endpoint']] = Http::response($case['response']);
|
|
|
|
|
+ }
|
|
|
|
|
+ $fakeResponses['*'] = Http::response([], 500);
|
|
|
|
|
+
|
|
|
|
|
+ Http::fake($fakeResponses);
|
|
|
|
|
+
|
|
|
|
|
+ $result = IP::getIPInfo($case['ip'] ?? '8.8.8.8', $case['name']);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertIsArray($result, "Provider {$case['name']} should return an array");
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($case['expected'] as $k => $v) {
|
|
|
|
|
+ $this->assertEquals($v, $result[$k] ?? null, "Provider {$case['name']} field {$k} mismatch");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_get_ip_info_caches_result_and_prevents_http_calls(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $ip = '9.9.9.9';
|
|
|
|
|
+ $cached = [
|
|
|
|
|
+ 'country' => 'CachedLand',
|
|
|
|
|
+ 'region' => 'CachedRegion',
|
|
|
|
|
+ 'city' => 'CachedCity',
|
|
|
|
|
+ 'latitude' => 1.23,
|
|
|
|
|
+ 'longitude' => 4.56,
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ // 将结果写入缓存
|
|
|
|
|
+ Cache::tags('IP_INFO')->put($ip, $cached, now()->addMinutes(10));
|
|
|
|
|
+
|
|
|
|
|
+ // 伪造 HTTP,如果有请求发生将返回 500(测试应从缓存直接返回)
|
|
|
|
|
+ Http::fake(['*' => Http::response([], 500)]);
|
|
|
|
|
+
|
|
|
|
|
+ $result = IP::getIPInfo($ip);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertIsArray($result);
|
|
|
|
|
+ $this->assertEquals('CachedLand', $result['country']);
|
|
|
|
|
+ // 确保没有执行任何外部 HTTP 请求
|
|
|
|
|
+ Http::assertNothingSent();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_get_ip_geo_returns_lat_lon_consistent_with_get_ip_info(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $ip = '5.6.7.8';
|
|
|
|
|
+
|
|
|
|
|
+ Http::fake([
|
|
|
|
|
+ 'http://ip-api.com/*' => Http::response([
|
|
|
|
|
+ 'status' => 'success',
|
|
|
|
|
+ 'country' => 'GeoTest',
|
|
|
|
|
+ 'lat' => 11.11,
|
|
|
|
|
+ 'lon' => 22.22,
|
|
|
|
|
+ ]),
|
|
|
|
|
+ '*' => Http::response([], 500),
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ // getIPInfo 会缓存并返回完整数据,getIPGeo 只返回 lat/lon
|
|
|
|
|
+ $info = IP::getIPInfo($ip);
|
|
|
|
|
+ $geo = IP::getIPGeo($ip);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertIsArray($info);
|
|
|
|
|
+ $this->assertIsArray($geo);
|
|
|
|
|
+ $this->assertArrayHasKey('latitude', $geo);
|
|
|
|
|
+ $this->assertArrayHasKey('longitude', $geo);
|
|
|
|
|
+ $this->assertEquals($info['latitude'], $geo['latitude']);
|
|
|
|
|
+ $this->assertEquals($info['longitude'], $geo['longitude']);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_local_database_providers_are_used_when_http_fails(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // 设为中文以便优先走本地库(例如 IPDB / ip2region / ipip 等)
|
|
|
|
|
+ App::setLocale('zh_CN');
|
|
|
|
|
+ $ip = '123.123.123.123';
|
|
|
|
|
+
|
|
|
|
|
+ // 强制 HTTP 全部失败,确保使用本地数据库驱动(已在测试环境通过 eval 注入本地驱动 mock)
|
|
|
|
|
+ Http::fake(['*' => Http::response([], 500)]);
|
|
|
|
|
+ Cache::tags('IP_INFO')->forget($ip);
|
|
|
|
|
+
|
|
|
|
|
+ $result = IP::getIPInfo($ip);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertIsArray($result);
|
|
|
|
|
+ // 这些值依赖于测试时注入的本地 DB mock(原测试中为 中国 / 北京 / 北京市)
|
|
|
|
|
+ $this->assertEquals('中国', $result['country'] ?? null);
|
|
|
|
|
+ $this->assertEquals('北京', $result['region'] ?? null);
|
|
|
|
|
+ $this->assertEquals('北京市', $result['city'] ?? null);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_get_ip_info_returns_null_when_http_timeout(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ App::setLocale('en_US');
|
|
|
|
|
+ $ip = '2.2.2.2';
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟超时情况
|
|
|
|
|
+ Http::fake([
|
|
|
|
|
+ '*' => function () {
|
|
|
|
|
+ // 模拟超时,抛出异常或返回超时错误
|
|
|
|
|
+ throw new ConnectionException('cURL error 28: Operation timed out');
|
|
|
|
|
+ },
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $result = IP::getIPInfo($ip, 'ipApi');
|
|
|
|
|
+ $this->assertNull($result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_get_ip_info_returns_null_when_invalid_json_response(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ App::setLocale('en_US');
|
|
|
|
|
+ $ip = '3.3.3.3';
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟返回无效JSON
|
|
|
|
|
+ Http::fake([
|
|
|
|
|
+ 'http://ip-api.com/*' => Http::response('Invalid JSON response', 200),
|
|
|
|
|
+ '*' => Http::response([], 500),
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $result = IP::getIPInfo($ip, 'ipApi');
|
|
|
|
|
+ $this->assertNull($result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_get_ip_info_with_specific_checker_returns_null_when_provider_fails(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ App::setLocale('en_US');
|
|
|
|
|
+ $ip = '5.5.5.5';
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟特定检查器失败,并阻止其他检查器被调用
|
|
|
|
|
+ Http::fake([
|
|
|
|
|
+ '*' => Http::response([], 500), // 确保其他所有请求也失败
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ // 使用指定的checker
|
|
|
|
|
+ $result = IP::getIPInfo($ip, 'ipApi');
|
|
|
|
|
+ $this->assertNull($result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_real_api_requests(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $testIp = '8.8.8.8'; // 使用一个公共的IP地址进行测试
|
|
|
|
|
+
|
|
|
|
|
+ $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'];
|
|
|
|
|
+
|
|
|
|
|
+ $successfulRequests = 0;
|
|
|
|
|
+ $failedRequests = 0;
|
|
|
|
|
+ $results = [];
|
|
|
|
|
+
|
|
|
|
|
+ // 为每个检查器执行实际请求
|
|
|
|
|
+ foreach ($checkers as $checker) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 清除之前的缓存
|
|
|
|
|
+ Cache::tags('IP_INFO')->forget($testIp);
|
|
|
|
|
+
|
|
|
|
|
+ // 执行实际的API请求
|
|
|
|
|
+ $result = IP::getIPInfo($testIp, $checker);
|
|
|
|
|
+
|
|
|
|
|
+ if (is_array($result) && ! empty(array_filter($result))) {
|
|
|
|
|
+ $successfulRequests++;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $failedRequests++;
|
|
|
|
|
+ }
|
|
|
|
|
+ $results[$checker] = $result;
|
|
|
|
|
+ } catch (Exception $e) {
|
|
|
|
|
+ $failedRequests++;
|
|
|
|
|
+ echo "Checker {$checker} failed with exception: ".$e->getMessage()."\n";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 输出测试结果摘要
|
|
|
|
|
+ echo "实际API请求测试结果:\n";
|
|
|
|
|
+ echo "成功请求: {$successfulRequests}\n";
|
|
|
|
|
+ echo "失败请求: {$failedRequests}\n";
|
|
|
|
|
+ echo '总请求数: '.($successfulRequests + $failedRequests)."\n";
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertGreaterThan(0, $successfulRequests, '至少应有一个API请求成功');
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($results as $checker => $result) {
|
|
|
|
|
+ echo "[$checker] - ".json_encode($result, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE)."\n";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 测试单个API的实际请求
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param string $checker 检查器名称
|
|
|
|
|
+ * @param string $ip 测试IP地址
|
|
|
|
|
+ */
|
|
|
|
|
+ public function test_single_real_api_request(string $checker = 'speedtest', string $ip = '8.8.8.8'): void
|
|
|
|
|
+ {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 执行实际的API请求
|
|
|
|
|
+ $result = IP::getIPInfo($ip, $checker);
|
|
|
|
|
+
|
|
|
|
|
+ // 输出结果
|
|
|
|
|
+ echo "检查器: {$checker}\n";
|
|
|
|
|
+ echo "测试IP: {$ip}\n";
|
|
|
|
|
+ echo '结果: '.json_encode($result, JSON_UNESCAPED_UNICODE)."\n";
|
|
|
|
|
+
|
|
|
|
|
+ // 验证结果
|
|
|
|
|
+ if (is_array($result) && ! empty(array_filter($result))) {
|
|
|
|
|
+ $this->assertIsArray($result);
|
|
|
|
|
+ echo "测试成功: {$checker} 返回了有效数据\n";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ echo "警告: {$checker} 没有返回有效数据\n";
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception $e) {
|
|
|
|
|
+ echo "检查器 {$checker} 失败,异常信息: ".$e->getMessage()."\n";
|
|
|
|
|
+ $this->markTestIncomplete("检查器 {$checker} 请求失败: ".$e->getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected function setUp(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ parent::setUp();
|
|
|
|
|
+
|
|
|
|
|
+ // 清理 HTTP 假造与缓存
|
|
|
|
|
+ Http::fake([]);
|
|
|
|
|
+ Cache::tags('IP_INFO')->flush();
|
|
|
|
|
+
|
|
|
|
|
+ // 重置 basicRequest
|
|
|
|
|
+ $ref = new ReflectionClass(IP::class);
|
|
|
|
|
+ if ($ref->hasProperty('basicRequest')) {
|
|
|
|
|
+ $prop = $ref->getProperty('basicRequest');
|
|
|
|
|
+ $prop->setAccessible(true);
|
|
|
|
|
+ $prop->setValue(null, null);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|