修正邮件接收时间和发件人展示
This commit is contained in:
@@ -45,6 +45,7 @@ public class AgentBusSourceMessageAdapter {
|
||||
text(source, "external_conversation_id"),
|
||||
text(frame, "id"),
|
||||
text(frame, "session_id"),
|
||||
firstInstant(source, "received_at", payload, "received_at"),
|
||||
parseInstant(text(source, "sent_at")),
|
||||
text(source, "sender"),
|
||||
text(source, "subject"),
|
||||
@@ -110,6 +111,17 @@ public class AgentBusSourceMessageAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从两个候选字段中读取第一个可解析的时间,格式非法时继续尝试后续字段。
|
||||
*/
|
||||
private Instant firstInstant(JsonNode firstNode, String firstField, JsonNode secondNode, String secondField) {
|
||||
Instant firstValue = parseInstant(text(firstNode, firstField));
|
||||
if (firstValue != null) {
|
||||
return firstValue;
|
||||
}
|
||||
return parseInstant(text(secondNode, secondField));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将渠道等稳定代码转为大写,缺失时使用服务端默认值。
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ public record SourceMessageInboxDraft(
|
||||
boolean duplicatePayloadChanged,
|
||||
String safeErrorSummary,
|
||||
LocalDateTime receivedAt,
|
||||
LocalDateTime capturedAt,
|
||||
LocalDateTime sourceSentAt,
|
||||
String senderSummary,
|
||||
String subject,
|
||||
|
||||
@@ -14,6 +14,7 @@ public record CaptureSourceMessageCommand(
|
||||
String externalConversationId,
|
||||
String providerFrameId,
|
||||
String providerSessionId,
|
||||
Instant sourceReceivedAt,
|
||||
Instant sourceSentAt,
|
||||
String senderIdentifier,
|
||||
String subject,
|
||||
@@ -23,4 +24,38 @@ public record CaptureSourceMessageCommand(
|
||||
String schemaVersion,
|
||||
List<CaptureSourceMessageMedia> mediaItems
|
||||
) {
|
||||
public CaptureSourceMessageCommand(
|
||||
String hotelId,
|
||||
String provider,
|
||||
String channel,
|
||||
String externalMessageId,
|
||||
String externalConversationId,
|
||||
String providerFrameId,
|
||||
String providerSessionId,
|
||||
Instant sourceSentAt,
|
||||
String senderIdentifier,
|
||||
String subject,
|
||||
String textBody,
|
||||
String htmlBody,
|
||||
String payloadJson,
|
||||
String schemaVersion,
|
||||
List<CaptureSourceMessageMedia> mediaItems) {
|
||||
this(
|
||||
hotelId,
|
||||
provider,
|
||||
channel,
|
||||
externalMessageId,
|
||||
externalConversationId,
|
||||
providerFrameId,
|
||||
providerSessionId,
|
||||
null,
|
||||
sourceSentAt,
|
||||
senderIdentifier,
|
||||
subject,
|
||||
textBody,
|
||||
htmlBody,
|
||||
payloadJson,
|
||||
schemaVersion,
|
||||
mediaItems);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import java.util.List;
|
||||
* @param id 内部 SourceMessage Inbox ID
|
||||
* @param externalMessageId 外部单封邮件 ID
|
||||
* @param externalConversationId 外部邮件会话 ID
|
||||
* @param senderSummary 发送人安全摘要
|
||||
* @param senderSummary 发件人展示值,当前不打码
|
||||
* @param subject 邮件主题摘要
|
||||
* @param receivedAt 本系统接收时间,UTC
|
||||
* @param receivedAt 邮件来源接收时间,UTC;缺失时回退本系统接收时间
|
||||
* @param sourceSentAt 邮件来源发送时间,UTC
|
||||
* @param textBody 完整纯文本正文
|
||||
* @param htmlBody 完整 HTML 正文,前端展示前必须 sanitize
|
||||
|
||||
@@ -36,11 +36,11 @@ public class SourceMessageInboxEntity {
|
||||
private Boolean duplicatePayloadChanged;
|
||||
/** 安全错误摘要,不包含正文、附件 URL、Token 或个人敏感信息。 */
|
||||
private String safeErrorSummary;
|
||||
/** 本项目接收消息的 UTC 时间。 */
|
||||
/** 邮件来源接收 UTC 时间,缺失时使用本项目接收消息的 UTC 时间。 */
|
||||
private LocalDateTime receivedAt;
|
||||
/** 来源系统提供的邮件发送 UTC 时间,缺失时为空。 */
|
||||
private LocalDateTime sourceSentAt;
|
||||
/** 发送人安全摘要,普通查询可展示。 */
|
||||
/** 发件人展示值,业务人员需要识别真实发件人,当前只清理首尾空白不打码。 */
|
||||
private String senderSummary;
|
||||
/** 脱敏后的邮件主题,普通查询可展示。 */
|
||||
private String subject;
|
||||
|
||||
@@ -244,7 +244,7 @@ public class MybatisSourceMessageInboxRepository implements SourceMessageInboxRe
|
||||
insertPayload(draft, inbox.getId());
|
||||
if (SourceMessageCaptureStatus.RECEIVED.code().equals(draft.captureStatus())) {
|
||||
insertBody(draft, inbox.getId());
|
||||
insertMedia(draft.mediaItems(), inbox.getId(), draft.receivedAt());
|
||||
insertMedia(draft.mediaItems(), inbox.getId(), draft.capturedAt());
|
||||
}
|
||||
return inbox.getId();
|
||||
}
|
||||
@@ -340,8 +340,8 @@ public class MybatisSourceMessageInboxRepository implements SourceMessageInboxRe
|
||||
inbox.setSenderSummary(draft.senderSummary());
|
||||
inbox.setSubject(draft.subject());
|
||||
inbox.setSafeSnippet(draft.safeSnippet());
|
||||
inbox.setCreatedAt(draft.receivedAt());
|
||||
inbox.setUpdatedAt(draft.receivedAt());
|
||||
inbox.setCreatedAt(draft.capturedAt());
|
||||
inbox.setUpdatedAt(draft.capturedAt());
|
||||
return inbox;
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ public class MybatisSourceMessageInboxRepository implements SourceMessageInboxRe
|
||||
payload.setPayloadJson(draft.payloadJson());
|
||||
payload.setPayloadSha256(draft.payloadSha256());
|
||||
payload.setSchemaVersion(draft.schemaVersion());
|
||||
payload.setCreatedAt(draft.receivedAt());
|
||||
payload.setCreatedAt(draft.capturedAt());
|
||||
payloadMapper.insert(payload);
|
||||
}
|
||||
|
||||
@@ -368,7 +368,7 @@ public class MybatisSourceMessageInboxRepository implements SourceMessageInboxRe
|
||||
body.setTextBody(draft.textBody());
|
||||
body.setHtmlBody(draft.htmlBody());
|
||||
body.setBodySha256(sha256(draft.textBody() + "\n" + draft.htmlBody()));
|
||||
body.setCreatedAt(draft.receivedAt());
|
||||
body.setCreatedAt(draft.capturedAt());
|
||||
bodyMapper.insert(body);
|
||||
}
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ public class SourceMessageCaptureServiceImpl implements SourceMessageCaptureServ
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建入库草稿,统一生成安全主题、发送人摘要和正文摘要。
|
||||
* 构建入库草稿,统一生成安全主题、发件人展示值和正文摘要。
|
||||
*/
|
||||
private SourceMessageInboxDraft buildDraft(
|
||||
CaptureSourceMessageCommand command,
|
||||
@@ -224,7 +224,9 @@ public class SourceMessageCaptureServiceImpl implements SourceMessageCaptureServ
|
||||
String payloadSha256,
|
||||
SourceMessageCaptureStatus captureStatus,
|
||||
String safeErrorSummary) {
|
||||
LocalDateTime now = nowUtc();
|
||||
LocalDateTime capturedAt = nowUtc();
|
||||
LocalDateTime sourceReceivedAt = toLocalDateTime(command.sourceReceivedAt());
|
||||
LocalDateTime receivedAt = sourceReceivedAt == null ? capturedAt : sourceReceivedAt;
|
||||
String textBody = command.textBody() == null ? "" : command.textBody();
|
||||
String htmlBody = command.htmlBody() == null ? "" : command.htmlBody();
|
||||
// 邮件主题也可能携带联系方式或带 token 的 URL,普通查询只保存可展示摘要。
|
||||
@@ -244,7 +246,8 @@ public class SourceMessageCaptureServiceImpl implements SourceMessageCaptureServ
|
||||
captureStatus.code(),
|
||||
false,
|
||||
safeErrorSummary,
|
||||
now,
|
||||
receivedAt,
|
||||
capturedAt,
|
||||
toLocalDateTime(command.sourceSentAt()),
|
||||
safetySanitizer.senderSummary(command.senderIdentifier()),
|
||||
safeSubject,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package cn.nianxx.thhotel.platform.message.service.impl;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 来源消息安全摘要工具。列表和普通详情只能使用脱敏后的摘要,不能透出原文或媒体 URL。
|
||||
* 来源消息安全摘要工具。正文和主题仍做安全摘要;发件人展示值只清理首尾空白。
|
||||
*/
|
||||
@Component
|
||||
public class SourceMessageSafetySanitizer {
|
||||
@@ -18,21 +17,10 @@ public class SourceMessageSafetySanitizer {
|
||||
private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+");
|
||||
|
||||
/**
|
||||
* 生成发送人安全摘要,避免普通列表直接暴露完整邮箱或客户标识。
|
||||
* 生成发送人展示值。业务人员需要识别邮件真实发件人,因此这里只清理首尾空白,不再打码。
|
||||
*/
|
||||
String senderSummary(String senderIdentifier) {
|
||||
String value = trimToEmpty(senderIdentifier);
|
||||
if (value.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
int atIndex = value.indexOf('@');
|
||||
if (atIndex > 0 && atIndex < value.length() - 1) {
|
||||
return value.charAt(0) + "***" + value.substring(atIndex).toLowerCase(Locale.ROOT);
|
||||
}
|
||||
if (value.length() <= 2) {
|
||||
return "*";
|
||||
}
|
||||
return value.charAt(0) + "***" + value.charAt(value.length() - 1);
|
||||
return trimToEmpty(senderIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,9 +27,9 @@ public record ReservationMessageConversationMessagesResult(
|
||||
*
|
||||
* @param externalSourceMessageId 外部来源消息 ID,对应 AgentBus source.external_message_id
|
||||
* @param externalConversationId 外部邮件会话 ID
|
||||
* @param senderSummary 发送人安全摘要
|
||||
* @param senderSummary 发件人展示值,当前不打码
|
||||
* @param subject 邮件主题安全摘要
|
||||
* @param receivedAt 本系统接收该邮件的 UTC 时间
|
||||
* @param receivedAt 邮件来源接收时间,UTC;缺失时回退本系统接收时间
|
||||
* @param sourceSentAt 来源系统发送时间
|
||||
* @param textBody 纯文本正文
|
||||
* @param htmlBodySanitized 后端清洗后的 HTML 正文
|
||||
|
||||
@@ -29,7 +29,7 @@ public record ReservationMessageConversationTasksResult(
|
||||
* @param orderId 任务当前挂靠订单 ID
|
||||
* @param externalSourceMessageId 外部来源消息 ID,对应 AgentBus source.external_message_id
|
||||
* @param externalConversationId 外部邮件会话 ID
|
||||
* @param sourceReceivedAt 本系统接收该邮件的 UTC 时间
|
||||
* @param sourceReceivedAt 邮件来源接收时间,UTC;缺失时回退本系统接收时间
|
||||
* @param sourceEventIndex AI transition 来源事件序号
|
||||
* @param catalogCode Skill 目录代码
|
||||
* @param skillId Skill 标识
|
||||
|
||||
@@ -17,8 +17,8 @@ import java.time.OffsetDateTime;
|
||||
* @param readonlyReasonCode 只读原因代码
|
||||
* @param sourceMessageId 来源 SourceMessage Inbox ID
|
||||
* @param sourceSubject 来源消息主题摘要
|
||||
* @param sourceSenderSummary 来源发送人安全摘要
|
||||
* @param sourceReceivedAt 本系统接收来源消息时间,UTC
|
||||
* @param sourceSenderSummary 来源发件人展示值,当前不打码
|
||||
* @param sourceReceivedAt 邮件来源接收时间,UTC;缺失时回退本系统接收时间
|
||||
* @param externalConversationId 外部邮件会话 ID
|
||||
* @param conversationMessageCount 同一外部邮件会话下的消息数量
|
||||
* @param createdAt 任务创建时间
|
||||
|
||||
@@ -11,8 +11,8 @@ import java.util.List;
|
||||
* @param orderId 订单 ID
|
||||
* @param sourceMessageId 来源 SourceMessage Inbox ID
|
||||
* @param sourceSubject 来源消息主题摘要
|
||||
* @param sourceSenderSummary 来源发送人安全摘要
|
||||
* @param sourceReceivedAt 本系统接收来源消息时间,UTC
|
||||
* @param sourceSenderSummary 来源发件人展示值,当前不打码
|
||||
* @param sourceReceivedAt 邮件来源接收时间,UTC;缺失时回退本系统接收时间
|
||||
* @param externalConversationId 外部邮件会话 ID
|
||||
* @param conversationMessageCount 同一外部邮件会话下的消息数量
|
||||
* @param systemTaskType 系统主任务类型
|
||||
|
||||
@@ -21,8 +21,8 @@ import java.time.OffsetDateTime;
|
||||
* @param readonlyReasonCode 只读原因代码
|
||||
* @param sourceMessageId 来源 SourceMessage Inbox ID
|
||||
* @param sourceSubject 来源消息主题摘要
|
||||
* @param sourceSenderSummary 来源发送人安全摘要
|
||||
* @param sourceReceivedAt 本系统接收来源消息时间,UTC
|
||||
* @param sourceSenderSummary 来源发件人展示值,当前不打码
|
||||
* @param sourceReceivedAt 邮件来源接收时间,UTC;缺失时回退本系统接收时间
|
||||
* @param externalConversationId 外部邮件会话 ID
|
||||
* @param conversationMessageCount 同一外部邮件会话下的消息数量
|
||||
* @param createdAt 任务创建时间
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
-- M001 SourceMessage 字段语义修正:received_at 表示邮件实际接收时间,sender_summary 不再打码。
|
||||
-- 说明:MySQL 8.0+ 执行以下注释指令;H2 MySQL Mode 会跳过,避免测试库不支持完整 MySQL COMMENT 语法。
|
||||
|
||||
/*!80000 ALTER TABLE platform_source_message_inbox
|
||||
MODIFY COLUMN received_at DATETIME(6) NOT NULL COMMENT '邮件来源接收时间,优先取 AgentBus payload received_at;缺失时使用本项目接收 UTC 时间' */;
|
||||
|
||||
/*!80000 ALTER TABLE platform_source_message_inbox
|
||||
MODIFY COLUMN sender_summary VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL COMMENT '发件人展示值,业务人员需要识别真实发件人,当前只清理首尾空白不打码' */;
|
||||
|
||||
-- 历史数据修正:已保存原始 payload 的旧记录,尽量回填邮件来源接收时间和完整发件人展示值。
|
||||
-- 说明:只处理合法 JSON 且字段存在的记录;无法解析的旧记录保留原值,避免迁移阻断上线。
|
||||
/*!80000 UPDATE platform_source_message_inbox inbox
|
||||
JOIN (
|
||||
SELECT
|
||||
payload.inbox_id,
|
||||
NULLIF(TRIM(
|
||||
CASE
|
||||
WHEN JSON_VALID(payload.payload_json) THEN JSON_UNQUOTE(COALESCE(
|
||||
JSON_EXTRACT(payload.payload_json, '$.source.sender'),
|
||||
JSON_EXTRACT(payload.payload_json, '$.sender')))
|
||||
ELSE NULL
|
||||
END
|
||||
), '') AS source_sender,
|
||||
NULLIF(TRIM(
|
||||
CASE
|
||||
WHEN JSON_VALID(payload.payload_json) THEN JSON_UNQUOTE(COALESCE(
|
||||
JSON_EXTRACT(payload.payload_json, '$.source.received_at'),
|
||||
JSON_EXTRACT(payload.payload_json, '$.received_at')))
|
||||
ELSE NULL
|
||||
END
|
||||
), '') AS source_received_at
|
||||
FROM platform_source_message_payload payload
|
||||
) payload_source ON payload_source.inbox_id = inbox.id
|
||||
SET
|
||||
inbox.sender_summary = COALESCE(payload_source.source_sender, inbox.sender_summary),
|
||||
inbox.received_at = CASE
|
||||
WHEN payload_source.source_received_at REGEXP '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,6})?Z$'
|
||||
THEN CASE
|
||||
WHEN payload_source.source_received_at REGEXP '\\.[0-9]{1,6}Z$'
|
||||
THEN STR_TO_DATE(REPLACE(REPLACE(payload_source.source_received_at, 'T', ' '), 'Z', ''), '%Y-%m-%d %H:%i:%s.%f')
|
||||
ELSE STR_TO_DATE(REPLACE(REPLACE(payload_source.source_received_at, 'T', ' '), 'Z', ''), '%Y-%m-%d %H:%i:%s')
|
||||
END
|
||||
ELSE inbox.received_at
|
||||
END
|
||||
WHERE payload_source.source_sender IS NOT NULL
|
||||
OR payload_source.source_received_at REGEXP '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,6})?Z$' */;
|
||||
@@ -44,6 +44,7 @@ class AgentBusSourceMessageAdapterTest {
|
||||
"external_url": "https://media.example.test/booking.pdf"
|
||||
}
|
||||
],
|
||||
"received_at": "2026-07-09T05:56:56Z",
|
||||
"source": {
|
||||
"channel": "email",
|
||||
"external_message_id": "outlook-message-001",
|
||||
@@ -65,6 +66,7 @@ class AgentBusSourceMessageAdapterTest {
|
||||
assertThat(command.externalConversationId()).isEqualTo("outlook-conversation-001");
|
||||
assertThat(command.providerFrameId()).isEqualTo("frame-agentbus-001");
|
||||
assertThat(command.providerSessionId()).isEqualTo("session-agentbus-001");
|
||||
assertThat(command.sourceReceivedAt()).isEqualTo(Instant.parse("2026-07-09T05:56:56Z"));
|
||||
assertThat(command.sourceSentAt()).isEqualTo(Instant.parse("2026-07-06T08:00:00Z"));
|
||||
assertThat(command.senderIdentifier()).isEqualTo("guest@example.test");
|
||||
assertThat(command.subject()).isEqualTo("Booking change");
|
||||
@@ -78,4 +80,40 @@ class AgentBusSourceMessageAdapterTest {
|
||||
assertThat(command.mediaItems().get(1).mediaType()).isEqualTo("ATTACHMENT");
|
||||
assertThat(command.mediaItems().get(1).fileName()).isEqualTo("booking.pdf");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPreferValidSourceReceivedAtAndFallbackToPayloadReceivedAt() throws Exception {
|
||||
JsonNode sourceReceivedFrame = objectMapper.readTree("""
|
||||
{
|
||||
"id": "frame-agentbus-002",
|
||||
"payload": {
|
||||
"received_at": "2026-07-09T05:56:56Z",
|
||||
"source": {
|
||||
"channel": "email",
|
||||
"external_message_id": "outlook-message-002",
|
||||
"received_at": "2026-07-09T05:50:00Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
JsonNode invalidSourceReceivedFrame = objectMapper.readTree("""
|
||||
{
|
||||
"id": "frame-agentbus-003",
|
||||
"payload": {
|
||||
"received_at": "2026-07-09T05:56:56Z",
|
||||
"source": {
|
||||
"channel": "email",
|
||||
"external_message_id": "outlook-message-003",
|
||||
"received_at": "bad-time"
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
CaptureSourceMessageCommand sourceCommand = adapter.toCaptureCommand("HOTEL-TEST", sourceReceivedFrame);
|
||||
CaptureSourceMessageCommand fallbackCommand = adapter.toCaptureCommand("HOTEL-TEST", invalidSourceReceivedFrame);
|
||||
|
||||
assertThat(sourceCommand.sourceReceivedAt()).isEqualTo(Instant.parse("2026-07-09T05:50:00Z"));
|
||||
assertThat(fallbackCommand.sourceReceivedAt()).isEqualTo(Instant.parse("2026-07-09T05:56:56Z"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class SourceMessageCaptureServiceImplTest {
|
||||
assertThat(summary.externalMessageId()).isEqualTo("mail-m001-001");
|
||||
assertThat(summary.externalConversationId()).isEqualTo("conversation-m001");
|
||||
assertThat(summary.captureStatus()).isEqualTo("RECEIVED");
|
||||
assertThat(summary.senderSummary()).isEqualTo("g***@example.test");
|
||||
assertThat(summary.senderSummary()).isEqualTo("guest@example.test");
|
||||
assertThat(summary.subject()).isEqualTo("Booking change request");
|
||||
assertThat(summary.safeSnippet()).contains("Please change the arrival date");
|
||||
assertThat(summary.safeSnippet()).doesNotContain("13800138000");
|
||||
@@ -187,6 +187,40 @@ class SourceMessageCaptureServiceImplTest {
|
||||
assertThat(inbox.getSafeErrorSummary()).contains("重复投递 payload");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPersistSourceReceivedAtAndExposeFullSenderIdentifier() {
|
||||
CaptureSourceMessageCommand command = new CaptureSourceMessageCommand(
|
||||
"HOTEL-TEST",
|
||||
"AGENTBUS",
|
||||
"EMAIL",
|
||||
"mail-source-received-time-001",
|
||||
"conversation-source-received-time",
|
||||
"frame-source-received-time-001",
|
||||
"session-source-received-time",
|
||||
Instant.parse("2026-07-09T05:56:56Z"),
|
||||
Instant.parse("2026-07-09T05:55:00Z"),
|
||||
"guest@example.test",
|
||||
"Source received time",
|
||||
"Please check received time.",
|
||||
"<html>Please check received time.</html>",
|
||||
"{\"received_at\":\"2026-07-09T05:56:56Z\",\"source\":{\"external_message_id\":\"mail-source-received-time-001\"}}",
|
||||
"agentbus-outlook-v1",
|
||||
List.of()
|
||||
);
|
||||
|
||||
SourceMessageCaptureResult result = captureService.capture(command);
|
||||
|
||||
SourceMessageSummaryResponse summary = queryService.getSummary(result.inboxId()).orElseThrow();
|
||||
assertThat(summary.receivedAt().toInstant()).isEqualTo(Instant.parse("2026-07-09T05:56:56Z"));
|
||||
assertThat(summary.sourceSentAt().toInstant()).isEqualTo(Instant.parse("2026-07-09T05:55:00Z"));
|
||||
assertThat(summary.senderSummary()).isEqualTo("guest@example.test");
|
||||
|
||||
SourceMessageInboxEntity inbox = inboxMapper.selectById(result.inboxId());
|
||||
assertThat(inbox.getReceivedAt()).isEqualTo(LocalDateTime.parse("2026-07-09T05:56:56"));
|
||||
assertThat(inbox.getCreatedAt()).isNotEqualTo(inbox.getReceivedAt());
|
||||
assertThat(inbox.getUpdatedAt()).isEqualTo(inbox.getCreatedAt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPersistFailedInboxWhenExternalMessageIdIsMissing() {
|
||||
CaptureSourceMessageCommand command = new CaptureSourceMessageCommand(
|
||||
@@ -319,7 +353,7 @@ class SourceMessageCaptureServiceImplTest {
|
||||
false,
|
||||
LocalDateTime.parse("2026-07-06T09:50:00"),
|
||||
LocalDateTime.parse("2026-07-06T09:50:00"),
|
||||
"g***@example.test",
|
||||
"guest@example.test",
|
||||
"Race delivery",
|
||||
"Race body"
|
||||
);
|
||||
@@ -373,7 +407,7 @@ class SourceMessageCaptureServiceImplTest {
|
||||
true,
|
||||
LocalDateTime.parse("2026-07-06T10:00:00"),
|
||||
LocalDateTime.parse("2026-07-06T10:00:00"),
|
||||
"g***@example.test",
|
||||
"guest@example.test",
|
||||
"Duplicate diagnostic fail",
|
||||
"Original body"
|
||||
);
|
||||
|
||||
@@ -91,7 +91,7 @@ class ReservationFrontendQueryControllerTest {
|
||||
.andExpect(jsonPath("$.items[0].readonly_reason_code").value("PROCESSABLE"))
|
||||
.andExpect(jsonPath("$.items[0].source_message_id").value(source.inboxId().toString()))
|
||||
.andExpect(jsonPath("$.items[0].source_subject").value("Frontend Query List Smoke"))
|
||||
.andExpect(jsonPath("$.items[0].source_sender_summary").value("g***@example.test"))
|
||||
.andExpect(jsonPath("$.items[0].source_sender_summary").value("guest@example.test"))
|
||||
.andExpect(jsonPath("$.items[0].source_received_at").value(matchesPattern(UTC_INSTANT_PATTERN)))
|
||||
.andExpect(jsonPath("$.items[0].external_conversation_id")
|
||||
.value("thread-mail-frontend-task-list-001"))
|
||||
@@ -193,7 +193,7 @@ class ReservationFrontendQueryControllerTest {
|
||||
.andExpect(jsonPath("$.tasks[0].task_status").value("COMPLETED"))
|
||||
.andExpect(jsonPath("$.tasks[0].source_message_id").value(source.inboxId().toString()))
|
||||
.andExpect(jsonPath("$.tasks[0].source_subject").value("Frontend Query Detail Smoke"))
|
||||
.andExpect(jsonPath("$.tasks[0].source_sender_summary").value("g***@example.test"))
|
||||
.andExpect(jsonPath("$.tasks[0].source_sender_summary").value("guest@example.test"))
|
||||
.andExpect(jsonPath("$.tasks[0].source_received_at").value(matchesPattern(UTC_INSTANT_PATTERN)))
|
||||
.andExpect(jsonPath("$.tasks[0].external_conversation_id")
|
||||
.value("thread-mail-frontend-order-detail-001"))
|
||||
|
||||
@@ -702,7 +702,7 @@ class SuperAgentTaskResultControllerTest {
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.task_card_type").value("NEW_BOOKING"))
|
||||
.andExpect(jsonPath("$.source_subject").value("M002 SuperAgent intake"))
|
||||
.andExpect(jsonPath("$.source_sender_summary").value("g***@example.test"))
|
||||
.andExpect(jsonPath("$.source_sender_summary").value("guest@example.test"))
|
||||
.andExpect(jsonPath("$.source_received_at").value(matchesPattern(UTC_INSTANT_PATTERN)))
|
||||
.andExpect(jsonPath("$.external_conversation_id").value("thread-mail-detail-matrix-001"))
|
||||
.andExpect(jsonPath("$.conversation_message_count").value(1))
|
||||
|
||||
Reference in New Issue
Block a user