File size: 3,670 Bytes
82eac1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
"""
Unit Tests β€” Webhook Server
"""

import hashlib
import hmac
import base64
import pytest
from fastapi.testclient import TestClient
from unittest.mock import AsyncMock, patch


# ─── Signature Helper ──────────────────────────────────────────────────────────

def make_signature(body: bytes, secret: str) -> str:
    h = hmac.new(secret.encode(), body, hashlib.sha256).digest()
    return base64.b64encode(h).decode()


# ─── Fixture: mock settings ───────────────────────────────────────────────────

@pytest.fixture(autouse=True)
def mock_settings(monkeypatch):
    monkeypatch.setenv("LINE_CHANNEL_SECRET", "test_secret")
    monkeypatch.setenv("LINE_CHANNEL_ACCESS_TOKEN", "test_token")
    monkeypatch.setenv("HF_API_TOKEN", "hf_test")
    monkeypatch.setenv("DATABASE_URL", "postgresql://u:p@localhost/test")
    monkeypatch.setenv("REDIS_URL", "redis://localhost:6379")


# ─── Tests ────────────────────────────────────────────────────────────────────

class TestSignatureVerification:
    def test_valid_signature(self):
        from app.main import verify_line_signature
        body = b'{"test": "data"}'
        sig  = make_signature(body, "test_secret")
        assert verify_line_signature(body, sig) is True

    def test_invalid_signature(self):
        from app.main import verify_line_signature
        body = b'{"test": "data"}'
        assert verify_line_signature(body, "wrong_sig") is False

    def test_empty_signature(self):
        from app.main import verify_line_signature
        assert verify_line_signature(b"body", "") is False


class TestPayloadParsing:
    def test_parse_text_message(self):
        from app.models import LineWebhookPayload
        raw = """{
            "destination": "U123",
            "events": [{
                "type": "message",
                "timestamp": 1700000000000,
                "replyToken": "token123",
                "source": {"type": "user", "userId": "Uabc"},
                "message": {"id": "m1", "type": "text", "text": "ΰΈͺΰΈ§ΰΈ±ΰΈͺΰΈ”ΰΈ΅ΰΈ„ΰΈ£ΰΈ±ΰΈš"}
            }]
        }"""
        payload = LineWebhookPayload.model_validate_json(raw)
        assert len(payload.events) == 1
        assert payload.events[0].message.text == "ΰΈͺΰΈ§ΰΈ±ΰΈͺΰΈ”ΰΈ΅ΰΈ„ΰΈ£ΰΈ±ΰΈš"

    def test_skip_non_text_event(self):
        from app.models import LineWebhookPayload
        raw = """{
            "destination": "U123",
            "events": [{
                "type": "follow",
                "timestamp": 1700000000000,
                "source": {"type": "user", "userId": "Uabc"}
            }]
        }"""
        payload = LineWebhookPayload.model_validate_json(raw)
        assert payload.events[0].type == "follow"
        assert payload.events[0].message is None


class TestSentimentResult:
    def test_label_mapping_positive(self):
        from app.models import SentimentResult
        r = SentimentResult(
            label="positive",
            score=0.91,
            raw_scores={"positive": 0.91, "neutral": 0.07, "negative": 0.02},
        )
        assert r.label == "positive"
        assert r.score == 0.91

    def test_fallback_is_neutral(self):
        from app.sentiment import _fallback_result
        r = _fallback_result()
        assert r.label == "neutral"
        assert r.score == 0.0