legawa / tests /test_surat.py
pebaryan
Expand canonical probes to remaining 10 sectors + fix surat strict toggle
3a01e32
Raw
History Blame Contribute Delete
3.98 kB
from __future__ import annotations
import json
import sys
import unittest
from dataclasses import dataclass
from pathlib import Path
SRC = Path(__file__).resolve().parents[1] / "src"
if str(SRC) not in sys.path:
sys.path.insert(0, str(SRC))
from legawa.agents.surat import reply
@dataclass
class FakeSettings:
strict_citations: bool = True
class FakeLLM:
def __init__(self, response: str):
self.response = response
self.calls: list[tuple[list[dict], dict]] = []
def chat(self, messages, **kwargs):
self.calls.append((messages, kwargs))
return self.response
class FakePool:
def __init__(self, small_response: str, big_response: str, *, strict_citations: bool = True):
self.small = FakeLLM(small_response)
self.big = FakeLLM(big_response)
self.settings = FakeSettings(strict_citations=strict_citations)
class FakePasalClient:
def __init__(self):
self.calls: list[dict] = []
def search(self, **kwargs):
self.calls.append(kwargs)
if kwargs["q"] in {"UU 13/2003", "UU 6/2023"}:
return {
"results": [
{
"title": f"Undang-Undang Nomor {kwargs['q'].split()[1].split('/')[0]} Tahun {kwargs['q'].split('/')[1]}",
"frbr_uri": f"akn/id/act/uu/{kwargs['q'].split('/')[1]}/{kwargs['q'].split()[1].split('/')[0]}",
"status": "berlaku",
}
]
}
return {"results": []}
class SuratReplyTests(unittest.TestCase):
def _triase_json(self) -> str:
return json.dumps(
{
"kategori": "aspirasi_kebijakan",
"urgensi": "tinggi",
"sentimen": "negatif",
"dapil_relevance": True,
"nama_pengirim": "Sukma",
"lokasi": "Bekasi",
"isu_utama": "Permenaker dianggap merugikan pekerja.",
"permintaan_konkret": ["RDP", "Revisi aturan"],
"peraturan_disebut": ["UU 13/2003"],
"perlu_tindak_lanjut_staf": True,
"rangkuman": "Surat menyoroti dampak kebijakan ketenagakerjaan.",
}
)
def test_reply_blocks_unverified_citation(self) -> None:
pool = FakePool(
self._triase_json(),
"Kami merujuk pada UU 13/2003 dan Perpres 76/2021 dalam balasan ini.",
)
pasal = FakePasalClient()
with self.assertRaises(ValueError) as ctx:
reply(pool, pasal, "surat uji", verify_law=True)
self.assertIn("Perpres 76/2021", str(ctx.exception))
self.assertEqual(len(pool.big.calls), 1)
self.assertGreaterEqual(len(pasal.calls), 2)
def test_reply_warns_instead_of_blocking_when_strict_disabled(self) -> None:
# With strict_citations=False, an unverifiable citation should warn,
# not block — consistent with penyusun and analis_ruu behaviour.
# Useful when pasal.id is rate-limited or returning transport errors.
pool = FakePool(
self._triase_json(),
"Kami merujuk pada UU 13/2003 dan Perpres 76/2021 dalam balasan ini.",
strict_citations=False,
)
pasal = FakePasalClient()
result = reply(pool, pasal, "surat uji", verify_law=True)
self.assertIsNotNone(result.balasan)
self.assertIn("Perpres 76/2021", result.balasan or "")
def test_reply_accepts_verified_citation(self) -> None:
pool = FakePool(
self._triase_json(),
"Kami merujuk pada UU 13/2003 dalam balasan ini.",
)
pasal = FakePasalClient()
result = reply(pool, pasal, "surat uji", verify_law=True)
self.assertIsNotNone(result.balasan)
self.assertEqual(len(pool.big.calls), 1)
self.assertGreaterEqual(len(pasal.calls), 2)
if __name__ == "__main__":
unittest.main()