mi-assessment/server/MiAssessment/tests/MiAssessment.Tests/Services/SystemServicePropertyTests.cs
2026-02-09 14:45:06 +08:00

546 lines
21 KiB
C#
Raw 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 Microsoft.Extensions.Logging;
using MiAssessment.Core.Interfaces;
using MiAssessment.Core.Services;
using MiAssessment.Model.Models.System;
using Moq;
using Xunit;
namespace MiAssessment.Tests.Services;
/// <summary>
/// SystemService 属性测试
/// 验证系统配置服务的响应格式一致性
/// </summary>
public class SystemServicePropertyTests
{
private readonly Mock<ILogger<SystemService>> _mockLogger = new();
#region Property 8: - GetAgreementAsync
/// <summary>
/// Property 8: GetAgreementAsync 返回包含 Content 属性的 AgreementDto
/// *For any* GetAgreementAsync call, the returned AgreementDto SHALL have a Content property.
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.1, 16.1, 16.2, 16.3**
/// </summary>
[Property(MaxTest = 100)]
public bool GetAgreementAsync_ReturnsAgreementDtoWithContentProperty(NonEmptyString content)
{
// Arrange
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("user_agreement"))
.ReturnsAsync(content.Get);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var result = service.GetAgreementAsync().GetAwaiter().GetResult();
// Assert: 验证返回的 AgreementDto 包含 Content 属性且值正确
return result != null && result.Content == content.Get;
}
/// <summary>
/// Property 8: GetAgreementAsync 返回非空 DTO即使配置值为空
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.1, 16.1, 16.2, 16.3**
/// </summary>
[Fact]
public void GetAgreementAsync_ReturnsNonNullDto_WhenConfigValueIsNull()
{
// Arrange
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("user_agreement"))
.ReturnsAsync((string?)null);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var result = service.GetAgreementAsync().GetAwaiter().GetResult();
// Assert: 即使配置值为空,也应返回非空 DTO
Assert.NotNull(result);
Assert.NotNull(result.Content);
Assert.Equal(string.Empty, result.Content);
}
/// <summary>
/// Property 8: GetAgreementAsync 返回非空 DTO即使配置值为空字符串
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.1, 16.1, 16.2, 16.3**
/// </summary>
[Fact]
public void GetAgreementAsync_ReturnsNonNullDto_WhenConfigValueIsEmpty()
{
// Arrange
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("user_agreement"))
.ReturnsAsync(string.Empty);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var result = service.GetAgreementAsync().GetAwaiter().GetResult();
// Assert
Assert.NotNull(result);
Assert.NotNull(result.Content);
Assert.Equal(string.Empty, result.Content);
}
/// <summary>
/// Property 8: GetAgreementAsync 响应格式一致性 - Content 属性始终存在
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.1, 16.1, 16.2, 16.3**
/// </summary>
[Property(MaxTest = 100)]
public bool GetAgreementAsync_ContentPropertyAlwaysExists(PositiveInt seed)
{
// Arrange: 使用不同的配置值
var configValues = new string?[] { null, "", "协议内容", "用户协议\n第一条...", $"Agreement_{seed.Get}" };
var configValue = configValues[seed.Get % configValues.Length];
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("user_agreement"))
.ReturnsAsync(configValue);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var result = service.GetAgreementAsync().GetAwaiter().GetResult();
// Assert: Content 属性始终存在且不为 null
return result != null && result.Content != null;
}
#endregion
#region Property 8: - GetPrivacyAsync
/// <summary>
/// Property 8: GetPrivacyAsync 返回包含 Content 属性的 PrivacyDto
/// *For any* GetPrivacyAsync call, the returned PrivacyDto SHALL have a Content property.
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.2, 16.1, 16.2, 16.3**
/// </summary>
[Property(MaxTest = 100)]
public bool GetPrivacyAsync_ReturnsPrivacyDtoWithContentProperty(NonEmptyString content)
{
// Arrange
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("privacy_policy"))
.ReturnsAsync(content.Get);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var result = service.GetPrivacyAsync().GetAwaiter().GetResult();
// Assert: 验证返回的 PrivacyDto 包含 Content 属性且值正确
return result != null && result.Content == content.Get;
}
/// <summary>
/// Property 8: GetPrivacyAsync 返回非空 DTO即使配置值为空
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.2, 16.1, 16.2, 16.3**
/// </summary>
[Fact]
public void GetPrivacyAsync_ReturnsNonNullDto_WhenConfigValueIsNull()
{
// Arrange
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("privacy_policy"))
.ReturnsAsync((string?)null);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var result = service.GetPrivacyAsync().GetAwaiter().GetResult();
// Assert: 即使配置值为空,也应返回非空 DTO
Assert.NotNull(result);
Assert.NotNull(result.Content);
Assert.Equal(string.Empty, result.Content);
}
/// <summary>
/// Property 8: GetPrivacyAsync 返回非空 DTO即使配置值为空字符串
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.2, 16.1, 16.2, 16.3**
/// </summary>
[Fact]
public void GetPrivacyAsync_ReturnsNonNullDto_WhenConfigValueIsEmpty()
{
// Arrange
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("privacy_policy"))
.ReturnsAsync(string.Empty);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var result = service.GetPrivacyAsync().GetAwaiter().GetResult();
// Assert
Assert.NotNull(result);
Assert.NotNull(result.Content);
Assert.Equal(string.Empty, result.Content);
}
/// <summary>
/// Property 8: GetPrivacyAsync 响应格式一致性 - Content 属性始终存在
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.2, 16.1, 16.2, 16.3**
/// </summary>
[Property(MaxTest = 100)]
public bool GetPrivacyAsync_ContentPropertyAlwaysExists(PositiveInt seed)
{
// Arrange: 使用不同的配置值
var configValues = new string?[] { null, "", "隐私政策", "隐私政策\n第一条...", $"Privacy_{seed.Get}" };
var configValue = configValues[seed.Get % configValues.Length];
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("privacy_policy"))
.ReturnsAsync(configValue);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var result = service.GetPrivacyAsync().GetAwaiter().GetResult();
// Assert: Content 属性始终存在且不为 null
return result != null && result.Content != null;
}
#endregion
#region Property 8: - GetAboutAsync
/// <summary>
/// Property 8: GetAboutAsync 返回包含 Content 和 Version 属性的 AboutDto
/// *For any* GetAboutAsync call, the returned AboutDto SHALL have Content and Version properties.
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.3, 16.1, 16.2, 16.3**
/// </summary>
[Property(MaxTest = 100)]
public bool GetAboutAsync_ReturnsAboutDtoWithContentAndVersionProperties(NonEmptyString content, NonEmptyString version)
{
// Arrange
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("about_us"))
.ReturnsAsync(content.Get);
mockConfigService
.Setup(x => x.GetConfigValueAsync("app_version"))
.ReturnsAsync(version.Get);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var result = service.GetAboutAsync().GetAwaiter().GetResult();
// Assert: 验证返回的 AboutDto 包含 Content 和 Version 属性且值正确
return result != null &&
result.Content == content.Get &&
result.Version == version.Get;
}
/// <summary>
/// Property 8: GetAboutAsync 返回非空 DTO即使配置值为空
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.3, 16.1, 16.2, 16.3**
/// </summary>
[Fact]
public void GetAboutAsync_ReturnsNonNullDto_WhenConfigValuesAreNull()
{
// Arrange
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("about_us"))
.ReturnsAsync((string?)null);
mockConfigService
.Setup(x => x.GetConfigValueAsync("app_version"))
.ReturnsAsync((string?)null);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var result = service.GetAboutAsync().GetAwaiter().GetResult();
// Assert: 即使配置值为空,也应返回非空 DTO
Assert.NotNull(result);
Assert.NotNull(result.Content);
Assert.NotNull(result.Version);
Assert.Equal(string.Empty, result.Content);
}
/// <summary>
/// Property 8: GetAboutAsync Version 有默认值,当未配置时
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.3, 16.1, 16.2, 16.3**
/// </summary>
[Fact]
public void GetAboutAsync_VersionHasDefaultValue_WhenNotConfigured()
{
// Arrange
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("about_us"))
.ReturnsAsync("关于我们内容");
mockConfigService
.Setup(x => x.GetConfigValueAsync("app_version"))
.ReturnsAsync((string?)null);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var result = service.GetAboutAsync().GetAwaiter().GetResult();
// Assert: Version 应该有默认值 "1.0.0"
Assert.NotNull(result);
Assert.Equal("1.0.0", result.Version);
}
/// <summary>
/// Property 8: GetAboutAsync Version 有默认值,当配置为空字符串时
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.3, 16.1, 16.2, 16.3**
/// </summary>
[Fact]
public void GetAboutAsync_VersionHasDefaultValue_WhenConfiguredAsEmpty()
{
// Arrange
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("about_us"))
.ReturnsAsync("关于我们内容");
mockConfigService
.Setup(x => x.GetConfigValueAsync("app_version"))
.ReturnsAsync(string.Empty);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var result = service.GetAboutAsync().GetAwaiter().GetResult();
// Assert: 当配置为空字符串时,应该返回空字符串(不是默认值)
// 因为 SystemService 只在 null 时使用默认值
Assert.NotNull(result);
Assert.Equal(string.Empty, result.Version);
}
/// <summary>
/// Property 8: GetAboutAsync 响应格式一致性 - Content 和 Version 属性始终存在
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.3, 16.1, 16.2, 16.3**
/// </summary>
[Property(MaxTest = 100)]
public bool GetAboutAsync_ContentAndVersionPropertiesAlwaysExist(PositiveInt seed)
{
// Arrange: 使用不同的配置值组合
var contentValues = new string?[] { null, "", "关于我们", "公司介绍\n...", $"About_{seed.Get}" };
var versionValues = new string?[] { null, "", "1.0.0", "2.1.3", $"v{seed.Get % 10}.{seed.Get % 100}.{seed.Get % 1000}" };
var contentValue = contentValues[seed.Get % contentValues.Length];
var versionValue = versionValues[(seed.Get / contentValues.Length) % versionValues.Length];
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("about_us"))
.ReturnsAsync(contentValue);
mockConfigService
.Setup(x => x.GetConfigValueAsync("app_version"))
.ReturnsAsync(versionValue);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var result = service.GetAboutAsync().GetAwaiter().GetResult();
// Assert: Content 和 Version 属性始终存在且不为 null
return result != null && result.Content != null && result.Version != null;
}
#endregion
#region
/// <summary>
/// Property 8: 所有系统配置方法返回非空 DTO
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.1, 14.2, 14.3, 16.1, 16.2, 16.3**
/// </summary>
[Property(MaxTest = 100)]
public bool AllSystemMethods_ReturnNonNullDtos(PositiveInt seed)
{
// Arrange: 随机配置值
var configValues = new string?[] { null, "", $"Content_{seed.Get}" };
var agreementValue = configValues[seed.Get % configValues.Length];
var privacyValue = configValues[(seed.Get + 1) % configValues.Length];
var aboutValue = configValues[(seed.Get + 2) % configValues.Length];
var versionValue = configValues[(seed.Get + 3) % configValues.Length];
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("user_agreement"))
.ReturnsAsync(agreementValue);
mockConfigService
.Setup(x => x.GetConfigValueAsync("privacy_policy"))
.ReturnsAsync(privacyValue);
mockConfigService
.Setup(x => x.GetConfigValueAsync("about_us"))
.ReturnsAsync(aboutValue);
mockConfigService
.Setup(x => x.GetConfigValueAsync("app_version"))
.ReturnsAsync(versionValue);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var agreement = service.GetAgreementAsync().GetAwaiter().GetResult();
var privacy = service.GetPrivacyAsync().GetAwaiter().GetResult();
var about = service.GetAboutAsync().GetAwaiter().GetResult();
// Assert: 所有方法都返回非空 DTO
return agreement != null && privacy != null && about != null;
}
/// <summary>
/// Property 8: 所有系统配置方法的响应属性不为 null
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.1, 14.2, 14.3, 16.1, 16.2, 16.3**
/// </summary>
[Property(MaxTest = 100)]
public bool AllSystemMethods_ResponsePropertiesAreNotNull(PositiveInt seed)
{
// Arrange: 随机配置值
var configValues = new string?[] { null, "", $"Content_{seed.Get}" };
var agreementValue = configValues[seed.Get % configValues.Length];
var privacyValue = configValues[(seed.Get + 1) % configValues.Length];
var aboutValue = configValues[(seed.Get + 2) % configValues.Length];
var versionValue = configValues[(seed.Get + 3) % configValues.Length];
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("user_agreement"))
.ReturnsAsync(agreementValue);
mockConfigService
.Setup(x => x.GetConfigValueAsync("privacy_policy"))
.ReturnsAsync(privacyValue);
mockConfigService
.Setup(x => x.GetConfigValueAsync("about_us"))
.ReturnsAsync(aboutValue);
mockConfigService
.Setup(x => x.GetConfigValueAsync("app_version"))
.ReturnsAsync(versionValue);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var agreement = service.GetAgreementAsync().GetAwaiter().GetResult();
var privacy = service.GetPrivacyAsync().GetAwaiter().GetResult();
var about = service.GetAboutAsync().GetAwaiter().GetResult();
// Assert: 所有响应属性都不为 null
return agreement.Content != null &&
privacy.Content != null &&
about.Content != null &&
about.Version != null;
}
/// <summary>
/// Property 8: 配置值正确传递到响应中
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.1, 14.2, 14.3, 16.1, 16.2, 16.3**
/// </summary>
[Property(MaxTest = 100)]
public bool ConfigValues_CorrectlyPassedToResponse(NonEmptyString agreementContent, NonEmptyString privacyContent, NonEmptyString aboutContent, NonEmptyString version)
{
// Arrange
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync("user_agreement"))
.ReturnsAsync(agreementContent.Get);
mockConfigService
.Setup(x => x.GetConfigValueAsync("privacy_policy"))
.ReturnsAsync(privacyContent.Get);
mockConfigService
.Setup(x => x.GetConfigValueAsync("about_us"))
.ReturnsAsync(aboutContent.Get);
mockConfigService
.Setup(x => x.GetConfigValueAsync("app_version"))
.ReturnsAsync(version.Get);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var agreement = service.GetAgreementAsync().GetAwaiter().GetResult();
var privacy = service.GetPrivacyAsync().GetAwaiter().GetResult();
var about = service.GetAboutAsync().GetAwaiter().GetResult();
// Assert: 配置值正确传递到响应中
return agreement.Content == agreementContent.Get &&
privacy.Content == privacyContent.Get &&
about.Content == aboutContent.Get &&
about.Version == version.Get;
}
/// <summary>
/// Property 8: 空配置值时使用默认值或空字符串
///
/// **Feature: miniapp-api, Property 8: 响应格式一致性**
/// **Validates: Requirements 14.1, 14.2, 14.3, 16.1, 16.2, 16.3**
/// </summary>
[Fact]
public void NullConfigValues_UseDefaultOrEmptyString()
{
// Arrange
var mockConfigService = new Mock<IConfigService>();
mockConfigService
.Setup(x => x.GetConfigValueAsync(It.IsAny<string>()))
.ReturnsAsync((string?)null);
var service = new SystemService(mockConfigService.Object, _mockLogger.Object);
// Act
var agreement = service.GetAgreementAsync().GetAwaiter().GetResult();
var privacy = service.GetPrivacyAsync().GetAwaiter().GetResult();
var about = service.GetAboutAsync().GetAwaiter().GetResult();
// Assert
Assert.Equal(string.Empty, agreement.Content);
Assert.Equal(string.Empty, privacy.Content);
Assert.Equal(string.Empty, about.Content);
Assert.Equal("1.0.0", about.Version); // Version 有默认值
}
#endregion
}