""" Tests for Audit Report Validator (audit_report_validator.py) """ import pytest from app.audit_report_validator import ( AuditClaim, AuditReportValidator, AuditRisk, SignalType, ) @pytest.fixture def validator() -> AuditReportValidator: return AuditReportValidator() @pytest.mark.asyncio async def test_no_claims_returns_safe(validator: AuditReportValidator) -> None: """No audit claims = safe (no red flags).""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=None, deploy_timestamp=1700000000, ) assert result.risk_level == AuditRisk.SAFE assert result.risk_score == 0.0 assert len(result.signals) == 0 @pytest.mark.asyncio async def test_empty_claims_returns_safe(validator: AuditReportValidator) -> None: """Empty claims list = safe.""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[], ) assert result.risk_level == AuditRisk.SAFE @pytest.mark.asyncio async def test_legitimate_certik_claim_passes(validator: AuditReportValidator) -> None: """A legitimate-looking Certik claim should produce no high-risk signals.""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{ "auditor_name": "Certik", "report_url": "https://www.certik.com/projects/example-project", "report_id": "Certik-Example-2024-001", "report_text": "We have thoroughly reviewed the smart contract for ExampleToken. " "Our analysis identified 3 low-risk findings related to reentrancy guards, " "access control patterns, and integer overflow protection. All findings " "have been addressed in the final deployment.", }], deploy_timestamp=1700000000, ) # Should not flag Certik's own domain assert not any(s.signal_type == SignalType.SUSPICIOUS_DOMAIN for s in result.signals) # Should not flag "certik" as fake auditor name assert not any(s.signal_type == SignalType.FAKE_AUDITOR_NAME for s in result.signals) @pytest.mark.asyncio async def test_fake_auditor_name_detected(validator: AuditReportValidator) -> None: """Misspelled auditor names are flagged.""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{ "auditor_name": "CertiK Pro Audit", "report_url": "https://certik-pro-audit.xyz/report/123", }], ) fake_signals = [s for s in result.signals if s.signal_type == SignalType.FAKE_AUDITOR_NAME] assert len(fake_signals) >= 1 assert result.risk_level in (AuditRisk.HIGH, AuditRisk.CRITICAL) @pytest.mark.asyncio async def test_suspicious_tld_flagged(validator: AuditReportValidator) -> None: """Audit hosted on .xyz domain should be flagged.""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{ "auditor_name": "SolidProof", "report_url": "https://audit-solidproof.xyz/report.pdf", }], ) domain_signals = [s for s in result.signals if s.signal_type == SignalType.SUSPICIOUS_DOMAIN] assert len(domain_signals) >= 1 assert any(".xyz" in s.detail for s in domain_signals) @pytest.mark.asyncio async def test_timeline_audit_before_deploy(validator: AuditReportValidator) -> None: """Audit dated before deployment should be flagged.""" # Deploy in 2025, audit in 2024 result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{ "auditor_name": "Certik", "report_date": "2024-06-01", }], deploy_timestamp=1735689600, # Jan 1 2025 ) timeline_signals = [s for s in result.signals if s.signal_type == SignalType.TIMELINE_ANOMALY] assert len(timeline_signals) >= 1 @pytest.mark.asyncio async def test_future_audit_date_flagged(validator: AuditReportValidator) -> None: """Audit dated in the future should be flagged.""" import time future_ts = int(time.time()) + 86400 * 30 # 30 days in future future_date_str = __import__("datetime").datetime.fromtimestamp( future_ts, tz=__import__("datetime").timezone.utc ).strftime("%Y-%m-%d") result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{ "auditor_name": "Hacken", "report_date": future_date_str, }], ) future_signals = [s for s in result.signals if s.signal_type == SignalType.TIMELINE_ANOMALY] assert len(future_signals) >= 1 @pytest.mark.asyncio async def test_template_language_detection(validator: AuditReportValidator) -> None: """Report text with many generic phrases should be flagged.""" text = ( "We have thoroughly reviewed the smart contract and no critical vulnerabilities " "were found. The contract appears to be secure. Our team of experienced auditors " "has completed the security audit. This confirms the safety of the contract. " "No centralization risks found. Liquidity is locked permanently. " "Ownership has been renounced. The code follows best practices. " "All findings have been resolved." ) result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{ "auditor_name": "Some Audit Firm", "report_text": text, }], ) template_signals = [ s for s in result.signals if s.signal_type in (SignalType.TEMPLATE_REUSE, SignalType.GENERIC_LANGUAGE) ] assert len(template_signals) >= 1 @pytest.mark.asyncio async def test_google_drive_hosting_flagged(validator: AuditReportValidator) -> None: """Audit hosted on Google Drive should be flagged.""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{ "auditor_name": "SlowMist", "report_url": "https://drive.google.com/file/d/abc123/view", }], ) domain_signals = [s for s in result.signals if s.signal_type == SignalType.SUSPICIOUS_DOMAIN] assert len(domain_signals) >= 1 @pytest.mark.asyncio async def test_badge_data_uri_flagged(validator: AuditReportValidator) -> None: """Badge using data URI should be flagged.""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{ "auditor_name": "Certik", "verified_badge_url": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjx0ZXh0PkNVTVNUT00gQVVESVQ8L3RleHQ+PC9zdmc+", }], ) forged_signals = [s for s in result.signals if s.signal_type == SignalType.FORGED_REPORT] assert len(forged_signals) >= 1 @pytest.mark.asyncio async def test_multiple_claims_aggregate_score(validator: AuditReportValidator) -> None: """Multiple suspicious claims should increase risk score.""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="bsc", claims=[ { "auditor_name": "Certiik Pro", "report_url": "https://certiik-pro.xyz/audit.pdf", "report_date": "2024-01-01", }, { "auditor_name": "Hacken Audit Service", "report_url": "https://hacken-audit-service.top/report/123", "report_date": "2025-12-01", }, ], deploy_timestamp=1735689600, # Jan 1 2025 ) # Multiple signals across claims assert len(result.signals) >= 3 # Score should be elevated assert result.risk_score >= 0.3 @pytest.mark.asyncio async def test_domain_impersonation_detected(validator: AuditReportValidator) -> None: """URL containing auditor name but not the official domain should be flagged.""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{ "auditor_name": "Certik", "report_url": "https://certik-verify.xyz/audit/example", }], ) # Should detect this as domain impersonation domain_signals = [s for s in result.signals if s.signal_type == SignalType.SUSPICIOUS_DOMAIN] assert len(domain_signals) >= 1 @pytest.mark.asyncio async def test_short_report_text_flagged(validator: AuditReportValidator) -> None: """Very short report text should be flagged as suspicious.""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{ "auditor_name": "FakeAudit", "report_text": "This contract is safe. No issues found. Trust us.", }], ) generic_signals = [s for s in result.signals if s.signal_type == SignalType.GENERIC_LANGUAGE] assert len(generic_signals) >= 1 @pytest.mark.asyncio async def test_generic_auditor_name_flagged(validator: AuditReportValidator) -> None: """Generic-sounding names should be flagged.""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{ "auditor_name": "Secure Audit Labs Consulting", "report_url": "https://secure-audit-labs.com/report", }], ) fake_signals = [s for s in result.signals if s.signal_type == SignalType.FAKE_AUDITOR_NAME] assert len(fake_signals) >= 1 @pytest.mark.asyncio async def test_date_parsing_various_formats(validator: AuditReportValidator) -> None: """Various date formats should be parsed correctly.""" # ISO format result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{"auditor_name": "Certik", "report_date": "2024-06-15T10:30:00Z"}], deploy_timestamp=1735689600, ) timeline_signals = [s for s in result.signals if s.signal_type == SignalType.TIMELINE_ANOMALY] assert len(timeline_signals) >= 1 @pytest.mark.asyncio async def test_invalid_date_format_handled(validator: AuditReportValidator) -> None: """Invalid date strings should not crash.""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{"auditor_name": "Hacken", "report_date": "not-a-date"}], ) # Should still work, just produce a metadata anomaly instead of crashing assert result.error is None @pytest.mark.asyncio async def test_combined_fake_badge_and_url(validator: AuditReportValidator) -> None: """Multiple red flags on same claim compound signals.""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{ "auditor_name": "CeRTiK", "report_url": "https://certik-verified-badge.top/check", "verified_badge_url": "data:image/png;base64,FAKEBADGE", "report_text": "This contract is safe and secure. No issues found.", }], ) assert len(result.signals) >= 3 assert result.risk_level in (AuditRisk.HIGH, AuditRisk.CRITICAL) assert result.risk_score >= 0.5 @pytest.mark.asyncio async def test_suspicious_subdomain(validator: AuditReportValidator) -> None: """URLs with 'verify' subdomains should raise low-severity flags.""" result = await validator.validate( token_address="0x1234567890abcdef1234567890abcdef12345678", chain="ethereum", claims=[{ "auditor_name": "SolidProof", "report_url": "https://verify.solidproof.io/audit", }], ) # This is a real pattern scammers use domain_signals = [s for s in result.signals if s.signal_type == SignalType.SUSPICIOUS_DOMAIN] assert len(domain_signals) >= 1