Files
shanxia2.0/backend/tests/test_wechat_approval_notices.py
T

194 lines
7.6 KiB
Python

from datetime import datetime, timedelta
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
@pytest.fixture
def notices(monkeypatch):
from app.services import wechat_approval_notices as svc
from app.services.wechat_notice_types import NoticeSnapshot, PendingApprover
from app.models.wechat_notice import WechatApprovalCycle, WechatNotice, WechatBinding
from app.models.models import SysUser
engine = create_engine('sqlite://')
for model in (SysUser, WechatApprovalCycle, WechatNotice, WechatBinding):
model.__table__.create(engine)
db = sessionmaker(bind=engine, autoflush=False)()
for uid in ('applicant', 'a', 'b'):
db.add(SysUser(id=uid, tenant_no='t1', user_no=uid, nick_name=uid))
db.commit()
monkeypatch.setattr(svc.settings, 'WECHAT_OA_ENABLED', True)
snapshot = NoticeSnapshot('报销审批', 'BX001', 'applicant', [PendingApprover('n1', 'a')])
monkeypatch.setattr(svc, '_snapshot', lambda *args: snapshot)
yield svc, db, snapshot, WechatNotice, WechatBinding
db.close()
def test_disabled_does_not_access_new_tables(monkeypatch):
from app.services import wechat_approval_notices as svc
monkeypatch.setattr(svc.settings, 'WECHAT_OA_ENABLED', False)
svc.sync_approval_notifications(None, 'bx_reimburse', 'x', 't1', new_cycle=True)
def test_template_mapping_and_transactional_rollback(notices):
svc, db, snapshot, Notice, _ = notices
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1', new_cycle=True)
db.flush()
row = db.query(Notice).one()
import json
assert json.loads(row.payload)['thing2']['value'] == '报销审批'
assert json.loads(row.payload)['character_string6']['value'] == 'BX001'
assert row.recipient_id == 'a'
db.rollback()
assert db.query(Notice).count() == 0
def test_dedup_next_step_result_and_resubmit(notices):
svc, db, snapshot, Notice, _ = notices
from app.services.wechat_notice_types import PendingApprover
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1', new_cycle=True)
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1')
db.flush()
assert db.query(Notice).count() == 1
snapshot.pending = [PendingApprover('n2', 'b')]
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1')
db.flush()
assert db.query(Notice).filter_by(status='pending').one().recipient_id == 'b'
snapshot.pending = []
snapshot.result = 'rejected'
snapshot.approver_id = 'b'
snapshot.occurred_at = datetime(2026, 10, 27, 14, 0)
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1')
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1')
db.flush()
import json
result = db.query(Notice).filter_by(kind='result').one()
assert result.recipient_id == 'applicant'
assert json.loads(result.payload)['const5']['value'] == '打回'
assert json.loads(result.payload)['thing6']['value'] == 'b'
snapshot.result = None
snapshot.pending = [PendingApprover('n1', 'a')]
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1', new_cycle=True)
db.flush()
assert db.query(Notice).filter_by(status='pending').count() == 1
def test_no_history_backfill_and_tenant_recipient_guard(notices):
svc, db, snapshot, Notice, _ = notices
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1')
db.flush()
assert db.query(Notice).count() == 0
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 'other', new_cycle=True)
db.flush()
assert db.query(Notice).count() == 0
class FakeClient:
def __init__(self, failure=None):
self.calls = []
self.failure = failure
def send(self, **kwargs):
self.calls.append(kwargs)
if self.failure:
raise self.failure
return 'wx-msg-id'
def bind(db, Binding):
from app.core.config import settings
db.add(Binding(id='binding', tenant_no='t1', user_id='a', appid=settings.WECHAT_OA_APPID,
openid='oa-openid', subscribed=1))
db.commit()
def test_worker_rechecks_withdrawal_before_send(notices):
svc, db, snapshot, Notice, Binding = notices
bind(db, Binding)
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1', new_cycle=True)
db.commit()
snapshot.pending = []
client = FakeClient()
svc.deliver_pending(db, client=client)
assert client.calls == []
assert db.query(Notice).one().status == 'cancelled'
def test_worker_sends_once_with_oa_openid(notices):
svc, db, snapshot, Notice, Binding = notices
bind(db, Binding)
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1', new_cycle=True)
db.commit()
client = FakeClient()
svc.deliver_pending(db, client=client)
svc.deliver_pending(db, client=client)
assert len(client.calls) == 1
assert client.calls[0]['openid'] == 'oa-openid'
assert db.query(Notice).one().status == 'sent'
def test_ambiguous_send_is_not_automatically_repeated(notices):
svc, db, snapshot, Notice, Binding = notices
from app.services.wechat_oa_client import WechatDeliveryUnknown
bind(db, Binding)
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1', new_cycle=True)
db.commit()
client = FakeClient(WechatDeliveryUnknown('发送结果待核对'))
svc.deliver_pending(db, client=client)
svc.deliver_pending(db, client=client)
assert len(client.calls) == 1
assert db.query(Notice).one().status == 'unknown'
def test_explicit_transient_failure_is_bounded_retry(notices):
svc, db, snapshot, Notice, Binding = notices
from app.services.wechat_oa_client import WechatAPIError
bind(db, Binding)
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1', new_cycle=True)
db.commit()
client = FakeClient(WechatAPIError(-1))
for _ in range(6):
svc.deliver_pending(db, client=client)
row = db.query(Notice).one()
row.next_attempt_at = datetime.now() - timedelta(seconds=1)
db.commit()
assert len(client.calls) == 5
assert db.query(Notice).one().status == 'failed'
@pytest.mark.parametrize('biz_type,page', [
('bx_advance', ''), ('bx_reimburse', ''), ('pl_recharge', ''),
('pl_freight', ''),
('crm_quote', 'pages/common/mp-webview?path=quote-detail'),
('crm_contract', 'pages/common/mp-webview?path=contract-detail'),
])
def test_worker_preserves_mini_page_without_web_fallback(notices, monkeypatch, biz_type, page):
svc, db, snapshot, Notice, Binding = notices
monkeypatch.setattr(svc.settings, 'WECHAT_OA_PUBLIC_BASE_URL', 'https://portal.example.test')
snapshot.page = page
bind(db, Binding)
svc.sync_approval_notifications(db, biz_type, 'x', 't1', new_cycle=True)
db.commit()
client = FakeClient()
assert svc.deliver_pending(db, client=client) == 1
assert client.calls[0]['page'] == page
assert not client.calls[0].get('url')
def test_claimed_assignment_invalid_after_transfer_back(notices):
svc, db, snapshot, Notice, _ = notices
from app.services.wechat_notice_types import PendingApprover
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1', new_cycle=True)
db.flush()
old = db.query(Notice).one()
old.status = 'sending'
db.commit()
snapshot.pending = [PendingApprover('n1', 'b')]
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1')
snapshot.pending = [PendingApprover('n1', 'a')]
svc.sync_approval_notifications(db, 'bx_reimburse', 'x', 't1')
db.commit()
assert not svc._valid_now(db, old)
assert svc._valid_now(db, db.query(Notice).filter_by(status='pending', recipient_id='a').one())