118 lines
6.0 KiB
Python
118 lines
6.0 KiB
Python
import json
|
|
import socket
|
|
import struct
|
|
|
|
from proxydoctor import ipquality as iq, media, routes, suite
|
|
|
|
|
|
def test_keyless_risk_sources_preserve_disagreement_and_identity(monkeypatch):
|
|
monkeypatch.delenv('PROXYDOCTOR_ABUSEIPDB_KEY',raising=False)
|
|
monkeypatch.delenv('PROXYDOCTOR_IPAPI_KEY',raising=False)
|
|
calls=[]
|
|
def request(url,**kwargs):
|
|
calls.append(url)
|
|
if 'api.ipapi.is' in url: return {'ip':'8.8.8.8','is_proxy':False,'company':'Primary'},'ok'
|
|
if 'proxycheck.io' in url:
|
|
return {'status':'ok','8.8.8.8':{'network':{'type':'Hosting','asn':'AS15169'},
|
|
'detections':{'proxy':True,'vpn':False,'hosting':True,'tor':False,'risk':33,'confidence':90},
|
|
'attack_history':{'login_attempt':2}}},'ok'
|
|
return {'ip':'1.1.1.1','country_name':'Wrong IP','is_proxy':False},'ok'
|
|
monkeypatch.setattr(iq,'request_json',request)
|
|
result=iq.collect()
|
|
assert len(calls)==3
|
|
assert result['values']['is_proxy'] is None
|
|
assert result['conflicts']['is_proxy']=={'ipapi.is':False,'proxycheck.io':True}
|
|
assert result['values']['is_datacenter'] is True
|
|
assert result['values']['company']=='Primary'
|
|
assert result['field_sources']['is_vpn']==['proxycheck.io']
|
|
assert result['provider_risk']['proxycheck.io']['risk']==33
|
|
assert result['provider_risk']['proxycheck.io']['attack_count']==2
|
|
assert result['sources']['IP2Location']['state']=='invalid_response'
|
|
assert 'Wrong IP' not in json.dumps(result)
|
|
|
|
|
|
def test_egress_fallback_then_keyless_sources_do_not_change_target(monkeypatch):
|
|
monkeypatch.delenv('PROXYDOCTOR_ABUSEIPDB_KEY',raising=False)
|
|
def request(url,**kwargs):
|
|
if 'ipapi' in url: return None,'rate_limited'
|
|
if 'ipify' in url: return {'ip':'8.8.8.8'},'ok'
|
|
if 'proxycheck' in url: return None,'rate_limited'
|
|
assert 'ip=8.8.8.8' in url
|
|
return {'ip':'8.8.8.8','country_name':'Example','asn':'15169','is_proxy':False},'ok'
|
|
monkeypatch.setattr(iq,'request_json',request)
|
|
result=iq.collect()
|
|
assert result['ip']=='8.8.8.8' and result['values']['country']=='Example'
|
|
assert result['values']['is_proxy'] is None # A limited open-proxy flag is not a general guarantee.
|
|
assert result['sources']['proxycheck.io']['state']=='rate_limited'
|
|
assert result['provider_risk']['IP2Location']['open_proxy_flag'] is False
|
|
|
|
|
|
def test_malformed_provider_risk_does_not_invent_values():
|
|
assert iq.valid_ip(134744072) is None
|
|
values=iq.normalize_proxycheck({'detections':{'proxy':'false','hosting':0}})
|
|
assert values['is_proxy'] is None and values['is_datacenter'] is None
|
|
|
|
|
|
def test_bbc_geo_json_needs_repeat_and_does_not_fetch_media(monkeypatch):
|
|
calls=[]
|
|
def page(url):
|
|
calls.append(url)
|
|
return media.Page(403,json.dumps({'result':'geolocation'}))
|
|
monkeypatch.setattr(media,'fetch_page',page)
|
|
result=media.check('BBC iPlayer')
|
|
assert result['state']=='restricted' and result['scope']=='bbc_one_metadata'
|
|
assert result['playback']=='not_tested'
|
|
assert calls==[media.BBC_SELECTOR]*2
|
|
assert media.classify_bbc_metadata(media.Page(403,'Access denied geolocation'))['state']=='unknown'
|
|
|
|
|
|
def test_bbc_metadata_does_not_export_signed_stream_url(monkeypatch):
|
|
page=media.Page(200,json.dumps({'media':[{'kind':'video','connection':[{'href':'https://media.example/live.m3u8?token=private'}]}]}))
|
|
monkeypatch.setattr(media,'fetch_page',lambda url:page)
|
|
result=media.check('BBC iPlayer')
|
|
assert result['state']=='metadata_available' and result['playback']=='not_tested'
|
|
assert 'private' not in json.dumps(result)
|
|
|
|
|
|
def test_ipv6_trace_uses_icmp6_error_queue_and_exports_hop(monkeypatch):
|
|
monkeypatch.setattr(routes.sys,'platform','linux')
|
|
monkeypatch.setattr(routes.GeoLocator,'lookup',lambda self,ip:'Estimated place')
|
|
target='2606:4700:4700::1111'
|
|
seen=[]
|
|
class Socket:
|
|
def __enter__(self):return self
|
|
def __exit__(self,*args):pass
|
|
def setsockopt(self,*args):seen.append(args)
|
|
def setblocking(self,*args):pass
|
|
def sendto(self,data,destination):self.destination=destination
|
|
def recvmsg(self,*args):
|
|
extended=struct.pack('=IBBBBII',111,3,1,4,0,0,0)
|
|
offender=struct.pack('=HHI',socket.AF_INET6,0,0)+socket.inet_pton(socket.AF_INET6,target)+b'\x00'*4
|
|
return b'',[(socket.IPPROTO_IPV6,25,extended+offender)],0,self.destination
|
|
monkeypatch.setattr(routes.socket,'socket',lambda family,kind:Socket())
|
|
records=[]
|
|
assert routes.trace(target,records=records,emit=lambda *a,**k:None)==0
|
|
assert (socket.IPPROTO_IPV6,25,1) in seen
|
|
assert records[0]['ip']==target and records[0]['state']=='reached'
|
|
|
|
|
|
def test_suite_includes_security_network_and_route_evidence(monkeypatch,tmp_path,capsys):
|
|
from test_suite import unavailable_bandwidth,unavailable_ip
|
|
monkeypatch.setattr(suite,'measure_bandwidth',unavailable_bandwidth)
|
|
monkeypatch.setattr(suite,'collect_ipquality',unavailable_ip)
|
|
monkeypatch.setattr(suite,'run_media',lambda *args,**kwargs:kwargs['results'].update(Netflix={'state':'unknown','playback':'not_tested'}))
|
|
def network(*args,**kwargs):kwargs['report'].update(dns=[{'family':4,'status':'success'}],transports=[]);return 0
|
|
monkeypatch.setattr('proxydoctor.connectivity.run_console',network)
|
|
def trace(*args,**kwargs):kwargs['records'].append({'hop':1,'ip':'8.8.8.8','latency_ms':2});return 0
|
|
monkeypatch.setattr(suite,'native_trace',trace)
|
|
path=tmp_path/'suite.json'
|
|
assert suite.run_suite(lang='en',port=2222,json_output=path)==1
|
|
result=json.loads(path.read_text(encoding='utf-8'))
|
|
output=capsys.readouterr().out
|
|
assert 'SSH port guard' in output and 'Local port 2222' in output
|
|
assert result['schema_version']==3 and result['target_port']==2222
|
|
assert result['network']['dns'][0]['status']=='success'
|
|
assert result['route']['state']=='reached'
|
|
assert result['route']['hops'][0]['ip']!='8.8.8.8'
|
|
assert result['summary']['playback']=='not_tested'
|