| """ |
| 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) |
|
|