Files

150 lines
5.9 KiB
Python

import json
import subprocess
import sys
import pytest
from proxydoctor.checks import Command, inspect
from proxydoctor.cli import redact
from test_checks import Ubuntu, by_id
class ConfiguredHost(Ubuntu):
def __init__(self, routes4='[]', routes6='[]', dns='nameserver 8.8.8.8\n', ssh=None, session=''):
commands = {
('ip','-j','-4','route','show','default'): Command(0, routes4),
('ip','-j','-6','route','show','default'): Command(0, routes6),
('sshd','-T'): ssh or Command(None, reason='命令不可用'),
}
super().__init__(commands)
self.dns, self.session = dns, session
def read(self, path):
return self.dns if path == '/etc/resolv.conf' else super().read(path)
def ssh_connection(self):
return self.session
@pytest.mark.parametrize('lang', ['zh','en'])
def test_ipv4_default_route_detected_without_connectivity_claim(lang):
host = ConfiguredHost(routes4=json.dumps([{'dst':'default', 'dev':'eth0', 'gateway':'192.0.2.1'}]))
result = by_id(inspect(host, lang=lang), 'default_route')
assert result['status'] == 'pass'
assert 'eth0' in result['evidence']
assert '192.0.2.1' not in str(redact(result, lang))
@pytest.mark.parametrize('routes4,routes6', [
('[]','[]'), ('broken','[]'), ('{}','[]'),
('[{"dst":"default"}]','[]'),
('[{"dst":"default","dev":"eth0","gateway":"not-an-ip"}]','[]'),
('[{"dst":"default","type":"blackhole"}]','[]'),
('[{"dst":"default","dev":"eth0"}]','broken'),
])
def test_routes_incomplete_or_malformed_never_pass(routes4,routes6):
assert by_id(inspect(ConfiguredHost(routes4, routes6)), 'default_route')['status'] == 'unknown'
@pytest.mark.parametrize('dns,status', [
('nameserver 1.1.1.1\nnameserver 2001:4860:4860::8888\n','pass'),
('nameserver 127.0.0.53\n','unknown'),
('nameserver ::1\n','unknown'), ('','unknown'), (None,'unknown'),
('nameserver invalid\n','unknown'), ('nameserver 0.0.0.0\n','unknown'),
('nameserver 1.1.1.1\nnameserver invalid','unknown'),
('# nameserver 1.1.1.1\nsearch example.com','unknown'),
])
def test_dns_config_results_do_not_claim_resolution(dns,status):
result=by_id(inspect(ConfiguredHost(dns=dns)), 'dns_config')
assert result['status']==status
assert '不主动发送 DNS' in result.get('advice','') or dns is None
assert '1.1.1.1' not in str(redact(result))
@pytest.mark.parametrize('config,session,port,status', [
('port 22\nport 2222\n','',22,'warn'),
('port 22\nport 2222\n','',2222,'warn'),
('port 2222\n','',8388,'unknown'),
('','192.0.2.1 50000 192.0.2.2 2200',2200,'warn'),
('','bad session',22,'unknown'),
('port invalid\n','',22,'unknown'),
('','',22,'unknown'),
])
@pytest.mark.parametrize('lang',['zh','en'])
def test_ssh_protection_warns_known_ports_otherwise_remains_unknown(config,session,port,status,lang):
host=ConfiguredHost(ssh=Command(0,config),session=session)
result=by_id(inspect(host,port=port,lang=lang),'ssh_port_safety')
assert result['status']==status
assert '192.0.2.' not in json.dumps(result)
def test_failed_ssh_configuration_does_not_suppress_valid_active_session():
host=ConfiguredHost(ssh=Command(1,''),session='192.0.2.1 40000 192.0.2.2 2222')
assert by_id(inspect(host,port=2222),'ssh_port_safety')['status']=='warn'
def test_failed_route_command_is_unknown_even_when_other_family_has_route():
host=ConfiguredHost(routes4='[{"dst":"default","dev":"eth0"}]')
host.commands[('ip','-j','-6','route','show','default')]=Command(None,reason='命令不可用')
assert by_id(inspect(host),'default_route')['status']=='unknown'
def test_public_ip_lookup_is_opt_in_and_never_implies_inbound_reachability():
host=ConfiguredHost()
calls=[]
host.detect_public_ip=lambda: calls.append(True) or '8.8.8.8'
inspect(host)
assert not calls
result=by_id(inspect(host,detect_public_ip=True,lang='en'),'public_ip')
assert calls == [True]
assert result['status']=='unknown'
assert 'inbound reachability' in result['detail']
assert '8.8.8.8' not in str(redact(result,'en'))
def test_public_ip_lookup_failure_remains_unknown():
host=ConfiguredHost()
host.detect_public_ip=lambda: None
result=by_id(inspect(host,detect_public_ip=True),'public_ip')
assert result['status']=='unknown'
assert '失败' in result['detail']
def test_public_ip_lookup_disabled_by_default_in_cli(monkeypatch):
from proxydoctor.checks import Host
def unexpected(_):
raise AssertionError('network used without opt-in')
monkeypatch.setattr(Host,'detect_public_ip',unexpected)
inspect(Host())
@pytest.mark.parametrize('payload,expected', [(b'8.8.8.8\n','8.8.8.8'), (b'127.0.0.1',None), (b'bad',None), (b'9'*129,None)])
def test_public_ip_lookup_validates_bounded_response(monkeypatch,payload,expected):
from proxydoctor.checks import Host
class Response:
def __enter__(self): return self
def __exit__(self,*args): pass
def read(self,size):
assert size==129
return payload
class Opener:
def open(self,url,timeout):
assert url=='https://api64.ipify.org' and timeout==5
return Response()
monkeypatch.setattr('proxydoctor.checks.urllib.request.build_opener',lambda *args: Opener())
assert Host().detect_public_ip()==expected
def test_public_ip_options_are_mutually_exclusive():
command=[sys.executable,'-m','proxydoctor','check','--public-ip','8.8.8.8','--detect-public-ip']
assert subprocess.run(command,capture_output=True).returncode==2
def test_show_ip_only_affects_terminal_never_saved_report(tmp_path):
report=tmp_path/'report.json'
command=[sys.executable,'-m','proxydoctor','check','--public-ip','8.8.8.8','--show-ip','--json','--output',str(report)]
result=subprocess.run(command,capture_output=True,encoding='utf-8')
assert result.returncode in (0,1)
assert '8.8.8.8' in result.stdout
assert '8.8.8.8' not in report.read_text(encoding='utf-8')