148 lines
4.6 KiB
Python
148 lines
4.6 KiB
Python
"""
|
|
Tests for Profile Flip / Identity Change Detector.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from app.profile_flip_detector import (
|
|
_check_cross_project_laundering,
|
|
_compute_identity_flip_score,
|
|
_detect_branding_flip,
|
|
_detect_wallet_behavior_flip,
|
|
_domain_age_risk,
|
|
_extract_domain,
|
|
detect_profile_flip,
|
|
)
|
|
|
|
|
|
class TestDomainRisk:
|
|
def test_suspicious_tld(self):
|
|
result = _domain_age_risk("project.xyz")
|
|
assert result["risk"] >= 0.3
|
|
assert any("suspicious_tld" in f for f in result["flags"])
|
|
|
|
def test_auto_generated_domain(self):
|
|
result = _domain_age_risk("abcdefghijklmnopqrstuvwxyz.com")
|
|
assert result["risk"] > 0.1
|
|
|
|
def test_clean_domain(self):
|
|
result = _domain_age_risk("rugmunch.io")
|
|
assert result["risk"] == 0.0
|
|
assert result["flags"] == []
|
|
|
|
|
|
class TestBrandingFlip:
|
|
def test_rebrand_keyword(self):
|
|
result = _detect_branding_flip("SafeMoon", "we are rebranding to V2")
|
|
assert result["risk"] > 0.1
|
|
assert any("rebrand_keyword" in f for f in result["flags"])
|
|
|
|
def test_generic_description(self):
|
|
result = _detect_branding_flip("TokenX", "the next 100x moon shot")
|
|
assert result["risk"] >= 0.1
|
|
|
|
def test_consistent_branding(self):
|
|
result = _detect_branding_flip("RugMunch", "RugMunch is a crypto security scanner")
|
|
assert result["risk"] == 0.0
|
|
|
|
|
|
class TestWalletBehavior:
|
|
def test_holder_to_drainer_high_risk(self):
|
|
result = _detect_wallet_behavior_flip(
|
|
{
|
|
"previous_behavior": "holder",
|
|
"current_behavior": "drainer",
|
|
"days_active": 365,
|
|
"tx_count": 100,
|
|
"eth_balance_change": -50,
|
|
}
|
|
)
|
|
assert result["risk"] >= 0.9
|
|
assert any("behavior_transition" in f for f in result["flags"])
|
|
|
|
def test_dormant_wallet_reactivated(self):
|
|
result = _detect_wallet_behavior_flip(
|
|
{
|
|
"previous_behavior": "holder",
|
|
"current_behavior": "trader",
|
|
"days_active": 200,
|
|
"tx_count": 20,
|
|
"eth_balance_change": 0,
|
|
}
|
|
)
|
|
assert any("dormant_wallet_reactivated" in f for f in result["flags"])
|
|
|
|
def test_no_data(self):
|
|
result = _detect_wallet_behavior_flip(None)
|
|
assert result["risk"] == 0.0
|
|
|
|
|
|
class TestIdentityFlipScore:
|
|
def test_critical_flip(self):
|
|
result = _compute_identity_flip_score(
|
|
social_risk=0.9,
|
|
domain_risk=0.8,
|
|
branding_risk=0.7,
|
|
wallet_behavior_risk=0.9,
|
|
cross_project_signals=["previous_scam_project:TestScam"],
|
|
age_days=5,
|
|
)
|
|
assert result["score"] >= 70
|
|
assert result["classification"] == "critical"
|
|
|
|
def test_low_risk(self):
|
|
result = _compute_identity_flip_score(
|
|
social_risk=0.0,
|
|
domain_risk=0.0,
|
|
branding_risk=0.0,
|
|
wallet_behavior_risk=0.0,
|
|
cross_project_signals=[],
|
|
age_days=365,
|
|
)
|
|
assert result["score"] < 25
|
|
assert result["classification"] == "low"
|
|
|
|
def test_young_project_bonus(self):
|
|
young = _compute_identity_flip_score(0.5, 0, 0, 0, [], 5)
|
|
old = _compute_identity_flip_score(0.5, 0, 0, 0, [], 365)
|
|
assert young["score"] > old["score"]
|
|
|
|
|
|
class TestCrossProjectLaundering:
|
|
def test_previous_scam(self):
|
|
signals = _check_cross_project_laundering(
|
|
"0xabc",
|
|
[
|
|
{"name": "SafeProject", "deployer": "0xabc", "status": "active"},
|
|
{"name": "RugProject", "deployer": "0xabc", "status": "rug_pull"},
|
|
],
|
|
)
|
|
assert len(signals) >= 1
|
|
assert any("previous_scam_project" in s for s in signals)
|
|
|
|
def test_no_laundering(self):
|
|
signals = _check_cross_project_laundering(None, [])
|
|
assert signals == []
|
|
|
|
|
|
class TestExtractDomain:
|
|
def test_full_url(self):
|
|
assert _extract_domain("https://rugmunch.io/page") == "rugmunch.io"
|
|
|
|
def test_no_domain(self):
|
|
assert _extract_domain("not-a-url") is None
|
|
|
|
|
|
class TestDetectProfileFlip:
|
|
@pytest.mark.asyncio
|
|
async def test_with_project_name(self):
|
|
result = await detect_profile_flip(
|
|
project_name="TestProject",
|
|
project_url="https://testproject.xyz",
|
|
social_handles={"twitter": "test12345"},
|
|
)
|
|
assert "score" in result
|
|
assert "classification" in result
|
|
assert "signals" in result
|
|
assert "breakdown" in result
|
|
assert isinstance(result["score"], float)
|