| """ |
| 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, |
| ) |
| |
| assert not any(s.signal_type == SignalType.SUSPICIOUS_DOMAIN for s in result.signals) |
| |
| 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.""" |
| |
| result = await validator.validate( |
| token_address="0x1234567890abcdef1234567890abcdef12345678", |
| chain="ethereum", |
| claims=[{ |
| "auditor_name": "Certik", |
| "report_date": "2024-06-01", |
| }], |
| 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_future_audit_date_flagged(validator: AuditReportValidator) -> None: |
| """Audit dated in the future should be flagged.""" |
| import time |
| future_ts = int(time.time()) + 86400 * 30 |
| 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, |
| ) |
| |
| assert len(result.signals) >= 3 |
| |
| 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", |
| }], |
| ) |
| |
| 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.""" |
| |
| 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"}], |
| ) |
| |
| 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", |
| }], |
| ) |
| |
| domain_signals = [s for s in result.signals if s.signal_type == SignalType.SUSPICIOUS_DOMAIN] |
| assert len(domain_signals) >= 1 |
|
|