xiangyixiangqin/server/tests/XiangYi.Application.Tests/Services/ChatServicePropertyTests.cs
2026-01-02 18:00:49 +08:00

493 lines
16 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using FsCheck;
using FsCheck.Xunit;
using Xunit;
using XiangYi.Application.Services;
using XiangYi.Core.Entities.Biz;
using XiangYi.Core.Enums;
namespace XiangYi.Application.Tests.Services;
/// <summary>
/// ChatService属性测试 - 消息发送持久化
/// </summary>
public class MessagePersistencePropertyTests
{
/// <summary>
/// **Feature: backend-api, Property 14: 消息发送持久化**
/// **Validates: Requirements 6.1, 6.2**
///
/// *For any* 发送的聊天消息, 应被正确保存到数据库且可通过消息列表查询到
/// </summary>
[Property(MaxTest = 100)]
public Property Message_ShouldBePersisted_AndQueryable()
{
// 生成消息ID1-10000
var messageIdArb = Gen.Choose(1, 10000);
// 生成消息类型(文本或语音)
var messageTypeArb = Gen.Elements((int)MessageType.Text, (int)MessageType.Voice);
// 生成非空消息内容
var contentArb = Gen.Elements("Hello", "你好", "Test message", "测试消息", "Hi there");
return Prop.ForAll(
messageIdArb.ToArbitrary(),
messageTypeArb.ToArbitrary(),
contentArb.ToArbitrary(),
(messageId, messageType, content) =>
{
var voiceUrl = "https://example.com/voice.mp3";
// Arrange - 创建消息
var message = new ChatMessage
{
Id = messageId,
SessionId = 1,
SenderId = 1,
ReceiverId = 2,
MessageType = messageType,
Content = messageType == (int)MessageType.Text ? content : null,
VoiceUrl = messageType == (int)MessageType.Voice ? voiceUrl : null,
Status = (int)MessageStatus.Normal,
IsRead = false,
CreateTime = DateTime.Now
};
// 模拟消息列表(持久化后的状态)
var messages = new List<ChatMessage> { message };
// Act - 验证消息是否应该被持久化
var shouldPersist = ChatService.ShouldMessageBePersisted(
messageType,
message.Content,
message.VoiceUrl);
// 验证消息是否可以在列表中查询到
var isInList = ChatService.IsMessageInList(messages, messageId);
// Assert - 如果消息应该被持久化,则应该可以在列表中查询到
return shouldPersist && isInList;
});
}
/// <summary>
/// 文本消息 - 有内容时应该被持久化
/// </summary>
[Property(MaxTest = 100)]
public Property TextMessage_WithContent_ShouldBePersisted()
{
var contentArb = Gen.Elements("Hello", "你好", "Test", "消息内容", "Some text");
return Prop.ForAll(
contentArb.ToArbitrary(),
content =>
{
var shouldPersist = ChatService.ShouldMessageBePersisted(
(int)MessageType.Text,
content,
null);
return shouldPersist;
});
}
/// <summary>
/// 文本消息 - 空内容时不应该被持久化
/// </summary>
[Property(MaxTest = 100)]
public Property TextMessage_EmptyContent_ShouldNotBePersisted()
{
var emptyContentArb = Gen.Elements("", " ", null, "\t", "\n");
return Prop.ForAll(
emptyContentArb.ToArbitrary(),
emptyContent =>
{
var shouldPersist = ChatService.ShouldMessageBePersisted(
(int)MessageType.Text,
emptyContent,
null);
return !shouldPersist;
});
}
/// <summary>
/// 语音消息 - 有URL时应该被持久化
/// </summary>
[Property(MaxTest = 100)]
public Property VoiceMessage_WithUrl_ShouldBePersisted()
{
var voiceUrlArb = Gen.Elements(
"https://example.com/voice1.mp3",
"https://example.com/voice2.mp3",
"https://storage.example.com/audio/123.wav");
return Prop.ForAll(
voiceUrlArb.ToArbitrary(),
voiceUrl =>
{
var shouldPersist = ChatService.ShouldMessageBePersisted(
(int)MessageType.Voice,
null,
voiceUrl);
return shouldPersist;
});
}
/// <summary>
/// 语音消息 - 无URL时不应该被持久化
/// </summary>
[Property(MaxTest = 100)]
public Property VoiceMessage_EmptyUrl_ShouldNotBePersisted()
{
var emptyUrlArb = Gen.Elements("", " ", null, "\t");
return Prop.ForAll(
emptyUrlArb.ToArbitrary(),
emptyUrl =>
{
var shouldPersist = ChatService.ShouldMessageBePersisted(
(int)MessageType.Voice,
null,
emptyUrl);
return !shouldPersist;
});
}
/// <summary>
/// 消息查询 - 正常状态的消息应该可以查询到
/// </summary>
[Property(MaxTest = 100)]
public Property Message_NormalStatus_ShouldBeQueryable()
{
var messageIdArb = Gen.Choose(1, 10000);
return Prop.ForAll(
messageIdArb.ToArbitrary(),
messageId =>
{
var messages = new List<ChatMessage>
{
new ChatMessage
{
Id = messageId,
Status = (int)MessageStatus.Normal,
CreateTime = DateTime.Now
}
};
var isInList = ChatService.IsMessageInList(messages, messageId);
return isInList;
});
}
/// <summary>
/// 消息查询 - 已删除的消息不应该被查询到
/// </summary>
[Property(MaxTest = 100)]
public Property Message_DeletedStatus_ShouldNotBeQueryable()
{
var messageIdArb = Gen.Choose(1, 10000);
return Prop.ForAll(
messageIdArb.ToArbitrary(),
messageId =>
{
var messages = new List<ChatMessage>
{
new ChatMessage
{
Id = messageId,
Status = (int)MessageStatus.Deleted,
CreateTime = DateTime.Now
}
};
var isInList = ChatService.IsMessageInList(messages, messageId);
return !isInList;
});
}
/// <summary>
/// 消息查询 - 已撤回的消息不应该被查询到
/// </summary>
[Property(MaxTest = 100)]
public Property Message_RecalledStatus_ShouldNotBeQueryable()
{
var messageIdArb = Gen.Choose(1, 10000);
return Prop.ForAll(
messageIdArb.ToArbitrary(),
messageId =>
{
var messages = new List<ChatMessage>
{
new ChatMessage
{
Id = messageId,
Status = (int)MessageStatus.Recalled,
CreateTime = DateTime.Now
}
};
var isInList = ChatService.IsMessageInList(messages, messageId);
return !isInList;
});
}
/// <summary>
/// 消息查询 - 不存在的消息ID不应该被查询到
/// </summary>
[Property(MaxTest = 100)]
public Property Message_NonExistentId_ShouldNotBeQueryable()
{
var existingIdArb = Gen.Choose(1, 1000);
var nonExistentIdArb = Gen.Choose(1001, 2000);
return Prop.ForAll(
existingIdArb.ToArbitrary(),
nonExistentIdArb.ToArbitrary(),
(existingId, nonExistentId) =>
{
var messages = new List<ChatMessage>
{
new ChatMessage
{
Id = existingId,
Status = (int)MessageStatus.Normal,
CreateTime = DateTime.Now
}
};
var existingInList = ChatService.IsMessageInList(messages, existingId);
var nonExistentInList = ChatService.IsMessageInList(messages, nonExistentId);
return existingInList && !nonExistentInList;
});
}
}
/// <summary>
/// ChatService属性测试 - 交换请求状态流转
/// </summary>
public class ExchangeStatusTransitionPropertyTests
{
/// <summary>
/// **Feature: backend-api, Property 15: 交换请求状态流转**
/// **Validates: Requirements 6.3, 6.4, 6.5**
///
/// *For any* 交换微信/照片请求, 状态应从"待响应"流转到"已同意"或"已拒绝"
/// </summary>
[Property(MaxTest = 100)]
public Property ExchangeRequest_StatusTransition_ShouldBeValid()
{
// 生成是否同意的布尔值
var isAgreedArb = Gen.Elements(true, false);
return Prop.ForAll(
isAgreedArb.ToArbitrary(),
isAgreed =>
{
// Arrange - 初始状态为待响应(0)
var initialStatus = 0;
// Act - 计算状态流转
var newStatus = ChatService.CalculateExchangeStatusTransition(initialStatus, isAgreed);
// Assert - 验证状态流转是否有效
var isValidTransition = ChatService.IsValidExchangeStatusTransition(initialStatus, newStatus);
var isCorrectStatus = ChatService.IsExchangeResponseStatusCorrect(initialStatus, isAgreed, newStatus);
return isValidTransition && isCorrectStatus;
});
}
/// <summary>
/// 交换请求 - 同意后状态应为1
/// </summary>
[Property(MaxTest = 100)]
public Property ExchangeRequest_Agreed_StatusShouldBeOne()
{
return Prop.ForAll(
Arb.Default.PositiveInt(),
_ =>
{
var initialStatus = 0;
var newStatus = ChatService.CalculateExchangeStatusTransition(initialStatus, isAgreed: true);
return newStatus == 1;
});
}
/// <summary>
/// 交换请求 - 拒绝后状态应为2
/// </summary>
[Property(MaxTest = 100)]
public Property ExchangeRequest_Rejected_StatusShouldBeTwo()
{
return Prop.ForAll(
Arb.Default.PositiveInt(),
_ =>
{
var initialStatus = 0;
var newStatus = ChatService.CalculateExchangeStatusTransition(initialStatus, isAgreed: false);
return newStatus == 2;
});
}
/// <summary>
/// 交换请求 - 已响应的请求不能再次流转
/// </summary>
[Property(MaxTest = 100)]
public Property ExchangeRequest_AlreadyResponded_ShouldNotTransition()
{
// 生成已响应的状态1或2
var respondedStatusArb = Gen.Elements(1, 2);
var isAgreedArb = Gen.Elements(true, false);
return Prop.ForAll(
respondedStatusArb.ToArbitrary(),
isAgreedArb.ToArbitrary(),
(respondedStatus, isAgreed) =>
{
// Act - 尝试再次流转
var newStatus = ChatService.CalculateExchangeStatusTransition(respondedStatus, isAgreed);
// Assert - 应该返回-1表示无法流转
return newStatus == -1;
});
}
/// <summary>
/// 交换请求 - 只有待响应状态可以流转
/// </summary>
[Property(MaxTest = 100)]
public Property ExchangeRequest_OnlyPendingCanTransition()
{
// 生成各种状态
var statusArb = Gen.Choose(-5, 5);
var isAgreedArb = Gen.Elements(true, false);
return Prop.ForAll(
statusArb.ToArbitrary(),
isAgreedArb.ToArbitrary(),
(status, isAgreed) =>
{
var newStatus = ChatService.CalculateExchangeStatusTransition(status, isAgreed);
// 只有状态为0时才能成功流转
if (status == 0)
{
return newStatus == (isAgreed ? 1 : 2);
}
else
{
return newStatus == -1;
}
});
}
/// <summary>
/// 交换请求 - 状态流转验证函数正确性
/// </summary>
[Property(MaxTest = 100)]
public Property ExchangeRequest_ValidationFunction_ShouldBeCorrect()
{
// 生成所有可能的状态组合
var fromStatusArb = Gen.Choose(-2, 5);
var toStatusArb = Gen.Choose(-2, 5);
return Prop.ForAll(
fromStatusArb.ToArbitrary(),
toStatusArb.ToArbitrary(),
(fromStatus, toStatus) =>
{
var isValid = ChatService.IsValidExchangeStatusTransition(fromStatus, toStatus);
// 只有从0到1或从0到2是有效的
var expectedValid = fromStatus == 0 && (toStatus == 1 || toStatus == 2);
return isValid == expectedValid;
});
}
/// <summary>
/// 交换请求 - 响应状态正确性验证
/// </summary>
[Property(MaxTest = 100)]
public Property ExchangeRequest_ResponseStatusCorrectness()
{
var isAgreedArb = Gen.Elements(true, false);
var newStatusArb = Gen.Choose(0, 3);
return Prop.ForAll(
isAgreedArb.ToArbitrary(),
newStatusArb.ToArbitrary(),
(isAgreed, newStatus) =>
{
// 原始状态为待响应
var originalStatus = 0;
var isCorrect = ChatService.IsExchangeResponseStatusCorrect(originalStatus, isAgreed, newStatus);
// 验证正确性
var expectedCorrect = (isAgreed && newStatus == 1) || (!isAgreed && newStatus == 2);
return isCorrect == expectedCorrect;
});
}
/// <summary>
/// 交换请求 - 非待响应状态的响应应该失败
/// </summary>
[Property(MaxTest = 100)]
public Property ExchangeRequest_NonPendingStatus_ResponseShouldFail()
{
// 生成非待响应状态
var nonPendingStatusArb = Gen.Elements(1, 2, 3, -1);
var isAgreedArb = Gen.Elements(true, false);
var newStatusArb = Gen.Choose(0, 3);
return Prop.ForAll(
nonPendingStatusArb.ToArbitrary(),
isAgreedArb.ToArbitrary(),
newStatusArb.ToArbitrary(),
(originalStatus, isAgreed, newStatus) =>
{
var isCorrect = ChatService.IsExchangeResponseStatusCorrect(originalStatus, isAgreed, newStatus);
// 非待响应状态的响应应该总是失败
return !isCorrect;
});
}
/// <summary>
/// 交换请求 - 状态流转的一致性
/// </summary>
[Property(MaxTest = 100)]
public Property ExchangeRequest_StatusTransition_Consistency()
{
var isAgreedArb = Gen.Elements(true, false);
return Prop.ForAll(
isAgreedArb.ToArbitrary(),
isAgreed =>
{
// 从待响应状态开始
var initialStatus = 0;
// 计算新状态
var newStatus = ChatService.CalculateExchangeStatusTransition(initialStatus, isAgreed);
// 验证流转有效性
var isValidTransition = ChatService.IsValidExchangeStatusTransition(initialStatus, newStatus);
// 验证响应正确性
var isCorrectResponse = ChatService.IsExchangeResponseStatusCorrect(initialStatus, isAgreed, newStatus);
// 所有验证应该一致
return isValidTransition && isCorrectResponse;
});
}
}