企微跳转改为账号密码登录,报价卡上的费用按原金额改回;航线按单价数量金额报价时不再记成海运费。 Co-authored-by: Cursor <cursoragent@cursor.com>
172 lines
7.2 KiB
Python
172 lines
7.2 KiB
Python
"""
|
|
私聊附件等材料波次:发卡等待、发图/H5 并入、30 分钟时效。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import unittest
|
|
|
|
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
if ROOT not in sys.path:
|
|
sys.path.insert(0, ROOT)
|
|
|
|
from agent.policy.attachment_wave import (
|
|
WAIT_TTL_SECONDS,
|
|
AttachWaveBook,
|
|
reset_attach_wave_book_for_test,
|
|
)
|
|
|
|
|
|
class AttachWaveTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.book = reset_attach_wave_book_for_test()
|
|
|
|
def test_wait_ttl_is_30_minutes(self) -> None:
|
|
self.assertEqual(WAIT_TTL_SECONDS, 1800)
|
|
|
|
def test_open_wait_is_active_without_files(self) -> None:
|
|
wave = self.book.open_wait("s1", precede_text="海运上海到纽约", token="tok1", now=100.0)
|
|
self.assertTrue(wave.wave_id)
|
|
self.assertEqual(wave.precede_text, "海运上海到纽约")
|
|
self.assertFalse(wave.started)
|
|
self.assertIsNotNone(self.book.active("s1", now=100.0))
|
|
|
|
def test_wait_expires_after_30_minutes_if_not_started(self) -> None:
|
|
self.book.open_wait("s1", precede_text="海运", token="tok1", now=100.0)
|
|
self.assertIsNone(self.book.active("s1", now=100.0 + 1800.1))
|
|
|
|
def test_follow_text_while_waiting(self) -> None:
|
|
self.book.open_wait("s1", precede_text="", token="tok1", now=1.0)
|
|
self.assertTrue(self.book.append_follow_text("s1", "只要整柜"))
|
|
self.assertFalse(self.book.append_follow_text("s1", ""))
|
|
|
|
def test_join_image_starts_recognition(self) -> None:
|
|
self.book.open_wait("s1", precede_text="海运", token="tok1", now=1.0)
|
|
wave, how = self.book.join_image("s1", "mid-a", filename="a.jpg", now=2.0)
|
|
self.assertEqual(how, "joined")
|
|
self.assertTrue(wave.started)
|
|
self.assertEqual(self.book.pending(wave), 1)
|
|
|
|
def test_add_files_keeps_upload_order(self) -> None:
|
|
self.book.open_wait("s1", precede_text="", token="tok1", now=1.0)
|
|
wave = self.book.add_files("s1", ["sea.xlsx", "air.pdf"], now=2.0)
|
|
self.assertTrue(wave.started)
|
|
self.assertEqual([m.filename for m in wave.materials], ["sea.xlsx", "air.pdf"])
|
|
self.assertEqual(self.book.pending(wave), 2)
|
|
|
|
def test_compose_oral_uses_material_order(self) -> None:
|
|
self.book.open_wait("s1", precede_text="开口字", token="tok1", now=1.0)
|
|
self.book.add_files("s1", ["a.xlsx", "b.pdf"], now=2.0)
|
|
self.book.append_follow_text("s1", "只要40尺")
|
|
wave = self.book.active("s1", now=2.0)
|
|
self.assertIsNotNone(wave)
|
|
self.book.complete_material("s1", 1, text="空运 上海到纽约")
|
|
self.book.complete_material("s1", 0, text="海运 宁波到汉堡")
|
|
oral = self.book.compose_oral("s1")
|
|
self.assertEqual(oral, "开口字\n海运 宁波到汉堡\n空运 上海到纽约\n只要40尺")
|
|
|
|
def test_empty_land_quote_sheet_still_makes_multi_with_sea(self) -> None:
|
|
"""陆运报价单被整理成空时,仍要按文件名留陆运段,不能只剩海运。"""
|
|
from agent.policy.multi_segments import classify_multi_text
|
|
|
|
self.book.open_wait("s1", precede_text="", token="tok1", now=1.0)
|
|
self.book.add_files(
|
|
"s1",
|
|
["WO202608240017_陆运报价单(1).xlsx", "手工报价海运.xlsx"],
|
|
now=2.0,
|
|
)
|
|
self.book.complete_material("s1", 0, text="")
|
|
self.book.complete_material(
|
|
"s1",
|
|
1,
|
|
text="运输方式:海运\n起运港:纽约港\n目的港:巴生西港",
|
|
)
|
|
oral = self.book.compose_oral("s1")
|
|
self.assertIn("陆运", oral)
|
|
self.assertIn("海运", oral)
|
|
self.assertIn("纽约港", oral)
|
|
self.assertEqual(classify_multi_text(oral), "multi")
|
|
|
|
def test_one_sea_quote_filename_is_single(self) -> None:
|
|
"""只传一份海运报价单:文件名带海运,表里再写运输方式,不能报同一方式两段。"""
|
|
from agent.policy.multi_segments import classify_multi_text
|
|
|
|
self.book.open_wait("s1", precede_text="", token="tok1", now=1.0)
|
|
self.book.add_files("s1", ["WO202608250012_海运报价单.xlsx"], now=2.0)
|
|
self.book.complete_material(
|
|
"s1",
|
|
0,
|
|
text="运输方式:海运\n起运港:纽约港\n目的港:巴生西港\n海运询价",
|
|
)
|
|
oral = self.book.compose_oral("s1")
|
|
self.assertIn("段1:海运", oral)
|
|
self.assertIn("运输方式:海运", oral)
|
|
self.assertEqual(classify_multi_text(oral), "single")
|
|
|
|
def test_ltl_filename_counts_as_land_segment(self) -> None:
|
|
"""零担询价表文件名没有「陆运」二字,也要和海运表切成两段。"""
|
|
from agent.policy.multi_segments import classify_multi_text
|
|
|
|
self.book.open_wait("s1", precede_text="", token="tok1", now=1.0)
|
|
self.book.add_files(
|
|
"s1",
|
|
["东莞-上海 整车及零担 询价.xlsx", "手工海运报价单.xlsx"],
|
|
now=2.0,
|
|
)
|
|
self.book.complete_material("s1", 0, text="始发站:东莞\n目的站:上海")
|
|
self.book.complete_material("s1", 1, text="起运港:宁波港")
|
|
oral = self.book.compose_oral("s1")
|
|
self.assertIn("陆运", oral)
|
|
self.assertIn("海运", oral)
|
|
self.assertEqual(classify_multi_text(oral), "multi")
|
|
|
|
def test_after_business_done_not_active(self) -> None:
|
|
self.book.open_wait("s1", precede_text="", token="tok1", now=1.0)
|
|
self.book.add_files("s1", ["a.xlsx"], now=2.0)
|
|
self.book.complete_material("s1", 0, text="海运")
|
|
self.book.mark_business_done("s1")
|
|
self.assertIsNone(self.book.active("s1", now=3.0))
|
|
self.assertFalse(self.book.append_follow_text("s1", "补一句"))
|
|
|
|
def test_image_after_business_rejected(self) -> None:
|
|
self.book.open_wait("s1", precede_text="", token="tok1", now=1.0)
|
|
self.book.mark_business_done("s1")
|
|
wave, how = self.book.join_image("s1", "mid-b", filename="b.jpg", now=2.0)
|
|
self.assertEqual(how, "inactive")
|
|
self.assertIsNone(wave)
|
|
|
|
def test_http_and_worker_share_kv(self) -> None:
|
|
kv = _MemoryWaveKV()
|
|
http = reset_attach_wave_book_for_test(kv=kv)
|
|
worker = AttachWaveBook(kv=kv)
|
|
http.open_wait("s1", precede_text="海运", token="tok", now=1.0)
|
|
self.assertIsNotNone(worker.active("s1", now=1.0))
|
|
worker.add_files("s1", ["a.xlsx"], now=2.0)
|
|
worker.complete_material("s1", 0, text="宁波到汉堡")
|
|
worker.mark_business_done("s1")
|
|
self.assertIsNone(http.active("s1", now=3.0))
|
|
|
|
|
|
class _MemoryWaveKV:
|
|
"""单测假 Redis:两份账本共用同一字典。"""
|
|
|
|
def __init__(self) -> None:
|
|
self._d: dict[str, dict] = {}
|
|
|
|
def get_json(self, kind: str, sender_id: str):
|
|
row = self._d.get(f"{kind}:{sender_id}")
|
|
return json.loads(json.dumps(row)) if row is not None else None
|
|
|
|
def set_json(self, kind: str, sender_id: str, data: dict) -> None:
|
|
self._d[f"{kind}:{sender_id}"] = json.loads(json.dumps(data))
|
|
|
|
def delete(self, kind: str, sender_id: str) -> None:
|
|
self._d.pop(f"{kind}:{sender_id}", None)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|