feat: init commint

This commit is contained in:
2026-09-18 15:26:19 +08:00
parent 3b61a767a0
commit 9f47dbb5cc
95 changed files with 7712 additions and 1137 deletions

View File

@@ -0,0 +1,204 @@
import pytest
from django.contrib.auth import get_user_model
from apps.channels.models import ChannelConversation, ChannelEndpoint, ChannelMessage
from apps.solutions.models import TicketResolution
from apps.tickets.automation import (
apply_automation_action,
approve_and_send,
close_due_ticket_automation,
handle_customer_feedback,
handle_customer_reply_delivery_failed,
)
from apps.tickets.models import (
CustomerReplyTask,
Ticket,
TicketAutomationEvent,
TicketAutomationRun,
TicketAutomationRunStatus,
)
from apps.workitems.models import WorkItem, WorkItemStatus, WorkItemType
@pytest.mark.django_db(transaction=True)
def test_one_approval_resolves_and_closes_without_duplicate_business_facts():
actor = get_user_model().objects.create_superuser(
username="automation-approver",
password="test-password",
)
ticket = Ticket.objects.create(
ticket_no="TK-AUTO-0001",
title="登录回调后仍停留在登录页",
description="生产环境登录回调成功,但页面没有跳转。",
status="pending_confirm",
)
reply_task = CustomerReplyTask.objects.create(
ticket=ticket,
status="draft",
draft_text="您好,请重新登录并确认页面已恢复。",
final_text="您好,请重新登录并确认页面已恢复。",
revision=1,
)
run = TicketAutomationRun.objects.create(
ticket=ticket,
status=TicketAutomationRunStatus.WAITING_APPROVAL,
current_step="approve_and_send",
reply_task=reply_task,
policy_snapshot={
"policy_version": "test",
"auto_close_hours": 24,
"max_retries": 2,
},
)
approve_and_send(
run,
actor=actor,
expected_reply_revision=1,
final_text=reply_task.final_text,
resolution_text="认证回调状态已恢复,会话可以正常写入。",
manual_delivery_confirmed=True,
)
ticket.refresh_from_db()
run.refresh_from_db()
reply_task.refresh_from_db()
assert ticket.status == "resolved"
assert reply_task.status == "sent"
assert run.status == TicketAutomationRunStatus.WAITING_CUSTOMER
assert run.next_action_at is not None
assert TicketResolution.objects.filter(ticket=ticket, source="system").count() == 1
assert not WorkItem.objects.filter(
ticket=ticket,
task_type=WorkItemType.TICKET_CLOSE,
status=WorkItemStatus.ACTIVE,
).exists()
# 浏览器重复提交相同批准不会产生第二条回复、解决记录或关闭计划。
approve_and_send(
run,
actor=actor,
expected_reply_revision=1,
final_text=reply_task.final_text,
resolution_text="认证回调状态已恢复,会话可以正常写入。",
manual_delivery_confirmed=True,
)
assert CustomerReplyTask.objects.filter(ticket=ticket).count() == 1
assert TicketResolution.objects.filter(ticket=ticket).count() == 1
assert TicketAutomationEvent.objects.filter(
run=run,
event_type="waiting_customer",
).count() == 1
assert close_due_ticket_automation(run.id, force=True) is True
assert close_due_ticket_automation(run.id, force=True) is False
ticket.refresh_from_db()
run.refresh_from_db()
assert ticket.status == "closed"
assert run.status == TicketAutomationRunStatus.COMPLETED
assert TicketAutomationEvent.objects.filter(
run=run,
event_type="completed",
).count() == 1
@pytest.mark.django_db(transaction=True)
def test_negative_feedback_after_close_creates_one_linked_follow_up_ticket():
source = Ticket.objects.create(
ticket_no="TK-AUTO-0002",
title="登录问题已关闭",
description="登录回调问题已经恢复。",
status="closed",
priority="high",
environment="生产环境",
related_system="统一身份平台",
)
run = TicketAutomationRun.objects.create(
ticket=source,
status=TicketAutomationRunStatus.COMPLETED,
current_step="closed",
policy_snapshot={"policy_version": "test"},
)
assert handle_customer_feedback(source.id, "还是不行,登录后继续报错") == (
"follow_up_created"
)
assert handle_customer_feedback(source.id, "还是不行,登录后继续报错") == (
"follow_up_created"
)
follow_up = Ticket.objects.get(follow_up_of=source)
assert follow_up.status == "pending"
assert follow_up.priority == "high"
assert follow_up.environment == "生产环境"
assert source.follow_up_tickets.count() == 1
assert source.events.filter(event_type="follow_up_created").count() == 1
assert run.events.filter(event_type="follow_up_created").count() == 1
@pytest.mark.django_db(transaction=True)
def test_failed_channel_delivery_retries_the_same_outbound_message(monkeypatch):
actor = get_user_model().objects.create_superuser(
username="automation-retry",
password="test-password",
)
ticket = Ticket.objects.create(
ticket_no="TK-AUTO-0003",
title="渠道重试",
description="验证渠道失败后的幂等重试。",
status="pending_confirm",
)
reply_task = CustomerReplyTask.objects.create(
ticket=ticket,
status="approved",
draft_text="请确认恢复结果。",
final_text="请确认恢复结果。",
revision=1,
)
run = TicketAutomationRun.objects.create(
ticket=ticket,
status=TicketAutomationRunStatus.WAITING_DELIVERY,
current_step="wait_delivery",
reply_task=reply_task,
approved_by=actor,
policy_snapshot={"policy_version": "test"},
)
endpoint = ChannelEndpoint.objects.create(name="自动化重试渠道")
conversation = ChannelConversation.objects.create(
endpoint=endpoint,
external_conversation_id="retry-conversation",
ticket=ticket,
)
message = ChannelMessage.objects.create(
endpoint=endpoint,
conversation=conversation,
ticket=ticket,
customer_reply_task=reply_task,
reply_revision=1,
direction="outbound",
idempotency_key="automation-retry-message",
text=reply_task.final_text,
status="failed",
error_message="上游超时",
created_by=actor,
)
queued = []
monkeypatch.setattr(
"apps.channels.tasks.deliver_channel_message_task.delay",
lambda message_id: queued.append(message_id),
)
handle_customer_reply_delivery_failed(reply_task.id, "上游超时")
run.refresh_from_db()
assert run.current_step == "delivery_failed"
assert run.last_error_code == "channel_delivery_failed"
apply_automation_action(run, "retry", actor=actor)
run.refresh_from_db()
message.refresh_from_db()
assert message.status == "queued"
assert message.idempotency_key == "automation-retry-message"
assert run.current_step == "wait_delivery"
assert run.last_error_message == ""
assert queued == [message.id]