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;
///
/// ChatService属性测试 - 消息发送持久化
///
public class MessagePersistencePropertyTests
{
///
/// **Feature: backend-api, Property 14: 消息发送持久化**
/// **Validates: Requirements 6.1, 6.2**
///
/// *For any* 发送的聊天消息, 应被正确保存到数据库且可通过消息列表查询到
///
[Property(MaxTest = 100)]
public Property Message_ShouldBePersisted_AndQueryable()
{
// 生成消息ID(1-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 { message };
// Act - 验证消息是否应该被持久化
var shouldPersist = ChatService.ShouldMessageBePersisted(
messageType,
message.Content,
message.VoiceUrl);
// 验证消息是否可以在列表中查询到
var isInList = ChatService.IsMessageInList(messages, messageId);
// Assert - 如果消息应该被持久化,则应该可以在列表中查询到
return shouldPersist && isInList;
});
}
///
/// 文本消息 - 有内容时应该被持久化
///
[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;
});
}
///
/// 文本消息 - 空内容时不应该被持久化
///
[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;
});
}
///
/// 语音消息 - 有URL时应该被持久化
///
[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;
});
}
///
/// 语音消息 - 无URL时不应该被持久化
///
[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;
});
}
///
/// 消息查询 - 正常状态的消息应该可以查询到
///
[Property(MaxTest = 100)]
public Property Message_NormalStatus_ShouldBeQueryable()
{
var messageIdArb = Gen.Choose(1, 10000);
return Prop.ForAll(
messageIdArb.ToArbitrary(),
messageId =>
{
var messages = new List
{
new ChatMessage
{
Id = messageId,
Status = (int)MessageStatus.Normal,
CreateTime = DateTime.Now
}
};
var isInList = ChatService.IsMessageInList(messages, messageId);
return isInList;
});
}
///
/// 消息查询 - 已删除的消息不应该被查询到
///
[Property(MaxTest = 100)]
public Property Message_DeletedStatus_ShouldNotBeQueryable()
{
var messageIdArb = Gen.Choose(1, 10000);
return Prop.ForAll(
messageIdArb.ToArbitrary(),
messageId =>
{
var messages = new List
{
new ChatMessage
{
Id = messageId,
Status = (int)MessageStatus.Deleted,
CreateTime = DateTime.Now
}
};
var isInList = ChatService.IsMessageInList(messages, messageId);
return !isInList;
});
}
///
/// 消息查询 - 已撤回的消息不应该被查询到
///
[Property(MaxTest = 100)]
public Property Message_RecalledStatus_ShouldNotBeQueryable()
{
var messageIdArb = Gen.Choose(1, 10000);
return Prop.ForAll(
messageIdArb.ToArbitrary(),
messageId =>
{
var messages = new List
{
new ChatMessage
{
Id = messageId,
Status = (int)MessageStatus.Recalled,
CreateTime = DateTime.Now
}
};
var isInList = ChatService.IsMessageInList(messages, messageId);
return !isInList;
});
}
///
/// 消息查询 - 不存在的消息ID不应该被查询到
///
[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
{
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;
});
}
}
///
/// ChatService属性测试 - 交换请求状态流转
///
public class ExchangeStatusTransitionPropertyTests
{
///
/// **Feature: backend-api, Property 15: 交换请求状态流转**
/// **Validates: Requirements 6.3, 6.4, 6.5**
///
/// *For any* 交换微信/照片请求, 状态应从"待响应"流转到"已同意"或"已拒绝"
///
[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;
});
}
///
/// 交换请求 - 同意后状态应为1
///
[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;
});
}
///
/// 交换请求 - 拒绝后状态应为2
///
[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;
});
}
///
/// 交换请求 - 已响应的请求不能再次流转
///
[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;
});
}
///
/// 交换请求 - 只有待响应状态可以流转
///
[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;
}
});
}
///
/// 交换请求 - 状态流转验证函数正确性
///
[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;
});
}
///
/// 交换请求 - 响应状态正确性验证
///
[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;
});
}
///
/// 交换请求 - 非待响应状态的响应应该失败
///
[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;
});
}
///
/// 交换请求 - 状态流转的一致性
///
[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;
});
}
}